LangChain 02:模型、消息与结构化输出
1、本篇任务:让模型输出成为程序可以依赖的合同
上一课只证明 Agent 能运行。本课暂时不接业务工具,只完成一个“客服分诊器”:输入用户问题,输出意图、紧急程度、是否需要人工和给用户的答复。
本课的核心不是学更多模型参数,而是理解三层合同:消息决定模型看到了什么;schema 决定程序期待什么;业务校验决定结果是否真的可用。
2、消息不是一段字符串,而是一条执行记录
一次工具型对话通常包含四种消息:
| 类型 | 由谁产生 | 作用 |
|---|---|---|
| System | 应用 | 角色、边界、稳定规则 |
| Human/User | 用户 | 当前请求和用户提供的数据 |
| AI/Assistant | 模型 | 文本、推理结果或 tool call |
| Tool | 应用工具 | 与某个 tool call 对应的执行结果 |
顺序不能随便改。模型发出 tool call 后,应用必须用对应的 tool call ID 返回 Tool Message;少一条或 ID 不匹配,模型就无法知道哪个调用产生了结果。
消息内容也不一定只是字符串。现代模型可能返回 text、tool call、图片、推理摘要等 content block。业务代码不要假设 content 永远是字符串;在边界处把 provider 格式归一化,再交给前端或数据库。
3、模型参数只控制生成,不控制业务正确性
常用参数包括模型名称、temperature、最大输出 token、超时和重试。低 temperature 只能降低随机性,不能让模型获得数据库事实;重试只能处理临时错误,不能修复错误 prompt;更大的模型也不能替代权限检查。
推荐把模型配置分成两类:
代码中稳定的能力要求:支持工具、支持结构化输出、超时策略
环境中可替换的运行配置:provider、model、最大 token、发布版本
模型切换前必须跑同一套评测,不能因为 API 接口一致就假设行为一致。
4、先定义分诊结果 schema
Python:
from typing import Literal
from pydantic import BaseModel, Field
from langchain.agents import create_agent
class TriageResult(BaseModel):
intent: Literal["policy", "order", "refund", "other"]
urgency: Literal["low", "normal", "high"]
reply: str = Field(min_length=1, max_length=800)
needs_human: bool
agent = create_agent(
model="openai:gpt-5.5",
tools=[],
system_prompt=(
"你负责客服分诊。没有工具,所以不能声称已查到订单。"
"退款威胁、法律投诉或无法判断时 needs_human=true。"
),
response_format=TriageResult,
)
result = agent.invoke({
"messages": [{"role": "user", "content": "商品破损,我今天必须退款"}]
})
triage: TriageResult = result["structured_response"]
print(triage.model_dump())
TypeScript:
import { createAgent } from "langchain";
import * as z from "zod";
const TriageResult = z.object({
intent: z.enum(["policy", "order", "refund", "other"]),
urgency: z.enum(["low", "normal", "high"]),
reply: z.string().min(1).max(800),
needsHuman: z.boolean(),
});
const agent = createAgent({
model: "openai:gpt-5.5",
tools: [],
systemPrompt: "你负责客服分诊。没有工具,不能声称已查到订单。无法判断时转人工。",
responseFormat: TriageResult,
});
const result = await agent.invoke({
messages: [{ role: "user", content: "商品破损,我今天必须退款" }],
});
console.log(result.structuredResponse);
框架会选择 provider 原生结构化输出或工具式结构化输出,具体取决于模型能力。无论采用哪种策略,应用拿到结果后仍要执行业务校验。
5、schema 合法不等于业务合法
下面结果完全符合 schema,却不能直接使用:
{
"intent": "refund",
"urgency": "high",
"reply": "你的订单已退款成功",
"needs_human": false
}
问题在于本课根本没有订单和退款工具。业务层应增加不变量:无工具证据时不能声称动作已完成;intent=refund 且用户要求真实操作时必须转下一流程;回复不能包含未提供的订单状态。
def validate_business(result: TriageResult) -> None:
forbidden = ("已退款", "退款成功", "已到账")
if any(text in result.reply for text in forbidden):
raise ValueError("UNSUPPORTED_ACTION_CLAIM")
结构化输出的价值是把不可控自然语言缩小为可检查对象,而不是消除所有错误。
6、错误处理应该发生在哪一层
| 错误 | 处理位置 | 建议行为 |
|---|---|---|
| 模型超时/限流 | 模型客户端或 middleware | 有上限重试、备用模型或友好失败 |
| schema 解析失败 | Agent/输出层 | 最多修复一次,仍失败则受控错误 |
| 业务不变量失败 | 领域服务 | 拒绝结果并记录错误码 |
| 用户输入过长 | API 入口 | 在调用模型前拒绝或截取 |
| 敏感信息 | 输入/输出治理层 | 脱敏、审计,不写入 trace |
不要用无限重试修复结构化输出。模型持续失败通常表示 schema 太复杂、字段说明不清或模型不适合,应当显式暴露问题。
7、测试与练习
准备至少六条固定输入:政策咨询、订单查询、退款请求、辱骂但无风险、法律投诉、无法分类的问题。断言枚举合法只是第一层;还要断言订单查询不会声称已查询、法律投诉会转人工、回复长度受限。
本课产物是稳定的 TriageResult。下一课会为 Agent 加入真实订单工具,让 order/refund 不再只是分类标签。
官方阅读:Python Messages、Python Structured Output、TypeScript Structured Output。
如果您觉得这篇文章有帮助,请点个赞吧~
评论
请登录后发表评论
去登录