从“让模型吐JSON”到“让模型产出可执行契约”:OutputParser的工程跃迁实战

2023年年初,我第一次用大模型做联系人提取时,脑子里想的很简单:无非就是让模型返回个JSON格式。 从“让模型吐JSON”到“让模型产出可执行契约”:OutputParser的工程跃迁实战 IT技术

这个想法让我在生产环境里摔了一个大跟头。

当时接手的项目要把销售录入的会议纪要自动转成CRM数据结构。Demo跑通那天我很兴奋,以为核心工作已经完成。结果上线第一周,数据库里开始出现各种“1994年出生”的奇怪记录——模型在补全缺失字段。 从“让模型吐JSON”到“让模型产出可执行契约”:OutputParser的工程跃迁实战 IT技术

这不是模型的错,是我从一开始就理解错了方向。

为什么大多数AIDemo死于“结构化”这一关

模型最擅长的是自然语言生成。但业务系统要的从来不是自然语言。

数据库要字段。工具系统要参数。前端要确定状态。工作流引擎要稳定节点输入。

这就是“演示能跑、上线就崩”的根本原因。Demo阶段只要人能看懂,工程阶段要求的是:字段必须完整、缺失值必须有约定、类型必须稳定、异常必须可兜底、输出必须能被下游直接消费。

拿销售录入这段话举例:「张琳,女,杭州人,阿里云解决方案架构师,电话13800138000,微信zhanglin_arch。1994年生,上月刚换工作。」

对人来说完全可以理解。但对CRM系统来说,这只是待解释文本。你必须回答:联系人是一条还是多条?哪些字段必填?出生日期没有精确日期要不要猜?公司是当前还是历史信息?识别失败怎么处置?

所以“输出控制”的本质是接口设计,不是模型技巧。

三个概念必须放到同一条链路里

很多人把toolcalling、JSONSchema、outputparser拆开讲,看完还是不知道怎么选。正确理解方式是把它们放回调用链路。

自然语言输出是模型原生能力,适合对话写作,但不适合驱动业务逻辑。结构化输出是在输出层加约束,让结果符合预设结构。它的目标不是限制模型表达,而是让结果进入程序世界。

toolcalling本质也是结构化输出——模型输出的结构变成了“调哪个工具、参数是什么”。JSONSchema是更严格的约束方式,某些供应商直接在模型层支持,优势是约束强代价是供应商耦合更深。

outputparser的作用是把模型输出转成程序更易消费的结果。流式场景下,它还有关键角色:把中间状态逐步还原成可观察的数据。

不要一上来就手写Parser

三种方案都能做结构化输出,但从工程默认值看它们并不等价。

withStructuredOutput适合大多数信息提取、表单回填、知识整理,API简洁跨模型更友好。原生json_schema适合对字段严格性要求极高且模型明确支持的场景,约束强但供应商耦合更强。直接使用outputparser适合流式预览、工具参数增量解析、定制解析链路,灵活度最高但代码复杂度明显上升。

建议很明确:做信息提取先上withStructuredOutput。需要模型原生严格schema再考虑json_schema。只有当你真的要处理流式chunk、工具参数增量显示、复杂解析逻辑时,才自己下沉到outputparser。

很多人一看到Parser就兴奋,觉得“更底层更灵活”。但业务开发里,稳定才是默认目标,灵活不是。能用高层抽象解决,就不要主动把系统做复杂。

智能录入的本质是把聊天记录变可入库联系人

Schema设计比提示词更关键。

很多Demo喜欢让模型“帮忙补全”字段,比如根据“30出头”推算生日。课堂演示很顺,业务系统里风险很大。因为一旦入库,猜出来的数据就会伪装成事实。

核心原则:可以提取就提取,明确缺失就返回null,不要为了结构完整伪造业务事实。

数组Schema的原因是业务输入天然可能一对多。今天导入名片可能只有一个人,明天导入会议纪要可能同一段文本里出现三位参会人。顶层设计成数组,系统就不用为单人多人做两套分支逻辑。

大量字段允许null是工程关键一步。字段全写成必填,模型为了满足结构很容易开始编造。表面上得到完整数据,实际上是在给数据库注入低质量事实。结构化输出的目标不是让字段填满,而是让不确定性被显式表达。

使用.strict()是因为数据库和业务系统最怕“多出来的字段悄悄混进来”。严格模式防止模型顺手返回你没定义的键,避免下游逻辑在字段演进时变得不可控。

流式Agent的两个真正难点

非流式Agent改成流式之后,核心变化只有两个。

难点一是你拿到的不再是完整AIMessage,而是一串AIMessageChunk。非流式模式下模型一次性返回完整消息。流式模式下完整消息被拆成很多chunk,必须先把它们重新拼起来才能得到最终可执行的AIMessage。

难点二是工具参数在流式阶段往往是不完整片段。模型真正重要的输出经常不在自然语言正文里,而在tool_call参数里。文件路径、文件内容、命令参数,这些才是关键进展。

如果你只流式打印content,但工具参数还藏在内部结构里,用户依然看不到关键进展。

先用高层抽象,再下沉Parser

这是本文的核心结论。

withStructuredOutput解决的是“让模型结果能直接进入业务链路”。outputparser解决的是“让结构化结果在流式过程中也能被观察和利用”。

智能录入里,整层能力让自然语言稳定地落进数据库。流式Agent里,工具参数不再是黑盒,用户终于能看到系统到底在生成什么、准备执行什么。

工程默认值应该是:先用高层抽象拿到稳定结构,再在确实需要中间态的时候,才下沉到Parser。