Skip to content

第 24 章 从 Prompt 到 Context 与 Harness ​

一本书写到这里,按说该结束了。前 23 章,把“Prompt 工程”这件事,从机制讲到直觉,从基础讲到要素,从结构讲到推理,从可靠性讲到工程化。读到这里,一个能独当一面的 Prompt 工程师,已经成形。

但这一路上有个隐形的边界,一直没跨过去。前 23 章几乎都在讲“如何写好一条 Prompt”,而真实的工程里,更常见的工作,反而是“如何组织起一整个 LLM 应用”。Prompt 工程师最终交付的,不是一条精心雕琢的字符串,而是一个能稳定运行、可供团队协作、能扛住生产负载的系统。

所以这最后一章,聊两个比“Prompt”更大的词,Context(上下文) 和 Harness(脚手架)。它们不是替代 Prompt Engineering,而是它的自然延伸,前者关注“喂给模型的所有东西”,后者关注“包在模型外面的所有代码”。把这两个词放进工具箱,从“调 prompt 的人”到“做 LLM 应用的人”的蜕变,才算真正完成。

24.1 模型看到的不止你写的那条指令 ​

Prompt Engineering 这个词本身,就有点“误导性”,它让人以为工程对象只是那一条 prompt。

“Prompt”,顾名思义,就是发给模型的那一条文字。但真的去搭建一个 LLM 应用的时候,就会发现,模型每一次回复的背后,它真正“看到”的东西,远不止写下的那一句 prompt。它是把整个“上下文窗口”里的所有内容,作为一条超长的、连续的文本序列来读。

这条超长序列里,可能有系统设定、有几轮历史对话、有用户当下输入、有检索回来的几个文档片段、有几个 few-shot 示例、有工具的返回结果、有结构化的元数据……所有这些,模型都会当作“提示”的一部分来处理。Prompt Engineering 关心的,是其中“亲手写”的那一段;Context Engineering 关心的,是“整个上下文窗口里所有内容的编排”。

这个区别,看起来只是视角的拉远,但落到工程里,会让思维方式发生根本性的变化。

重新定义“输入” ​

早期 LLM 应用刚火起来的时候,大家的思路普遍是“写一条 prompt,然后循环调用”。那时候模型没有持久记忆,每次调用都是孤立的。

但现在去看任何一个稍微成熟一点的 LLM 产品,不管是 ChatGPT、豆包这样的通用助手,还是某个垂直行业的客服机器人,都会发现它们的 prompt 文件里,已经远远不止“一句话指令”了。里面通常有一段写在最前面的 system prompt,定义模型的角色、边界、输出格式;有一段固定或动态插入的 few-shot 示例,告诉模型“像我这么回答就行”;有一段 RAG 召回的相关文档片段,作为模型的“参考资料”;有一段对话历史,可能经过压缩、摘要或者选择性保留;有一段工具调用结果,比如“我帮你查了天气,北京今天晴,25 度”;还有一段用户当前的输入。

所有这些内容,以某种顺序,拼成了一条长长的字符串,然后一起送进模型。这就是 Anthropic 在 2025 年 9 月那篇引用极广的博客里说的“Context Engineering”,也就是精心策划每一次 LLM 调用时输入窗口里的所有内容,而不是只盯着手写的那一小段。

这个词不是 Anthropic 发明的。2025 年 6 月,Shopify 的 CEO Tobi Lütke 最早在推文里用了 context engineering;一周后,Andrej Karpathy 发帖说它比 prompt engineering 更贴切,把它定义为“用恰到好处的信息填满上下文窗口的精细艺术与科学”,这个词才真正传开。Anthropic 那篇 9 月的博客把它系统化:给了定义、组件清单和工程方法。这个术语在 2025 年下半年随着 Chroma 的 Context Rot 报告(2025-07)和 Anthropic 的工程博客(2025-09)迅速进入主流视野。

马上会冒出一个问题。这与前 23 章学的 Prompt Engineering,有什么本质区别?

区别在哪 ​

区分这两件事,关键看管的对象。

Prompt Engineering 管的是你亲手写的那段文本,措辞怎么组织、结构怎么安排、示例怎么给,核心问题是这段文字怎么写,模型才能给出想要的输出。

Context Engineering 管的是送进模型的整个输入,哪些信息进上下文、按什么顺序、用什么格式、何时更新,核心问题是这次调用的输入怎么编排,应用才能稳定可靠。

会写 prompt,不代表会做 Context Engineering。后者要求的,是一种更全局、更结构化、更工程化的视角。

具体来说,Context Engineering 至少要回答四个问题。第一是信息从哪来,第二是按什么顺序排,第三是能塞多少,第四是怎么保持新鲜。

先看信息从哪来。是模型训练时就“知道”的背景知识,还是每次调用临时塞进上下文窗口的?是程序生成的,还是用户输入的?来源不同,可靠度和时效都不一样。

再看信息怎么排。什么放前面、什么放后面?用 XML 标签还是 markdown 标题?纯文本还是结构化的 JSON?顺序和格式,都会改变模型读到每段信息的效果。

然后是信息能塞多少。模型的上下文窗口是有上限的,到 2026 年中,主流旗舰模型的窗口已经从 4K、8K,扩展到 200K、1M。但窗口并不是越大越好,一是成本线性上涨,二是内容一长,质量就要打折扣。

这里有两个独立的研究发现。一个是上下文腐化(Context Rot):Chroma 公司 2025 年的技术报告实测,上下文越长,模型对其中内容的利用率整体越低,这是长度效应。另一个是第 14 章讲过的“Lost in the Middle”:同样长的输入,模型对开头和结尾用得多,对中间用得少,这是位置效应。所以窗口大不等于窗口好用,1M 的窗口也救不了塞满无关内容的上下文。

最后看信息怎么更新。会话历史越滚越长,怎么压缩?知识库会过时,怎么刷新?用户状态会变,怎么同步?

四大核心原则 ​

翻一翻 Anthropic、Shopify,以及一些大厂 LLM 团队的技术博客,会发现他们在 Context Engineering 这件事上做法各异。把这些做法放在一起归纳,可以提炼出四条共通的原则,下面把这四条翻译成工程师更熟悉的语言。

相关性原则。上下文窗口里,每一段信息都应该“为这一次回答服务”。那些“也许有用、但大概率用不上”的内容,统统不要塞。有人也许会想“以防万一”,但每一个 token 都在消耗成本、稀释注意力、增加模型分心的概率。举个常见的例子,RAG 检索回来十个文档片段,和当前问题真正相关的往往只有两三个,剩下那七八个塞进去,无关细节反而可能把模型带偏,让它从错误的片段里摘出一句话来回答。同样能完成任务,上下文越简洁越好。

密度原则。同样长度的一段文字,能塞进去的信息量可以差出好几倍。一句“用户是一个 35 岁的男性程序员,正在调试 Python 代码,希望模型用简洁专业的语言回答”,比“用户看起来还蛮年轻的,应该是个搞电脑的,可能对编程比较了解,希望回答不要太长也不要太短”,前者每个 token 都在“传递有效信息”,后者充满了水分。信息密度,直接决定一段上下文的质量。

结构原则。人是这样,模型也是这样,有结构的信息,比无结构的信息更易消化。用 XML 标签、Markdown 标题、JSON Schema 这种“明显的视觉锚点”去组织上下文,比把一切都写成一段连续散文,效果要好得多。第 7 章学到的 CO-STAR、CRISPE 这些框架,骨子里都是在做“上下文结构化”这件事。

新鲜原则。上下文窗口里的内容,不能“过时”。系统时间要更新、用户状态要同步、知识库的版本要刷新、对话历史要压缩。一份“看起来很全但其实是三个月前的快照”的上下文,比一份“只覆盖关键信息但都是最新的”上下文,危险性大得多。

一个真实的对比 ​

来看一个具体的例子。

Prompt Engineering 视角的客服 prompt:

你是一个电商客服。请礼貌回答用户的问题。
用户问题:{user_query}

Context Engineering 视角的客服 prompt(简化示意):

[SYSTEM]
你是一名电商平台"豆芽"的客服助手。
- 角色定位:耐心、专业、有同理心,不回避用户的负面情绪。
- 输出风格:先共情,再解决,最后给出明确的下一步。
- 严格约束:不能编造订单状态、不能承诺超出权限的退款额度。
- 工具使用:查询订单用 query_order;申请退款用 request_refund。

[USER PROFILE]
姓名:张三;会员等级:金卡(已满 2 年);最近 30 天下单:3 单;上次接触:昨天,因物流延迟投诉过。

[CONVERSATION HISTORY]
(已压缩为摘要)用户因昨天延迟到货,已表达不满。今日再次进线,情绪可能仍带有波动。

[RAG RETRIEVAL]
- 《豆芽平台退款政策 v3.2》第 4.1 条:"生鲜类商品到货超过 24 小时,用户可申请全额退款"。
- 《金卡会员权益》第 2 条:"金卡会员每月享一次'急速退款'特权,无需审核即可先退款后查证"。

[FEW-SHOT EXAMPLES]
示例 1:用户抱怨"还没收到货",回答模板:"很抱歉让您久等了,我已经帮您查到了最新物流……"
示例 2:用户询问"能退吗",回答模板:"可以的,根据您的会员等级,您享有……"

