引言
在大部分使用 AI 的场景中,AI 往往是以“对话”的模式呈现在我们的使用体验里。我们向 AI 提出问题,AI 给出回答;我们继续追问,AI 再基于上下文进行补充。这种交互方式非常自然,也非常接近人与人之间的沟通方式,因此成为了大多数 AI 产品最常见的形态。
但是,当我们开始设计 Agent,或者构建 AI 驱动的程序时,仅仅让大模型“像聊天一样回答”往往是不够的。
在真实的软件系统中,程序需要的是可以被稳定解析、可以被验证、可以被自动执行的结果。换句话说,我们不只是希望大模型“说得像那么回事”,而是希望它按照我们预先约定好的格式,产出结构化的数据。只有这样,AI 的输出才能更好地进入后续流程,被程序消费、判断和执行。
例如,一个普通的对话式 AI 可能会这样回答:
我认为用户想要查询天气,你可以调用天气接口获取北京今天的天气情况。
{"intent": "query_weather","tool": "weather_api","param": {"city": "北京","date" : "today"}}
一 langchain中的结构化输出
from pydantic import BaseModel, Fieldfrom langchain.agents import create_agentclass ContactInfo(BaseModel):"""联系人信息"""name: str = Field(description="联系人姓名")email: str = Field(description="联系人邮箱")phone: str = Field(description="联系人电话")agent = create_agent(model="openai:gpt-4o-mini",tools=[],response_format=ContactInfo)result = agent.invoke({"messages": [{"role": "user","content": "张三的邮箱是 zhangsan@example.com,电话是 13800000000"}]})print(result["structured_response"])
ContactInfo(name="张三",email="zhangsan@example.com",phone="13800000000")
二 langchain如何实现的结构化输出
LangChain 实现结构化输出,核心思路是:先定义一个 Schema,然后让模型按照这个 Schema 返回结果。
2.1 create_agent 归一化 response_format
if response_format is None:initial_response_format = Noneelif isinstance(response_format, (ToolStrategy, ProviderStrategy)):initial_response_format = response_formatelif isinstance(response_format, AutoStrategy):initial_response_format = response_formatelse:initial_response_format = AutoStrategy(schema=response_format)
2.2 把 schema 包成 _SchemaSpec
Pydantic modeldataclassTypedDictJSON Schema dictUnion schema,主要用于 ToolStrategy
if isinstance(schema, dict):schema_kind = "json_schema"json_schema = schemaelif issubclass(schema, BaseModel):schema_kind = "pydantic"json_schema = schema.model_json_schema()elif is_dataclass(schema):schema_kind = "dataclass"json_schema = TypeAdapter(schema).json_schema()elif is_typeddict(schema):schema_kind = "typeddict"json_schema = TypeAdapter(schema).json_schema()else:raise ValueError(...)
2.3 两种结构化输出策略ProviderStrategy 和 ToolStrategy
ProviderStrategy: 使用模型提供商原生 structured output 能力 ToolStrategy: 使用 tool calling 模拟 structured output
create_agent(model="openai:gpt-4o",response_format=ContactInfo,)
AutoStrategy(schema=ContactInfo)ifisinstance(response_format, AutoStrategy):if _supports_provider_strategy(request.model, tools=request.tools):effective_response_format = ProviderStrategy(schema=response_format.schema)else:effective_response_format = ToolStrategy(schema=response_format.schema)
AutoStrategy-> 如果模型支持原生 structured outputProviderStrategy-> 否则ToolStrategy
2.4 ProviderStrategy 的实现----原生能力的依赖

schema-> ProviderStrategyBinding-> model.bind_tools(..., structured output kwargs)-> provider 原生返回结构化结果-> LangChain parse / validate-> state["structured_response"]
kwargs = effective_response_format.to_model_kwargs()return (request.model.bind_tools(final_tools,strict=True,**kwargs,**request.model_settings,),effective_response_format,)
ifisinstance(effective_response_format, ProviderStrategy):if not output.tool_calls:provider_strategy_binding = ProviderStrategyBinding.from_schema_spec(effective_response_format.schema_spec)structured_response = provider_strategy_binding.parse(output)return {"messages": [output],"structured_response": structured_response,}return {"messages": [output]}
model.invoke(...)-> provider 原生 structured output-> AIMessage-> ProviderStrategyBinding.parse(output)-> Pydantic / dict-> structured_response
2.4 ToolStrategy 的实现----巧用tool args
把“结构化输出 schema”伪装成一个 tool。让模型通过 tool_call 提交结构化结果。LangChain 不真的执行这个 tool,而是拦截这个 tool_call,解析 args。
class ContactInfo(BaseModel):name: stremail: str
Tool name: ContactInfoTool schema:name: stringemail: string
structured_output_tools = {}if tool_strategy_for_setup:for response_schema in tool_strategy_for_setup.schema_specs:structured_tool_info = OutputToolBinding.from_schema_spec(response_schema)structured_output_tools[structured_tool_info.tool.name] = structured_tool_info
final_tools = list(request.tools)if isinstance(effective_response_format, ToolStrategy):structured_tools = [info.tool for info in structured_output_tools.values()]final_tools.extend(structured_tools)
request.model.bind_tools(final_tools,tool_choice=...)
用户传入的普通工具middleware 动态工具structured output artificial tools
模型调用后,ToolNode 真正执行
模型调用后,LangChain 拦截 tool_call, parse args 成 structured_response, 不进入 ToolNode 执行真实工具
AIMessage(tool_calls=[{"name": "ContactInfo","args": {"name": "John", "email": "john@example.com"},"id": "call_123"}])
structured_tool_calls = [tc for tc in output.tool_callsif tc["name"] in structured_output_tools]
tool_call = structured_tool_calls[0]structured_tool_binding = structured_output_tools[tool_call["name"]]structured_response = structured_tool_binding.parse(tool_call["args"])
return {"messages": [output,ToolMessage(content=tool_message_content,tool_call_id=tool_call["id"],name=tool_call["name"],),],"structured_response": structured_response,}
Returning structured response: {...}AIMessage(tool_calls=[{"name": "ContactInfo", ...},{"name": "EventDetails", ...},])
iflen(structured_tool_calls) > 1:exception = MultipleStructuredOutputsError(tool_names, output)should_retry, error_message = _handle_structured_output_error(...)
ToolMessage(content=error_message,tool_call_id=tc["id"],name=tc["name"],)
{"messages": [output, *tool_messages]}2.5 ProviderStrategy vs ToolStrategy 对比
ProviderStrategyBinding.parse(output) | OutputToolBinding.parse(tool_call["args"]) | |
state["structured_response"] | state["structured_response"] |
2.6 Model.with_structured_output() 的实现
structured_llm = model.with_structured_output(ContactInfo)structured_llm.invoke("...")
with_structured_output:直接包装一个 ChatModel输入 prompt输出结构化对象create_agent(response_format=...):在 agent model/tool 循环中内联结构化输出输出放到 final state["structured_response"]
2.7 整体的源码级别的流程图

三 总结
ProviderStrategy:让 provider 原生强制模型按 schema 输出,LangChain 解析并验证 AIMessage。 ToolStrategy:把 schema 包装成人工 tool,让模型通过 tool_call 提交结构化参数,LangChain 拦截 tool_call,parse args,成功后写入 state["structured_response"],失败则用 ToolMessage 反馈错误并让模型重试。
夜雨聆风