在 LangChain 中,结构化输出(Structured Output) 是一种机制,用于确保语言模型(LLM)生成符合特定格式的输出,例如 JSON、字典或其他结构化数据格式。结构化输出在需要精确解析模型响应或与下游系统交互的场景中尤为重要,例如 API 调用、数据库操作或前端展示。通过结构化输出,开发者可以约束模型的输出格式,减少解析错误并提高应用的可靠性。
以下是对 LangChain 中结构化输出的详细介绍,涵盖其定义、工作原理、实现方式、应用场景、代码示例、优化建议以及与生态系统的结合。
1. 什么是 LangChain 的结构化输出?
结构化输出是指语言模型生成的结果被约束为特定的数据结构,而不是自由文本。常见结构化输出格式包括:
- JSON:键值对格式,适合 API 和数据交换。
- Pydantic 模型:Python 中定义的强类型数据模型。
- 字典:简单的键值对结构。
- 自定义格式:如 YAML、CSV 或特定模板。
结构化输出的核心目标是:
- 格式一致性:确保输出符合预定义的模式,便于解析。
- 减少错误:避免自由文本导致的格式不一致或解析失败。
- 自动化处理:便于下游系统直接使用输出。
- 开发者友好:通过类型检查和验证提高代码可靠性。
在 LangChain 中,结构化输出通常通过以下方式实现:
- 提示工程:在提示中明确要求特定格式。
- 输出解析器(Output Parsers):解析模型输出并强制转换为结构化格式。
- Pydantic 集成:使用 Pydantic 模型定义输出结构。
- 工具调用(Tool Calling):利用模型的函数调用能力生成结构化输出。
- with_structured_output:直接约束模型生成特定格式。
2. 结构化输出的工作原理
结构化输出的工作流程如下:
- 定义输出格式:
- 使用 Pydantic 模型、JSON Schema 或提示模板指定期望的结构。
- 提示模型:
- 在提示中明确要求模型生成特定格式的输出(如 JSON)。
- 或使用工具调用功能直接指定输出模式。
- 生成输出:
- 模型根据提示或工具定义生成结构化响应。
- 解析与验证:
- 使用输出解析器或 Pydantic 验证输出是否符合预期格式。
- 如果不符合,抛出错误或尝试修复。
- 返回结果:
- 将结构化输出传递给下游任务或系统。
关键组件:
- Pydantic:定义和验证输出结构。
- Output Parsers:如
StructuredOutputParser
,JsonOutputParser
。 - Tool Calling:如 OpenAI 的函数调用或 LangChain 的工具接口。
- with_structured_output:直接约束模型输出。
3. LangChain 中实现结构化输出的方式
LangChain 提供了多种方法来实现结构化输出,以下是主要方式的详细说明:
(1) 使用 Pydantic 模型
- 功能:通过 Pydantic 定义输出结构,模型生成符合该结构的输出。
- 特点:
- 强类型验证,确保输出符合定义。
- 支持复杂嵌套结构。
- 适合需要严格格式的场景。
- 适用场景:
- 生成 API 响应。
- 提取结构化信息(如实体、分类结果)。
- 示例:
from langchain_openai import ChatOpenAI from pydantic import BaseModel, Field from langchain_core.prompts import ChatPromptTemplate # 定义输出结构 class Answer(BaseModel): question: str = Field(description="用户的问题") answer: str = Field(description="回答内容") confidence: float = Field(description="回答的置信度,0到1之间") llm = ChatOpenAI(api_key="your-openai-key") # 创建提示模板 prompt = ChatPromptTemplate.from_template( "回答以下问题并以 JSON 格式返回,包含问题、回答和置信度:\n问题:{question}" ) # 使用 with_structured_output structured_llm = llm.with_structured_output(Answer) chain = prompt | structured_llm # 调用 result = chain.invoke({"question": "量子计算是什么?"}) print(result)
- 输出:
Answer( question="量子计算是什么?", answer="量子计算是一种基于量子力学原理的计算范式。", confidence=0.95 )
- 注意:
- 需要模型支持结构化输出(如 OpenAI 的 GPT-4)。
- Pydantic 提供自动验证,抛出格式错误。
(2) 使用 StructuredOutputParser
- 功能:通过输出解析器强制将模型输出解析为结构化格式。
- 特点:
- 支持动态定义输出格式。
- 提供格式化指令,提示模型生成特定结构。
- 适合不支持原生结构化输出的模型。
- 适用场景:
- 老旧模型或不支持工具调用的场景。
- 需要灵活解析输出的场景。
- 示例:
from langchain_core.output_parsers import StructuredOutputParser, ResponseSchema from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate # 定义输出结构 response_schemas = [ ResponseSchema(name="question", description="用户的问题", type="string"), ResponseSchema(name="answer", description="回答内容", type="string"), ResponseSchema(name="confidence", description="回答置信度,0到1之间", type="float") ] output_parser = StructuredOutputParser.from_response_schemas(response_schemas) # 创建提示模板 prompt = ChatPromptTemplate.from_template( "回答以下问题并按照指定格式返回:\n{format_instructions}\n问题:{question}" ).partial(format_instructions=output_parser.get_format_instructions()) llm = ChatOpenAI(api_key="your-openai-key") chain = prompt | llm | output_parser # 调用 result = chain.invoke({"question": "量子计算是什么?"}) print(result)
- 输出:
{ 'question': '量子计算是什么?', 'answer': '量子计算是一种基于量子力学原理的计算范式。', 'confidence': 0.95 }
- 注意:
- 模型可能不完全遵循格式,需依赖解析器修复。
- 适合通用场景,灵活性高。
(3) 使用 JsonOutputParser
- 功能:专门解析 JSON 格式的输出。
- 特点:
- 简化的 JSON 解析流程。
- 支持 Pydantic 模型或字典定义。
- 适用场景:
- 需要 JSON 输出的 API 集成。
- 简单结构化数据提取。
- 示例:
from langchain_core.output_parsers import JsonOutputParser from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from pydantic import BaseModel # 定义输出结构 class Answer(BaseModel): question: str answer: str confidence: float llm = ChatOpenAI(api_key="your-openai-key") parser = JsonOutputParser(pydantic_object=Answer) # 创建提示模板 prompt = ChatPromptTemplate.from_template( "回答以下问题并以 JSON 格式返回:\n{format_instructions}\n问题:{question}" ).partial(format_instructions=parser.get_format_instructions()) chain = prompt | llm | parser # 调用 result = chain.invoke({"question": "量子计算是什么?"}) print(result)
- 输出:
{ 'question': '量子计算是什么?', 'answer': '量子计算是一种基于量子力学原理的计算范式。', 'confidence': 0.95 }
(4) 使用工具调用(Tool Calling)
- 功能:利用模型的函数调用能力生成结构化输出。
- 特点:
- 适合支持工具调用的模型(如 OpenAI、Anthropic)。
- 输出直接为结构化数据,减少解析开销。
- 支持复杂工具定义。
- 适用场景:
- 代理或工具驱动的任务。
- 需要高可靠性结构化输出的场景。
- 示例:
from langchain_openai import ChatOpenAI from langchain_core.tools import tool from pydantic import BaseModel # 定义工具和输出结构 class Answer(BaseModel): question: str answer: str confidence: float @tool def answer_question(question: str) -> Answer: """回答问题并返回结构化结果""" # 模拟逻辑,实际由模型生成 return Answer( question=question, answer="量子计算是一种基于量子力学原理的计算范式。", confidence=0.95 ) llm = ChatOpenAI(api_key="your-openai-key").bind_tools([answer_question]) chain = llm | (lambda x: x.tool_calls[0]["args"]) # 调用 result = chain.invoke("量子计算是什么?") print(result)
- 输出:
{ 'question': '量子计算是什么?', 'answer': '量子计算是一种基于量子力学原理的计算范式。', 'confidence': 0.95 }
(5) 提示工程(Prompt Engineering)
- 功能:通过提示直接要求模型生成结构化输出。
- 特点:
- 简单但不可靠,依赖模型遵循提示。
- 适合快速原型或不支持解析器的场景。
- 适用场景:
- 临时任务。
- 模型不支持原生结构化输出。
- 示例:
from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate llm = ChatOpenAI(api_key="your-openai-key") prompt = ChatPromptTemplate.from_template( "回答以下问题并以 JSON 格式返回,包含 question, answer, confidence 字段:\n问题:{question}" ) chain = prompt | llm result = chain.invoke({"question": "量子计算是什么?"}) print(result.content)
- 输出:
{ "question": "量子计算是什么?", "answer": "量子计算是一种基于量子力学原理的计算范式。", "confidence": 0.95 }
- 注意:
- 模型可能生成不规范的 JSON,需额外解析。
- 不推荐生产环境使用。
4. 结构化输出的应用场景
结构化输出在以下场景中尤为有用:
- API 集成:
- 生成符合 API 规范的 JSON 响应。
- 示例:将模型输出直接发送到后端服务。
- 数据提取:
- 从文本中提取结构化信息(如实体、分类结果)。
- 示例:从用户评论中提取情感和主题。
- 数据库操作:
- 将模型输出直接插入数据库。
- 示例:将问答结果存储为记录。
- 前端展示:
- 提供结构化数据供前端渲染。
- 示例:生成表格或图表数据。
- 代理任务:
- 代理调用工具时需要结构化输入输出。
- 示例:代理生成 JSON 格式的工具调用参数。
- 自动化工作流:
- 将模型输出传递给自动化系统。
- 示例:触发 Zapier 动作。
5. 结构化输出的实现与代码示例
综合示例:问答系统生成结构化输出
以下是一个结合 Pydantic 和工具调用的问答系统:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser
from pydantic import BaseModel, Field
# 定义输出结构
class QAResponse(BaseModel):
question: str = Field(description="用户的问题")
answer: str = Field(description="回答内容")
confidence: float = Field(description="回答置信度,0到1之间")
sources: list[str] = Field(description="参考来源")
# 初始化模型和解析器
llm = ChatOpenAI(api_key="your-openai-key")
parser = JsonOutputParser(pydantic_object=QAResponse)
# 创建提示模板
prompt = ChatPromptTemplate.from_template(
"回答以下问题并以 JSON 格式返回,包含问题、回答、置信度和来源:\n{format_instructions}\n问题:{question}"
).partial(format_instructions=parser.get_format_instructions())
# 创建链
chain = prompt | llm | parser
# 调用
result = chain.invoke({"question": "量子计算是什么?"})
print(result)
输出:
{
'question': '量子计算是什么?',
'answer': '量子计算是一种基于量子力学原理的计算范式,使用量子比特进行计算。',
'confidence': 0.95,
'sources': ['Wikipedia', 'Arxiv']
}
6. 结构化输出的优化建议
(1) 提高输出可靠性
- 使用支持结构化输出的模型:
- 优先选择支持工具调用或原生结构化输出的模型(如 GPT-4、Claude)。
- 明确的提示:
- 在提示中清晰指定格式要求。
- 示例:
以 JSON 格式返回,包含字段 question, answer, confidence。
- Pydantic 验证:
- 使用 Pydantic 确保输出符合预期。
- 定义严格的字段类型和约束。
class Answer(BaseModel): confidence: float = Field(ge=0, le=1) # 限制 0-1
(2) 提高性能
- 缓存结果:
- 使用 LangChain 的缓存机制存储常见查询的结构化输出。
from langchain.globals import set_llm_cache from langchain.cache import SQLiteCache set_llm_cache(SQLiteCache(database_path="cache.db"))
- 批量处理:
- 批量生成结构化输出,减少 API 调用。
results = chain.batch([{"question": "q1"}, {"question": "q2"}])
- 异步调用:
- 使用异步模型调用提高并发性能。
result = await chain.ainvoke({"question": "量子计算是什么?"})
(3) 错误处理
- 解析错误处理:
- 使用解析器自动修复不规范输出。
from langchain_core.output_parsers import JsonOutputParser parser = JsonOutputParser() parser.parse_with_prompt(invalid_json, prompt) # 尝试修复
- 回退机制:
- 为结构化输出配置回退逻辑。
chain_with_fallback = chain.with_fallbacks([fallback_chain])
- 默认值:
- 为 Pydantic 字段设置默认值,处理缺失字段。
class Answer(BaseModel): confidence: float = Field(default=0.5)
(4) 监控与调试
- 回调:
- 使用回调记录模型输出和解析过程。
from langchain_core.callbacks import BaseCallbackHandler class OutputCallback(BaseCallbackHandler): def on_llm_end(self, response, **kwargs): print(f"原始输出:{response}")
- LangSmith:
- 分析结构化输出的正确性和性能。
from langsmith import Client chain.invoke(input, config={"callbacks": [Client(api_key="your-langsmith-key")]})
(5) 格式兼容性
- 输出规范化:
- 使用
OutputParser
确保输出与下游系统兼容。
from langchain_core.output_parsers import PydanticOutputParser parser = PydanticOutputParser(pydantic_object=Answer)
- 使用
- 后处理:
- 转换输出格式(如 JSON 转 CSV)。
import csv with open("output.csv", "w") as f: writer = csv.DictWriter(f, fieldnames=result.keys()) writer.writeheader() writer.writerow(result)
7. 注意事项
- 模型支持:
- 并非所有模型支持原生结构化输出,需选择支持工具调用或函数调用的模型。
- 老旧模型可能需要提示工程和解析器。
- 格式一致性:
- 模型可能生成不规范的 JSON,需依赖解析器或验证。
- 测试边缘情况(如空输出、异常值)。
- 性能:
- 结构化输出可能增加推理时间,尤其是复杂格式。
- 使用缓存和批量处理优化。
- 安全性:
- 验证输出以防止注入攻击。
- 避免将敏感信息(如 API 密钥)包含在输出中。
- 成本:
- 结构化输出可能增加 token 使用量,需监控 API 费用。
- 优先缓存高频查询。
8. 与其他模块的结合
- 链(Chains):
- 结构化输出常用于
RetrievalQA
或自定义链。
from langchain.chains import RetrievalQA qa_chain = RetrievalQA.from_chain_type(llm, retriever=retriever, output_parser=parser)
- 结构化输出常用于
- 代理(Agents):
- 代理使用结构化工具调用生成结构化输出。
agent = initialize_agent(tools, llm, agent_type=AgentType.OPENAI_FUNCTIONS)
- 工具(Tools):
- 工具返回结构化输出,供代理处理。
@tool def structured_tool(input: str) -> Answer: return Answer(question=input, answer="...", confidence=0.9)
- 回调(Callbacks):
- 监控结构化输出的生成过程。
- 缓存(Cache):
- 缓存结构化输出,减少重复生成。
- LangSmith:
- 分析结构化输出的正确性和性能。
9. 学习资源
- 官方文档:https://python.langchain.com/docs/guides/structured_output
- GitHub 示例:https://github.com/langchain-ai/langchain
- LangSmith:用于调试结构化输出(https://smith.langchain.com)。
- 社区教程:LangChain 官方博客、YouTube 视频。
10. 总结
- 定义:结构化输出约束语言模型生成特定格式的输出,如 JSON 或 Pydantic 模型。
- 实现方式:
- Pydantic 模型:强类型验证。
StructuredOutputParser
:通用解析。JsonOutputParser
:JSON 解析。- 工具调用:结构化工具输出。
- 提示工程:简单但不可靠。
- 工作原理:定义格式 → 提示模型 → 生成输出 → 解析验证 → 返回结果。
- 应用场景:API 集成、数据提取、数据库操作、前端展示、代理任务、自动化。
- 优化点:可靠性、性能、错误处理、监控、格式兼容性。
- 注意事项:模型支持、格式一致性、性能、安全性、成本。