[USER CURRENT INPUT]
{user_query}

[FINAL INSTRUCTION]
请基于以上所有信息,给出一个完整、简洁、礼貌的回复。

这两种 prompt 在“长度”上差了 10 倍都不止,但要问效果,后者几乎在所有维度上都会优于前者。

原因就在于,前者把“客服”这件事的所有负担,都推给了模型,只告诉它“你是客服”,剩下所有信息(用户是谁、会员什么等级、政策怎么写、对话到哪一步了)都得靠模型自己“猜”。而后者,是把所有相关信息主动地、结构化地塞进了模型的上下文,让它“看一眼就知道”。

这就是 Context Engineering 的价值。模型猜不出没有喂给它的信息,用户是谁、会员什么等级、政策怎么写,都得工程师在上下文里给足。前者把这些留给模型去猜,后者把它们写进输入,差别就在这里。

什么时候用 Prompt,什么时候用 Context ​

那么,是不是以后所有 prompt 都得这么“重”?不是。

一条实用的建议。一次性任务、临时调试、单次提问,用 Prompt Engineering 就够了,简短直接;而生产环境、有状态的对话、复杂业务流,必须做 Context Engineering,否则模型“猜”的成本,远高于“写”的成本。判断标准很简单。如果这个 LLM 应用,明天还要继续用、还要继续迭代,那就别偷懒,把 Context 当作“一等公民”来设计。

实践建议:从这里开始 ​

如果正在做(或即将做)一个 LLM 产品,下面这四个动作,是 Context Engineering 的“最小可行集”,恰好是上面四条原则的落地:定模板对应结构原则,查新鲜度对应新鲜原则,做瘦身对应相关性原则,上下文分析对应密度原则。做完它们,Context Engineering 就算入了门。

第一,给上下文定一个模板。把 system prompt、用户档案、对话历史、RAG、工具这几块,固定成一个结构(比如 XML 标签或 markdown 章节)。不要每次调用都“即兴拼接”。

第二,给每一类信息加上“新鲜度检查”。系统时间、用户状态、知识库版本、对话历史摘要,这些都该有“过期机制”。每次调用前,自动刷新一遍。

第三,给上下文做“瘦身”。会话超过 10 轮,就主动做摘要;RAG 召回超过 5 个 chunk,按相关性排序截断;few-shot 示例动态选最相关的 1-2 个,而不是每次都全塞。

第四,做“上下文分析”。用一些 LLM 可观测性工具(比如 LangSmith、Langfuse、PromptLayer 等)去看每一次调用里,每一段上下文各占多少 token、模型实际引用了哪几段。看完多半会惊讶地发现,很多原以为是“必须”的信息,模型根本没用上。

平台代劳的上下文管理:上下文编辑与记忆工具 ​

前面四个动作,全部要工程师在自己的代码里实现。2025 年起,这里出现了新变化。Anthropic 把上下文管理中最通用的两个部分做进了平台,旧内容的清理由服务端自动执行,跨会话的记忆由一个专门的工具承接。这两个能力指向同一个趋势,Context Engineering 里可以标准化的部分,正在下沉为平台能力。

第一个能力是上下文编辑(Context Editing)。这是一个 beta 能力,开启 beta 头 context-management-2025-06-27 后生效。它做的事一句话能说清,服务端在把 prompt 送给模型之前,自动清理历史里不再需要的内容。最常用的策略是工具结果清理(clear_tool_uses_20250919)。默认配置下,输入一旦超过 10 万 token,服务端就按时间顺序清掉最旧的工具结果,只保留最近 3 次调用,被清掉的位置换上一段占位文本,告诉模型这里的内容已经移除。

触发阈值、保留条数都可以调,还能把某些工具排除在外,比如要求搜索结果永远保留。工程上有一点很关键,清理发生在服务端,客户端保存的完整历史不受影响,不需要自己做两边同步。

代价在缓存。提示缓存按前缀命中,清理一动历史,缓存前缀就作废。所以这个能力还提供 clear_at_least 参数,规定每次至少清出多少 token,保证省下的输入费用能覆盖重建缓存的成本。

另一条路是记忆工具(Memory Tool)。它在客户端实现,模型通过 view、create、str_replace、insert、delete、rename 六个命令,在一个 /memories 目录下读写自己的记忆文件。文件存在应用自己的存储里,跨会话持久。启用它之后,API 会自动往系统提示里注入一段指令,要求模型开工前先查看记忆目录,做的过程中随手把进展记下来。

两个能力组合起来,正好解决长会话的两个难题,上下文装不下,关键信息又不能丢。官方文档专门描述了这个配合,上下文接近清理阈值时,模型会先收到自动警告,把重要信息写进记忆文件,然后旧的工具结果才被清掉。会话变短了,关键信息没丢,需要时再从记忆里查回来。配套的还有服务端压缩(compaction),在对话逼近窗口上限时自动把整段历史摘要替换,官方把服务端压缩列为大多数场景的首选,上下文编辑留给需要精细控制的场景。

这对 Context Engineering 意味着什么。四条原则没有变,变的是执行层。清理由服务端代劳,记忆由工具承接,工程师的工作从自己实现清理和记忆逻辑,上移到决定什么该留、什么可以丢。平台的自动化只执行配置,不会替业务判断哪些上下文才是相关的,这个判断永远是工程师的。

学到这里,结论已经清楚。Context Engineering 不是 Prompt Engineering 的对立面,而是把工程对象从一条 prompt 扩大到整个输入之后的进一步工程化。写 prompt 的能力,决定了能不能“说清楚”;做 Context Engineering 的能力,决定了 LLM 应用能不能“跑得稳”。

24.2 Harness 是包在模型外面的工程代码 ​

说完了 Context,再聊 Harness。

“Harness”这个词,在 LLM 工程领域还比较新,中文没有统一译名(有人叫它“驾驭系统”),这本书保留英文原词。去英文社区搜一搜,会看到 Anthropic 的工程师、LangChain 的创始人、Vercel 的 AI 团队,都在不同场合用过它。最直接的理解是,harness 就是包在模型外面的那层工程代码,装配上下文、调用模型、解析输出、校验、重试、编排工具、记录观测、守住护栏,全都归它管。

为什么需要 Harness ​

模型不是已经有 API 了吗?为啥还需要 harness?

答案藏在 LLM 的一个根本性特征里,它是非确定性的。

传统程序,输入是什么、输出就是什么。写 f(x) = x + 1,输入 1 就是 2,输入 100 就是 101。永远如此。

但 LLM 不一样。把同样的 prompt 喂给同一个模型 10 次,它可能给出 10 个不同的答案,可能大部分都对,但偶尔会“抽风”。这种“抽风”,可能是因为模型的随机采样(temperature > 0),也可能是因为模型本身的幻觉,还可能是因为输入里有一点点没注意到的变化。

更麻烦的是,模型的输出不一定是“合法”的。让模型“输出 JSON”,它可能给出一段纯文字、一个不完整的 JSON(少个右括号)、一个完整但字段名拼错的 JSON、一个完整且字段名正确但值类型不对的 JSON,甚至一个完整且一切正确但完全是“胡编”内容的 JSON。

这些都是传统编程里几乎不会遇到的问题。在传统编程里,如果 f(x) 返回了一个不符合类型签名的值,那叫 bug,修掉就是。但 LLM 不是函数,它的内部逻辑改不了,这些行为也没法像 bug 那样修掉,只能在模型外面加代码,把输出接住、校验、兜底。

管它的方式,就是 Harness。

Harness 的核心组件 ​

Harness 这个词看起来抽象,但拆开看,它由一组非常具体的工程组件构成。下面这些组件,一个“合格”的 LLM 应用至少该有——

先从输出解析器(Output Parser)说起。模型的原始输出是字符串,Harness 要做的第一件事,就是把这串字符串“消化”成结构化数据,JSON、XML、YAML,或者自定义的对象。

这一关看似简单,但工程上的坑非常多。比如,模型可能在 JSON 前后加一些“解释性话语”(“下面是结果:{...} 希望你满意”)。怎么把这些废话剔掉?正则?前缀匹配?JSON 提取库?每种方案都有边界情况。

一个健壮的 harness,会用防御性的方式去做解析,先尝试严格解析,失败就退到宽松解析,宽松解析也失败就重试或者回退。绝对不要假设“模型一定会输出干净的 JSON”。

接下来是 Schema 校验器(Schema Validator)。解析完 JSON,只是第一步,接下来得用 Pydantic、Zod 这样的工具,去检查这个 JSON 是不是符合期望的“形状”,字段都在不在、类型对不对、值在不在允许的范围内、必填项有没有填。

第 12 章 12.5 节的“解析器协同”,其实已经讲过这一步:用 Pydantic 这类工具做严格校验,校验失败就自动修复。这里没有新的知识点,新的只是视角。在 harness 里,它从一条技巧归位成了一个标准组件,每个 LLM 应用都默认该有。

