- 大模型
- AI 应用
- 后端
【免费下载链接】TypeChat
TypeChat is a library that makes it easy to build natural language interfaces using types.
导读
Coffee Shop 是 TypeChat(基于 TypeScript 的"用类型构建自然语言界面"库)中极具代表性的示例:它把用户随口说出的咖啡点单需求,通过一个纯 TypeScript 类型 Schema 翻译成结构化的 JSON 订单对象,并以UnknownText类型优雅兜底无法识别的输入。读完本文,你将掌握 TypeChat 的 Schema 建模思路、完整的环境配置与运行流程,以及从createJsonTranslator到 JSON 校验、失败修复的底层调用链。
Coffee Shop 示例在 TypeChat 中的定位
在 typescript/examples/README.md 列出的系列示例中,Coffee Shop 被描述为"一个咖啡店的智能代理,将用户意图翻译成咖啡订单条目列表"。与最基础的 Sentiment 情感分类示例(TypeChat 的 "hello world")相比,Coffee Shop 的独特价值在于:
- 对象集合建模:一个自然语言句子可能包含多个商品、多个选项,需要翻译成一组"名词"(line items);
- 判别式联合类型:用
type字段区分商品类别与选项类别,让模型可以精确地为每个 token 归类; - 容错设计:专门用
UnknownText类型承接无法匹配任何已知类型的输入,形成"已知清单 + 未知兜底"的双通道结构。
从仓库根目录的 README.md 可以看到,TypeChat 的核心主张是"类型即协议":把 TypeScript 类型当作 LLM 与程序之间的共享接口。Coffee Shop 就是这一主张的最小完整落地。
理解 Schema:订单本质是"名词集合"
Coffee Shop 的核心思想在 typescript/examples/coffeeShop/README.md 中一句话点明:将用户意图捕获为一组"名词"(nouns),这里的名词是咖啡订单中的条目,合法的条目从Cart类型开始定义。
顶层容器:Cart
typescript/examples/coffeeShop/src/coffeeShopSchema.ts 定义了整个订单的顶层结构:
export interface Cart { items: (LineItem | UnknownText)[]; }items是一个数组,每个元素要么是可执行的订单条目(LineItem),要么是无法理解的内容(UnknownText)。这决定了模型输出永远是"一组条目",天然支持一句自然语言中包含多件商品。
兜底类型:UnknownText
// Use this type for order items that match nothing else export interface UnknownText { type: "unknown", text: string; // The text that wasn't understood }UnknownText是 Coffee Shop 区别于大多数 Schema 设计的关键:当用户输入中出现roses are red、two lawnmowers这类与菜单无关的内容时,模型不必强行把它塞进某个商品类型,而是可以诚实地标注为unknown并保留原文。这意味着:
- 校验永远"有处安放"——任何输入都能映射到
Cart的结构; - 业务层可以精确感知"哪些话没被理解",从而给出针对性反馈(见下文
main.ts中的处理逻辑)。
商品与选项的判别式联合
Product类型把商品聚合为四种可判别联合:
export type Product = BakeryProducts | LatteDrinks | EspressoDrinks | CoffeeDrinks;每种商品都带有type字段作为判别标记,例如:
export interface LatteDrinks { type: "LatteDrinks"; name: "cappuccino" | "flat white" | "latte" | "latte macchiato" | "mocha" | "chai latte"; temperature?: CoffeeTemperature; size?: CoffeeSize; // The default is "grande" options?: (Milks | Sweeteners | Syrups | Toppings | Caffeines | LattePreparations)[]; }值得注意的几个 Schema 设计细节:
- 字面量联合即枚举:
name字段用字符串字面量联合列出全部可售品名("cappuccino" | "flat white" | ...),LLM 在翻译时被约束只能从中取值,从源头杜绝了"编造菜单"; - 可选字段与默认值注释:
size?: CoffeeSize并注释// The default is "grande"。TypeScript 类型本身没有默认值概念,但注释会随 Schema 文本一起进入 Prompt,引导模型对未提及的尺寸按默认值处理或省略字段; - 选项按商品类别差异化:不同饮品开放不同的选项集合——拿铁类(
LatteDrinks)可选牛奶、甜味剂、糖浆、配料、咖啡因与制备方式(LattePreparations),而浓缩咖啡类(EspressoDrinks)则用Creamers替代Milks,尺寸枚举也从CoffeeSize换成更贴切的EspressoSize("solo" | "doppio" | "triple" | "quad")。
选项量化:OptionQuantity
部分选项(如甜味剂、糖浆、配料)还带有一个量化字段:
export type OptionQuantity = "no" | "light" | "regular" | "extra" | number;这是一个"枚举 + 数字"的混合类型,正好覆盖了自然语言的表达空间:no foam(no)、light whipped cream(light)、a pack of sugar(1,以 number 表示)、three pumps of vanilla(3)都能被统一收纳。从 typescript/examples/coffeeShop/src/input.txt 可以看到大量这类表达,例如i'd like a light nutmeg espresso、a flat white with five pumps of caramel syrup。
菜单全貌一览
Schema 中完整定义了四类商品、九类选项/制备方式与四种枚举,汇总如下:
| 类别 | 类型名 | 关键字段 |
|---|---|---|
| 烘焙 | BakeryProducts | name(4 种 muffin / bagel),options(BakeryOptions或BakeryPreparations) |
| 拿铁类 | LatteDrinks | 6 种品名,temperature/size,选项含Milks、Sweeteners、Syrups、Toppings、Caffeines、LattePreparations |
| 浓缩咖啡类 | EspressoDrinks | 4 种品名,选项含Creamers等 |
| 咖啡类 | CoffeeDrinks | americano/coffee |
| 糖浆 | Syrups | 10 种口味,optionQuantity? |
| 咖啡因 | Caffeines | regular/half caf等 5 档 |
| 牛奶 | Milks | 7 种奶 |
| 奶精 | Creamers | 10 种,含half and half、heavy cream |
| 配料 | Toppings | cinnamon、foam、ice等 |
| 制备方式 | LattePreparations | for here cup、with room、dry、wet等 |
| 甜味剂 | Sweeteners | equal、sugar、splenda等 |
此外还有四个类型别名:CoffeeTemperature(hot | extra hot | warm | iced)、CoffeeSize(short | tall | grande | venti)、EspressoSize、OptionQuantity。
运行效果:从自然语言到 JSON
typescript/examples/coffeeShop/README.md 给出了最直观的运行示例。当输入:
☕> we'd like a cappuccino with a pack of sugar模型输出:
{ "items": [ { "type": "lineitem", "product": { "type": "LatteDrinks", "name": "cappuccino", "options": [ { "type": "Sweeteners", "name": "sugar", "optionQuantity": "regular" } ] }, "quantity": 1 } ] }这个输出清晰地展示了三层结构:
- 外层是
Cart.items,本例只含一个LineItem; - 商品层
product命中LatteDrinks,品名为cappuccino; - 选项层
options中sugar被归类为Sweeteners,并把"a pack of sugar"量化为optionQuantity: "regular"(而不是数字1)——这体现了模型对"一包糖"的口语化理解,而非机械计数。
quantity: 1表明"we'd like a ..."整体对应一件商品。若输入包含多件商品(如input.txt中的i'd like a tall decaf latte iced a grande cappuccino double espresso and a warmed poppyseed muffin sliced in half),items数组会相应包含多个LineItem。
手把手运行 Coffee Shop 示例
第一步:准备环境
Coffee Shop 示例依赖 Node.js 与 npm。按 typescript/examples/README.md 的指引,在本仓库的typescript目录下安装依赖:
cd typescript npm install(该 README 同时也介绍了 GitHub Codespaces 的云开发环境方案,本地运行只需 Node.js 18.16.0 LTS 或更新版本。)
第二步:构建示例
在仓库根目录执行:
npm run build-all该命令会同时构建 TypeChat 库本体与全部示例。Coffee Shop 示例自身的构建脚本定义在 typescript/examples/coffeeShop/package.json 中:
"scripts": { "build": "tsc -p src", "postbuild": "copyfiles -u 1 src/**/*Schema.ts src/**/*.txt dist" }注意postbuild会把Schema.ts与input.txt一并复制到dist目录——这是运行时fs.readFileSync读取 Schema 文本所必需的(见下文main.ts分析)。
第三步:配置模型凭据
示例通过createLanguageModel(process.env)从环境变量选择 OpenAI 或 Azure OpenAI 端点。推荐在typescript目录下创建.env文件:
# For OpenAI OPENAI_MODEL=... OPENAI_API_KEY=... # For Azure OpenAI AZURE_OPENAI_ENDPOINT=... AZURE_OPENAI_API_KEY=...各变量含义如下:
| 变量 | 说明 |
|---|---|
OPENAI_MODEL | OpenAI 模型名(如gpt-3.5-turbo或gpt-4) |
OPENAI_API_KEY | OpenAI API Key |
OPENAI_ENDPOINT | OpenAI API 端点,可选,默认https://api.openai.com/v1/chat/completions |
OPENAI_ORGANIZATION | OpenAI 组织 ID,可选,默认空 |
AZURE_OPENAI_ENDPOINT | Azure OpenAI REST API 完整 URL(含 deployment 与 api-version) |
AZURE_OPENAI_API_KEY | Azure OpenAI API Key |
如果端点需要经过代理访问(例如受限网络区域),还可以在.env中配置:
HTTPS_PROXY=http://127.0.0.1:7890 # Optional: comma-separated hosts that should bypass the proxy NO_PROXY=localhost,127.0.0.1第四步:运行
进入coffeeShop目录,支持两种运行方式:
交互模式——从终端逐条输入:
node ./dist/main.js文件模式——逐行处理输入文件(示例自带的 input.txt 与 input2.txt):
node ./dist/main.js ./dist/input.txt注意示例的main指向dist/main.js,文件参数也要用构建后复制到dist中的input.txt路径。交互模式下输入quit或exit结束会话(该行为由processRequests实现,见下文)。
input.txt与input2.txt的内容设计也值得玩味:前者包含 47 条由易到难的句子,覆盖单品、多件、尺寸调整、重复表达、数字泵数、法语/丹麦语(un petit cafe、en lille kaffe)以及完全无关的句子(roses are red、two lawnmowers, a grande latte and a tall tree),用于系统性考验 Schema 的边界;后者则浓缩了其中最有挑战性的 8 条,适合快速回归。
代码级剖析:main.ts 的完整链路
typescript/examples/coffeeShop/src/main.ts 仅约 43 行,却串起了 TypeChat 的全流程:
import { createJsonTranslator, createLanguageModel } from "typechat"; import { createTypeScriptJsonValidator } from "typechat/ts"; import { processRequests } from "typechat/interactive"; import { Cart } from "./coffeeShopSchema"; const dotEnvPath = findConfig(".env"); assert(dotEnvPath, ".env file not found!"); dotenv.config({ path: dotEnvPath }); const model = createLanguageModel(process.env); const schema = fs.readFileSync(path.join(__dirname, "coffeeShopSchema.ts"), "utf8"); const validator = createTypeScriptJsonValidator<Cart>(schema, "Cart"); const translator = createJsonTranslator(model, validator);1. 环境变量加载
findConfig(".env")从当前目录向上逐级查找.env文件,找不到时直接assert失败并提示.env file not found!,然后由dotenv注入环境变量。
2. 把 Schema 源码作为"字符串协议"加载
fs.readFileSync把coffeeShopSchema.ts的源码文本读入内存——这正是 TypeChat 的设计精髓:Schema 不是编译期类型,而是运行时被原样拼进 Prompt 的"共享协议"。它同时被用作:
- LLM 翻译时的格式约束(进入 Prompt);
- TypeScript 编译器校验时的类型来源(进入内存编译器)。
3. 创建校验器与翻译器
createTypeScriptJsonValidator<Cart>(schema, "Cart")返回一个TypeChatJsonValidator,其中"Cart"是目标类型名;createJsonTranslator(model, validator)把它们组合成翻译器。两者的接口定义在 typescript/src/typechat.ts。
4. 请求处理与结果消费
processRequests("☕> ", process.argv[2], async (request) => { const response = await translator.translate(request); if (!response.success) { console.log(response.message); return; } const cart = response.data; console.log(JSON.stringify(cart, undefined, 2)); if (cart.items.some(item => item.type === "unknown")) { console.log("I didn't understand the following:"); for (const item of cart.items) { if (item.type === "unknown") console.log(item.text); } return; } processOrder(cart); console.log("Success!"); });processRequests("☕> ", process.argv[2], callback)来自 typescript/src/interactive/interactive.ts:当传入第二个命令行参数(输入文件路径)时,逐行读取文件并调用回调;否则进入交互循环,直到用户输入quit或exit。
回调里值得注意的业务逻辑:UnknownText不只是 Schema 的兜底,也是业务分支的入口——一旦items中出现type === "unknown",程序不会打印Success!,而是列出所有未被理解的原文,把"翻译不确定性"显式暴露给用户。这正是 README 强调该类型"捕获不匹配现有类型的用户输入"的落地应用。
Result判别联合(success标志 +data/message)定义在 typescript/src/result.ts,是贯穿 TypeChat 所有 API 的返回约定。
底层原理:TypeChat 如何完成"翻译 + 校验 + 修复"
翻译循环与自动修复
typescript/src/typechat.ts 中的translate是核心。它首先构造请求 Prompt(createRequestPrompt):
You are a service that translates user requests into JSON objects of type "Cart" according to the following TypeScript definitions:{Schema 源码}
The following is a user request: """ {用户输入} """ The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined:然后进入循环:取模型回复 → 用首尾{/}截取 JSON 片段 →JSON.parse→ 交给 validator 校验。若校验失败且attemptRepair为true(默认值,见 typescript/src/typechat.ts),则追加一轮"修复对话"(createRepairPrompt):
The JSON object is invalid for the following reason: """ {校验器返回的错误信息} """ The following is a revised JSON object:把首次校验错误回灌给模型重试一次。这是 TypeChat 显著降低 LLM 输出格式错误率的关键机制。translate还暴露了stripNulls开关(默认false):部分模型(如 gpt-3.5-turbo)倾向于给可选属性填null,开启后会在校验前递归删除 null 值属性(typescript/src/typechat.ts)。
内存中的 TypeScript 编译器校验
typescript/src/ts/validate.ts 实现了"用 TypeScript 编译器校验 JSON"的机制:validate把 JSON 对象转换成一段临时模块代码
import { Cart } from './schema'; const json: Cart = { ... };再用内存中的ts.createProgram组合lib.d.ts(一个精简的内置类型声明,见 typescript/src/ts/validate.ts)、schema.ts(即读入的 Schema 源码)和json.ts三份虚拟文件进行编译,收集语法与语义诊断作为校验错误。这意味着任何 TypeScript 类型层面的违规——拼错品名、漏掉必填字段、选项类型不符——都会被编译器精确捕获。仓库中的 validate.test.ts 等测试文件正是围绕这套校验器展开的。
语言模型封装与重试策略
createLanguageModel(typescript/src/model.ts)根据环境变量自动选择 OpenAI(OPENAI_API_KEY)或 Azure OpenAI(AZURE_OPENAI_API_KEY)实现;底层complete封装了 fetch 请求、temperature: 0、n: 1参数、瞬时错误(429/500/502/503/504)重试(默认最多 3 次、间隔 1000ms)、单请求超时(默认 10 分钟)与响应体大小上限(默认 100MB)等健壮性细节(typescript/src/model.ts)。
进阶探索:从 Coffee Shop 走向更复杂的 Schema
Coffee Shop 是学习 TypeChat Schema 建模的最佳起点,沿着 typescript/examples/README.md 推荐的阅读顺序可以继续深入:
- Restaurant 示例:与 Coffee Shop 同属"订单翻译"范式,但 Schema 更复杂,用文本(prose)文件展示简单与高级模型在处理复合句、干扰信息与修正表达时的差异;
- Math 示例:把计算题翻译成调用四则运算 API 的"程序",展示 TypeChat 的程序生成能力,是理解
program.ts与model.ts关系的进阶案例; - Drawing 示例:把绘画请求翻译成结构化形状(box、ellipse、arrow)再渲染为 SVG;
- Music 示例:把每个用户意图翻译成一系列 JSON 动作,构成一个简单的数据流程序。
这些示例共同印证了 Coffee Shop 所体现的方法论:先定义好"名词世界"(类型 Schema),再让 LLM 在约束内自由发挥——类型即协议,校验即防线。
小结
Coffee Shop 示例用不到 110 行的 Schema 加 43 行的main.ts,完整演示了 TypeChat 的核心闭环:TypeScript 类型作为 Prompt 协议约束 LLM 输出 → 内存 TypeScript 编译器校验 JSON → 失败时自动回灌错误修复。Cart的联合类型设计、UnknownText的容错兜底、OptionQuantity的"枚举 + 数字"量化,都是可以迁移到任意垂直领域(点单、预约、检索、客服)的通用建模范式。对想用"类型驱动"方式构建自然语言界面的开发者而言,这个示例既是入门教材,也是可复用的工程模板。
- 大模型
- AI 应用
- 后端
【免费下载链接】TypeChat
TypeChat is a library that makes it easy to build natural language interfaces using types.
相关推荐
TypeChat Coffee Shop 实战指南:用 Schema 类型把自然语言咖啡点单翻译为结构化 JSON
TypeChat Coffee Shop 实战指南:用 Schema 类型把自然语言咖啡点单翻译为结构化 JSON 本指南以 TypeChat Python 示
大模型AI 应用后端TypeChat 情感分析示例深度解析:用 TypeScript 类型把自然语言映射为负/中/正三分类
TypeChat 情感分析示例深度解析:用 TypeScript 类型把自然语言映射为负/中/正三分类 Sentiment 是 TypeChat 官方 Type
大模型AI 应用后端TypeChat 实战:用 Zod Schema 构建自然语言情感分类器(sentiment-zod 示例全解析)
TypeChat 实战:用 Zod Schema 构建自然语言情感分类器(sentiment zod 示例全解析) 导读 本篇文章基于 TypeChat 仓库中
大模型AI 应用后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考