乐于分享
好东西不私藏

一切皆插件:DeepSeek Harness 的底层机制以及最佳实践

一切皆插件:DeepSeek Harness 的底层机制以及最佳实践

圈里聊 Agent,大家默认把注意力全放在模型上:今天 DeepSeek 又出了什么新权重、推理又便宜了多少。可你真自己搭过一个能跑的 Agent 就会知道,模型只是那个「会说话的大脑」,真正决定它能不能干活、能不能上生产的,是外面那层叫 harness 的壳。

harness 这个词越来越常被提起,但很多人把它和「Agent 框架」混为一谈。其实它比框架更底层:框架是给你一堆现成积木,harness 是那台把积木拼起来、喂数据、收结果、还负责兜底不崩的运行时。而 DeepSeek 这一脉开源模型起来之后,harness 的设计哲学出现了一个非常明确的收敛方向——一切皆插件(everything is a plugin)

这篇文章我想把这件事拆透:这个「一切皆插件」到底指什么,它的底层机制是怎么转起来的,以及如果你真要照着搭一套,哪些最佳实践最值得抄。我默认你已经写过至少一个调用大模型 API 的 demo,所以不从头讲什么是 tool calling。

先说清楚:什么是「一切皆插件」

传统 Agent 代码长什么样?基本是一坨「特殊逻辑」:硬编码调用某个模型、手动拼一段 system prompt、if-else 判断要不要调搜索、调完把结果塞回上下文。模型是特殊的,工具是特殊的,记忆是特殊的,连那个「先想后做」的循环都是写死的。

「一切皆插件」是反过来的一刀:把上面所有这些东西,统一抽象成一个 Plugin 接口,全部丢进同一个注册表(Registry)里。于是——

  • 模型客户端是插件(DeepSeek adapter、OpenAI adapter、本地 vLLM adapter)
  • 工具是插件(搜索、代码执行、数据库查询、HTTP 请求)
  • 记忆/检索是插件(向量库、KV 缓存、对话历史压缩)
  • 路由/调度是插件(该调哪个模型、该用哪组工具)
  • 护栏/安全是插件(敏感词、权限校验、输出过滤)
  • 连「Agent 循环」本身都是插件
    (ReAct 循环、Plan-and-Execute、带反思的自循环)

关键点在于:它们长得一模一样,都实现同一套 load / invoke / unload 契约,都通过同一个上下文对象通信。harness 内核只干三件事——持有注册表、按协议分派、维护那个上下文对象——其余全在外面。

为什么这个抽象在 DeepSeek 时代特别成立?因为 DeepSeek 的开放权重 + 强原生 tool calling,让「换模型」从「改一大段代码」变成了「换一个 adapter 插件」。当模型本身变得可替换、可混用、可灰度,你自然就会想把其它部分也做成可替换的。一切皆插件,本质是对「模型不再神圣」这件事的工程回应。

底层机制一:契约(Contract)先于实现

插件能互相替换的前提,是它们对外长一样。所以 harness 的第一块地基不是代码,是一个契约。每个插件在注册时都要声明一份 manifest:

id: deepseek-v3-adaptertype: modelversion: 1.4.0capabilities: [chat, tool_call, json_mode]input_schema:  {...}   # 它吃什么样的请求output_schema: {...}   # 它吐什么样的响应requires: [logger, event_bus]

这套 manifest 就是插件的「身份证 + 接口说明书」。harness 在启动时不关心插件里面怎么实现,只关心它能不能满足 input_schema / output_schema边界校验发生在 harness 手里,而不是插件自己内部——这正是后面能安全换模型、能灰度、能 A/B 的根。

我踩过一个坑要提醒你:契约最怕「约定俗成」。早期我们团队的工具参数全靠口头约定「这个字段传字符串」,结果换个人写插件就传对象,harness 不校验,runtime 半夜崩。后来的铁律是:所有插件的输入输出必须是可机器校验的 JSON Schema,harness 在分派前强制 validate 一遍。多写三行校验,少救三晚火。

底层机制二:注册表 + 依赖注入

插件写完不是直接 new 出来的,而是「自注册」进一个全局 Registry。harness 启动时扫一遍插件目录(或读配置),把每个插件实例化、把它的 requires 解析掉。