重试与回退机制(Retry & Fallback)也必不可少。模型“抽风”的时候,Harness 要有重试策略。第一次失败,可以换个 prompt 重试一次;第二次失败,可以换个模型(比如从 mini 档换到旗舰档,或者切到 DeepSeek、通义千问 Qwen 这类性价比更高的型号);第三次失败,可以走回退路径,比如返回一个默认值,或者把任务交给人类处理,也就是人类在环(Human-in-the-Loop),或者直接给用户一个友好的错误提示。第 18 章 18.5 的模型故障切换,说的就是这一层的事。

但重试不是无脑地重试。 如果是网络错误,重试有意义;但如果是模型“不理解指令”,重试一万次也是同样的结果。好的 harness 会区分错误的类型,对症下药。

再看上下文管理器(Context Manager)。还记得上一节说的 Context Engineering 吗?Harness 是 Context Engineering 的“执行者”,它要负责的事情很具体,每次调用前,把 system prompt、用户档案、对话历史、RAG 结果、few-shot 示例,按模板拼好送进模型;每次调用后,把模型输出、工具结果、用户反馈,存回记忆系统(数据库、向量库、Redis 等);会话跨度过长时,自动摘要、压缩或者用滑动窗口裁剪。

这就是前面说的“Context Engineering 的最小可行集”,在代码层的具体落地。

如果 LLM 应用要用到工具,查天气、查数据库、调用外部 API,那还需要工具调用编排器(Tool Orchestrator),它负责把模型的“工具调用请求”翻译成真实的代码调用,再把调用结果回填给模型。

这一层听起来简单,做起来非常复杂,至少要考虑工具调用的并发(模型一次可能要调 5 个工具,并发还是串行?)、超时(数据库查询太久,要不要给模型一个“超时”反馈而不是一直等?)、权限(这个 API 用户有没有权限调用?)和成本(这次 API 调用要花多少钱,超预算了怎么办?)。

观测性层(Observability)管的是“跑起来之后怎么办”。harness 不是一个“跑起来就不管”的系统,跑起来之后,每一次调用都要盯着看、记下来——把输入、输出、用到的工具、花了多少 token、延迟多少、成本多少、是否触发重试回退,都收进日志。

没有观测性的 LLM 应用,运行时处于完全不可观测的状态。没人知道它什么时候会出问题,出问题的时候也无从查起。LangSmith、Langfuse、PromptLayer 这些工具,就是专门给 LLM 应用做观测性的。它们让工程师能像看传统应用的日志和指标一样,看 LLM 应用的每一次调用。第 18 章从运维角度展开讲过这套体系,在 harness 这里,它就是一个标准组件。

安全护栏(Guardrails)是最后一道防线,harness 还得负责“拦着模型”,不能让它说什么就说什么。这包括 PII(个人敏感信息)过滤、越狱提示的检测、有害内容的拦截,以及输出格式的最终兜底校验。

这一层,第 13 章“Prompt 安全与合规”里已经学过,但 harness 视角下,它是“代码层的护栏”,而不是“提示词层的护栏”。两层都要有,缺一不可。

Harness 的设计模式 ​

理解了“组件”,再来看“模式”,也就是这些组件怎么组合起来。最常见的组合有这三种。

最简单的是“一问一答”模式,一个用户请求,对应一次 LLM 调用、一次工具调用、一次返回,绝大多数 LLM 应用一开始都是这种结构。复杂一些的是“流水线”模式,一个用户请求,对应多个 LLM 调用 + 多个工具调用,按顺序或者并行执行,这是第 22 章看到的 Pipeline 工作流的代码层实现。还有“循环”模式,一个用户请求,可能需要多轮 LLM + 工具的迭代,直到任务完成或达到终止条件,这就是第 20-22 章看到的 Agent 循环。

Agent 和 harness 是什么关系?一句话:Agent 就是带控制循环的 harness。第 20 章讲的“感知、规划、行动”循环是引擎,第 21、22 章用 ReAct 和多 Agent 编排把它落成产品形态;工具、记忆、规划、反思,全都编排在这个循环里。ReAct 只是实现循环的一种范式,不是 Agent 的定义。

实战:从零搭一个最小 Harness ​

讲了这么多抽象的,具体怎么从零开始搭一个?

下面是一段最最简化的 Python 伪代码,用来建立直觉:

# 一个最简 LLM Harness 的骨架
class LLMHarness:
 def __init__(self, model, system_prompt, tools, memory):
 self.model = model
 self.system_prompt = system_prompt
 self.tools = tools
 self.memory = memory

 def run(self, user_input):
 # 1. 装配 Context
 context = self._assemble_context(user_input)

 # 2. 调用 LLM(带 retry)
 for attempt in range(3):
 try:
 raw_output = self.model.generate(context, temperature=0)
 break
 except Exception as e:
 # 真实实现:先按错误类型分流,只有网络超时这类瞬时错误才值得重试
 if attempt == 2: raise

 # 3. 解析输出
 parsed = self._parse_output(raw_output)

 # 4. Schema 校验
 if not self._validate(parsed):
 return self._fallback(user_input)

 # 5. 工具调用(如有)
 if parsed.get("tool_call"):
 tool_result = self.tools[parsed["tool_call"]].invoke(parsed["args"])
 return self._format_response(tool_result)

 # 6. 记忆写入
 self.memory.append(user_input, raw_output)

 # 7. 观测性埋点
 self._log(user_input, raw_output, parsed)

 # 8. 返回
 return parsed["answer"]

 def _assemble_context(self, user_input):
 return f"{self.system_prompt}\n\n{self.memory.summary}\n\n用户:{user_input}"

这不是一段可以“复制粘贴就能用”的代码,但它包含了 harness 的大部分核心组件:上下文装配、重试、解析、校验、工具调用、记忆、观测。省略掉的东西也有意为之:异常没有分类,一律原样重试(真实实现要像上文说的那样区分错误类型);工具调用分支直接返回,跳过了记忆写入和埋点;护栏层也没有画进来。真实代码会复杂 10 倍、100 倍,但骨架就是这个。

多 Agent harness 编排 ​

单 Agent 能解决六成任务,剩下的复杂场景——例如一次完整的行业调研、自动写一篇带图的报告、跨多个内部系统的数据核对——单个 Agent 拉不动,要么是上下文装不下,要么是工具太多互相干扰,要么是规划能力撑不住复杂度。这时候需要把一个 Agent 拆成多个,每个负责一段,harness 负责把多个 Agent 串成一个流水线。

三个编排模式最常见。第一是 supervisor 模式:一个中心 Agent 当调度器,接收用户请求后拆成子任务,派给 worker Agent,收集结果再汇总。中心 Agent 知道全图,worker 只知道自己手头的活。LangGraph、AutoGen、CrewAI 默认都是 supervisor 风格。第二是 peer-to-peer 模式:所有 Agent 平等,没有中心调度,每个 Agent 都能向其他 Agent 发消息。这种模式灵活度高,但容易出现循环通信(Agent A 问 B,B 反问 A)和无限跑题,调试也难。第三是 hierarchical 模式:分层结构,上层 Agent 拆任务、中层 Agent 分配资源、下层 Agent 执行,类似公司组织架构。这种模式适合有清晰责任边界的场景,例如“总编 Agent + 选题 Agent + 写作 Agent + 校对 Agent + 配图 Agent”一条龙。

选哪个模式看三件事。一是任务能否清晰拆成不重叠的子任务——能拆才适合多 Agent,拆不开强行拆只会增加通信开销。二是要不要集中决策——需要统一规划走 supervisor,不需要走 peer-to-peer。三是 Agent 之间要不要共享上下文——要共享走 hierarchical(上层统一装配上下文),要隔离走 supervisor(每个 worker 拿到独立的子上下文)。

举个多 Agent 调研团队的例子。一个市场调研请求进来,supervisor Agent 把它拆成“搜集竞品信息、搜集用户评价、搜集行业数据”三个子任务,分别派给三个 worker Agent;worker 各自跑 RAG、爬网页、调数据库;supervisor 拿到三份结果后写综合报告。这种 supervisor 模式的好处是上下文隔离干净(每个 worker 不被其他任务污染),debug 时单步重跑某个 worker 即可。代价是 supervisor 本身成了瓶颈——拆任务的质量决定整个流水线的质量。

多 Agent harness 落地还有几个常见坑。一是通信 token 成本——Agent A 的输出全量塞给 Agent B,三轮对话就把上下文窗口挤爆。解决办法是只传关键摘要而不是全量轨迹。二是状态一致性——多 Agent 并行跑时,谁先谁后、谁失败谁重试,harness 必须有清晰的事务语义,否则容易出现“任务完成但结果丢了”的状态。三是观测粒度——多 Agent 跑一次产生的轨迹比单 Agent 多一个量级,LangSmith/Langfuse 这类工具必须打深埋点,否则事后排查无从下手。

到这里 Harness 这一节收尾。harness 把模型、上下文、工具、记忆、护栏、观测、多 Agent 协作串成一个完整的工程系统;写 harness 的功夫,决定一个 LLM 应用能不能从 demo 走到 production。


24.3 MCP 与 Prompt Caching ​

24.2 讲的 Harness 解决了“模型外面那层代码怎么搭”。但还有一个问题没解决:模型怎么接外部工具?传统的做法是给每个工具写一套定制集成,接一个 GitHub 写一套,接一个 Notion 又写一套,接一个 Slack 再写一套。一个稍微复杂点的 LLM 应用背后可能有几十个集成,每集成都要单独维护。

