作者指出 AI 编程输出质量的核心决定因素是上下文(context)量,而非模型选择或 prompt 技巧,并通过对比实验验证:有无上下文直接影响代码改动效果。
Part 2 of a series on building an AI-native platform. This one is about the thing that actually determines output quality — and a feature we shipped because of it.
代码是确定性的。同样的输入,同样的输出,每次都是如此。这个特性正是我们能够测试、缓存和推理它的全部原因。
AI 不是这样的。同样的 prompt,不同的答案,再怎么用大写字母吼叫也改变不了底层的事实:你是在从一个分布中采样。如果你真的在 LLM 之上构建过什么,你打心底里就已经知道这一点:demo 能跑,eval 很飘,"上线吧"和"删了吧"之间的差距往往来自于一些你说不清道不明的东西。
所以,对于任何一个用这些模型构建产品的人来说,真正重要的问题是:如果模型不能保证质量,那质量从何而来?
在这类东西上折腾了几年之后,诚实的答案是:context。不是 prompt engineering 的民间偏方,不是模型稍微聪明一点,而是模型在回答的那个时刻,能看到多少真实的情况。
我最熟悉的感知这一点的方式是:拿同样的任务跑两遍。
# Run A — no context
"Add pagination to the product list."
# Run B — same task, repo in context
"Add pagination to the product list."
# ...with the actual ProductList component, the API client,
# and the existing useProducts hook in the window.
Run A 给出一个看似合理但泛泛的答案,假设了一个你没在用的框架和一个你不存在的 API 形状。Run B 给出的是一个能直接 apply 的 diff。同样的模型,同样的 temperature,同样的 prompt 字符串。唯一变化的是它能看到什么。
这不是一个微妙的效果。在实践中,它就是"能交付的输出"和"只能扔掉的输出"之间的大部分差距。而且它有一个特性,安静地支配着下游的一切:context 是有限的。有预算的。你在一个东西上花的每个 token,都是你没有花在另一个东西上的 token。
一旦你接受了这个事实,提升结果就只有两个杠杆。我们两个都上线了,而第二个才是这篇文章真正要讲的功能。
我们暴露了一个 MCP server。曾经它有 59 个工具。现在有 43 个,而且我们打算继续保持精简。
这个精简在内部推动起来是反直觉的,因为功能页面上"更多工具"读起来就是"更有能力"。但观察一个 session 中实际发生了什么。每一个 MCP client 加载的工具都是一份 schema——名称、描述、参数列表——所有这些都在模型做任何事之前进入 context window。59 个工具是一份菜单,模型在每个 turn 都要从头读到尾,不管当前任务需不需要其中任何一个。
工具扩张看起来像是能力,实际上表现出来的却是噪音。把表面精简到那些真正有分量的工具,合并重叠的,让模型把注意力放在你的项目上而不是你的 API 参考文档上。我们用无聊的方式衡量了效果——在模型前后跑同样的构建任务——精简后的表面在每一个任务上都胜出了。
如果你在交付一个 MCP server,这是没人谈论的那个杠杆:你的工具数量是你的用户模型每次调用时交的税。要把 context 当贵的来设计,因为对消费它的东西来说,它确实是贵的。
这是让我悄悄恼火了一年的事情。
对于任何一个已有网站的人来说,他们拥有的最密集的 context 的一块,完全对他们可用的 AI 不可见。你的信息架构。你的用你自己的语气写的文案。你的视觉系统。你做过的然后忘记自己做过的上百个小决定。所有这些都躺在一台服务器上,而你在求助的助手完全看不到它们。
于是行业的建议变成了:重新开始。打开一个空白的 prompt,凭记忆描述你的网站,然后希望结果长得像你本来已有的东西。
这不是一个全新的开始。这是一个已经存在的东西的有损重编码,通过一个狭窄的文本通道传输。你重新争论那些你已经敲定过的决定,而且你默默丢失那些你记不起来做过的决定。对于一类价值主张是"用你自己的话"的工具,让人扔掉他们最有 context 的资产是本末倒置的。
所以我们做了这个 importer。你给它一个你拥有的网站 URL;它在这个平台上生成一个真实的、可编辑的项目,然后你可以在上面用你已经用惯的助手继续构建。
天真地克隆一个网站的方式是把所有东西扁平化到一个目录,然后重写每一个引用以匹配新的布局。它能工作,但它是一枚重写炸弹:每一个 href、每一个 src、每一个 CSS 里的 url()、每一个藏在 JSON blob 里的路径,都必须被找到并变更,而每一次变更都是破坏它的机会。
我们走了另一条路。页面获得干净的 URL——about.html 变成 /about,contact.php 变成 /contact,末尾斜杠被规范化——因为这些是人类会看到和分享的 URL。但同源资源保持原始路径。引用了 ../../uploads/2019/hero.jpg 的样式表仍然精确地引用它,因为那个文件仍然精确地生活在那个路径上。
回报是重写器缩小到几乎为零。不再需要重映射每一个引用,你只需要做主机级规范化和一小套页面 slug 重写。三遍处理:
主机规范化——将 https://site.com、//site.com 和同源绝对路径统一为一种内部形式。
Slug 重写——页面扩展名变化(.html/.php → 干净 URL),最长匹配优先应用,这样更长的路径不会被一个更短的前缀路径截断。
清理——剥离属于旧主机、不属于你内容的东西:源站的 robots 指令、它的 canonical 标签、平台注入的启动脚本。更少的重写、更少的边界情况、更少的方式导致一个 99% 导入的(即 broken 的)网站。
概念是一个周末的事。它不是一个周末的原因,是网络是三十年的积累怪异,一个 99% 正确导入的网站是一个 broken 网站。真正决定这能不能工作的边界情况抽样:
转义的 JSON-LD。 WordPress + Yoast 发出的结构化数据带有转义斜杠:
{"@type":"CoffeeShop","url":"https:\/\/site.com\/"}
一个只能理解 href="..." 的重写器会径直走过那个 URL,在你的 schema graph 里留下一个死引用。你必须解析那个转义形式。
扩展名 vs. DOM 上下文。 一个文件可以对自己的类型撒谎。
<link rel="stylesheet" href="/theme/css/variables.php">
那是一个 .php 文件被作为 text/css 服务。如果你在扩展名上路由,你会把它归档为一个页面,然后网站失去样式。<link rel="stylesheet"> 上下文才是真相;扩展名不是。上下文胜出——你把它存为样式表,从原始 URL 获取,查询字符串完整保留。
百分号编码的文件名。 café-interior.jpg 在 HTML 中显示为 caf%C3%A9-interior.jpg。解码错误,或者解码两次,或者交给一个剥离非 ASCII 字符的 slug sanitizer,引用和存储的文件就不再指向彼此。(题外话:在这项工作期间,我们发现自己的 asset store 在上传时悄悄剥离了 é。你自己的技术栈也是三十年怪异之一。)
协议相对 URL 和 srcset。 //host/img.jpg 和逗号分隔的 srcset 阶梯都在 naive 的仅扫描 src 的方式会错过的地方藏有引用。
重定向链。 /old → /older → /current,链中某处的一跳漫游到了一个你无法控制的主机。你跟到终点,或者拒绝并说明——你不能做的事是默默丢弃它然后报告成功。
最后一条是真正的原则。如果你绑定了覆盖率,说明你丢弃了什么。一份报告说"100%,0 failed"而页面没有 CSS,这不是报告,这是一个带进度条的谎言。
静态导入不能带来动态行为,装作不是这样只是把失望往下游推移。源站的表单 post 到一个不再存在的 origin。AJAX 端点、服务器端渲染的个性化、任何需要后端的东西——没有一个能在拷贝中存活,因为另一边没有后端来接收它。
所以 importer 标记了这些而不是伪造它们。表单以 markup 形式进来;之后你把它们重新连接到平台的表单层,一旦你知道哪些表单需要它,这是一分钟的工作。skip 报告就是那个列表所在的地方。
对于即将测试这个的人,也有一个值得明确说明的真正边界:它是同主机的。从单独的 CDN 子域名服务的资源——cdn.yoursite.com、static.yoursite.com——默认不会被拉进来。如果你的主题在一个主机而你的媒体在另一个,你需要计划这个 case,而不是假设它。我想让你在这里读到这些,而不是在导入中途发现它。
我给在这个领域构建的人的一句话:在你构建迁移功能之前,检查一下你是否实际上在构建一个 context 功能。它改变了"完成"的定义。
迁移在字节移动时完成。context 功能在你移动的东西现在在模型能够在其上推理的地方时完成——它能寻址的干净 URL、它能编辑的 SEO 字段、它能导航的结构。同样的爬取,不同的成功定义,而第二个是你的用户真正能感受到的。
Context 是稀缺资源。谨慎地花费它,用用户自己的作品填满它。
我们构建了 WebsitePublisher——一个 MCP 平台,在那里你的 AI 用真实后端构建真实网站:认证、支付、表单、结构化数据。导入功能已上线。如果你试了而有些东西没有过来,skip 报告会告诉你是什么以及为什么——我真的想知道我们没有预料到的那些 case。