登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

LangChain structured output 怎样固定工具返回 schema

来源:17golang原创

时间:2026-11-01 03:29:24 165浏览 收藏

如果 agent 调用工具后仍然返回一段不稳定的自然语言,问题通常不在“提示词写得不够长”,而在 schema 绑定错了位置。LangChain 里至少有两个边界:工具的输入参数 schema,以及 agent 最终交给业务代码的结构化响应。要固定后者,应把 Pydantic 模型交给 response_format,需要明确走工具调用时使用 ToolStrategy,再从结果的 structured_response 读取。

官方地址:https://docs.langchain.com/

下面用一个订单查询工具作为例子。示例中的指标是用于设计验收的基线,不代表 LangChain 官方性能承诺。

先把两个 schema 边界分开

工具参数描述“模型怎样调用工具”,例如订单号是什么类型;结构化响应描述“agent 最终交给应用的结果”,例如订单状态、是否需要人工处理和解释。只给工具函数写返回类型,并不会自动把整个 agent 的最终输出固定成这个类型。

边界绑定位置业务代码读取
工具输入类型注解或 args_schema工具函数参数
agent 最终结果response_format + Pydanticstate["structured_response"]
LangChain 工具输入 schema 与 agent 结构化响应的边界关系图
图1:结构说明图,展示工具输入 schema 与 agent 最终响应 schema 的两条边界,不是运行截图。

用 Pydantic 把业务结果写成可校验结构

先定义一个足够小的结果模型。字段描述会进入模型可见的 schema,枚举和范围约束则让错误结果有明确的修正方向。

from typing import Literal

from pydantic import BaseModel, Field


class OrderDecision(BaseModel):
    """订单查询后交给业务层的固定结果。"""

    order_id: str = Field(description="用户提供的订单号")
    status: Literal["pending", "paid", "shipped", "cancelled"]
    needs_human: bool = Field(description="是否需要人工跟进")
    reason: str = Field(description="给业务人员看的简短原因")


# 约束写在模型中,后续的 agent 不必依赖字符串解析。
print(OrderDecision.model_json_schema()["properties"].keys())

这里的 Literal 限制状态集合,needs_human 明确为布尔值。不要为了“兼容模型”把所有字段都改成字符串,否则结构看似固定,业务层仍要二次猜测。

用 ToolStrategy 固定 agent 的最终响应

当模型支持工具调用、但不一定支持 provider-native structured output 时,显式使用 ToolStrategy 最直观。工具只负责查数据,response_format 负责把 agent 的最终结果收口。

from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy


def lookup_order(order_id: str) -> str:
    """按订单号查询脱敏后的订单状态。"""
    # 示例返回固定文本;真实项目应在这里访问订单服务并处理超时。
    return f"订单 {order_id} 已发货,物流单号为 SF123456"


agent = create_agent(
    model="openai:gpt-5.5",
    tools=[lookup_order],
    response_format=ToolStrategy(OrderDecision),
    system_prompt=(
        "你是订单助手。只能根据工具返回的信息填写 schema,"
        "不能补造订单号、状态或物流事实。"
    ),
)

result = agent.invoke({
    "messages": [{
        "role": "user",
        "content": "查询订单 A-1007 的状态,并告诉我是否需要人工处理。"
    }]
})

# 业务层读取结构化对象,而不是解析最后一条自然语言消息。
decision = result["structured_response"]
print(decision.order_id, decision.status, decision.needs_human)

这个组合形成两步约束:模型先按工具定义生成调用参数,工具执行后再按 OrderDecision 生成唯一的结构化响应。若模型或 provider 不支持原生结构化输出,LangChain 会用工具调用策略完成这一步。

让校验失败反馈给模型,而不是静默吞掉

ToolStrategy 默认会处理结构化输出错误。生产代码可以把提示改成业务语言,让重试针对字段约束,而不是无限重复同一个请求。

from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy


agent = create_agent(
    model="openai:gpt-5.5",
    tools=[lookup_order],
    response_format=ToolStrategy(
        schema=OrderDecision,
        # 只说明如何修正格式,不把业务事实写死在错误提示里。
        handle_errors="请重新输出单个 OrderDecision:status 必须是允许的四个值,needs_human 必须是布尔值。",
        tool_message_content="订单结果已按固定 schema 整理。",
    ),
)

# 真实项目应记录重试次数,避免把模型错误变成无上限成本。
result = agent.invoke({
    "messages": [{"role": "user", "content": "查询订单 A-1007"}]
})
print(result["structured_response"].model_dump())

如果应用希望所有异常直接抛出,可以把 handle_errors 设为 False;如果只想捕获某一类异常,也可以传入异常类型或自定义处理函数。注意:重试只能修正结构,不会替你验证订单服务返回的事实。

LangChain structured output schema 校验失败后反馈并重试的流程图
图2:结果说明图,展示 schema 校验失败、错误反馈与重新生成结构化结果的静态链路,不是运行截图。

用一组小指标判断 schema 是否真的稳定

可以把验收拆成三项,而不是只看“这次返回成功”。例如对一组包含已发货、待付款和信息不足的固定样例,记录结构通过率、人工兜底率和平均重试次数:

指标基线写法目标判断
结构通过率自然语言 + 手写解析应接近全部样例都能通过模型校验
人工兜底率字段缺失时靠业务猜测只在事实缺失或规则冲突时触发
平均重试次数默认提示不记录按模型、字段约束和输入类型分组观察

不要把“结构通过率提高”直接等同于“答案正确”。schema 只能保证形状、类型和部分范围;订单状态是否真实,仍要由工具服务、权限和业务规则负责。

工具多、模型能力不同的时候怎么选

如果模型供应商原生支持结构化输出,可以直接传 schema 类型,让 LangChain 自动选择 provider strategy;需要兼容更多支持 tool calling 的模型时,显式使用 ToolStrategy。当 agent 同时绑定普通工具和结构化响应时,模型还必须支持两者并用。

最后记住三个边界:

  • 工具的返回字符串不是最终 schema;要固定 agent 结果,就配置 response_format。
  • structured_response 是结果读取入口,不能再从自然语言消息中二次正则解析。
  • 多个结构化结果、字段校验失败和真实业务事实错误是三类问题,应分别记录和处理。

常见问题

只给工具函数加返回类型可以吗? 可以帮助阅读和静态检查,但不能替代 agent 的 response_format。

为什么用了 Pydantic 仍然会重试? Pydantic 负责校验结果;模型第一次生成的字段仍可能越界或缺失,handle_errors 会把错误反馈给模型。

工具本身也需要输入 schema 怎么办? 给工具参数添加类型注解,或使用 args_schema 绑定一个独立的 Pydantic 输入模型;它和最终响应模型可以不同。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>