MCP 是什么:工具接入的协议层 ​

Anthropic 在 2024 年 11 月 25 日发布的 Model Context Protocol(MCP,)就是为了解决这个问题。MCP 是一个开放标准,让 AI 应用和数据源、工具之间用同一个协议通信。一个数据源(比如 GitHub)只要按 MCP 规范暴露一次接口,所有支持 MCP 的客户端(Claude Desktop、Cursor、各类 IDE 插件)都能直接连上,不用再单独适配。

MCP 的架构分两端。服务器端(MCP Server)由数据源或工具提供方实现,按 MCP 规范暴露能力,包括三类:可调用的工具(比如查询订单),结构化的资源(比如数据库表 schema),预定义的提示模板片段。客户端(MCP Client)由 LLM 应用实现,负责发现服务器的能力、把模型生成的调用请求转发给服务器、把结果回填给模型。两者通过 JSON-RPC over stdio 或 HTTP 通信。

MCP 与之前讲过的 Function Calling 是什么关系?Function Calling 是模型能力层的协议,定义了“模型怎么生成调用 JSON”。MCP 是工具接入层的协议,定义了“工具怎么暴露能力、客户端怎么发现和调用工具”。两者不冲突,而是互补。MCP 在 Function Calling 之上,把工具的接入从“为每个工具写适配器”标准化成“按一个规范暴露”。

Anthropic 在发布 MCP 时同时给出了首批预构建的 MCP 服务器,覆盖 Google Drive、Slack、GitHub、Git、Postgres、Puppeteer 等常见企业系统。同时也开源了 Python 和 TypeScript 的 SDK,文档站 modelcontextprotocol.io。工程师接入 MCP 的成本从“为每个工具写一两周的适配器”降到“启动一个 MCP 服务器加上客户端连一下”。

到这里 MCP 是什么讲清楚了。下一节看 MCP 在 2025 年被哪些主流厂商采纳。

想一想:去 modelcontextprotocol.io 看一遍规范,试着为一个本地 SQLite 数据库写一个简单的 MCP Server(不超过 100 行 Python),让 Claude Desktop 能直接查询本地数据库。

MCP 的工业采纳:2025 年事实标准形成 ​

MCP 在 2024 年 11 月由 Anthropic 提出的时候,更像是“一家的协议”。但 2025 年第一季度发生了几件大事,让 MCP 实质上成为 LLM 工具接入的事实标准。

最先跟进的是开发工具厂商。MCP 发布同月,Zed、Replit、Codeium、Sourcegraph 这几家 IDE 与代码协作工具就宣布集成或试用 MCP。这些厂商的共同需求是“让 AI 在 IDE 里能读懂用户的代码仓库、PR 历史、Issue 列表”,过去每家要单独给 Anthropic、OpenAI、Google 的模型写适配器;MCP 出来后,只要写一次 MCP 服务器,所有支持 MCP 的模型都能用。

企业侧的首批采纳者是 Block(Square 的母公司)和 Apollo(销售智能平台)。这两家把内部数据源(CRM、知识库、协作工具)通过 MCP 服务器暴露给 AI 助手,工程师不用再为每个 AI 客户端写一份集成。

2025 年 3 月 11 日 OpenAI 发布 Agents SDK,同月底加入对 MCP 的支持。OpenAI 的 SDK 文档明确列出两种 MCP 接入模式:本地启动 MCP 服务器、由 SDK 直接调用,或者远程 MCP 服务器通过 URL 接入。这等于 OpenAI 正式把 MCP 接纳为自家 Agents SDK 的标准扩展机制。

2025 年 4 月 9 日 Google DeepMind 在官方开发者博客发布“Build rich, interactive AI experiences with MCP”,宣布 Gemini API 和 SDK 支持 MCP。DeepMind 在文章里把 MCP 描述为“让开发者用一次集成就能接入任何兼容 MCP 的工具”。至此,三家前沿模型厂商(Anthropic、OpenAI、Google DeepMind)全部支持 MCP。

社区生态也同步扩张。modelcontextprotocol/servers 这个官方仓库收集了大量社区贡献的 MCP 服务器,覆盖文件系统、Postgres、SQLite、Git、Notion、Linear、Figma 等。开发者不需要从零写服务器,多数场景下 clone 一个现成的、改改配置就能用。

到 2026 年中,主流 LLM 框架(LangChain、LlamaIndex、Vercel AI SDK 等)都默认提供 MCP 客户端支持。一个 LLM 应用要接入外部工具,几乎不用再问“要为哪个模型写哪种集成”,只要问“要连哪个 MCP 服务器”。

到这里 MCP 的工业采纳讲完了。下一节看 Prompt Caching,这是和 MCP 同样重要的另一项 harness 基础设施。

想一想:去 GitHub 搜 modelcontextprotocol servers,按 star 数排一下,看看社区生态最活跃的 MCP 服务器集中在哪些场景(数据库?知识库?协作工具?),再想想为什么。

Prompt Caching:上下文成本与时延的双重削减 ​

MCP 解决了“工具怎么接”,Prompt Caching 解决“长上下文怎么便宜”。2024 年下半年起,三家前沿厂商相继推出 Prompt Caching,把长上下文的成本和时延同时砍掉一截。

Anthropic 是最早动手的。2024 年 8 月 14 日 Claude API 开放 Prompt Caching。机制是:开发者标记 cache breakpoints(断点通常落在 system prompt、工具定义、长文档前缀),模型在生成时如果发现前缀命中已缓存的 KV 计算结果,直接复用,不重新做 attention 计算。初版定价(2024 年 8 月,Claude 3.5 Sonnet 时期)按相对基础输入价比的方式定价,写入为基准的 1.25 倍(3.75 美元每百万 token)、命中读取为基准的 0.1 倍(0.30 美元每百万 token),相当于相比同等输入正常定价最高便宜约 90%;之后随模型迭代价格多次调整,使用时应以 console.anthropic.com 的实时价表为准。这对 system prompt + 工具定义 + 长 RAG 文档拼起来几十 K token 的 Agent 应用尤其值钱。

OpenAI 在 2024 年 11 月推出 Automatic Caching。机制略有差异:不需要显式打 cache_control 标记,API 自动把前缀里不少于 1024 token 的稳定内容识别成可缓存段,超过一定长度后命中即享 50% 折扣。OpenAI 的设计哲学是“开发者不用关心缓存”,代价是粒度由平台控制、对特定断点的控制力弱于 Anthropic。

Google Gemini 走的是 Implicit Caching(2024 年 11 月公布,2025 年 5 月公开定价,)。和 OpenAI 思路类似——命中即降价,不暴露显式 API——但 Gemini 的优势是默认上下文窗口极大(Gemini 1.5 Pro 1M token,2.0 Flash 同样支持 1M),长上下文本身成本和时延压力都比另两家小,缓存更多是“顺手省一些”而不是关键开关。

三家的差异可以这样记:Anthropic 是显式加最大折扣(适合严格成本控制的 Agent);OpenAI 是自动加中等折扣(适合快速集成);Gemini 是隐式加折扣较小但上下文最长(适合超大文档任务)。截止到 2026 年中,Anthropic 的显式 cache_control 仍然是控制力最强的方案,多数 production Agent 首选这条。

成本维度之外,Prompt Caching 还有第二个收益:首 token 时延。命中缓存后,模型不需要重新计算 attention 的 prefix KV,前缀越长,时延节省越明显。一个 50K token system prompt 加工具定义的 Agent,命中缓存后首 token 时延能从数秒降到几百毫秒。

到此 Prompt Caching 讲完了。下一节看 harness 里的另外两个工程支柱:评估驱动开发与沙箱隔离。

想一想:去 Anthropic 控制台写一段带 cache_control 的 system prompt 加工具定义的代码,对比不开缓存和开缓存的 latency 与 cost,看 prompt 长度多大时缓存的收益开始抵消其管理成本。


24.4 评估驱动与沙箱隔离 ​

评估和沙箱是两个容易被低估、但 production Agent 跑一天就会撞上的工程支柱。评估负责“Agent 改完还是不是同一个 Agent”,沙箱负责“Agent 跑代码时不会把生产搞坏”。这一节把这两件事拆开讲透。

为什么 Agent 需要评估驱动开发 ​

传统软件用单元测试守住质量:开发者写一段代码,立刻写一个 test case 断言输入和输出,下一次提交前 CI 会跑全套测试,断言错一个就不许合并。这套机制能成立,是因为函数行为是确定的:给定输入必然返回同一个输出,开发者用断言就能锁死它。

Agent 的输出是概率的。同一个 prompt 第二次跑,模型抽样出另一个 token,答案文字不同、调用工具的次数不同、中间步骤也不同。开发者没法写硬断言锁住“模型应该说哪句话”,这意味着传统意义的单元测试在 Agent 领域失效。

评估(Eval)是替代方案。开发者准备一批带 ground truth 的样本 case,让 Agent 跑这一批 case,然后拿 Agent 的输出和 ground truth 比。每个 case 给一个分数(0/1 或者连续值),一批 case 跑下来得到一个聚合分数。改 prompt、改 harness 后重跑同一批 case,对比分数变化。分数掉了说明退化,分数涨了说明改进。