这一步本质是依赖注入(DI)。比如一个检索插件声明 requires: [vector_store],harness 就从 Registry 里找出已注册的 vector_store 插件,把实例塞给它。插件之间不直接 import 彼此,它们只认识 Registry 和那份契约。这就实现了图里标出来的「插件互不认识,只认总线」。

图里这条时序线很关键:注册(register)→ 初始化(load,建连接、加载权重/配置)→ 就绪(READY,进分派队列)→ 调用(invoke)→ 卸载(unload)。注意 load 和 invoke 是分开的两个阶段——很多团队把初始化成本摊在第一次调用里,导致首请求慢得离谱;把重活放在 load 阶段、用就绪探针隔开,首请求延迟能砍掉一大截。

底层机制三:统一的 PluginContext,是解耦的真正秘密

如果插件互不 import,它们靠什么交换信息?靠一个在调用时被 harness 注入的上下文对象 PluginContext。它大概长这样:

ctx = PluginContext(    state=shared_state,      # 跨插件共享的可序列化状态    logger=logger,           # 统一日志    config=config,           # 本插件可见的配置    model=registry.get("model"),   # 模型客户端,也是个插件    bus=event_bus,           # 事件总线,插件只发事件不直连)result = plugin.invoke(ctx, input)

这是整个「一切皆插件」能成立的真正秘密:不是因为接口统一,而是因为通信方式统一。所有插件拿到的都是同一个 ctx 形状,它们不直接拿对方的引用,而是通过 ctx.state 和 ctx.bus 间接协作。结果就是——你可以把任意两个插件对调,只要它们的契约对得上,彼此根本感知不到对方变了。

一个工程上的硬约束:ctx.state 必须可序列化(能 JSON / pickle 出来),绝不能塞一个进程内对象引用进去。否则你的 Agent 永远只能跑在单进程里,扩展、回放、分布式全废。我们早期就栽在这:状态里藏了个数据库连接,结果想做多轮会话持久化时整个架构推倒重来。

底层机制四:Agent 循环本身也是插件

这是最反直觉、也最能体现代价的一点。大多数人把「思考—行动—观察」那个循环写死在主程序里。一切皆插件范式下,这个循环是一个策略插件

为什么值得这么做?因为不同任务需要不同的循环策略:

  • 简单问答:直接 chat,不走工具
  • 多步任务:ReAct 循环
  • 复杂项目:Plan-and-Execute(先出计划再逐步执行)
  • 易错场景:带自我反思 / 自我校验的循环

把它们做成可插拔的策略插件,harness 就能根据任务类型、根据路由插件的判断,动态选循环。你甚至可以对同一批工具跑两套循环做对照实验。我个人的判断是:这一步是「框架」和「harness」的分水岭——框架给你固定循环,harness 让你换循环像换模型一样便宜。

底层机制五:工具调用的「翻译层」是真正的护城河

讲到这必须点一句 DeepSeek 相关的真问题。各家模型的 tool calling 协议并不统一:DeepSeek 的 tools 参数结构、OpenAI 的 function calling、Anthropic 的 tool_use,字段名和返回值形状都不一样。如果你的工具插件直接按某一家的格式写死,换模型就等于重写所有工具。

harness 的正确做法,是在模型插件和工具插件之间架一层标准化翻译层

  1. 工具插件只用「规范 schema」声明能力(不关心哪家模型)
  2. 模型适配器插件负责把规范 schema 翻译成自家模型能懂的格式
  3. 模型吐回的 tool_call,再由适配器翻译回规范格式
  4. harness 拿着规范格式去 Registry 里找对应工具插件、分派执行

这张图就是一次请求完整的调度链路:用户请求 → 路由插件选循环和模型 → 循环插件驱动 → 需要工具时经适配器翻译 → 分派到具体工具插件 → 结果回填 ctx.state → 模型再生成 → 直到循环结束。注意工具插件本身是完全「 dumb」的:它不认识模型、不认识协议,只认规范输入、吐规范输出。所有脏活(协议转换、上下文管理、错误重试)都在 harness 内核和适配器里。这就是护城河——你换十家模型,工具一行不用改。

底层机制六:隔离、降级与事件总线

插件多了,第一个要命的问题是「一个烂插件拖死全场」。所以 harness 内核必须给每个插件的 invoke 加隔离与降级

  • 超时(timeout):单个插件卡死不能阻塞整个循环
  • 熔断(circuit breaker):某插件连续失败,先熔断,别雪崩
  • 沙箱:能碰外部资源的插件(代码执行、HTTP)必须受限,能力用 capability token 显式授予,而不是默认全开
  • 失败 containment:插件抛错被 harness 捕获,循环可以选择重试、换插件或优雅降级

同时,每个插件都是事件源:它在 load、invoke 开始/结束、报错时往 event_bus 发事件。这一条看似不重要,实则是可观测性的脊梁——后面会讲为什么。

图里特意把「harness 内核 = 信任边界」画出来:模型不可信、工具不可信、连循环都不可信,唯一掌握全局、做校验和兜底的是内核。安全不该寄托在「模型足够聪明不乱调工具」上,而该落在 harness 在插件边界做的权限与校验上。这一点,对用 DeepSeek 这种开放模型尤其重要——模型是本地或第三方跑的,你没法假设它「善良」。

最佳实践:哪些最该被抄

机制讲完,落到「如果你要搭一套,照着抄什么」。下面这几条是我认为最值钱的。

1. 一切皆插件,但内核要保持「薄」。 别把注册表、上下文协议、分派逻辑也插件化——那是内核,是信任边界,必须稳定。插件化的是「能力」,不是「机制」。内核越薄、契约越硬,系统越稳。

2. 用契约(Schema)而非约定。 所有插件 I/O 强制 JSON Schema 校验,在边界处由 harness 完成。这是模型可替换、工具可灰度、A/B 可做的根本前提。

3. 模型客户端务必做成插件。 把 DeepSeek 包在一个 ModelAdapter 后面。换来换去只是改配置,多模型路由(简单问题用小模型、难题用大模型)顺手就实现了。这是开放模型时代最划算的一笔投资。

4. 上下文协议全局唯一。 全链路只认一个 PluginContext 形状,禁止隐藏的全局变量,state 必须可序列化。否则你的 Agent 永远锁死在单进程。

5. 工具保持「dumb」,脏活给翻译层。 工具只管规范输入→规范输出,协议转换、重试、上下文拼接交给 harness。换模型不换工具,这是护城河。

6. 隔离与降级内建,不当补丁。 超时、熔断、沙箱、capability token,从第一天就长在 harness 里,而不是出了事再补。一个坏插件不该能拖垮整个 Agent。

7. 可观测性不是可选项。 让每个插件成为事件源,trace 贯穿一次请求的完整链路。我见过太多 Agent 上线后「偶尔答错」却完全无法定位——因为没有 trace,你连是哪一步、哪个插件出了问题都不知道。能看见,才修得了。

8. 生命周期与灰度常态化。 插件带版本号,新工具先 canary 一小部分流量,出问题一键回滚。把「上线一个工具」当成「发一次版」来对待。

我的看法

把上面这些拼起来,「一切皆插件」不是又一个花哨架构名词,而是大模型进入「模型可替换、可混用、可本地化」时代后,harness 最自然的一种收敛。DeepSeek 这类开放权重模型的崛起,恰恰把「模型不再神圣」这件事摆到了台面上——既然模型都能即插即换,凭什么工具和循环还要焊死?

我对想做 Agent 基础设施的团队有个判断:未来的应用服务器,不再是 Tomcat/Spring 那一套,而是一个 harness 内核 + 一堆插件 的组合。内核负责信任边界、契约校验和分派;业务价值全在插件里。谁把这套内核做得薄而稳、把插件生态做得厚而活,谁就握住了 Agent 时代的「应用服务器」。

当然要泼盆冷水:一切皆插件不是银弹。插件粒度太细会陷入「config 地狱」,太粗又回到硬编码。契约设计错了,后面全歪。它是组织复杂度的一把手术刀,不是免思考的咒语。方向对,但刀法得练。

如果你正在被「模型换了工具全重写」「Agent 一崩全崩」「换个循环要改主程序」这类问题折磨,不妨照着上面的机制拆一刀试试。先把模型客户端插件化,这一步投入最小、回报最直观——当你把 DeepSeek 换成另一家模型只是改一行配置时,你就已经踏进「一切皆插件」的门了。

— 完 —