导语:很多人第一次听到 DeepSeek Harness(命令行 dsh),会以为它是 DeepSeek 的推理引擎。其实恰恰相反——它不含任何模型算力,而是一个 Agent 运行框架。本文从
<div class="detail-content-box has-mask large">
<section style="margin:0 auto;max-width:690px;padding:0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;color:#1f2937;font-size:16px;line-height:1.9;"><blockquote style="margin:1.1em 0;padding:0.82em 1.02em;border-left:3px solid #f59e0b;background:#fff8e6;color:#5f4b20;line-height:1.85;border-radius:6px;"><p style="margin:0.95em 0;line-height:1.92;font-size:16px;color:#2b2f38;text-align:justify;letter-spacing:0.01em;text-indent:0;"><span leaf="">导语:很多人第一次听到 <a href="https://www.53ai.com/news/tishicijiqiao/2025020128143.html">DeepSeek</a> Harness(命令行 </span><code><span leaf="">dsh</span></code><span leaf="">),会以为它是 DeepSeek 的推理引擎。其实恰恰相反——它不含任何模型算力,而是一个 </span><strong style="font-weight:800;color:#9a3412;background:#fff3d6;padding:0 2px;border-radius:3px;"><span leaf=""><a href="https://www.53ai.com/news/LargeLanguageModel/2024052823549.html">Agent</a> 运行框架</span></strong><span leaf="">。本文从架构师视角,把它拆成三层、四个核心包、两个代码模板,讲清楚它"为什么这么设计"。</span></p></blockquote>
DeepSeek Harness 的官方定位是一条公式:
Agent = Model + Harness
Model 负责推理和决策,Harness 负责模型之外的一切:记忆、工具、权限、执行循环、会话管理、沙箱。模型算力是通过"模型适配器"接入的外部服务(DeepSeek、Anthropic、OpenAI,或任意 OpenAI 兼容网关)。
所以你想学它,其实是两件事:①它的插件化架构(如何把 Agent 运行时拆成可拼装的件);②它的程序应用(怎么写插件、怎么接自己的模型)。下面分而治之。
整个框架只有三层,别被"200+ 个包"吓到:
--patch 叠加插件树,运行时热插拔。packages/llm)。唯一和"推理引擎"交界的地方:LlmAdapter 契约 + StreamChunk 流协议 + 内容块词汇表。一句话总结设计哲学:
免费获取企业 AI 成熟度诊断报告,发现转型机会
读任何核心包之前,先建立这五个概念——它们是整个框架的"地基":
apply 的对象、或 Service 子类 | |
ctx.,按 key 查服务,从不 import 实现 | |
inject | |
ctx.effect()ctx.on() 装的东西,卸载时自动逆序撤销 |
四种分发模式,读 agent-loop 和 tools 管道前必须懂:
emit | ||||
waterfall |
中间件/短路 | |||
parallel | ||||
serial |
Waterfall 语义是理解一切拦截的关键:监听器收到 (...args, next),调 next() 委托给下游,不调 next() 直接 return = 短路。"策略监听器有决定权就短路,观察监听器必须委托"。
Session 是一份由类型化 SessionEvent 组成的仅追加日志,是 agent 交互历史的唯一真源。LLM 消息历史是从日志"派生"出来的,从不单独存储;回放 = 从同一组事件重新派生。这就是 DDD 里的 Event Sourcing,只不过"聚合根"换成了一次对话。
三个设计分离,是它的精髓:
user/message、assistant/message、tool/result)进入"有序 surface"。它们携带 SurfaceOp:append(追加)或 replace(遮蔽一个区间)。日志永远只增不减,但"模型看到的历史"可以变短——这是压缩长上下文的机制。assistant/chunk(token 级原始流,保回放保真)与 assistant/message(组装后,派生历史用)分开存。session/end-seed 标记,区分"种子"与"本进程实时写入"。数据完整性靠 append 的三道闸:JSON 可序列化校验(BigInt/函数/循环引用直接 throw)、deep-freeze(普通 JS 无法改写历史)、seq 连续性(seq === log.length,持久化不能过滤任何事件)。
这一层最关键的决定是接口与实现分离:agent/ 包只声明 Agent 接口,agent-loop/ 是唯一具体实现。扩展插件只依赖 agent,绝不依赖 agent-loop——于是替换 Agent 循环不需要改任何消费方。教科书级的依赖倒置。
四个 waterfall 拦截点是控制中枢:
agent/pre-step |
模型看到什么 |
agent/request |
调用配置 |
agent/request-error |
重试{kind:'retry'} 接管恢复 |
agent/turn-stopping |
轮次关闭steer() 一步 |
最值得细品的是 agent/turn-stopping 的语义:数据决定结果,监听器顺序无法改变结果——机器重读 inbox,有 pending 就再跑一步,没有就关轮次。控制流被表达成了数据状态。
一次工具调用依次经过这条链:
tools/pre-execute (waterfall: allow/deny/ask) ↓ 单调 guard(只能 deny,不能 allow) ↓ tools/execute (waterfall: 超时/重试/指标) ↓ tools/post-execute (waterfall: accept/replace/block) ↓ finalizeContent → tools/result (emit)三个安全设计值得抄走:
arguments 一旦进日志就是审计证据,谁都不能改——历史、审计、UI、执行必须一致。value(不持久化),模型看到 content(持久化)。回放能重现展示,重建不了规范中间值。LlmAdapter 抽象类唯一必须实现的方法是 stream(),但配套一堆"必须遵守"的约定——薄接口 + 强约定:
usagefinish 之前,finish 之后无分片LlmFailure核心洞察:适配器把"提供方的千奇百怪"归一化成"框架的单一真相"。上层永远面对干净的、提供方无关的语义,绝不去猜各家各报什么错。
读完四件套,会发现四条主线贯穿所有包:
LlmFailure、单一 code、空响应可重试。import { readFile } from 'node:fs/promises' import type { Context } from '@deepseek-ai/cordis' import { defineTool } from '@deepseek-ai/dsh-tools' export const name = 'my-tool' export const inject = ['tools'] // 声明依赖,等 ctx.tools 就绪才启动 export function apply(ctx: Context) { ctx.tools.register(defineTool({ // 注册是副作用,卸载自动注销 name: 'read_file', description: 'Read a file from disk.', parameters: { // 一个 schema 三合一:类型+校验+模型schema path: { type: 'string', required: true, description: 'Absolute path' }, limit: { type: 'number' }, }, output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: value }], // value→content }, async execute(args, exec) { // args 已被校验并类型收窄;exec.signal 必须透传 return readFile(args.path, { encoding: 'utf8', signal: exec.signal }) }, })) }import { LlmAdapter } from '@deepseek-ai/dsh-llm' class VllmAdapter extends LlmAdapter { async *stream(options: GenerateOptions): AsyncIterable { const res = await fetch(`${this.baseUrl}/v1/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiKey}` }, body: JSON.stringify({ model: options.model, messages: options.messages, tools: options.tools, stream: true }), signal: options.signal, // 必须遵守取消信号 }) // 解析 SSE,按序 yield StreamChunk(text-delta / usage / finish...) } } export function apply(ctx: Context, config: { baseUrl: string; apiKey: string }) { ctx.llm.registerAdapter(['my-vllm'], new VllmAdapter(config.baseUrl, config.apiKey)) }你要做的全部:实现一个 stream(),把 vllm 的 OpenAI 兼容输出翻译成框架的 StreamChunk,然后 registerAdapter。重试、缓存、审计、日志全由框架接管。
一句话总结这个框架的设计哲学:
Harness 是一个事件溯源的、一切皆插件的、依赖注入的执行底座——存的是事实(append-only 日志),跑的是循环(可替换的 driver),改的是数据(waterfall 决策 + 单调策略),接的是翻译器(LlmAdapter 归一化协议)。
如果你也在做 Agent 工程,最值得搬走的三样东西是:事件溯源 + 派生历史(存事实不存视图)、单调安全策略(顺序无关的默认拒绝)、薄接口 + 强约定(适配器归一化一切差异)。
关注公众号

扫码关注,获取最新 AI 资讯
3 步完成企业诊断,获取专属转型建议
已有 200+ 企业完成诊断