☰
118.Agent-LangChain核心组件-StructuredOutPut结构化输出-工具策略(ToolStrategy )
2026/9/25 18:20:06 网站建设 项目流程

摘要:本文介绍 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中只能设置一个

代码:

# ============================================================================ # 【文件主题】使用 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)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询