Structured Output:客服意图与参数提取|课堂课件
今天在学习路径中的位置

当前是阶段 02:大模型应用与智能交互工程。今天围绕“意图与槽位”、“结构校验”、“缺失与澄清”,朝着“可校验的客服意图”这个目标推进。图是学习路线,具体机制、例子和操作在下面逐步展开。
预习核对:查看本课预习问题解答。讲到相关知识点时核对思路,课后可再次查阅。
课程名称:Structured Output:客服意图与参数提取|项目任务:客服意图与偏好对象|编号:W04-D03
课前先打开:
- 课前预习
- D02 Prompt(提示词) V2 和实验表
- 至少一条“JSON(结构化数据交换格式) 看起来正确,但业务仍不能使用”的例子
合法 JSON 为什么还会出事故
模型返回:
{
"customer_intent": {
"intent": "PRODUCT_ASSISTANCE",
"customer_id": "C9999",
"order_id": null,
"missing_slots": [],
"needs_handoff": false,
"confidence": 0.99
},
"preference_slots": {
"category": "数码音频",
"budget_max_yuan": "五百元",
"use_case": "通勤"
}
}它能被 json.loads 解析。
但至少有三个问题:
- customer_id 不允许由模型生成;
- budget_max_yuan 不是可计算金额;
- PreferenceSlots(偏好字段) 缺少合同字段。
所以“能解析”只是第一关。
从 Prompt 候选到客服业务对象
今天要建立四层判断:
- JSON 语法是否合法;
- 字段、类型、枚举和额外字段是否符合 Schema(数据结构约束);
- 跨字段业务规则是否成立;
- 后续回答里的事实是否来自可核对来源。
今天完成前三层。
第四层要等 D04 Commerce(商城业务) API(应用程序接口)。
JSON、JSON Mode、JSON Schema 不是一回事
JSON 是数据格式。
JSON Mode 或等价能力通常只提高“输出是 JSON”的概率。
JSON Schema 进一步描述:
- 必填字段;
- 类型;
- 枚举;
- 数值范围;
- 数组和对象结构;
- 是否允许额外字段。
即使远端 Provider(服务提供方) 宣称 strict,应用仍要做本地校验:
- Provider 支持的 Schema 子集可能不同;
- 工具参数仍来自不可信模型输出;
- 业务跨字段规则可能不在远端合同里;
- 真实数据和应用版本可能变化。
Pydantic 在这里做什么
Pydantic(Python数据校验库) 把 Python 类型和验证规则组合成可运行合同。
from enum import StrEnum
from decimal import Decimal
from pydantic import BaseModel, ConfigDict, Field
class IntentName(StrEnum):
PRODUCT_INQUIRY = "PRODUCT_INQUIRY"
PRODUCT_ASSISTANCE = "PRODUCT_ASSISTANCE"
ORDER_STATUS = "ORDER_STATUS"
AFTER_SALES_POLICY = "AFTER_SALES_POLICY"
AFTER_SALES_REQUEST = "AFTER_SALES_REQUEST"
HUMAN_HANDOFF = "HUMAN_HANDOFF"
UNKNOWN = "UNKNOWN"
class StrictModel(BaseModel):
model_config = ConfigDict(extra="forbid")
class PreferenceSlots(StrictModel):
category: str | None
budget_max_yuan: Decimal | None = Field(default=None, ge=0)
use_case: str | None
required_features: list[str]
excluded_features: list[str]
missing_slots: list[str]这里最关键的不是把类写出来,而是决定:
- 哪些字段允许为空;
- 哪些值只能从枚举取;
- 哪些额外字段必须拒绝;
- 金额怎样保持人民币元的两位精度;
- 缺失信息怎样显式表达。
为什么要 extra=forbid
如果默认忽略额外字段,模型输出 customer_id 可能悄悄消失。
你可能觉得“反正没用到”。
但这会掩盖一个更危险的问题:模型正在尝试把不属于它的字段送进系统。
显式拒绝带来两个好处:
- 漂移立即暴露;
- 安全边界可测试。
错误出现后,程序可以决定修复、澄清或停止,而不是悄悄吞掉。
可信客户上下文不在模型 Schema 里
TrustedCustomerContext(可信模拟客户上下文) 包含:
- customer_id;
- context_source:FIXTURE(固定测试数据或测试装置) 或 CLASSROOM_SESSION;
- context_id。
它由 Host(宿主应用) 注入。
用户消息、Prompt 和 Tool(工具) arguments 都不能生成或覆盖它。
本课程没有真实登录,也不实现 Authentication(身份认证) 或 Authorization(授权)。
D04 只用这个可信虚构上下文做订单归属一致性检查。
三层校验怎样分工
工程定位|提示词与程序校验的边界
这一节把提示词工程的边界落到代码:提示词告诉模型应输出什么,Schema(数据结构约束)检查它实际输出了什么。模型候选不能未经校验就成为业务事实。
语法层
发现:
- 少括号;
- 多逗号;
- JSON 外夹带解释文本。
Schema 层
发现:
- 未知 intent;
- budget 类型错误;
- 缺少 required 字段;
- 多出 customer_id;
- confidence 超范围。
业务规则层
发现:
- ORDER_STATUS 没有 order_id,也没有声明 missing_slots;
- PRODUCT_ASSISTANCE 没有 PreferenceSlots;
- HUMAN_HANDOFF 却 needs_handoff=false(假);
- 同一字段既必选又排除;
- 预算小于 0。
每层解决不同问题。
跨字段规则
Pydantic 的 model_validator 可以检查对象内部关系:
例如“查订单”与“缺订单号”两个字段单独都合法,放在一起却必须要求澄清。@model_validator(mode="after")(模型级校验器)让 Pydantic 在字段解析成功后执行这个实例方法;self 是已构造的候选对象。规则不成立时抛 ValueError,Pydantic 将它汇入 ValidationError;通过时必须 return self,继续交给调用方。它不能查询真实订单或替代归属检查,那些仍在业务层。
下面延续本课前面定义的 StrictModel、IntentName 和 Field,不是独立文件。类型里的 str | None 表示值可以为空,并不等于可以省略这个字段;要允许省略,需显式给出默认值。StrEnum(字符串枚举)限制意图名称,Field(字段约束)约束单字段,model_validator 约束字段之间的关系。
from pydantic import model_validator
class CustomerIntent(StrictModel):
intent: IntentName
product_id: str | None
order_id: str | None
missing_slots: list[str]
needs_handoff: bool
confidence: float = Field(ge=0.0, le=1.0)
@model_validator(mode="after")
def check_required_context(self):
if (
self.intent == IntentName.ORDER_STATUS
and self.order_id is None
and "order_id" not in self.missing_slots
):
raise ValueError("缺少订单号时必须显式声明 order_id")
return self这里不要偷偷猜订单号。
正确结果是保留缺失,并让上层生成澄清问题。
confidence 能说明什么
confidence 是模型给出的候选置信信号。
它可以帮助决定:
- 是否进入人工;
- 是否要求澄清;
- 是否记录为低置信 Bad Case(失败或边界案例)。
它不能等同:
- 分类真实准确率;
- 商品匹配质量;
- 购买概率;
- 业务成功率。
这些需要固定评测或真实业务数据。
先写红灯
至少建立以下失败样本:
| case | 预期 |
|---|---|
| intent=BUY_NOW | 拒绝未知枚举 |
| customer_id=C1001 | 拒绝额外字段 |
| budget_max_yuan=五百 | 拒绝错误类型 |
| confidence=1.7 | 拒绝越界 |
| ORDER_STATUS 无 order_id 且无 missing | 拒绝业务矛盾 |
| PRODUCT_ASSISTANCE 无 PreferenceSlots | 拒绝或明确澄清 |
先让测试红起来,再写模型。
如果骨架直接返回原字典,合法样本可能“通过”,但坏样本也会漏过去。
ValidationError 要保留
不要只返回:
解析失败至少保留脱敏摘要:
- case_id;
- error_type;
- field_path;
- rule;
- attempt;
- request_id;
- usage(用量);
- final_status。
不记录完整敏感消息,但要足够定位。
有限修复为什么只能有限
模型第一次输出失败后,可以把 ValidationError 的摘要送回模型,请它修一次。
但不能无限循环。
每次修复都会带来:
- 新 Token(词元);
- 新延迟;
- 新随机性;
- 事实漂移;
- 成本上升。
今天 Baseline(基线) 最多修复一次。
超过上限后:
- 返回明确失败;
- 请求用户澄清;
- 或标记人工处理。
不能产生半可信对象继续下游。
修复成功后为什么还要留第一次失败
如果最终只保存成功结果,你会失去:
- 原始故障;
- 修复成本;
- 模型漂移情况;
- Prompt 是否需要改;
- 同类问题是否反复出现。
一条完整轨迹应包含:
{
"first_validation": "FAILED",
"repair_count": 1,
"final_validation": "PASSED",
"first_error_fields": ["customer_intent.intent"],
"total_usage": {
"input_tokens": 188,
"output_tokens": 73
}
}真实数值只在你真实的调用后记录。
哪一层应该改
改 Prompt
- 模型经常遗漏同一字段;
- 缺失策略写得不清;
- 输出任务和最终回答混在一起。
改 Schema
- 字段类型、枚举或额外字段边界不完整;
- 新增合法可选字段;
- 版本合同发生明确变化。
改业务规则
- 字段单独合法,但组合不成立;
- 某个意图需要特定槽位;
- 某些状态不能共存。
改数据或 Commerce API
- 模型和 Schema 都正确,但真实商品事实错误。
不要看到任何失败都继续加 Prompt。
Schema 演进不能只追求“新样本通过”
假设你想增加可选字段 preferred_color。
正确步骤:
- 写新字段缺失时旧样本仍通过;
- 写新字段合法时通过;
- 写类型错误时失败;
- 生成新 Schema;
- 全量回归;
- 记录版本影响。
如果直接把它设为 required,所有旧样本都会回归失败。
受控 AI 编程
让 AI(人工智能) 生成 Pydantic 候选时,把以下检查清单一起给它:
- 只修改 schema_contract.py;
- extra 必须 forbid;
- customer_id 不得出现在模型输出对象;
- 金额以人民币元 Decimal(十进制数值类型) 处理;
- intent 使用冻结枚举;
- 加跨字段规则;
- bounded repair 最多一次;
- 先满足现有红灯;
- 不引入工具调用。
审查 AI diff(代码差异):
- 是否把所有字段变成 Optional;
- 是否为了让测试通过而忽略错误;
- 是否自动把“五百”转换为 500;
- 是否删除原始 ValidationError;
- 是否增加未来周对象。
学生独立变式
从下面选一个:
- 增加可选 preferred_color;
- 增加“必须特征和排除特征不能完全相同”的规则;
- 增加低 confidence 时 needs_handoff 的业务约束;
- 增加一个未知 intent Bad Case。
要求:
- 先红灯;
- 说明放在哪一层;
- 让 AI 提局部候选;
- 审查 extra、类型和兼容性;
- 绿灯;
- 全量回归。
今天的完成定义
- CustomerIntent(客户意图) 和 PreferenceSlots 有严格本地校验;
- 未知枚举、额外 customer_id、错类型、越界值和跨字段矛盾被拒绝;
- TrustedCustomerContext 不在模型输出合同里;
- 修复最多一次,并保留第一次失败;
- 失败后没有半可信对象进入 D04;
- 学生能判断应该改 Prompt、Schema、业务规则还是数据。
面试官可能继续问
JSON Mode 和 Structured Output 有什么区别
JSON Mode 主要约束输出格式;Structured Output(结构化输出) 进一步按 Schema 约束字段和类型。无论远端怎样约束,本地仍要做业务校验。
为什么远端 strict 不能替代本地验证
Provider 能力、Schema 子集和版本可能不同,业务跨字段规则也属于应用。模型输出仍是外部输入。
为什么不自动把“五百元”修成 500
自动规范化可能掩盖歧义。应先确认业务允许的格式和证据来源,再在明确的规范化层处理,不能为让测试通过而偷偷猜。
今天留下什么给 D04
- 通过本地校验的 IntentEnvelope;
- CustomerIntent;
- PreferenceSlots;
- ValidationError 摘要;
- bounded repair 轨迹;
- 失败终点;
- Schema 与版本记录。
D04 只允许这些已验证对象进入商品筛选或只读工具。
预习问题:课上怎么解答、怎么用在业务里
按预习里的小节和题号核对;观察题以你实际看到的页面为准,下面给出判断依据,不用复制一份统一的观察记录。
业务交接三条
Q:
假设你是商城客服主管,收到三条交接记录:
- “用户想买耳机,预算不清楚”;
- “用户订单可能有问题,订单号没有记录”;
- “用户说自己是 C9999,请直接查询”。
分别写下:哪些信息可以继续使用,哪些必须补问,哪些不能因为客户自己说了就当成可信事实。思考为什么企业交接记录需要固定字段,而不能只写一段随意文字。
A(参考解答):
耳机意图可以保留,预算需补问;订单问题可保留但查询需订单号;自称C9999不可作可信身份。固定字段让下游知道已知、缺失与禁止信任的内容,不靠猜一段自由文本。
技术 1—3
Q:
Prompt(提示词) 倾向返回 JSON(结构化数据交换格式),但客服系统仍可能收到未知 intent、字符串预算、额外 customer_id、缺 order_id 或自相矛盾字段。今天要在模型与业务程序之间建立可拒绝的合同。
先判断
-
json.loads成功能证明类型、枚举和业务规则合法吗? -
budget_max_yuan: “五百元” 为什么不应静默当成可计算金额?
-
合法 JSON 中多了
customer_id,忽略它还是拒绝它?为什么?
A(参考解答):
json.loads只验证JSON语法,不验证枚举/业务。‘五百元’不是合同规定金额字符串,不能静默拿去算;应受控提取后重新校验或澄清。额外customer_id按冻结白名单拒绝,防未经授权字段混入。
技术 4—6
Q:
Prompt(提示词) 倾向返回 JSON(结构化数据交换格式),但客服系统仍可能收到未知 intent、字符串预算、额外 customer_id、缺 order_id 或自相矛盾字段。今天要在模型与业务程序之间建立可拒绝的合同。
先判断
-
ORDER_STATUS没有 order_id 时,程序应猜、修复、澄清还是停止? -
confidence 能表示事实正确率或购买概率吗?
-
JSON Mode、远端 strict、本地 Schema(数据结构约束) 和业务校验分别保护什么?
A(参考解答):
ORDER_STATUS(订单状态查询)缺order_id走澄清/受控停止,不猜订单。confidence(置信度)不是校准事实正确率,更不是购买概率。JSON Mode(JSON输出模式)侧重语法,远端strict(严格结构约束)约束支持的结构,本地Schema仍检查类型/字段,业务校验检查关系和权限。
技术 7—9
Q:
Prompt(提示词) 倾向返回 JSON(结构化数据交换格式),但客服系统仍可能收到未知 intent、字符串预算、额外 customer_id、缺 order_id 或自相矛盾字段。今天要在模型与业务程序之间建立可拒绝的合同。
先判断
-
修复成功后为什么仍要保留第一次失败和 Token(词元) 成本?
-
无限 repair 会怎样影响成本、延迟和事实漂移?
-
新增一个可选字段时,怎样证明旧样本仍兼容?
把判断写入 notes/d03_prediction.md。课堂先跑坏结构,再决定约束应放在哪一层。
A(参考解答):
修复前失败也是成本与质量事实,应连同尝试次数/Token记录。无限repair(输出修复)拖长延迟且可能漂移事实;设上限并允许失败。新增可选字段用旧样本、缺省、新字段和非法值回归,不只看新例。
业务应用
模型说“预算大概五百”不能直接传给商品筛选器。Structured Output(结构化输出)建立明确候选形状,本地校验后才允许业务使用;结构正确仍不能保证内容有依据。
官方资料
下课前最后三个问题
- 为什么 json.loads 成功仍不能把对象交给工具执行?
- customer_id 应该出现在哪个上下文,为什么不在模型 Schema?
- 修复一次仍失败时,程序应该返回什么终点?
相关材料
回到项目:提示词与程序校验的边界
合法JSON为什么还可能不符合业务要求?
这一节把提示词工程的边界落到代码:提示词告诉模型应输出什么,Schema(数据结构约束)检查它实际输出了什么。模型候选不能未经校验就成为业务事实。
顺着本课已有的代码和案例,说出输入、处理发生在哪里、正常和异常各是什么结果。用一处真实记录支撑你的判断,没跑过的结果不要写成已经验证。