数据截至 (上游 commit 2019bf5ebe50)
第 4 章:统一模型入口、工具、结构化输出
本章讲三件让 agent"接上现实"的事:怎么用一行连上任意厂商的模型、工具节点怎么执行工具、以及怎么让模型吐出可解析的结构化结果。
4.1 init_chat_model:一行连上任意厂商
要解决的问题: OpenAI、Anthropic、Google 的 SDK 各不相同。你想"换个厂商不改业务代码",就需要一个统一入口。
用法: 一个 "provider:model" 字符串(factory.py:868):
model = init_chat_model("openai:gpt-5.5")
model = init_chat_model("anthropic:claude-sonnet-4-5-20250929")
怎么实现的。 核心三步(chat_models/base.py:510-518):
def _init_chat_model_helper(model, *, model_provider=None, **kwargs):
model, model_provider = _parse_model(model, model_provider) # 1. 拆出 provider
creator_func = _get_chat_model_creator(model_provider) # 2. 找到该 provider 的工厂
return creator_func(model=model, **kwargs) # 3. 造出模型对象
- 没写 provider 前缀时,
_attempt_infer_model_provider(chat_models/base.py:521)按模型名前缀猜(比如gpt-→ openai)。 _get_chat_model_creator查_BUILTIN_PROVIDERS注册表(chat_models/base.py:78-99),拿到(包名, 类名, ...),然后懒加载那个集成包(_import_module,chat_models/base.py:103-118)。所以你只需装你用到的那个langchain-openai,不必装全部。
可配置模型。 不传具体 model、或显式给 configurable_fields,会返回 _ConfigurableModel(chat_models/base.py:493-507):模型/温度等可以在 .invoke(..., config={"configurable": {...}}) 时运行时切换,适合"同一段代码跑不同模型做对比"。
4.2 工具:从函数到可执行节点
你传给 create_agent 的 tools 可以是裸函数、BaseTool 或 dict(factory.py:842)。它们最终进 tools 节点(底层 ToolNode,tools/tool_node.py),由该节点在运行时执行并把结果包成 ToolMessage。
模型怎么知道有哪些工具?在 _get_bound_model 里把工具绑上模型(factory.py:1402):
request.model.bind_tools(final_tools, **bind_kwargs)
bind_tools(language_models/chat_models.py:2355)把工具的名字、描述、参数 schema 转成厂商要的格式塞进请求——这样模型才能"知道有这些工具、按 schema 填参数"。工具节点支持 InjectedState / InjectedStore / ToolRuntime 等注入,让工具能读到 agent 的 state(tools/tool_node.py:3 re-export 这几个;另一个常用的 InjectedToolCallId 在 core/langchain_core/tools/__init__.py:48)。
4.3 结构化输出:让模型吐出能解析的结果
问题: 你想要模型返回一个 Weather(temp=20, sky="sunny") 对象,而不是一段自由文本。怎么逼模型"按 schema 说话"?
LangChain 提供三种策略(structured_output.py:ToolStrategy:196 / ProviderStrategy:262 / AutoStrategy:448),由 response_format 参数选择:
| 策略 | 思路 | 何时用 |
|---|---|---|
ProviderStrategy | 用厂商原生的结构化输出能力(如 OpenAI 的 response_format) | 模型支持时,最干净 |
ToolStrategy | 把目标 schema 伪装成一个工具,逼模型"调用"它,从 tool_call 的参数里取出结构体 | 模型不支持原生时 |
AutoStrategy | 先不定,运行时按模型能力自动选上面两种 | 默认,省心 |
自动选择的逻辑(factory.py:1355-1370):
if isinstance(response_format, AutoStrategy):
if _supports_provider_strategy(request.model, tools=request.tools):
effective_response_format = ProviderStrategy(schema=...) # 模型支持原生 → 用它
else:
effective_response_format = ToolStrategy(schema=...) # 不支持 → 退回"伪装工具"
create_agent 入口处,裸 schema(比如直接传个 Pydantic 类)会被包进 AutoStrategy,但会先转成 ToolStrategy 把那个"伪工具"提前建好,以便建图时就把工具登记好(factory.py:1024-1036),运行时再视模型能力可能换成 ProviderStrategy。
ToolStrategy 的解析。 模型"调用"了结构化伪工具后,_handle_model_output 把对应 tool_call 的参数喂给 binding 解析成对象,塞进 state["structured_response"](factory.py:1256-1276)。一旦有了 structured_response,条件边就让循环退出(回顾第 1 章 _make_model_to_tools_edge 第 5 条)。
容错。 解析失败或模型一次吐了多个结构化输出,会按策略决定"报错"还是"塞一条错误 ToolMessage 让模型重试"(factory.py:1232-1294,MultipleStructuredOutputsError / StructuredOutputValidationError)。
4.4 巧妙之处
- provider 懒加载。
_BUILTIN_PROVIDERS+_import_module让你按需装集成包,核心库不背所有厂商 SDK 的依赖(chat_models/base.py:78-118)。 - 结构化输出"双轨自适应"。 同一份 schema,模型强就走原生、模型弱就降级成"伪装工具",对用户透明(
factory.py:1355-1370)。这是"模型能力差异"在框架层被抹平的好例子。 - 结构化输出复用工具循环。 把"要结构化结果"实现成"调一个特殊工具",于是结构化输出和普通工具调用共用同一套循环和退出逻辑,不需要单独的代码路径(
factory.py:1032-1036+_make_model_to_tools_edge)。
4.5 边界与局限
- 厂商能力靠"模型名正则"兜底判断(
factory.py:174-194的FALLBACK_MODELS_WITH_STRUCTURED_OUTPUT):当 model-profile 数据缺失时,按名字猜某模型是否支持原生结构化输出——新模型名若没进这张正则表,可能被误判而走 ToolStrategy。 - 真正的工具执行细节(注入、并发、错误包装)在
ToolNode,其主体来自 LangGraph 的 prebuilt(tools/tool_node.py大量从langgraph.prebuiltre-export),本章未深入。
4.6 代码地图
| 主题 | 文件路径 | 关键符号 |
|---|---|---|
| 统一模型入口 | libs/langchain_v1/langchain/chat_models/base.py | init_chat_model / _init_chat_model_helper |
| provider 注册表 + 懒加载 | libs/langchain_v1/langchain/chat_models/base.py | _BUILTIN_PROVIDERS / _import_module / _get_chat_model_creator |
| provider 推断 | libs/langchain_v1/langchain/chat_models/base.py | _attempt_infer_model_provider |
| 可配置模型 | libs/langchain_v1/langchain/chat_models/base.py | _ConfigurableModel |
| 绑定工具 | libs/core/langchain_core/language_models/chat_models.py | BaseChatModel.bind_tools |
| 工具节点 | libs/langchain_v1/langchain/tools/tool_node.py | ToolNode / InjectedState |
| 结构化输出策略 | libs/langchain_v1/langchain/agents/structured_output.py | ToolStrategy / ProviderStrategy / AutoStrategy |
| 策略自动选择 | libs/langchain_v1/langchain/agents/factory.py | _get_bound_model / _supports_provider_strategy |
| 结构化输出解析 | libs/langchain_v1/langchain/agents/factory.py | _handle_model_output |