本文详解如何通过 LangChain 的 JsonOutputParser 或 PydanticOutputParser,让 LLM Agent 稳定输出符合要求的节假日字典列表(如 [{"name": "New Year's Day", "date": "2024-01-01"}, ...]),解决 StructuredOutputParser 仅返回单条重复键覆盖结果的问题。
本文详解如何通过 langchain 的 `jsonoutputparser` 或 `pydanticoutputparser`,让 llm agent 稳定输出符合要求的节假日字典列表(如 `[{"name": "new year's day", "date": "2024-01-01"}, ...]`),解决 `structuredoutputparser` 仅返回单条重复键覆盖结果的问题。
在使用 LangChain 构建 LLM Agent 提取节假日信息时,一个常见痛点是:期望获得多个节日及其日期组成的列表(如 List[Dict[str, str]]),但实际只得到最后一个条目。根本原因在于原始代码中使用的 StructuredOutputParser 基于 ResponseSchema,其底层将同名字段(如重复的 "holiday" 和 "date")解析为单个字典——JSON 中键名不可重复,后续值会覆盖前序值,最终仅保留末项(如 {'holiday': 'Christmas Day', 'date': '2024-12-25'})。
要真正实现「列表化结构化输出」,必须升级为支持嵌套对象与数组的解析器。推荐使用 JsonOutputParser(基于 Pydantic v1)或 PydanticOutputParser(兼容 Pydantic v2),二者均能明确定义嵌套数据模型,强制 LLM 按指定 JSON Schema 生成合规响应。
✅ 正确做法:定义嵌套 Pydantic 模型 + JsonOutputParser
以下为完整可运行示例(适配 LangChain ≥ 0.1.0):
from langchain.agents import AgentType, initialize_agent, load_tools
from langchain_community.chat_models import ChatOpenAI
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.pydantic_v1 import BaseModel, Field # 注意:LangChain 当前默认依赖 Pydantic v1
from langchain.prompts import ChatPromptTemplate
# 1. 定义结构化数据模型(关键!)
class Holiday(BaseModel):
name: str = Field(description="节日全称,例如 'New Year's Day' 或 'Ascension Day'")
date: str = Field(description="ISO 格式日期字符串,例如 '2024-01-01'")
class HolidaysResponse(BaseModel):
holidays: list[Holiday] = Field(description="节假日字典列表,每个元素含 name 和 date 字段")
# 2. 初始化输出解析器(自动注入 format_instructions)
output_parser = JsonOutputParser(pydantic_object=HolidaysResponse)
format_instructions = output_parser.get_format_instructions()
# 3. 构建 Prompt(显式嵌入格式指令)
prompt_template = """
你是一个专业的节假日信息提取助手。请使用 serpapi 工具搜索指定地区和年份的公共假期,并严格按以下 JSON 格式返回结果:
{format_instructions}
用户查询:```{input}```
"""
prompt = ChatPromptTemplate.from_template(prompt_template)
# 4. 初始化 Agent(保持原有逻辑)
tools = load_tools(["serpapi"])
llm = ChatOpenAI(model="gpt-4", temperature=0.0)
agent = initialize_agent(
tools=tools,
llm=llm,
agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True,
handle_parsing_errors=True
)
# 5. 执行并解析
input_query = "holidays in Berlin in 2024"
messages = prompt.format_messages(
input=input_query,
format_instructions=format_instructions
)
raw_response = agent.run(messages)
parsed_result = output_parser.parse(raw_response) # ✅ 返回 HolidaysResponse 实例
# 6. 提取标准字典列表(供下游使用)
holidays_list = [h.dict() for h in parsed_result.holidays]
print(holidays_list)
# 输出示例:
# [
# {"name": "New Year's Day", "date": "2024-01-01"},
# {"name": "Good Friday", "date": "2024-03-29"},
# {"name": "Labour Day", "date": "2024-05-01"},
# ...
# ]⚠️ 关键注意事项
- 模型选择:gpt-4 或 gpt-3.5-turbo-1106 及以上版本对 JSON 格式遵循更稳定;避免使用早期 turbo 版本。
- Prompt 引导强度:务必在 system prompt 中明确要求“必须输出且仅输出 JSON,不加任何解释性文字”,并在 format_instructions 后添加强调句(如 "注意:不要输出任何 JSON 以外的内容!")。
- 错误处理增强:启用 handle_parsing_errors=True 仅捕获解析异常,建议额外包裹 try/except 并对失败响应做重试或日志记录。
- Pydantic v2 用户:若项目已升级至 Pydantic v2,请改用 from langchain_core.output_parsers import PydanticOutputParser,并确保 BaseModel 来自 pydantic.BaseModel(非 pydantic_v1),其余逻辑一致。
✅ 总结
StructuredOutputParser 适用于扁平单对象输出,而多条目列表结构必须依赖 JsonOutputParser 或 PydanticOutputParser + 显式嵌套模型定义。通过精准建模(HolidaysResponse 包含 List[Holiday]),配合强约束 Prompt 和高兼容性 LLM,即可可靠获取结构清晰、可直接序列化/存库的节假日数据列表,彻底规避键名覆盖陷阱。

















