“ 大模型的格式化输出只能靠提示词约束,没有其它办法;Langchain的输出解析器只不过是封装了格式化输出,其本质还是通过提示词来实现。”
在大模型应用开发中,格式化输出是很重要的一个环节,因为没有格式化输出就不会有大模型的应用。
但是大模型的格式化输出是怎么实现的呢?
今天我们就以Langchain框架为例,来讲一下大模型的格式化输出问题。
基于Langchain的格式化输出
在前面的文章中有提到过Langchain输出解析器的问题,其实输出解析器的本质,就是把需要的返回值封装成实体对象或JSON等对象,然后把对象的描述拼接到提示词模板中,然后由大模型按照模板的格式要求进行数据输出;之后,输出解析器再把模型输出的字符串转换成需要的对象格式。
如下图所示,其中parser就是一个输出解析器,其可以是字符串解析器,json解析器,也可以是PydanticOutputParser对象解析器。

通过parser.get_format_instructions()就可以获取到需要的输出格式;如下所示,标识需要输出的是一个对象;当然,生成式模型无法生成json或实体对象,只能生成字符串格式的数据,因此拿到大模型的输出之后,需要把字符串转换成json或实体对象。

所以,从这里可以看出大模型格式化输出的本质,就是通过提示词告诉模型我需要什么样的格式,然后模型就可以根据要求生成对应的格式;而Langchain中的解析器的作用,一是提供一个默认的格式化输出提示词;二是把模型输出的字符串解析成对应的数据格式。
当然,在Langchain中提供了多种不同的解析器,其使用场景也不一样;但其中最重要的解析器,应该就是PydanticOutputParser了,原因就在于其格式化程度最强,并且可以使用类型验证来保证模型输出的准确性。
当然,由于模型自身不稳定的特性,其输出并不是每一次都是按照要求生成的;因此,作为一个合格的输出解析器必须要有异常处理,也就是当模型输出错误格式的数据时,能够抛出异常,或者进行处理然后重试等。
"""langchain 提示词 格式化输出 和 链"""
import os
from pydantic import BaseModel, Field
import config
from langchain_openai import ChatOpenAI
from langchain_core.prompts import
(
PromptTemplate,
ChatPromptTemplate
)
from langchain.prompts import
(
ChatPromptTemplate,
MessagesPlaceholder,
SystemMessagePromptTemplate,
HumanMessagePromptTemplate
)
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser, CommaSeparatedListOutputParser
from langchain.output_parsers import ResponseSchema, StructuredOutputParser, PydanticOutputParser
os.environ['OPENAI_API_KEY'] = config.AIModel.openai_api_key
os.environ['OPENAI_API_BASE'] = config.AIModel.openai_api_base
# prompt = PromptTemplate.from_template("你是一个智能助手 你能回答用户问题")
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个智能助手 能够根据用户的需求提取信息 并返回json格式的数据 以以下JSON格式返回:{format_instructions}"),
MessagesPlaceholder(variable_name="chat_history"),
("human", "用户问题如下: {query}")
])
# prompt_value = prompt.invoke({"query": "你是谁?", "chat_history": []})
#
# print(f"prompt_value: {type(prompt_value)}, {prompt_value}")
# print(f"prompt: {type(prompt)}, {prompt}")
model = ChatOpenAI(model_name=config.AIModel.model, temperature=0.8, streaming=True, max_tokens=1024)
# parser = StructuredOutputParser.from_response_schemas(response_schemas)
# parser = StrOutputParser()
# parser = JsonOutputParser()
class Student(BaseModel):
stu_no: str = Field(description="学号")
name: str = Field(description="姓名")
parser = PydanticOutputParser(pydantic_object=Student)
print(f"parser: {parser}, {parser.get_format_instructions()}")
# prompt.partial_variables = {
# "format_instructions": parser.get_format_instructions()
# }
prompt = prompt.partial(
format_instructions=parser.get_format_instructions()
)
chain = prompt | model | parser
inputs = {
"query": "张三是一名学生 他的学号是st132 ",
"chat_history": []
}
result = chain.invoke(inputs)
print(f"result: {result}")
在Langchain中,输出解析器设计的都是基于字典形式的输出,所以输出解析器无法直接输出列表形式的数据;原因在于列表形式的数据格式灵活性没有字典形式的数据格式灵活性高。
其次,可以通过封装的形式来让模型输出列表形式的数据;以Student对象为例,如果想让模型输出一个学生对象列表,那么就可以再封装一个对象的列表集合,通过这种方式来实现让模型输出列表形式的数据。
class StudentList(BaseModel):
student_list: List[Student] = Field(description="学生列表")
(文:AI探索时代)