在使用LangChain进行结构化数据抽取时,常见的两种方式分别是OutputParser和Structured Output。二者在调用OpenAI API时的请求报文和底层机制存在明显差异。本文通过具体代码示例,详细对比这两种方式的实现细节、API请求体、响应内容,并分析各自的优缺点和适用场景,可根据实际需求做出选择。
随着大语言模型(LLM)在信息抽取、结构化问答等场景的广泛应用,如何让模型输出符合预期结构的数据成为开发者关注的重点。 LangChain作为主流的LLM应用开发框架,提供了多种结构化输出方案,其中OutputParser和Structured Output是最常用的两种。
- OutputParser:通过提示工程(Prompt Engineering)和解析器,将模型输出转换为指定的数据结构。
- Structured Output:利用OpenAI API的原生
response_format参数,直接要求模型生成符合JSON Schema的结构化数据。
下面分别介绍这两种方式及其在API求和响应中的表现。
OutputParser 示例及其OpenAI API请求报文
OutputParser方式通过在提示中明确指定输出格式要求,然后用解析器将模型输出转换为所需结构。适用于模型本身不支持原生结构化输出的场景,或需要自定义复杂格式时。
示例代码:
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import PydanticOutputParser
import os
class RestaurantReview(BaseModel):
"""Restaurant review structure"""
restaurant_name: str = Field(description="餐厅的名称")
food_rating: int = Field(description="菜品口味评分,1-5分")
service_rating: int = Field(description="服务质量评分,1-5分")
ambiance_rating: int = Field(description="餐厅环境评分,1-5分")
recommended_dishes: str = Field(description="推荐的特色菜品")
overall_comment: str = Field(description="对餐厅的总体评价和建议")
def main():
load_dotenv()
model_name = os.getenv("MODEL_NAME")
model = ChatOpenAI(model=model_name, temperature=0)
output_parser = PydanticOutputParser(pydantic_object=RestaurantReview)
prompt_template = ChatPromptTemplate.from_messages(
[
(
"system",
"你是一个专业的餐厅评价分析助手,能够从用户描述中提取结构化信息。",
),
(
"human",
"请根据用户的描述,生成一个结构化的餐厅评价。\n{format_instructions}\n用户描述: {query}",
),
]
)
chain = prompt_template | model | output_parser
review_result = chain.invoke(
{
"query": "我昨天去了一家叫'江南小厨'的餐厅,菜品很美味,尤其是红烧肉和清蒸鱼非常好吃。服务员态度一般,有时需要等很久才能叫到人。餐厅装修很有江南风格,环境优雅。总的来说还是值得一去的。",
"format_instructions": output_parser.get_format_instructions(),
}
)
assert isinstance(review_result, RestaurantReview)
print(review_result)
if __name__ == "__main__":
main()请求LLM API的请求体:
{
"messages":[ {
"content":"你是一个专业的餐厅评价分析助手,能够从用户描述中提取结构化信息。","role":"system"
}
, {
"content":"请根据用户的描述,生成一个结构化的餐厅评价。\nThe output should be formatted as a JSON instance that conforms to the JSON schema below.\n\nAs an example, for the schema {\"properties\": {\"foo\": {\"title\": \"Foo\", \"description\": \"a list of strings\", \"type\": \"array\", \"items\": {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\nthe object {\"foo\": [\"bar\", \"baz\"]} is a well-formatted instance of the schema. The object {\"properties\": {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\n\nHere is the output schema:\n```\n{\"description\": \"Restaurant review structure\", \"properties\": {\"restaurant_name\": {\"description\": \"餐厅的名称\", \"title\": \"Restaurant Name\", \"type\": \"string\"}, \"food_rating\": {\"description\": \"菜品口味评分,1-5分\", \"title\": \"Food Rating\", \"type\": \"integer\"}, \"service_rating\": {\"description\": \"服务质量评分,1-5分\", \"title\": \"Service Rating\", \"type\": \"integer\"}, \"ambiance_rating\": {\"description\": \"餐厅环境评分,1-5分\", \"title\": \"Ambiance Rating\", \"type\": \"integer\"}, \"recommended_dishes\": {\"description\": \"推荐的特色菜品\", \"title\": \"Recommended Dishes\", \"type\": \"string\"}, \"overall_comment\": {\"description\": \"对餐厅的总体评价和建议\", \"title\": \"Overall Comment\", \"type\": \"string\"}}, \"required\": [\"restaurant_name\", \"food_rating\", \"service_rating\", \"ambiance_rating\", \"recommended_dishes\", \"overall_comment\"]}\n```\n用户描述: 我昨天去了一家叫'江南小厨'的餐厅,菜品很美味,尤其是红烧肉和清蒸鱼非常好吃。服务员态度一般,有时需要等很久才能叫到人。餐厅装修很有江南风格,环境优雅。总的来说还是值得一去的。","role":"user"
}
],"model":"xxx","stream":false,"temperature":0.0
}可以看到,对于OutputParser,通过在提示中明确指定输出格式要求,然后使用解析器将模型输出转换为所需的结构。
LLM API返回的响应:
{
"id":"chatcmpl-9fcb70a754484716aaf8f8eef9f97832",
"object":"chat.completion",
"model":"xxx",
"choices":[ {
"index":0,
"message": {
"role":"assistant",
"reasoning_content":null,
"content":"<think>\n好的,我需要根据用户的描述生成一个符合指定JSON结构的餐厅评价。首先,我要仔细阅读用户提供的信息,然后按照schema的要求提取各个字段。\n\n用户提到的餐厅名称是"江南小厨",这个直接填入restaurant_name。接下来是三个评分:菜品口味、服务质量和环境。用户说菜品很美味,所以food_rating应该给高分,比如5分。服务方面,用户提到服务员态度一般,有时需要等很久,所以service_rating可能给3分。环境方面,用户称赞装修有江南风格且优雅,所以ambiance_rating可以是4或5分,这里选5分。\n\n推荐的特色菜是红烧肉和清蒸鱼,需要合并成一个字符串,用逗号分隔。总体评价部分要总结用户的感受,包括正面和需要改进的地方,比如总体不错但服务可以提升。最后检查所有必填字段是否齐全,确保没有遗漏。确认每个字段的类型正确,特别是评分是整数,推荐菜品是字符串,总体评价也是字符串。完成后,生成正确的JSON结构。\n</think>\n\n```json\n{\n \"restaurant_name\": \"江南小厨\",\n \"food_rating\": 5,\n \"service_rating\": 3,\n \"ambiance_rating\": 5,\n \"recommended_dishes\": \"红烧肉, 清蒸鱼\",\n \"overall_comment\": \"餐厅菜品美味,环境优雅具有江南特色,值得推荐。但服务响应速度有待提升,建议加强员工培训。\"\n}\n```",
"tool_calls":[]
}
,"logprobs":null,
"finish_reason":"stop",
"stop_reason":null
}
],"usage": {
"prompt_tokens":437,"total_tokens":754,"completion_tokens":317,"prompt_tokens_details":null
}
,"prompt_logprobs":null
}示例代码的运行输出:
restaurant_name='江南小厨' food_rating=5 service_rating=3 ambiance_rating=5 recommended_dishes='红烧肉, 清蒸鱼' overall_comment='餐厅菜品美味,环境优雅具有江南特色,值得推荐。但服务响应速度有待提升,建议加强员工培训。'Structured Output示例及其OpenAI API请求报文
Structured Output方式是OpenAI API的原生能力,通过response_format参数直接要求模型输出符合特定结构的数据。LangChain通过 .with_structured_output()方法简化了这一过程。
示例代码:
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
from langchain_core.prompts import ChatPromptTemplate
import os
class RestaurantReview(BaseModel):
"""Restaurant review structure"""
restaurant_name: str = Field(description="餐厅的名称")
food_rating: int = Field(description="菜品口味评分,1-5分")
service_rating: int = Field(description="服务质量评分,1-5分")
ambiance_rating: int = Field(description="餐厅环境评分,1-5分")
recommended_dishes: str = Field(description="推荐的特色菜品")
overall_comment: str = Field(description="对餐厅的总体评价和建议")
def main():
load_dotenv()
model_name = os.getenv("MODEL_NAME")
model = ChatOpenAI(model=model_name, temperature=0).with_structured_output(RestaurantReview)
prompt_template = ChatPromptTemplate.from_messages(
[
(
"system",
"你是一个专业的餐厅评价分析助手,能够从用户描述中提取结构化信息。",
),
(
"human",
"请根据用户的描述,生成一个结构化的餐厅评价。\n用户描述: {query}",
),
]
)
chain = prompt_template | model
review_result = chain.invoke(
{
"query": "我昨天去了一家叫'江南小厨'的餐厅,菜品很美味,尤其是红烧肉和清蒸鱼非常好吃。服务员态度一般,有时需要等很久才能叫到人。餐厅装修很有江南风格,环境优雅。总的来说还是值得一去的。",
}
)
assert isinstance(review_result, RestaurantReview)
print(review_result)
if __name__ == "__main__":
main()请求LLMAPI的请求体:
{
"messages":[ {
"content":"你是一个专业的餐厅评价分析助手,能够从用户描述中提取结构化信息。","role":"system"
}
, {
"content":"请根据用户的描述,生成一个结构化的餐厅评价。\n用户描述: 我昨天去了一家叫'江南小厨'的餐厅,菜品很美味,尤其是红烧肉和清蒸鱼非常好吃。服务员态度一般,有时需要等很久才能叫到人。餐厅装修很有江南风格,环境优雅。总的来说还是值得一去的。","role":"user"
}
],"model":"xxx",
"response_format": {
"type":"json_schema",
"json_schema": {
"schema": {
"description":"Restaurant review structure","properties": {
"restaurant_name": {
"description":"餐厅的名称","title":"Restaurant Name","type":"string"
}
,"food_rating": {
"description":"菜品口味评分,1-5分","title":"Food Rating","type":"integer"
}
,"service_rating": {
"description":"服务质量评分,1-5分","title":"Service Rating","type":"integer"
}
,"ambiance_rating": {
"description":"餐厅环境评分,1-5分","title":"Ambiance Rating","type":"integer"
}
,"recommended_dishes": {
"description":"推荐的特色菜品","title":"Recommended Dishes","type":"string"
}
,"overall_comment": {
"description":"对餐厅的总体评价和建议","title":"Overall Comment","type":"string"
}
},
"required":["restaurant_name","food_rating","service_rating","ambiance_rating","recommended_dishes","overall_comment"],
"title":"RestaurantReview",
"type":"object",
"additionalProperties":false
},
"name":"RestaurantReview",
"strict":true
}
}
,"stream":false,"temperature":0.0
}可以看出,Structured output是大模型API的原生功能,通过API参数直接要求模型输出符合特定结构的内容。对于OPENAI API规范通过/v1/chat/completions端点的response_format参数实现,模型会在底层直接生成符合要求的结构化数据。
LLM API返回的响应:
{
"id":"chatcmpl-46ce47aafb7b4c7e8894c4b6d8690e1d",
"object":"chat.completion",
"model":"xxx",
"choices":[ {
"index":0,
"message": {
"role":"assistant",
"reasoning_content":null,
"content":"{\n\n\"restaurant_name\": \"江南小厨\",\n\"food_rating\": 5,\n\"service_rating\": 3,\n\"ambiance_rating\": 4,\n\"recommended_dishes\": \"红烧肉, 清蒸鱼\",\n\"overall_comment\": \"总的来说还是值得一去的\"\n}",
"tool_calls":[]
}
,"logprobs":null,
"finish_reason":"stop",
"stop_reason":null
}
],"usage": {
"prompt_tokens":104,"total_tokens":164,"completion_tokens":60,"prompt_tokens_details":null
}
,"prompt_logprobs":null
}示例代码的运行输出:
restaurant_name='江南小厨' food_rating=5 service_rating=3 ambiance_rating=4 recommended_dishes='红烧肉, 清蒸鱼' overall_comment='总的来说还是值得一去的'对比分析与总结
| 方式 | 格式约束方式 | API 支持 | 适用场景 |
|---|---|---|---|
| OutputParser | Prompt+解析器 | 通用 | 兼容所有模型,格式灵活 |
| Structured Output | API请求中的response_format参数 | OpenAI | 仅限支持response_format的模型 |
- OutputParser 适合需要兼容多种模型或自定义复杂结构的场景,但对 prompt 设计和解析器健壮性要求较高。
- Structured Output 则更适合追求开发效率和数据可靠性的场景,但依赖于模型和 API 的原生支持。
建议在LLM API支持的前提下优先使用Structured Output,其他情况可采用OutputParser 方案。