118.Agent-LangChain核心组件-StructuredOutPut结构化输出-工具策略(ToolStrategy )

**摘要:**本文介绍 LangChain 中 ToolStrategy 结构化输出策略的实现方式。当模型不支持原生结构化输出时,可将 Pydantic、Dataclass、TypedDict 或 JSON Schema 包装成工具,让模型通过工具调用的方式返回结构化数据。文章通过一个产品评价分析示例,演示了如何定义 Pydantic 模型、创建 Agent 并提取结构化结果,同时对比了 ToolStrategy 与 ProviderStrategy 的核心区别,并说明了 ToolStrategy 会在消息历史末尾留下一条 ToolMessage 这一重要副作用。

内容参考于:图灵AI大模型全栈

工具策略是在模型不支持结构化输出时使用的

它的逻辑是通过给下图红框的函数传递一个 pydantic、Dataclass、TypedDict、JSON Schema类型中的一种,下图是用的 pydantic,其它的从上一节中复制过来就行了,给了一个 pydantic 后,它会通过ToolStrategy把传递的pydantic 类型搞成一个工具,这个工具的逻辑就是获取 pydantic 里面的字段,然后从大模型的返回内容中提取出对应的内容

也就是从大模型的回复中提取出下图红框的内容

结构化输出使用场景,多智能体通信、利用大模型的回复调用api、决策,下一步走向,结构化输出在Agent中只能设置一个

代码:

python 复制代码
# ============================================================================
# 【文件主题】使用 ToolStrategy 做结构化输出
# ============================================================================
#
# 【承接上文】
#   之前我们用的是 ProviderStrategy(把 schema 交给模型提供商的原生接口处理)。
#   这篇代码换成了 ToolStrategy(把 schema 包装成一个"工具"让模型调用)。
#
# 【两种策略的核心区别】
#   ProviderStrategy:
#       - 走模型 API 原生的 response_format / function calling 通道
#       - 由模型提供商在推理层做约束,输出稳定、准确率高
#       - 依赖提供商支持该能力
#
#   ToolStrategy:
#       - 把 schema 包装成"一个工具",让模型"调用这个工具"来返回结构化数据
#       - 本质上是 Agent 的 tool-calling 循环的一部分
#       - 兼容性更好(几乎所有支持工具调用的模型都能用)
#       - 代价:多一步工具调用,可能多消耗一点 token
#
# 【一个重要副作用】
#   用 ToolStrategy 时,消息历史的最后一条会是一条 ToolMessage,
#   内容是 "Returning structured response: ..."(见文件末尾的说明)。
#   这是因为"结构化数据"被当成"工具调用的返回结果"塞进了消息流。
#   而 ProviderStrategy 不会产生这条 ToolMessage。
# ============================================================================
从 pydantic 导入 BaseModel 和 Field:
- BaseModel:Pydantic 模型的基类,继承它就能获得校验、序列化等能力
- Field:给字段附加元数据(description、ge、le 等约束)
from pydantic import BaseModel, Field
从 typing 导入 Literal:
Literal["a", "b"] 表示"值只能是 'a' 或 'b' 中的一个"。
这在结构化输出里很有用------相当于给模型一个"枚举"约束,
让模型只能在给定选项里挑,避免它自由发挥造出奇怪的分类。
from typing import Literal
create_agent:LangChain v1 构建 Agent 的高层工厂函数(底层基于 LangGraph)
from langchain.agents import create_agent
ToolStrategy:结构化输出的策略之一
含义:把 schema 包装成一个工具,让模型通过"工具调用"来产出结构化数据
与 ProviderStrategy 相对(后者走提供商原生通道)
from langchain.agents.structured_output import ToolStrategy
ChatQwen:LangChain 为通义千问(Qwen)封装的专用客户端
from langchain_qwq import ChatQwen
load_dotenv:从 .env 文件加载环境变量
from dotenv import load_dotenv
os:读取环境变量
import os
加载 .env 中的环境变量(API Key、Base URL 等敏感信息不写死在代码里)
load_dotenv()
初始化大模型客户端
llm = ChatQwen(
model="qwen3.7-flash",                        # 模型名称
api_key=os.getenv("DASHSCOPE_API_KEY"),       # 从环境变量读 DashScope 的 API Key
base_url=os.getenv("DASHSCOPE_BASE_URL")      # 从环境变量读 DashScope 兼容端点
)
============================================================================
【关键说明】ToolStrategy 也支持四种 schema 定义方式
----------------------------------------------------------------------------
和 ProviderStrategy 完全一致:
1. Pydantic 模型
2. Dataclass
3. TypedDict
4. JSON Schema
也就是说,无论你用哪种方式定义 schema,只要外面套上 ToolStrategy(...),
框架就会把它转成"一个工具",让模型调用。
本文件用 Pydantic 举例。
============================================================================
定义 Pydantic 模型,描述"产品评价分析结果"的结构
class ProductReview(BaseModel):
"""产品评价分析结果"""
# rating:产品评分
#   - int | None:类型可以是整数,也可以是 None(表示模型无法判断评分)
#     这里允许 None 是为了容错------如果用户评价里没提分数,模型可以返回 null
#   - Field(ge=1, le=5):约束取值范围,ge = greater or equal(≥1),
#     le = less or equal(≤5)。如果模型返回 0 或 6,Pydantic 会报错。
#     这类约束也会进入 JSON Schema,一起发给模型,帮它自我约束。
rating: int | None = Field(description="产品评分", ge=1, le=5)

