with_structured_output()方法介绍一般聊天模型的输出都是聊天式的字符串,但如果我们希望能把模型的输出存储到数据库,或者载入到程序,么从普通字符串到结构化的输出就尤为重要。
此时我们可以使用模型的with_structured_output()方法让它输出由输入结构指定的数据结构。
它的定义如下
1 2 3 4 5 6 7 8 9 def with_structured_output ( self, schema: _DictOrPydanticClass | None = None , *, method: Literal ["function_calling" , "json_mode" , "json_schema" ] = "json_schema" , include_raw: bool = False , strict: bool | None = None , **kwargs: Any , ) -> Runnable[LanguageModelInput, _DictOrPydantic]:
解释下关键参数:
schema:可以传字典,Json Schema或者Pydantic类,前两种会返回一个字典,最后一种会返回Pydantic类
method:表示LLM的生成方法
json_schema:默认值,表示使用OpenAI的结构化输出API
function_calling:使用OpenAI的工具调用
json_mode:使用OpenAI的Json mode
**include_raw**:
如果为 False(默认),则仅返回解析的结构化输出。如果在模型输出解析过程中发生错误,则会引发错误。
如果为 True,则将返回原始模型响应(BaseMessage)和解析的模型响应。如果在输出解析过程中发生错误,它也会被捕获并返回。最终输出始终是带有键 "raw"、"parsed" 和 "parsing_error" 的字典。
**strict**:
如果为 True,保证模型输出与 schema 完全匹配。输入 schema 也将根据支持的 schema 进行验证。
如果为 False,输入 schema 将不会验证,模型输出也不会被验证。
如果为 None(默认),则不会将 strict 参数传递给模型。
**tools**:要绑定到聊天模型的工具列表。要求:method 为 'json_schema'、strict=True、include_raw=True。则生成的 AIMessage 将在 "raw" 中包含工具调用。
**kwargs(Any)**:任何附加参数都直接传递给 bind()。
示例:返回Pydantic类的示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from langchain_openai import ChatOpenAIfrom typing import Optional from pydantic import BaseModel,Fieldclass JokeSchema (BaseModel ): """给用户讲一个笑话""" setup: str = Field(description="这个笑话的开头" ) punchline:str = Field(description="这个笑话的妙语" ) rating:Optional [int ] = Field(description="这个笑话的打分,满分10分" , default=None ) model = ChatOpenAI(model="gpt-4o-mini" ) structured_model = model.with_structured_output(JokeSchema) print (structured_model.invoke("给我讲一个关于牛顿与苹果的笑话" ))
1 2 3 4 5 D:\program_software\AnaConda\envs\langChainP13\python.exe D:\codes\code_pycharm\langChainTool\output.py setup='你知道牛顿和苹果之间发生了什么搞笑的事情吗?' punchline='牛顿说:‘我不是想引力,我只想摘个苹果!’' rating=7 Process finished with exit code 0
示例:返回字典的示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from langchain_openai import ChatOpenAIfrom typing import Optional from typing_extensions import TypedDict,Annotatedclass JokeSchema (TypedDict ): """给用户讲一个笑话""" setup: Annotated[str ,...,"这个笑话的开头" ] punchline: Annotated[str ,...,"这个笑话的妙语" ] rating: Annotated[Optional [int ],None , "从0到10给这个笑话的好笑程度打分" ] model = ChatOpenAI(model="gpt-4o-mini" ) structured_model = model.with_structured_output(JokeSchema) print (structured_model.invoke("给我讲一个关于牛顿与苹果的笑话" ))
1 2 3 4 D:\program_software\AnaConda\envs\langChainP13\python.exe D:\codes\code_pycharm\langChainTool\output.py {'setup': '为什么牛顿和苹果之间的关系总是那么紧张?', 'punchline': '因为每次牛顿想要放松时,苹果总会让他想到重力!', 'rating': 8} Process finished with exit code 0
可以看到此时模型的输出为字典类型,如果想要像之前一样输出原始内容,则可以在方法参数中添加include_raw=True,即:
1 2 3 4 5 model = ChatOpenAI(model="gpt-4o-mini" ) structured_model = model.with_structured_output(JokeSchema, include_raw=True ) result = structured_model.invoke("给我讲一个关于牛顿与苹果的笑话" ) print (result)
1 {'raw': AIMessage(content='{"setup":"牛顿为什么不吃苹果?","punchline":"因为他发明了重力,知道一口下去会重到下不来!","rating":8}', additional_kwargs={'parsed': None, 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 40, 'prompt_tokens': 189, 'total_tokens': 229, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_provider': 'openai', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_51db84afab', 'id': 'chatcmpl-CdXGJHZn0jPCiOVYj8URpJ8mz4zvd', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--95fef42f-8a7b-4c0a-be90-3fa2b2a53e47-0', usage_metadata={'input_tokens': 189, 'output_tokens': 40, 'total_tokens': 229, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), 'parsed': {'setup': '牛顿为什么不吃苹果?', 'punchline': '因为他发明了重力,知道一口下去会重到下不来!', 'rating': 8}, 'parsing_error': None}
示例:返回JSon 为了返回Json,我们需要传入Json Schema
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 from langchain_openai import ChatOpenAIjson_schema = { "title" : "joke" , "description" : "给⽤⼾讲⼀个笑话。" , "type" : "object" , "properties" : { "setup" : { "type" : "string" , "description" : "这个笑话的开头" , }, "punchline" : { "type" : "string" , "description" : "这个笑话的妙语" , }, "rating" : { "type" : "integer" , "description" : "从1到10分,给这个笑话评分" , "default" : None , }, }, "required" : ["setup" , "punchline" ], } model = ChatOpenAI(model="gpt-4o-mini" ) structured_model = model.with_structured_output(json_schema) result = structured_model.invoke("给我讲一个关于牛顿与苹果的笑话" ) print (result)
输出为json格式
1 2 3 4 D:\program_software\AnaConda\envs\langChainP13\python.exe D:\codes\code_pycharm\langChainTool\output.py {'setup': '牛顿在树下休息,突然一个苹果掉下来了,', 'punchline': '他抬头看看,说:‘我就知道,树上有个苹果有问题!’', 'rating': 8} Process finished with exit code 0
从多种输出格式中选择 我们可以创建多种输出格式,并最后用Union类型聚合在一个类中传入给LLM,同时他会自动选择输出格式,参照下面的代码示例,特别的,此时最后一个用于聚合的类,不需要注释解释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from langchain_openai import ChatOpenAIfrom typing import Optional ,Union from pydantic import BaseModel,Fieldclass JokeSchema (BaseModel ): """给用户讲一个笑话""" setup: str = Field(description="这个笑话的开头" ) punchline:str = Field(description="这个笑话的妙语" ) rating:Optional [int ] = Field(description="这个笑话的打分,满分10分" , default=None ) class ConversationalSchema (BaseModel ): """以聊天对话的方式回应,用闲聊的语气""" response: str = Field(description="对用户问候的回应" ) class FinalResponse (BaseModel ): final_output: Union [JokeSchema, ConversationalSchema] model = ChatOpenAI(model="gpt-4o-mini" ) structured_model = model.with_structured_output(FinalResponse) print (structured_model.invoke("给我讲一个关于牛顿与苹果的笑话" ))print (structured_model.invoke("你好,你是谁" ))
输出如下,可以看到有两种输出格式
1 2 3 4 5 D:\program_software\AnaConda\envs\langChainP13\python.exe D:\codes\code_pycharm\langChainTool\output.py final_output=JokeSchema(setup='牛顿为了研究重力,有一天在果园里,看到一个苹果掉下来,问:', punchline='你为什么掉下来?苹果回答:因为我不想被你研究了!', rating=8) final_output=ConversationalSchema(response='你好!我是一个人工智能助手,随时准备帮助你解答问题或聊天。你有什么想知道的呢?') Process finished with exit code 0
部分实用场景示例 1. 作为数据提取器 我们可以让LLM作为数据提取器,从文本中提取数据并返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 from typing import Optional from pydantic import BaseModel, Fieldfrom langchain_core.messages import HumanMessage, SystemMessagefrom langchain_openai import ChatOpenAImodel = ChatOpenAI(model="gpt-4o-mini" ) class Person (BaseModel ): """一个人的信息。""" name: Optional [str ] = Field(default=None , description="这个人的名字" ) hair_color:Optional [str ] = Field(default=None , description="如果知道这个人头发的颜色" ) skin_color:Optional [str ] = Field(default=None ,description="如果知道这个人的肤色" ) height_in_meters: Optional [str ] = Field(default=None , description="以米为单位的高度" ) structured_model = model.with_structured_output(schema=Person) messages = [ SystemMessage(content="你是一个提取信息的专家,只从文本中提取相关信息。如果您不知道要提取的属性的值,属性值返回null" ), HumanMessage(content="史密斯身高6英尺,金发。" ) ] result = structured_model.invoke(messages) print (result)messages.append(result) result = structured_model.invoke("约翰身高1.8米,是一位长着白发的黑人老人" ) print (result)
输出如下
1 2 3 D:\program_software\AnaConda\envs\langChainP13\python.exe D:\codes\code_pycharm\langChainTool\output.py name='史密斯' hair_color='金发' skin_color=None height_in_meters='1.83' name='约翰' hair_color='白色' skin_color='黑色' height_in_meters='1.8'
可以看到我用不同的句子问了两次,都很好地提取到了信息
OutParser输出解析器介绍 之前我们使用的是接口,接下来我们介绍一下使用Runnable组件实现输出解析器
StrOutputParser解析文本输出 我们已经用过很多回这个输出解析器了,他能提取AIMessage中的主要文本,这里不多做介绍
PydanticOutputParser使用Pydantic类描述输出 它来自langchain_core.output_parsers.pydantic.PydanticOutputParser,初始化的时候需要按名称pydantic_object传入一个描述输出结构的Pydantic类,同时它还提供了invoke()方法和get_format_instructions() → str方法,其中后面的那个方法用于填充提示词模板
比如我们让LLM按模板输出一个笑话
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 from langchain_openai import ChatOpenAIfrom langchain_core.output_parsers import PydanticOutputParserfrom typing import Optional from pydantic import BaseModel,Fieldfrom langchain_core.prompts import PromptTemplateclass JokeSchema (BaseModel ): """给用户讲一个笑话""" setup: str = Field(description="这个笑话的开头" ) punchline:str = Field(description="这个笑话的妙语" ) rating:Optional [int ] = Field(description="这个笑话的打分,满分10分" , default=None ) parser = PydanticOutputParser(pydantic_object=JokeSchema) prompt = PromptTemplate( template="{query}\nAnswer in this following template\n{template}" , input_variables=["query" ], partial_variables={"template" :parser.get_format_instructions()} ) model = ChatOpenAI(model="gpt-4o-mini" ) chain = prompt | model | parser print (chain.invoke({"query" :"给我讲一个关于牛顿与苹果的笑话" }))
其它输出 langchain还提供了其它描述输出的方法,使用起来没有太大差别:
Json解析器:JsonOutputParser
XML解析器:XMLOutputParser
Yaml解析器:YamlOutputParser
CSV解析器:CommaSeparatedListOutputParser
枚举解析器:EnumOutputParser
⽇期解析器:DatetimeOutputParser 等等,更多类型参考这⾥