Agent 的评估通常分三层。第一层是工具调用正确率:Agent 该调的工具调了没有,参数对不对,顺序合不合理。第二层是终态答案准确率:Agent 最后输出的答案和 ground truth 的语义匹配度。第三层是轨迹质量:中间步骤是否走过死路、是否做了无谓的重试、是否绕了远路。一个 Agent 跑出正确答案不等于跑得合理——它可能调了 20 次工具才答对,而 ground truth 只用 3 次,这就要看轨迹分数。

Eval-driven dev 的循环很直接:写一批 eval case → 跑 baseline → 改 prompt 或 harness → 重跑 eval → 看分数 diff → 退化就回滚。一个 200 行的 system prompt 改动,落到这个循环里就是几分钟的事。起步阶段准备 50 个核心 case,覆盖最常见的用户路径;Agent 长大后 case 集长到 200-500 个,覆盖边界 case 和历史回归。

举个具体的例子。一个客服 Agent 的 eval 集大致长这样:

  • 用户问运费多少 → 期望调用 get_shipping_fee 工具,输出含具体数字
  • 用户问能否退换 → 期望调用 return_policy 工具,输出引用政策原文
  • 用户问竞品价格 → 期望不调用 internal_pricing 工具,转人工
  • 用户投诉产品质量 → 期望调用 complaint_intake 工具,生工单而非口头安抚
  • 用户反复问同一问题 → 期望保持耐心而非发脾气

每个 case 配一段 ground truth:期望的工具序列、期望的输出关键信息、期望的拒答情形。改 prompt 后这五个 case 同时重跑,分数变化一目了然。

到这里“为什么要做评估”讲完了。下一节看评估这件事具体怎么落地,用什么工具链。

想一想:挑一个你手头的 Agent(哪怕只是写过的 demo),列出 5 个你最怕它答错的输入,配上你期望的输出行为,这就是你的第一版 eval 集。

评估框架与工具链:从离线 case 到在线回归 ​

评估这件事说起来简单,落到工程里要管的事不少。要存 case、要记录跑出来的轨迹、要聚合分数、要对回归报警。这些事可以由三个主流工具链分担,分别是 LangSmith、Langfuse、Braintrust。

LangSmith 是 LangChain 团队推出的闭源平台。和 LangChain 集成最深,从 trace(轨迹)记录到 eval 集管理一条龙。开发者用 LangChain 或 LangGraph 写的 Agent,LangSmith 自动捕获每一次 LLM 调用、工具调用、中间步骤;开发者从 web UI 能直接看到每条 trace 的可视化时间线。eval 集可以用 dataset 功能管,每个 case 配一个 evaluator 函数(exact match、模糊匹配、LLM-as-judge 都行),重跑后看分数趋势。

Langfuse 是开源方案。和 LangSmith 功能类似——trace 记录、dataset 管理、evaluator 框架——但可以自托管,数据落在自己的数据库里。对数据合规要求高的团队(金融、医疗、政企)更友好。Langfuse 也提供云托管版,免运维但数据仍在欧洲托管。和框架的集成比 LangSmith 略广,LangChain、LlamaIndex、原生 OpenAI/Anthropic SDK、自定义 harness 都能接。

Braintrust 是相对新的玩家。主打“evaluations as code”,把 eval 写进代码仓库而不是 web UI 里,每次 CI 跑测试时一起跑 eval。优势是和现有工程流程(PR 检查、回归测试)贴合,开发者不需要切到另一个平台。

评估指标的选择直接决定这套机制能不能发现真问题。exact match 适合答案有唯一标准答案的场景(数学计算、SQL 生成);fuzzy match(BLEU、ROUGE、embedding 相似度)适合开放性回答但有参考答案的场景;LLM-as-judge 是 2024 年后主流的方案——让一个强模型(通常是 GPT-4o 或 Claude Sonnet)当裁判,给 Agent 输出一份结构化评分。LLM-as-judge 的风险是裁判模型本身有偏差,需要用人类标注的“金标准”子集定期校准裁判。

评估还要分在线和离线两类。离线评估跑固定 dataset,适合发版前回归;在线评估挂在线上流量上,按 5%-10% 的概率采样真实用户请求打标。离线评估守住了“代码改完没退化”,在线评估守住了“线上真实分布变没变”。两者互补,不可互相替代。

到这里评估的工具链讲完了。下一节看 harness 里另一根支柱:沙箱隔离。

想一想:挑一个上面提到的工具(LangSmith / Langfuse / Braintrust),读一遍它的官方 quickstart 文档,看你的项目 30 分钟内能不能搭起一个最小的 eval pipeline。

沙箱隔离与代码执行型 Agent 安全 ​

Agent 越来越能干,干的事越来越接近真实系统的边界。一个会调 shell 的 Agent、一个会跑 Python 的 Agent、一个会写 SQL 直连生产库的 Agent,能力边界离生产数据只差一层。这一层就是沙箱。

代码执行型 Agent 是最容易出事的场景。Claude Code、Devin、Cursor 的 Agent 模式、OpenAI 的 Code Interpreter 工具——这些 Agent 拿到一个用户请求后会自己写代码、自己执行。代码可以是 print("hello") 这样的无害片段,也可以是 rm -rf / 这样的灾难指令。开发者必须假设 Agent 会写出有害代码,然后用沙箱把有害代码的影响限制在隔离环境里。

沙箱至少要做四件事。第一是文件系统隔离:Agent 写文件只能写到 sandbox 内部,不能写到宿主机其他目录。第二是网络隔离:Agent 发的 HTTP 请求只能访问白名单内的目标,不能任意访问外网更不能访问生产内网。第三是资源限制:CPU 时间、内存、磁盘、网络带宽都有上限,单次执行不能耗光宿主资源。第四是凭据隔离:宿主机的环境变量、API key、SSH key 默认不进入 sandbox,sandbox 内部要访问外部服务时按需注入最小权限的凭据。

主流的代码执行沙箱有三类。第一类是 E2B,开源云沙箱,每个 session 启动一个隔离的 microVM,支持自定义镜像和持久化文件系统,远程 API 调用友好。第二类是 Daytona,定位类似 E2B,但更强调企业级特性和 IDE 集成。第三类是 Anthropic Claude Code 自带的沙箱机制:每个 Claude Code session 在 macOS 上用 Apple Seatbelt 沙盒规则限制文件系统和网络访问,在 Linux 上用 bubblewrap(bwrap)实现同样的隔离。

沙箱不是银弹。最容易在沙箱审计里翻车的是“凭据泄漏”:某公司的工程师在 docker compose 文件里给 sandbox 容器 mount 了整个 .env 文件而不是单独 mount 几个 key,结果 sandbox 内部能读到生产数据库的全部连接字符串。一次复盘发现,sandbox 启动时拿到的 env 里包含生产 DB 凭据,Agent 在写 SQL 工具里直接拼了出来。事故不是沙箱本身漏了,是 mount 配置错了,凭据该按需注入而不是整体挂载。

类似的翻车还有“网络白名单过宽”:sandbox 网络默认能访问生产内网 IP 段,Agent 在做网络请求时扫到了内网未授权服务,触发了横向移动警报。复盘结论是 sandbox 的 egress 规则只放开了几个 SaaS 域名,没有收紧到最小集合。

到这里沙箱讲完了。下一节看 harness 的最后一块拼图:DSPy 与自动提示词优化。

想一想:去 E2B 或 Anthropic Claude Code 的官方文档读它的 sandbox 隔离机制,列出它的“默认允许”与“默认禁止”分别是什么,再想想你的项目里哪些场景会被默认禁止挡掉。


24.5 DSPy 与自动提示词优化 ​

前面几节讲的工具——MCP、Prompt Caching、评估、沙箱——大多是“模型外面的工程”。这一节反过来,模型里面的 Prompt 怎么从“手写”走向“程序化生成”。DSPy 是这条路最有代表性的工程实践。

DSPy 是什么:把 Prompt 当代码写 ​

手写 prompt 调试起来累。改一个词要等几秒模型响应,再跑几十个 case 看 diff,再决定保留还是回滚。整个流程重复 100 轮后,工程师的耐心耗尽。能不能让编译器自动找最优 prompt?

DSPy(Stanford NLP, 2023 年开源,)是这个问题的工程答案。它把 prompt 视为可编译的代码,开发者不直接写 prompt 字符串,而是定义类型化的 Signature,让 DSPy 在背后生成、测试、优化 prompt。

DSPy 的三个核心抽象值得记住。Signature 定义输入输出的类型契约,开发者描述“输入是什么、输出应该是什么样”,DSPy 负责把它翻译成实际的 prompt 文字。Module 是组合 LLM 调用的可执行单元,相当于一个有类型的函数,里面可以串 ChainOfThought、ReAct、Retrieve 等内置模块。Optimizer(DSPy 里叫 Teleprompter)是自动调 prompt 的“编译器”,开发者喂给它一批 few-shot examples 和一个目标函数,Optimizer 自动挑出最优的 prompt 和 few-shot 组合。