# sentiment:情感倾向
#   - Literal["positive", "negative"]:值只能是这两个字符串之一。
#     这是最实用的约束方式------相当于给模型一个"二选一"的选项,
#     避免模型自由发挥出 "happy"/"good"/"正向" 这类不受控的值。
#     在 JSON Schema 里会变成 {"enum": ["positive", "negative"]}。
sentiment: Literal["positive", "negative"] = Field(description="评价的情感倾向")

# key_points:评价的关键要点列表
#   - list[str]:字符串列表。
#     模型会从评价里提取若干要点,比如 ["产品很棒", "发货快", "价格偏高"]。
#     列表类型在结构化输出里很常见,用来承载"不定数量"的抽取结果。
key_points: list[str] = Field(description="评价的关键要点")
创建 Agent
agent = create_agent(
# 传入模型客户端
model=llm,
# response_format:指定结构化输出的 schema 和策略
#
# 这里用 ToolStrategy(ProductReview),意思是:
#   "把 ProductReview 这个 schema 包装成一个工具,
#    让模型通过工具调用的方式来产出结构化数据。"
#
# 与 ProviderStrategy 的区别在运行时体现为:
#   - ProviderStrategy:模型直接输出 JSON 文本,框架解析后写入 structured_response
#   - ToolStrategy:模型发起一次工具调用(call),工具返回结构化数据,
#     然后这条 ToolMessage 会进入消息历史
#
# 为什么有时候要用 ToolStrategy?
#   1. 某些模型/提供商对原生 response_format 支持不好 → 用工具方式更稳
#   2. 想把"结构化提取"复用成一个"子智能体"或"工具"(见下面的注释)
#   3. 需要和多智能体架构结合:主智能体分发给子智能体,子智能体返回结构化结果
response_format=ToolStrategy(ProductReview)
)
执行代理
传入一条用户消息:让它分析一条产品评价。
消息内容里包含:
- 5 星(对应 rating=5)
- "很棒"(对应 sentiment=positive)
- "发货很快"、"有点贵"(对应 key_points)
result = agent.invoke({
"messages": [{"role": "user", "content": "分析这条产品评价:'很棒的5星产品。发货很快,但是有点贵'"}]
})
从 state 里取出结构化结果。
因为用的是 ToolStrategy,框架已经把工具调用的返回值反序列化成了
ProductReview 的实例(Pydantic 模型),所以可以直接 .rating / .sentiment 访问。
print(result["structured_response"])
============================================================================
【重要注意点】ToolStrategy 会在消息历史末尾留下一条 ToolMessage
----------------------------------------------------------------------------
与 ProviderStrategy 不同,ToolStrategy 是通过"工具调用"完成结构化输出的。
因此 Agent 执行完之后,result["messages"] 的最后一条会是一条 ToolMessage,
内容是 "Returning structured response: ...",形如:
ToolMessage(
content="Returning structured response: rating=5 sentiment='positive' key_points=['产品很棒', '发货速度快', '价格偏高']",
name='ProductReview',
id='475c134e-39c7-44d3-a2ee-563b3c05c069',
tool_call_id='call_07384fcbdd3b494ca4b7aeff'
)
【为什么会有这条消息?】
因为 ToolStrategy 把结构化输出建模为"工具调用":
- 模型先发起一个 tool_call(要调用 ProductReview 这个"工具")
- 框架执行这个"工具"(其实就是把参数结构化 + 校验)
- 工具返回结果 → 作为一条 ToolMessage 进入消息历史
这是 OpenAI/Anthropic 等 tool-calling 协议的标准流程。
【这意味着什么?】
1. 如果你把 result["messages"] 直接拿去做下一步对话,
这条 ToolMessage 会一起被带进去,影响后续上下文。
2. 如果只是想要结构化结果,直接读 result["structured_response"] 就行,
不必关心这条 ToolMessage。
3. 用 ProviderStrategy 时不会产生这条消息------这是两种策略的一个明显区别。
【什么时候这个区别会真正影响你?】
- 多轮对话:ToolMessage 会进入历史,可能被下一轮 LLM 看到
- 状态持久化:checkpointer 会把这条消息也存下来
- 调试:看消息历史时,会看到这条"人造"的工具消息
============================================================================
打印完整 result(含 messages、structured_response 等),
方便你直观看到上面说的 ToolMessage 结尾。
print(result)

相关推荐
桃西西呀1 小时前
别被"秒回"骗了:推理模型背后那只"吞金兽",吃的是你看不见的预算
人工智能·llm·ai编程
coderMax1 小时前
MCP 架构概览
agent
掰头战士1 小时前
多重影分身!恨不得把一个agent掰成两个? 还真能干!
typescript·llm·agent
龙亘川1 小时前
AI + 人社新范式:智慧人社系统如何为民生治理数字化难题提供帮助
人工智能·智慧城市·数据可视化·政务
水如烟1 小时前
孤能子视角:蓝星文明篇·市——交换机制的运行化:从偶发交换到日常运行的制度化
人工智能
技灵AI1 小时前
Wan 3.0 API怎么做多参考商品视频?从图片、视频、音频分工到30秒交付
人工智能·prompt·aigc·音视频·wan 3.0
武子康1 小时前
CLAUDE.md 引用 AGENTS.md 后,两边真的读到同一套规则吗?
人工智能·llm·agent
动恰客流统计1 小时前
线下零售数字化浪潮下,客流统计的3个核心发展趋势
大数据·前端·人工智能
johnsong2 小时前
效率的边界:当推理突破遇见语言革命
人工智能·语言模型
染指11102 小时前
119.Agent-LangChain核心组件-Runtime运行时
人工智能·langchain·agent