1. 从一条推荐说起:typesafe 编码智能体到底在解决什么问题
Elvis Saravia 这个名字,做 NLP 和提示工程的朋友应该不陌生,他长期在整理各类提示工程、智能体实践的资料。这次他推荐的“typesafe 编码智能体笔记”,核心其实不是某个具体工具,而是一套思路:让编码智能体(coding agent)在类型系统的约束下工作,从而把“能跑”变成“跑得对”。我第一眼看到这个标题时的直觉是,这背后一定牵扯到 agent harness、MCP、模型路由这几个关键词,因为它们恰好构成了当前编码智能体落地的三根支柱。
先说清楚这个内容是什么。它是一份围绕“类型安全”这个切入点,去组织编码智能体工作流的实践笔记。能做什么?简单讲,就是让智能体在生成、修改、重构代码时,不是凭感觉输出一段文本,而是被类型检查器、编译器、schema 校验这些“硬约束”卡住,只有通过校验的产物才算完成。解决了什么问题?解决的是智能体“看起来写对了、实际一跑就崩”的老大难。适合谁看?我觉得三类人最该看:一是正在把编码智能体接进自己项目里的工程师,二是搭 agent harness 的平台开发者,三是想搞清楚 MCP 和模型路由怎么配合落地的技术负责人。
为什么“typesafe”这个词值得单独拎出来讲?因为大模型本质是概率机器,它输出的代码在语法层面往往没问题,但在类型层面经常是灾难。你让它写一个函数,它可能返回any,可能漏掉一个可选字段,可能把string当number用。在没有类型约束的环境里,这些错误要等到运行时才暴露,而运行时可能就是生产环境。typesafe 编码智能体的核心主张就是:把类型检查前移到智能体的决策回路里,让类型错误成为智能体必须自己解决的反馈信号。这跟传统“生成完再人工 review”的模式完全不同,它把校验变成了闭环的一部分。
我个人的判断是,这份笔记真正的价值不在于教你某个 API 怎么调,而在于它给出了一种“约束优先”的智能体设计哲学。接下来我会从整体设计思路、核心细节、实操落地、问题排查几个层面,把这条推荐背后的东西拆开讲透,尽量让你看完能直接抄作业。
2. 整体设计思路:为什么是 typesafe,而不是“更聪明的提示词”
2.1 类型系统作为智能体的“地面真值”
很多人做编码智能体,第一反应是优化提示词,把 system prompt 写得无比详尽,告诉模型“你要注意类型、要注意边界”。我试过,效果有限。原因是提示词是软约束,模型可以“理解”但不必“遵守”。而类型系统是硬约束,编译器不会跟你讲情面,类型不匹配就是编译失败。
typesafe 编码智能体的设计思路,本质上是把类型检查器当成智能体的“地面真值”(ground truth)。智能体生成代码后,不是直接返回给用户,而是先过一遍类型检查。如果失败,错误信息会作为新的上下文喂回给智能体,让它自己修。这个循环可以重复若干轮,直到通过或者达到重试上限。这样做的好处是,智能体的“完成”定义被重新锚定了——不是“我写完了”,而是“类型检查过了”。
这个思路背后有个关键认知:大模型在“局部推理”上很强,但在“全局一致性”上很弱。类型系统恰好是全局一致性的形式化表达。你让模型自己保证全局一致,它做不到;但你给它一个能自动判定一致性的工具,它就能在反馈中逐步逼近正确。这就是为什么 typesafe 值得作为独立主题来讨论。
2.2 agent harness 与 agent 的区别:约束层和执行层
热词里反复出现“harness 和 agent 区别”,这个问题在 typesafe 语境下特别关键。我的理解是:agent 是“决策者”,harness 是“约束与执行环境”。agent 负责想下一步做什么,harness 负责把这一步放进一个受控的沙箱里执行,并收集反馈。
打个比方,agent 像司机,harness 像整辆车加上交通规则和仪表盘。司机决定往哪开,但方向盘、刹车、限速、油表都是 harness 提供的。没有 harness,司机再聪明也可能开进沟里。typesafe 编码智能体里,harness 的职责包括:提供类型检查能力、管理文件读写、控制命令执行、把编译错误结构化后回传给 agent。
为什么这个区分重要?因为很多人把两者混为一谈,结果把约束逻辑写进了 agent 的提示词里,导致系统脆弱。正确的做法是:agent 保持“薄”,harness 承担“厚”。agent 只需要知道“我有一个类型检查工具可以调用”,而不需要知道类型检查器具体怎么配置。这样换语言、换项目、换检查器时,agent 的逻辑不用大改。
2.3 MCP 在其中的角色:把工具标准化
MCP(Model Context Protocol)在这里的作用,是把 harness 里的各种能力标准化成 agent 可以调用的“工具”。类型检查、文件操作、命令执行、甚至浏览器操作,都可以封装成 MCP server。agent 通过 MCP 协议去调用,不需要为每个工具写一套适配代码。
热词里 MCP 相关的内容特别多,从 figma mcp 到 playwright mcp 到 ida pro mcp,说明这个协议正在快速铺开。在 typesafe 编码智能体这个场景里,MCP 的价值是解耦:类型检查器可以是本地的 tsc,也可以是远程的校验服务,只要它暴露成 MCP 工具,agent 就能用。这让整个系统更容易替换和扩展。
我自己的经验是,MCP 最大的坑不在协议本身,而在工具描述的粒度。工具太粗,agent 不知道怎么用;工具太细,agent 会被淹没。typesafe 场景下,我建议把“运行类型检查”做成一个工具,返回结构化的错误列表,而不是把每个错误做成一个工具。
2.4 模型路由:不同环节用不同模型
模型路由是另一个容易被忽略但很关键的设计点。typesafe 编码智能体的工作流里,其实有几种不同的任务:理解需求、生成代码、根据类型错误修复、总结变更。这些任务对模型能力的要求不一样。
生成代码可能需要最强的模型,修复类型错误可能中等模型就够,总结变更用便宜的小模型即可。模型路由就是根据任务类型,把请求分发到不同的模型上。这样做的好处是成本和延迟都能优化。热词里“模型路由”出现,说明大家已经开始意识到“一个模型打天下”不划算。
我的建议是,路由策略要跟 harness 的反馈循环结合。比如第一轮生成用强模型,后续修复轮次用中等模型,因为修复任务更局部、更明确。这样既保证质量,又控制成本。
3. 核心细节解析:typesafe 编码智能体的关键构件
3.1 类型检查的三种接入方式与取舍
把类型检查接进智能体回路,有三种常见方式,各有取舍。
第一种是进程内调用,直接在 harness 里 import 类型检查器的 API。优点是快、无网络开销;缺点是耦合强,换语言就重写。适合单一技术栈的团队。
第二种是命令行调用,harness 执行tsc --noEmit之类的命令,解析 stdout。优点是通用、语言无关;缺点是要处理输出格式,不同工具输出不一样。这是目前最主流的做法。
第三种是MCP 服务调用,把类型检查封装成独立服务。优点是解耦彻底、可远程;缺点是有网络延迟,且需要维护服务。适合多项目、多语言的平台。
我实测下来,中小项目用命令行调用性价比最高,平台级项目才值得上 MCP 服务。选型时别一上来就追求“架构优雅”,先把闭环跑通。
3.2 错误信息的结构化:决定修复质量的关键
类型检查器输出的错误信息是给人看的,不是给模型看的。直接把这堆文本喂给模型,效果往往不好,因为里面夹杂了文件路径、行列号、颜色转义符。结构化的错误信息是 typesafe 智能体修复质量的分水岭。
我的做法是写一个解析层,把错误转成这样的结构:
{ "file": "src/utils.ts", "line": 42, "column": 10, "code": "TS2322", "message": "Type 'string' is not assignable to type 'number'", "context": "const count: number = getUserId();" }然后只把message和context喂给模型,附带文件路径。这样模型能聚焦在“类型不匹配”这个核心问题上,而不是被格式干扰。实测修复成功率能提升不少。
注意:不要把整个编译日志无脑塞进上下文,token 浪费不说,还会稀释关键信息。错误超过一定数量时,优先处理前 N 个,因为很多错误是连锁的,修好根因后面自动消失。
3.3 重试循环的终止条件设计
typesafe 智能体的核心是一个“生成-检查-修复”的循环。这个循环必须有明确的终止条件,否则可能无限打转。
我一般设三个终止条件:一是类型检查通过,正常结束;二是达到最大重试次数(通常 3 到 5 次),强制结束并报告;三是错误数量不再下降,说明模型卡住了,提前结束。
第三个条件最容易被忽略,但最有用。我遇到过模型反复改同一处代码,错误数在 2 和 3 之间来回跳,这时候继续重试就是浪费。检测到“错误数不降”就停,把当前状态交给人工,反而更高效。
3.4 上下文管理:只给模型需要的
编码智能体的上下文窗口是稀缺资源。typesafe 场景下,上下文里通常有:原始需求、相关文件内容、类型错误、历史修复尝试。全塞进去很快就爆了。
我的策略是按需加载。先只给模型出错的文件和直接依赖的类型定义,如果修复失败,再逐步扩大范围。这样既省 token,又减少干扰。热词里“mcp本地文件”这类需求,本质就是让 agent 能按需读取本地文件,而不是一次性全量加载。
3.5 工具描述的写法:让 agent 知道怎么用
如果类型检查是通过 MCP 工具暴露的,工具描述(tool description)的写法直接影响 agent 的使用效果。我见过太多工具描述写得像 API 文档,agent 根本不知道怎么用。
好的工具描述应该包含:这个工具做什么、什么时候用、输入是什么、输出是什么、有什么限制。比如:
- 名称:
run_type_check - 描述:对指定文件或整个项目运行类型检查,返回结构化错误列表。在生成或修改代码后调用,用于验证类型正确性。
- 输入:
{ "path": "可选,文件或目录路径,默认整个项目" } - 输出:
{ "errors": [...], "passed": boolean }
这样 agent 一看就知道“我改完代码应该调这个”。
4. 实操落地:从零搭一个 typesafe 编码智能体
4.1 环境准备与依赖选择
假设你要做一个 TypeScript 项目的 typesafe 编码智能体,环境准备大概是这样:
- Node.js 18+,因为要用到较新的 ESM 和 fetch
- TypeScript 5.x,类型检查用
tsc - 一个支持工具调用的模型 API
- 一个 MCP 客户端库(如果用 MCP 方案)
我建议先用最简方案跑通:不用 MCP,直接在 harness 里用child_process调tsc。等闭环稳定了,再考虑抽象成 MCP 服务。这样能快速验证思路,避免一开始就陷入架构复杂度。
4.2 核心循环的代码骨架
下面是一个简化的核心循环,用伪代码表达:
def typesafe_agent_loop(task, max_retries=5): context = build_initial_context(task) last_error_count = float('inf') for attempt in range(max_retries): code = model.generate(context) write_files(code) errors = run_type_check() if not errors: return {"status": "success", "code": code} if len(errors) >= last_error_count: return {"status": "stuck", "errors": errors} last_error_count = len(errors) context = update_context(context, code, errors) return {"status": "max_retries", "errors": errors}这个骨架里,run_type_check是关键,它负责执行类型检查并返回结构化错误。update_context负责把错误信息整理后加回上下文。注意last_error_count的判断,这是防止死循环的保险。
4.3 类型检查命令的配置细节
tsc的配置直接影响检查结果。我建议在项目里单独放一个tsconfig.agent.json,专门给智能体用,和开发用的配置分开。原因是你可能想对智能体更严格,比如开启strict、noImplicitAny、strictNullChecks,但不希望这些影响日常开发。
命令大概是:
npx tsc --noEmit --project tsconfig.agent.json --pretty false--pretty false很重要,它去掉颜色转义符,让输出更容易解析。--noEmit表示只检查不产出文件,避免污染工作区。
4.4 错误解析层的实现
解析tsc输出,可以用正则匹配。tsc的错误格式大致是:
src/utils.ts(42,10): error TS2322: Type 'string' is not assignable to type 'number'.对应的正则:
const errorRegex = /^(.+)\((\d+),(\d+)\): error (TS\d+): (.+)$/;逐行匹配,把结果组装成结构化对象。注意有些错误是多行的,比如类型不匹配会附带类型定义,这些附加行要单独处理,可以拼到message里。
提示:不同版本
tsc输出格式略有差异,建议先跑一次把原始输出存下来,再写解析逻辑,别凭记忆写正则。
4.5 模型路由的配置示例
模型路由可以用一个简单的映射表实现:
const routingTable = { "generate": "strong-model", "fix": "medium-model", "summarize": "cheap-model" }; function route(taskType) { return routingTable[taskType] || "medium-model"; }在循环里,第一轮生成用generate,后续修复用fix,最后总结用summarize。这样成本能降下来,质量也不受太大影响。实测下来,修复任务用中等模型和强模型的效果差距不大,因为修复的上下文更明确。
4.6 一个完整的实操记录
我拿一个真实的小项目试过这套流程。任务是“给一个用户管理模块添加邮箱格式校验”。初始需求给模型后,它生成了一个validateEmail函数,但返回类型写成了string,而调用处期望boolean。
类型检查报了一个 TS2322。结构化后喂回模型,它第二轮把返回类型改成boolean,但漏了一个边界情况,类型检查又报了一个 TS2345。第三轮修复后通过。整个过程三轮,耗时大概十几秒,比人工写快,而且类型是保证正确的。
这个记录说明,typesafe 循环的价值在于它把“正确性”变成了可自动验证的目标。模型不需要一次写对,它只需要能根据反馈改对。
5. 常见问题与排查技巧实录
5.1 类型检查通过但逻辑错误怎么办
这是 typesafe 方案的边界。类型检查只能保证类型正确,不能保证逻辑正确。比如一个函数返回boolean,类型没问题,但逻辑写反了,类型检查发现不了。
我的做法是叠加测试。在 harness 里再加一个“运行测试”的工具,类型检查通过后跑测试。测试失败也作为反馈喂回模型。这样约束就从“类型正确”升级到“行为正确”。当然,测试的维护成本更高,适合核心模块。
5.2 模型反复修不好同一个错误
这种情况通常是错误信息不够清晰,或者模型缺少必要的上下文。排查步骤:
- 检查错误信息是否结构化,有没有把关键的类型定义带上
- 检查上下文里有没有出错文件的完整内容
- 检查是不是多个错误互相纠缠,先隔离单个错误让模型修
我遇到过模型修不好是因为它看不到被调用函数的类型定义,只看到调用处。把定义文件加进上下文后,一次就修好了。
5.3 类型检查太慢拖垮循环
大型项目全量类型检查可能要几十秒,循环几轮就几分钟。优化方向:
- 只检查改动的文件及其依赖,用
tsc --incremental - 用更快的检查器,比如
swc或esbuild做语法层面的快速检查 - 把类型检查放到后台,agent 先做其他事
我一般用增量检查,第一次慢,后续快很多。
5.4 MCP 工具调用失败排查
如果用 MCP 方案,常见问题有:工具没注册、参数格式不对、服务没启动。排查顺序:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| agent 说找不到工具 | 工具未注册 | 检查 MCP server 的工具列表 |
| 调用返回参数错误 | schema 不匹配 | 对照工具定义的 input schema |
| 调用超时 | 服务未启动或卡死 | 检查服务进程和日志 |
| 返回空结果 | 工具逻辑问题 | 单独调用工具测试 |
注意:MCP 工具的 schema 要写严格,别用
any,否则 agent 传参经常出错。
5.5 上下文爆炸的处理
循环几轮后上下文容易爆。处理技巧:
- 每轮只保留最近一次的错误,历史错误压缩成摘要
- 文件内容只保留改动部分,用 diff 而不是全文
- 用模型做一次上下文压缩,把历史对话总结成要点
我实测下来,压缩历史能省一半以上 token,且不影响修复质量。
5.6 独家避坑清单
- 别让 agent 直接改生产代码,一定在沙箱或分支里跑
- 类型检查配置要和项目实际配置一致,否则检查通过但实际编译失败
- 重试次数别设太高,3 到 5 次足够,再多是浪费
- 错误解析要处理多行错误,否则会丢信息
- 模型路由别太复杂,先简单映射,有需要再细化
- 工具描述要写“什么时候用”,不只是“是什么”
6. 我对这套思路的几点个人体会
搭过几套编码智能体之后,我越来越觉得 typesafe 这个切入点选得准。它没有去追求“让模型更聪明”,而是去追求“让环境更严格”。这个思路的普适性很强,不限于 TypeScript,任何有形式化校验的领域都能套用:数据库 schema、API 契约、配置格式,都可以做成类似的闭环。
Elvis Saravia 推荐这份笔记,我猜也是看中了这种“约束优先”的工程思维。热词里 MCP、模型路由、agent harness 这些概念还会继续热,但真正决定智能体能不能落地的,往往是这些不起眼的约束设计。工具会换,协议会变,但“让校验成为回路一部分”这个原则,我觉得会长期有效。
最后分享一个小技巧:如果你刚开始做,别急着上 MCP 和模型路由,先用最土的办法——命令行调类型检查、单模型、简单循环——把闭环跑通。跑通之后你会发现,后面所有的优化都是在这个骨架上加东西,而不是推倒重来。这个顺序反了,很容易陷在架构里出不来。