ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

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

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中只能设置一个代码# # 【文件主题】使用 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 # - BaseModelPydantic 模型的基类继承它就能获得校验、序列化等能力 # - Field给字段附加元数据description、ge、le 等约束 from pydantic import BaseModel, Field # 从 typing 导入 Literal # Literal[a, b] 表示值只能是 a 或 b 中的一个。 # 这在结构化输出里很有用——相当于给模型一个枚举约束 # 让模型只能在给定选项里挑避免它自由发挥造出奇怪的分类。 from typing import Literal # create_agentLangChain v1 构建 Agent 的高层工厂函数底层基于 LangGraph from langchain.agents import create_agent # ToolStrategy结构化输出的策略之一 # 含义把 schema 包装成一个工具让模型通过工具调用来产出结构化数据 # 与 ProviderStrategy 相对后者走提供商原生通道 from langchain.agents.structured_output import ToolStrategy # ChatQwenLangChain 为通义千问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( modelqwen3.7-flash, # 模型名称 api_keyos.getenv(DASHSCOPE_API_KEY), # 从环境变量读 DashScope 的 API Key base_urlos.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(ge1, le5)约束取值范围ge greater or equal≥1 # le less or equal≤5。如果模型返回 0 或 6Pydantic 会报错。 # 这类约束也会进入 JSON Schema一起发给模型帮它自我约束。 rating: int | None Field(description产品评分, ge1, le5) # 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( # 传入模型客户端 modelllm, # response_format指定结构化输出的 schema 和策略 # # 这里用 ToolStrategy(ProductReview)意思是 # 把 ProductReview 这个 schema 包装成一个工具 # 让模型通过工具调用的方式来产出结构化数据。 # # 与 ProviderStrategy 的区别在运行时体现为 # - ProviderStrategy模型直接输出 JSON 文本框架解析后写入 structured_response # - ToolStrategy模型发起一次工具调用call工具返回结构化数据 # 然后这条 ToolMessage 会进入消息历史 # # 为什么有时候要用 ToolStrategy # 1. 某些模型/提供商对原生 response_format 支持不好 → 用工具方式更稳 # 2. 想把结构化提取复用成一个子智能体或工具见下面的注释 # 3. 需要和多智能体架构结合主智能体分发给子智能体子智能体返回结构化结果 response_formatToolStrategy(ProductReview) ) # 执行代理 # 传入一条用户消息让它分析一条产品评价。 # 消息内容里包含 # - 5 星对应 rating5 # - 很棒对应 sentimentpositive # - 发货很快、有点贵对应 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( # contentReturning structured response: rating5 sentimentpositive key_points[产品很棒, 发货速度快, 价格偏高], # nameProductReview, # id475c134e-39c7-44d3-a2ee-563b3c05c069, # tool_call_idcall_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)
返回列表