落地流程很清楚。开发者先定义 Signature,比如“输入是一段上下文和问题,输出是简短的答案”;再写 Module,组合 Retrieve + ChainOfThought 变成一个 RAG pipeline;准备 50-100 条 examples(输入 + 期望输出);跑 BootstrapFewShot 或 MIPRO 这类 Optimizer;Optimizer 自动试几十种组合,按目标函数打分,返回分数最高的 Module 实例。开发者拿到的不是一串 prompt 文字,而是一个训练好的 dspy.Module 对象,可以直接调用。

举一个完整的 RAG 例子(代码摘自 DSPy 官方文档,):

python
import dspy

class RAGSignature(dspy.Signature):
 """Answer questions with short factoid answers."""
 context = dspy.InputField(desc="may contain relevant facts")
 question = dspy.InputField
 answer = dspy.OutputField(desc="often between 1 and 5 words")

class RAG(dspy.Module):
 def __init__(self, num_passages=3):
 super.__init__
 self.retrieve = dspy.Retrieve(k=num_passages)
 self.generate = dspy.ChainOfThought(RAGSignature)

 def forward(self, question):
 context = self.retrieve(question).passages
 prediction = self.generate(context=context, question=question)
 return prediction

这个 RAG Module 跑起来就是一个完整的 RAG pipeline。开发者不写“please answer the question based on the context…”这类文字,Signature 描述完意图后 DSPy 自动生成引导指令。

到这里“什么是 DSPy”讲完了。下一节看 DSPy 背后的自动优化机制具体怎么工作。

想一想:去 dspy.ai 读 quickstart 文档,用 BootstrapFewShot 在你手头的任务上跑一次优化,对比优化前后的准确率。

OPRO 与 TextGrad:让 LLM 自己优化 Prompt ​

DSPy 跑 Optimizer 是个黑盒,开发者只能看到分数变化。打开看,背后的机制大多属于两条路线,一条是 LLM 当优化器,走 OPRO 风格;另一条是 LLM 当梯度,走 TextGrad 风格。

OPRO(Optimization by PROmpting,Google DeepMind,arXiv:2309.03409,2023-09 首次提交,2024 年正式发表,)是“LLM 当优化器”的代表。机制是让一个 LLM 反复生成新 prompt 候选,每一轮都基于上一轮的高分候选做微调。具体流程:起点是几个候选 prompt,LLM 在 GSM8K 这类基准上跑分数;分数最高的 top-k prompt 喂回给同一个 LLM,让它再生成 k 个新候选;新候选再去跑分;迭代直到分数收敛。论文里 OPRO 在 GSM8K 上从基线 70% 准确率一路涨到 80%+,超过人工设计的 prompt。

OPRO 的优势是简单、通用,可以套到任何 prompt 优化的场景。代价是搜索成本高(每轮要跑 N 次完整推理)、优化结果不可解释(拿到的 prompt 看起来很奇怪但分数高),而且容易被“分数黑客”——某些高分 prompt 恰好让模型在评测集上过拟合,对真实分布泛化差。

TextGrad(arXiv:2306.04221, 2024 年,)是“LLM 当梯度”的代表。机制是把 LLM 输出当作可微计算图的一部分,让另一个 LLM 给前一个 LLM 的中间步骤写“批评意见”,批评作为文本梯度反向传播回 prompt。优点是优化过程可读(每一步都能看到批评说了什么),缺点是结构复杂,对小 prompt 优化收益不如 OPRO 直观。

两条路线的对比可以这样记:OPRO 像黑盒搜索,结果不可解释但简单粗暴;TextGrad 像可解释的反馈循环,每一步都有迹可循但工程实现更重。截止到 2026 年中,OPRO 类方法在工业 Agent 里更常见,因为工程实现门槛低;TextGrad 在需要可解释性的领域(医学、法律)更受关注。

到此“自动优化怎么工作”讲完了。下一节看 DSPy 工业落地的真实场景——什么时候用、什么时候不用。

想一想:选一个你手头的 prompt,在不同温度(0.0 / 0.5 / 1.0)下跑同一个 eval 集,看哪个温度下分数最稳定。理解“概率输出”这件事本身就是 DSPy 优化要解决的问题。

DSPy 工业落地:什么时候值得用,什么时候不必 ​

DSPy 在论文里很亮眼,落到生产项目里要冷静评估。DSPy 不是 prompt engineering 的必经路,而是一条适合特定场景的工业化路径。

适合用 DSPy 的场景有三个特征。第一是大量 case 可复现——团队已经搭好了 eval pipeline,有 100+ 个带 ground truth 的样本,DSPy 才能跑出有意义的优化结果。第二是 prompt 是稳定模板——RAG、分类、抽取、结构化输出这类“输入输出结构固定”的任务,DSPy 的 Signature 抽象正好契合;创意写作、营销文案、个性化对话这类“输出因人而异”的任务,DSPy 帮不上忙。第三是团队有工程能力——愿意维护 dspy.Module 对象、能写自定义 Optimizer、把 DSPy 集成进 CI。

不适合用 DSPy 的场景同样明确。单点 prompt、几行 system prompt、手工调一下就稳定的轻量 Agent,DSPy 的引入成本(学习曲线、依赖、调试)大于收益。强依赖语义而非结构的场景,例如开放式对话、个性化推荐,DSPy 的类型化抽象反而束手束脚。prompt 必须可解释的场景——金融合规、医疗诊断、法律文书——DSPy 优化出来的 prompt 往往不可读,监管或审计要求每条指令可追溯,DSPy 不达标。

收益与风险分开看。收益侧,DSPy 能把 prompt 调试时间砍掉 30%-50%,prompt 改动可以版本化管理(dspy.Module 对象存进 git),多人协作不会撞 prompt。风险侧,DSPy Optimizer 跑一次成本可能几百美元(每一轮要调用目标 LLM 多次打 batch 推理),优化结果不可解释(高分 prompt 看起来像乱码),optimizer 可能过拟合 eval 集。

落地建议分三步走。第一步先用 LangSmith / Langfuse 搭起 eval pipeline,没有 eval 就没有优化。第二步挑选 2-3 个稳定模板型任务试 DSPy,对比手工 prompt 的分数差异,确认收益。第三步把 DSPy 跑出来的 Module 接入生产 harness,但保留手工 prompt 作为兜底——一旦优化结果在真实分布上退化,能立刻回滚。

到这里 DSPy 讲完了。本章还剩最后一个话题,Skill。它处理的是另一类闲置的东西,那些调好了却散落在聊天记录和文档里的提示词经验。

想一想:把你最近写的 prompt 改写成 DSPy Signature,对比 Signature 自动生成的 prompt 和你手写的 prompt,跑同一个 eval 集看分数差。如果差距不大,说明你的 prompt 已经接近最优,DSPy 的边际收益小。


24.6 Agent Skills 与能力复用 ​

前面五节讲的组件,MCP、Prompt Caching、评估、沙箱、DSPy,解决的大多是"单次运行"的工程问题。但有一类东西一直没着落,人写好的经验。一个团队磨了几十轮才定稿的发布检查清单,一套踩坑换来的排障步骤,一段反复粘贴进对话的输出格式规范,散落在聊天记录、内部文档和每个人的记忆里,人来人往就丢了。2025 年 10 月出现的 Agent Skills(智能体技能),给了这类经验一个标准的文件格式,也是这一章的最后一个话题。下文把它简称为 Skill。

Skill 是什么:一个文件夹加一份 SKILL.md ​

先看形态。一个 Skill 就是一个普通文件夹,里面放一份 SKILL.md,合起来构成一项可供 AI 助手按需加载的能力。SKILL.md 分两段。开头是 YAML 元信息(frontmatter),夹在两条 --- 之间,最要紧的字段是 description,用几句话讲清这项能力做什么、什么情况下用。后面是 Markdown 正文,写着模型执行这项能力时要遵循的指令。文件夹放进约定目录,AI 助手就能发现并使用它。

Anthropic 在 2025 年 10 月 16 日发布了这套机制,同时把它作为开放标准发布在 agentskills.io,其他 AI 工具都可以实现,配套的示例技能库开源在 GitHub 的 anthropics/skills 仓库。官方最先给出的四个示例都是办公文件生成,带公式的 Excel 表格、PowerPoint 演示文稿、Word 文档、可填写的 PDF 表单。早期接入的企业里,乐天(Rakuten)用 Skill 跑管理会计场景的多表汇总、异常检查和报表生成,团队给出的评价是,过去要花一天的工作,现在一个小时完成。

Skill 真正值钱的设计在一个机制上,渐进式披露(Progressive Disclosure)。AI 助手启动时,会把所有可用 Skill 的名字和描述列成一张清单放进上下文。这张清单很省,描述加上触发说明,每个 Skill 在清单里最多占 1536 个字符,Claude Code 给整张清单划的预算默认约为上下文窗口的 1%。

正文完全是另一回事。只有两种情况会加载它,一种是用户输入 /技能名 直接调用,另一种是模型发现对话内容和描述匹配,自己决定加载。文件夹里还可以放更多东西,详细参考文档、示例、可执行脚本,正文里写清每个文件是什么、什么时候读,模型按需打开,平时一个字符都不占。一份长长的参考资料,存放成本接近零,用到才有开销。

