如果你最近在写 LLM 应用,一定遇到过这样的场景:模型返回了一段看起来很像 JSON 的文本,但是字段名和你代码里定义的完全对不上;或者它多了一个你完全没预料到的字段,导致前端渲染直接崩溃;更常见的是,你花了一个下午调试 prompt,只为让模型“按格式输出”,但它偶尔还是会“自由发挥”。
过去一年,大家讨论 AI 应用开发,更多聚焦在模型选型、Prompt 工程、RAG 架构这些话题上。但随着 LLM 从“聊天玩具”走向“业务系统”,一个非常传统、甚至有点“古典”的工程概念正在重新回到舞台中央,它就是类型系统。本文想聊的话题是:Types with AI——当我们用类型去约束、校验、管理 LLM 的输入输出时,AI 应用开发会发生什么变化。
我的判断是:类型系统正在成为 LLM 应用工程化的基础设施。它不会替代 Prompt,也不会替代模型微调,但它会像当年 TypeScript 拯救 JavaScript 一样,把 AI 应用从“能跑就行”推向“可维护、可测试、可上线”的工程状态。这篇文章会用 Python、TypeScript 和 Function Calling 三个完整示例,带你打通“类型化 LLM 开发”的完整路径,并给出可直接落地的工程建议。
1. 为什么说类型是 LLM 开发的“隐形刚需”
先放下概念,看一个几乎所有人都会遇到的真实场景。
你写了一个客服机器人,需要从用户提问中抽取订单相关信息。你精心设计了 Prompt,让模型输出如下格式:
{ "order_id": "20250101001", "user_name": "张三", "issue_type": "退款" }第一版测试一切正常。上线一周后,你发现日志里开始出现解析异常。排查后发现,模型某次输出了这样的内容:
{ "orderId": "20250101001", "user_name": "张三", "issue_type": "refund" }字段命名从order_id变成了orderId,枚举值从中文变成了英文。你没有做任何改动,但模型在某个时间点之后“改变风格”了。而你的业务代码此时已经因为找不到字段而抛出了KeyError或undefined。
这类问题的本质是什么?大模型输出天然是非结构化的,而业务系统天然是结构化的。两者的交汇点如果没有一层强约束,所有的不可靠性都会传导到下游。
很多人的第一反应是:把 Prompt 写得再严格一点,让模型“一定不要乱输出”。但熟悉大模型行为方式的开发者都清楚,Prompt 是软约束,不是硬约束。模型有概率性,任何指令都存在被“忽略”的可能。更好的做法,是在模型和业务代码之间加装一道“类型围栏”:
- 第一层,在调用前用类型定义声明期望的结构;
- 第二层,在模型返回后立即做结构校验和数据清洗;
- 第三层,让失败尽早暴露,而不是等业务逻辑执行到一半才崩。
这不是新的发明,而是把传统软件工程里已经验证过无数次的“契约优先”理念,平移到了 LLM 应用开发中。TypeScript 解决了 JavaScript 缺乏类型约束导致的巨型项目维护难题,同理,类型系统也能解决 LLM 应用输出不可控导致的工程混乱。
2. 核心概念:类型、结构化输出与“LLM 契约”
在展开代码之前,先把几个关键概念讲清楚。这些概念在后续所有示例中都会反复出现。
2.1 类型系统在 LLM 开发中是什么
传统编程里的类型,是编译器用来看住变量的——字符串不能参与数学运算,对象必须有 declared 的属性。在 LLM 开发语境里,类型承担的角色更接近“对模型输出的结构声明”。
你告诉系统:模型的输出应该是一个对象,包含一个字符串类型的name字段,一个数字类型的rating字段,以及一个取值范围限定的status字段。这就是类型定义。它同时服务于人和机器:
- 对开发者,它写明了“模型到底应该返回什么”;
- 对代码,它提供了运行时校验的依据;
- 对 IDE,它提供了自动补全和静态检查的基础。
2.2 结构化输出(Structured Output)是什么
大模型在训练时学习的是“下一个 token 的概率”,它的天然输出是自然语言,不是 JSON。所谓结构化输出,就是通过解码策略、提示词约束或后处理手段,让模型的最终输出落在我们期望的数据结构里。
目前比较成熟的技术路径包括:
- JSON Mode:让模型只输出合法 JSON,但 JSON 内部结构不可控;
- JSON Schema 约束:在模型解码时限制它只能生成符合 Sche