用JSON Schema守住LLM自动操作边界
当LLM分类结果会触发CRM操作时,文章建议用JSON Schema约束标签集合、字段类型、必填项及不确定性表达。响应应先校验,失败则拒绝或隔离,通过后再映射为业务命令。
AI 深度解读
LLM 给销售通话打标签,一旦接上 CRM,错误就可能变成真实的跟进任务、字段修改或升级处理。原文的结论是:这种场景需要 JSON Schema,在模型输出和业务动作之间放一道可检查的契约。
它解决的不是模型能否输出 JSON,而是程序凭什么接受这份结果。对正在把 AI 接进业务流程的开发者,这篇最有用的提醒是:格式合法、契约合规、业务判断正确,是三件不同的事。
能解析成 JSON,为什么还不能写进 CRM
原文用物流销售通话举例:货主询问运输线路、交付时间和报价,模型返回 intent、urgency、follow_up_required、confidence、evidence 五个字段。示例把 Chicago 到 Dallas 线路的询价归为 quote_request,紧急程度为 normal,需要跟进,置信度为 0.86。
JSON 解析器只能回答字符能否解码。至于紧急程度是否合法、置信度是不是数字、后续流程需要的字段是否齐全,还得另行检查。
作者要求四项约束:
- 标签只能来自封闭集合,不能随意换拼写或翻译。
- 字段必须具有正确类型,例如跟进标记是布尔值。
- 必填字段必须齐全,不能漏掉判断依据。
- 不确定性必须有明确表示,供后续流程处理。
第四项在示例里主要靠 confidence 和低分转审核承接,并没有专门定义「无法判断」状态。other 虽然合法,也不能自动等同于不确定。
Schema 能暴露不合契约的结果,不能证明分类正确。
比如货主同时说「这次送晚了」和「下个月还要报价」,系统究竟选 service_issue,还是 quote_request?Schema 可以拒绝用数组冒充单个意图,却不能替团队决定多意图、主意图和人工复核之间的取舍。这是业务规则,需要负责人明确。
分隔符省下的代码,最后去了哪里
作者最初觉得,用 | 分隔几个字段会更简单。实际麻烦却集中在异常处理:多出一列怎么办,标签换个写法怎么办,看似数值的评分实际返回 high 又怎么办。
按分隔符切字符串,还要处理空值、重复分隔符、证据文本自带分隔符,以及模型额外附上的解释。字段错位后,结果甚至可能看起来合理,继续流入 CRM。
这些补丁积累起来,解析器就成了一份没有声明出来的 Schema。
隐式转换也有代价。把 yes 改成 true、把 urgent 映射成 high、直接丢弃陌生字段,能让数据表面整齐,却让人难以还原模型究竟返回了什么。作者主张只做契约允许的归一化,其他失败应拒绝或隔离。
重试同样属于业务流程。重新发送通话、要求更短回答、直接标为未分类,会留下不同的审计记录,也可能导致重复任务。记录具体校验错误,比统一进入「解析失败」分支更有诊断价值。
校验必须放在业务写入之前
原文给出的关键路径是:分类、校验、按约定归一化,最后执行业务动作。下面这段代码同时展示了结构校验和低置信度分流:
from json import loads
from jsonschema import ValidationError, validate
CLASSIFICATION_SCHEMA = {
"type": "object",
"required": ["intent", "urgency", "follow_up_required", "confidence", "evidence"],
"additionalProperties": False,
"properties": {
"intent": {
"type": "string",
"enum": ["quote_request", "status_question", "service_issue", "other"],
},
"urgency": {"type": "string", "enum": ["low", "normal", "high"]},
"follow_up_required": {"type": "boolean"},
"confidence": {"type": "number", "minimum": 0, "maximum": 1},
"evidence": {"type": "string", "minLength": 1},
},
}
def classify_call(transcript: str) -> dict:
raw_text = call_classifier(transcript)
try:
result = loads(raw_text)
validate(instance=result, schema=CLASSIFICATION_SCHEMA)
except (ValueError, ValidationError) as error:
store_for_review(transcript=transcript, raw_response=raw_text, reason=str(error))
raise UnclassifiedCall from error
if result["confidence"] < 0.70:
store_for_review(transcript=transcript, raw_response=raw_text, reason="low confidence")
return result
crm_action = to_crm_action(result)
write_crm_action(crm_action)
return result
要盯住 validate(instance=result, schema=CLASSIFICATION_SCHEMA) 的位置:它在 to_crm_action 和 write_crm_action 之前。模型返回的字符串,经过验证才进入业务映射;低于 0.70 的记录即使结构合法,也只进入审核流程。
additionalProperties: False 拒绝额外字段,confidence 被限定在 0 到 1,evidence 至少有一个字符。不过,非空证据仍可能错误,合法分数也未必可信。函数名是通用示意,调用模型、存储审核记录和 CRM 写入都没有实现,不能直接当成完整服务。
生产环境还需要两类配套措施。
第一类是幂等性。每份通话应有幂等键,避免写入超时后重试,生成两条「联系货主」任务。这不是 JSON Schema 的能力,需要应用自己保证。
第二类是契约管理。新增一个 intent 会影响报表和路由,不能只当作一行配置修改。作者建议保留契约版本,为每个合法标签准备样例,并覆盖缺少证据、未知意图、字符串评分、额外字段和低分合法记录。
观测也要按失败原因拆分。未知意图增多,可能是标签过时;缺失字段增多,可能与提示词或运行环境变化有关;低分增多,则可能来自转录质量或新通话类型。这些是排查线索,不是确定诊断。
原始响应、校验错误、重试决定、人工处理结果和最终 CRM 动作应能关联还原。通话涉及个人及商业信息,原文要求原始内容受访问控制,普通日志做脱敏或最小化记录。
我的判断
我认同把校验放在业务写入之前。对会创建持久任务的分类,显式契约比不断追加字符串解析补丁更容易审查,失败也更容易定位。
但原文没有提供分类准确率、误建任务数量、延迟或维护成本的对照数据。作者关于分隔符的描述属于个人经验,生产架构多用「我会这样做」表达,不能据此断言他们团队已验证整套方案。
0.70 尤其不能照搬为通用门槛。原文没有解释模型分数是否经过校准,也没有说明阈值如何确定。让模型填写 confidence,不等于获得可靠概率。evidence 同样只是审核线索,Schema 没有验证它是否真由通话支持。
通过校验,只代表获得了进入下一步判断的资格。
相较分隔符,Schema 适合需要稳定字段和审计记录的自动流程;人工阅读的临时分析、用完即弃的实验,则可以容忍更自由的输出。长篇分析也没必要把每句话塞进封闭结构,只约束下游真正使用的元数据即可。审核队列还必须有人处理,并约定处理时限,否则只是把自动错误变成积压。
能拿来做什么
- 给 Node 服务接入 AI 分类时,把模型响应作为不可信输入,在创建任务或修改字段前执行 Schema 校验,并集中维护分类到业务动作的映射。
- 为分类流程准备异常样例,至少覆盖未知标签、缺字段、错误类型、额外字段和低置信度,检查失败后是否确实停止自动写入。
- 把标签变更当作契约变更,同时检查路由、报表和历史记录解释方式,保留各版本及对应样例。
- 在日常 AI 分析中,先看输出是否触发机器动作。人工读完即弃的内容可以保持自由文本;需要进入业务系统的字段,再补上明确约束和失败处理。