正文加载之后也不会反复刷新。调用一次,渲染后的内容就留在会话上下文里,直到会话结束;触发自动压缩时,每个已加载的 Skill 只保留最近一次调用的前 5000 个 token,多个 Skill 共享 25000 的总额度,从最近调用的开始保。所以正文里适合写贯穿任务的常设指令,不适合写一次性的开场白。

这个设计和系统提示(包括 CLAUDE.md 这类项目记忆文件)正好互补。系统提示里的内容每次会话全量在场,适合放每次都要遵守的底线规则;Skill 的正文按需加载,适合放"用到才需要"的流程和资料。判断标准很直接。这段内容是不是每次对话都必须记得?是,就写进系统提示。不是,就做成 Skill。

在 Claude Code 里,早期的自定义命令已经并入了这套机制,.claude/commands/deploy.md 的旧文件和 .claude/skills/deploy/SKILL.md 的新写法,调用起来是同一个 /deploy 命令。官方自己也在重度使用这个形态,Claude Code 内置的代码评审、调试、批量处理等功能,做的就是 Skill,用提示词驱动,而不是一段写死的固定逻辑。

同一份格式横跨三类入口。Claude 的网页与桌面端在设置里启用;Claude Code 从本地目录或插件市场加载;API 侧通过 /v1/skills 接口做版本管理。存放在哪个目录,决定谁能用它。个人级的放 ~/.claude/skills/,本机所有项目可用;项目级的放仓库的 .claude/skills/,随代码提交,团队人人可用;企业级的由管理员统一下发;还有一种打包进插件,随插件启停。多数团队从项目级起步,把和仓库强相关的流程做成 Skill 提交进版本管理。

到这里 Skill 的形态和加载机制讲清楚了。下一节动手写一个,看一份 SKILL.md 从空文件到带着脚本跑起来,要经历什么。

想一想:翻一遍你最近一个月的对话记录,找出粘贴过三次以上的指令段落。每一段都是一个候选 Skill,挑最频繁的一段,给它起个名字。

写一个 Skill:从反复粘贴到脚本打包 ​

场景落到一个具体团队。某公司的后端组每周发版,发版前要人工整理一份变更摘要,贴进群里的格式固定三段,新特性、修复、注意事项。工程师每次打开 git log,肉眼扫一遍提交记录,手工归类,一次十分钟,还常漏。团队决定把这件事做成一个 Skill。

动手前先分清两类内容。官方把 Skill 内容分成参考型和任务型。参考型装的是规范和领域知识,比如这个代码库的 API 约定,模型加载后在当前对话里照着执行;任务型装的是分步操作,比如部署、迁移、生成报告,通常等人来调用。release-notes 属于任务型,但触发说法清晰、没有副作用,可以放开让模型自动加载。

第一步建文件夹,取名 release-notes,放在仓库的 .claude/skills/release-notes/。第二步往里写 SKILL.md,整份文件如下。

yaml
---
name: release-notes
description: 整理两次发布之间的变更摘要。当用户问"这次上线改了什么"、
  要求生成发版说明、变更记录或周报里的变更段时使用。
argument-hint: [起点标签] [终点标签]
allowed-tools: Bash(git log *), Bash(git describe *)
---

## 整理范围

- 带参数调用($ARGUMENTS 传入两个标签)时,只整理这两个标签之间的提交
- 不带参数时,整理最近一个标签到当前的全部提交

## 变更记录

!`git log --oneline $(git describe --tags --abbrev=0 2>/dev/null || echo HEAD~30)..HEAD`

## 整理要求

按 reference.md 里的团队模板归类输出,三段结构。

- 新特性:面向用户的行为变化,一条一行,写清影响
- 修复:缺陷修复,注明对应的问题编号
- 注意事项:涉及配置、数据迁移或兼容性的提交,单独提醒

没有提交记录时,直接说明没有变更,不要编造条目。

## 支撑文件

- 完整模板和历史版本见 [reference.md](reference.md)
- 一份合格的摘要样例见 [examples/sample.md](examples/sample.md)

配套的目录结构长这样。

text
release-notes/
├── SKILL.md            # 主说明,必需
├── reference.md        # 团队模板与归类标准,需要时才读
├── examples/
│   └── sample.md       # 合格样例,需要时才读
└── scripts/            # 本例没用到,放可执行脚本的位置

逐个看 frontmatter 里的字段。name 是清单里的显示名,省略时直接用目录名,调用命令始终跟着目录名走,所以建目录时就要想好 /release-notes 这个命令好不好敲。description 决定自动触发,写法后面单独说。argument-hint 是命令补全时显示的参数提示,告诉调用者这里能接什么。allowed-tools 给这一轮调用预授权一批工具,既省去逐步确认,也让动态注入的命令能顺利通过权限检查,授权到下一条消息发出为止自动清空。

正文里的三个机关也各有来头。$ARGUMENTS 是参数占位符,命令后面跟的输入会原样替换进来,/release-notes v1.2 v1.3 里那两个标签就是这样进正文的;要按位置取单个参数,写 $ARGUMENTS[0] 或简写 $0。## 变更记录 下面那行反引号命令是动态上下文注入,Skill 加载时工具先执行命令、把输出替换进正文,模型读到的已经是提交记录本身,不是命令,多行命令换成以三个反引号加感叹号开头的代码块。最后的支撑文件引用,是渐进式披露在文件层面的落法,reference.md 和样例平时不进上下文,模型读到引用、判断需要时才去打开。

正文写作有一条纪律,克制。Skill 的正文一旦加载,整个会话都占着上下文,每一行都是持续发生的 token 成本。所以只写做什么,不写为什么;长篇规范一律挪进支撑文件;官方给的经验值是 SKILL.md 控制在 500 行以内。

第三步测试,两种触发都要试。先直接输入 /release-notes,看输出是不是三段格式;再开一个全新会话,用自然语言问"帮我整理一下这次上线改了什么",看模型会不会按描述自动加载。自动触发不灵,多半是 description 写得不准,把团队实际会说的说法补进去,比如"发版说明""变更记录""changelog"。

这里有一个和全书主题直接相关的观察。决定自动触发成败的,是 description 里那几十个词。它本质上是一条写给模型看的检索指令,模型拿用户的对话内容来和它匹配,匹配上才加载正文。写 description 就是在写一条极小的 Prompt,要把最典型的使用场景放在最前面,覆盖用户真实的说法,又不能宽泛到处处误触发。前面各章练的功夫,压缩、具体化、面向匹配对象写作,在这里全部用得上。

文字之外,Skill 还能装脚本,让模型做单靠生成 token 做不好的事。把一个 Python 脚本放进 scripts/ 子目录,正文里指示模型去执行它,脚本路径用 ${CLAUDE_SKILL_DIR} 书写,这个变量无论 Skill 装在哪一级目录,都解析到它自己的位置。比如一个生成仓库结构图的 Skill。

yaml
---
name: repo-map
description: 生成仓库目录的可折叠结构图,标注文件大小。用户说"看一下项目结构"、
  "找出最大的目录"时使用。
allowed-tools: Bash(python3 *)
---

# 仓库结构图

在项目根目录运行打包的脚本,生成 repo-map.html 并在浏览器打开:

```bash
python3 ${CLAUDE_SKILL_DIR}/scripts/visualize.py .
```

## 图里有什么

- 可折叠的目录树,逐层点开
- 每个文件的大小和类型标识
- 按文件类型汇总的大小占比

脚本只依赖 Python 标准库,不需要装包。它在模型外执行,算得快也算得准,模型只负责决定什么时候跑它、怎么向用户解释结果。

类似的内置变量还有两个常用。${CLAUDE_PROJECT_DIR} 解析到项目根目录,让 Skill 引用仓库里的脚本时不受安装位置影响;${CLAUDE_SESSION_ID} 是当前会话的编号,适合给日志和产物文件起不会重名的名字。

最后两个开关留给特殊场合。有的动作有副作用,部署、发消息、删数据,不能让模型自己挑时机,frontmatter 里写 disable-model-invocation: true,之后只有人能通过命令触发。再给这类任务加一行 context: fork,Skill 会挪到一个独立的子会话里执行,正文变成子会话的完整任务书,它看不到当前对话历史,干完活把结果带回来,需要时还能用 agent 字段指定执行它的子代理类型。比如一个上线 Skill。

yaml
---
name: ship
description: 跑完检查并把当前分支推上生产环境
context: fork
disable-model-invocation: true
---

在独立会话里完成上线:

1. 跑全量测试,失败即停并报告
2. 构建生产镜像
3. 推到生产环境
4. 验证健康检查,汇报每一步结果

反过来,有的内容只是背景知识,比如一份老系统的架构说明,挂成命令没有意义,写 user-invocable: false 把它从命令菜单里藏起来,留给模型按需取用。

想一想:给 release-notes 的 frontmatter 加一行 disable-model-invocation: true,再测一遍自然语言触发,观察模型行为的差别;然后想清楚,什么情况下你希望这个开关是开着的。

Skill、MCP 与系统提示:能力扩展的三条路线 ​

到这一节,手里有了三样往外扩能力的东西,容易混,摆在一起比。

Skill 扩的是"模型知道怎么做"。它装的是指令、参考资料和脚本,改变的是模型面对某类任务时的做法,不提供新的数据源。MCP 扩的是"模型够得着什么"。它是一个协议,把数据库、SaaS 服务、代码仓库这类外部系统接进来,模型通过它读原本读不到的数据、调原本不存在的操作。系统提示定的是"每次都要遵守的规矩",全量常驻,不按需。

三者不是竞争关系,是分工。一个数据服务型 Agent 可能同时用着三样,MCP 接进工单系统和数据库,Skill 装着"怎么排查工单""怎么写这个库的 SQL"的团队流程,系统提示里放着安全与格式底线。选型归成三问。要碰到新数据或新动作吗?要,上 MCP。有一套反复使用的流程或规范吗?有,做成 Skill。这段话每次对话都必须生效吗?必须,进系统提示。

复用之外,Skill 还改变了经验的协作方式。做成文件的流程进了版本管理,谁改的、为什么改、改完效果如何,全有记录。Claude 官方的 skill-creator 插件把评估也自动化了,它给一个 Skill 生成一批测试提示,在启用和禁用 Skill 的隔离会话里分别跑,对比通过率、耗时和 token 消耗;两个版本的 Skill 之间还能跑盲测 A/B,改动先用数据确认是提升,再提交进仓库。这套做法和 24.4 讲的评估驱动开发是同一个思想,评估对象从 Agent 换成了 Skill。

安全上同样有要过的一道关。Skill 的正文就是模型照着执行的指令,第三方来源的 Skill 等于一段别人写的 Prompt,装进来之前要像审代码一样逐条过,重点看 frontmatter 里的 allowed-tools。这个字段能在调用期间给一批工具预授权、跳过逐次确认,权限给宽了,等于发了一段不受约束的指令直接动手的许可。这和第 13 章讲的提示注入、13.8 节讲的 MCP 投毒是同一类风险面,经验直接平移过来,来源可信、权限最小、敏感动作保留人工确认。

到这里,第 24 章的最后一个话题讲完了。回头看这一章的六个话题,Context、Harness、MCP 与缓存、评估与沙箱、DSPy、Skill,它们共同回答一个问题,模型能力是买来的,工程是自己的。Prompt 决定这一次对话的质量,Context 决定模型看到什么,Harness 决定系统怎么运转,而 Skill、MCP、DSPy 这些机制,决定团队的工程经验能不能积累下来、传下去。

想一想:带着第 13 章的安全视角,逐行审一遍上面那份 release-notes 的 SKILL.md。如果它来自第三方,标出你会追问出处的每一行,说明理由。


一句话总结 ​

Prompt 管的是这一次对模型说什么,Context Engineering 管的是送进模型的输入怎么组织,Harness 管的是模型外面的系统怎么运转。三者不是并列关系,而是层层包含:Context Engineering 里包含着写好每一条 prompt,Harness 里又包含着组织好每一次上下文。从一条 prompt,到整个输入,再到整个系统,工程的对象一级一级放大,工程师要管的东西也一级一级变多。哪一层断了都跑不稳:Prompt 写不好,模型听不懂说什么;Context 没组织好,模型不知道该考虑哪些信息;Harness 不扎实,LLM 应用跑不起来、跑不放心。

Harness 真正落地的四条工程支柱也别忘掉。MCP 把外部工具接进模型,Prompt Caching 把长上下文的成本与时延压下去,评估驱动开发守住“改了还是不是同一个 Agent”,沙箱隔离守住“Agent 跑代码时不会把生产搞坏”。DSPy 这一类自动提示词优化工具把 prompt engineering 从手写抬到了工业化,但要清醒看到它的边界——优化结果不可解释、跑一次几百刀、不是所有任务都值得。2025 年 10 月面世的 Skill 机制则把复用推到了经验这一层,团队调好的流程做成文件、按需加载、随代码进版本管理,Prompt 工程由此有了团队协作的载体。

写完这本书的最后一章,一个认识变得清晰,Prompt Engineering 真正的验收标准,不是能写出多惊艳的 prompt,而是能用 prompt + context + harness,搭出一个可以持续运行、供团队协作、能扛住真实生产负载的系统。这件事,比任何一条具体的 prompt 都重要。

参考资料 ​

  1. Anthropic. Effective context engineering for AI agents(官方工程博客,2025 年 9 月 29 日发布,文中给出了 context engineering 的定义). https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents

  2. Liu, N. F., Lin, K., Hewitt, J., Paranjape, A., Bevilacqua, M., Petroni, F., Liang, P. Lost in the Middle: How Language Models Use Long Contexts. TACL, 2023. arXiv:2307.03172. https://arxiv.org/abs/2307.03172

  3. Shopify Engineering. Building production-ready agentic systems: Lessons from Shopify Sidekick (2025)(含 just-in-time instructions 等上下文管理实践). https://shopify.engineering/building-production-ready-agentic-systems

  4. Anthropic. Harness design for long-running application development(Anthropic Engineering,2026 年 3 月). https://www.anthropic.com/engineering/harness-design-long-running-apps

  5. LangChain(Harrison Chase). Agent Frameworks, Runtimes, and Harnesses - oh my!. https://www.langchain.com/blog/agent-frameworks-runtimes-and-harnesses-oh-my

  6. Vercel. AI SDK 7 is now available(官方博文中使用 agent harness 一词). https://vercel.com/blog/ai-sdk-7

  7. Yao, S., Zhao, J., Yu, D., Du, N., Shafran, I., Narasimhan, K., Cao, Y. ReAct: Synergizing Reasoning and Acting in Language Models. ICLR, 2023. arXiv:2210.03629. https://arxiv.org/abs/2210.03629

  8. Karpathy(A. Karpathy). X 帖(2025-06-25):把 context engineering 定义为“用恰到好处的信息填满上下文窗口的精细艺术与科学”,该词由此在工程圈流行(Shopify CEO Tobi Lütke 于同月更早提出). https://x.com/karpathy/status/1937902205765607626

  9. Chroma(A. Hong, N. Troynikov, B. Huber). Context Rot: The Erosion of Context Windows in Large Language Models(技术报告,2025 年 7 月). https://research.trychroma.com/context-rot

  10. Anthropic. Introducing the Model Context Protocol(2024-11-25,官方协议发布). https://www.anthropic.com/news/model-context-protocol

  11. Model Context Protocol 官方规范站. https://modelcontextprotocol.io

  12. Anthropic. Prompt Caching(Claude API 文档,2024-08-14 上线). https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching

  13. OpenAI. Automatic Prompt Caching(OpenAI Cookbook / API 文档,2024-11 上线). https://platform.openai.com/docs/guides/prompt-caching

  14. Google DeepMind. Gemini API implicit caching(2024-11 公布,2025-05 公开定价). https://ai.google.dev/gemini-api/docs/caching

  15. OpenAI. New tools for building agents(Agents SDK 发布说明,2025-03-11,文中包含 MCP 支持). https://openai.com/index/new-tools-for-building-agents/

  16. Google DeepMind. Build rich, interactive AI experiences with MCP(官方开发者博客,2025-04-09). https://blog.google/technology/google-deepmind/google-ai-studio-mcp/

  17. LangSmith(LangChain 推出的闭源 LLM 可观测与评估平台). https://docs.smith.langchain.com

  18. Langfuse(开源 LLM 可观测与评估平台,支持自托管). https://langfuse.com

  19. Braintrust(evaluations as code 平台). https://www.braintrust.dev

  20. E2B(开源云代码执行沙箱). https://e2b.com

  21. Daytona(企业级代码执行沙箱). https://www.daytona.io

  22. DSPy(Stanford NLP 推出的程序化 Prompt 框架,2023 年开源). https://dspy.ai

  23. Yang, C., Wang, X., Lu, Y., Liu, H., Tang, Q., Cheng, S., Gu, H. OPRO: Large Language Models as Optimizers. arXiv:2309.03409, 2023-09 首次提交,2024 年正式发表. https://arxiv.org/abs/2309.03409

  24. Anthropic. Agent Skills(官方博客,2025-10-16 发布,2025-12-18 更新组织级管理与技能目录). https://claude.com/blog/skills

  25. Agent Skills 开放标准规范站. https://agentskills.io

  26. anthropics/skills(官方开源示例技能库). https://github.com/anthropics/skills

  27. Claude Code. Extend Claude with skills(官方文档,SKILL.md 格式、frontmatter 字段、渐进式披露与调用控制). https://code.claude.com/docs/en/skills

  28. Yuksekgonul, M., Bianchi, F., Boen, J., Wang, S., Huang, Z., Palangi, H., et al. TextGrad: Automatic Differentiation via Textual Feedback. arXiv:2306.04221, 2024. https://arxiv.org/abs/2306.04221

  29. Anthropic. Context editing(Claude Platform Docs,beta 能力 context-management-2025-06-27). https://platform.claude.com/docs/en/build-with-claude/context-editing

  30. Anthropic. Memory tool(Claude Platform Docs,客户端记忆工具 memory_20250818). https://platform.claude.com/docs/en/agents-and-tools/tool-use/memory-tool