让chatgpt来给我讲解下deepseek harness的插件体系
最近deepseek发布了harness,试用了一下结合deepseek-4-flash,用它尝试写一个php的现代版phpmyadmin,因为也是一个比较重要且有一点复杂度的应用
试了下除了数据展示有点问题,修了一轮就完成了,整体表现还是不错的
然后看了下它主打的是万物都是插件
这个概念还是比较复杂的,尝试让gpt老师帮我讲解下
DeepSeek Harness 就像一个“由很多小员工临时组成的公司”。
没有一个超级总经理包办所有事情:
- 一个员工负责问大模型;
- 一个员工负责读文件;
- 一个员工负责运行命令;
- 一个员工负责审批危险操作;
- 一个员工负责保存聊天记录;
- 一个员工负责显示网页界面。
这些“员工”就是插件。
需要什么员工,就把什么员工叫来;不需要了,就让它离开,并自动收拾自己留下的东西。
用一个真实请求走一遍
你对 DeepSeek Harness 说:
帮我读取 package.json,看看项目用了哪些依赖。
系统内部大致发生了这些事:
1 | 你输入问题 |
关键点是:
上面每一个步骤都可以由不同插件负责。
比如你可以:
- 把 DeepSeek 换成其他模型;
- 把本地文件系统换成远程沙箱;
- 给文件读取前增加权限检查;
- 给模型调用增加重试;
- 给工具调用增加日志;
- 换掉整个 Agent Loop。
其他部分不需要跟着重写。
为什么普通插件系统做不到这么灵活
普通软件的结构经常是:1
2
3
4固定核心程序
├── 插件位置 A
├── 插件位置 B
└── 插件位置 C
核心程序是老板,插件只能填几个预留位置。
DeepSeek Harness 更像:1
2
3
4
5
6
7极小的组装平台
├── Agent Loop 插件
├── 模型插件
├── 工具插件
├── 权限插件
├── 存储插件
└── UI 插件
连“核心 Agent 怎么循环运行”本身也是插件。
所以它不是:
一个 Agent,加上一些插件。
而是:
很多插件组合起来以后,才形成一个 Agent。
插件之间怎么找到对方
假设你写了一个天气工具插件。
它不能一启动就直接工作,因为它要先找到“工具管理器”。
所以它会声明:1
我需要:tools 服务
代码里大概是:1
export const inject = ['tools']
意思就是:
只有工具管理器存在时,才启动我。
系统启动时可能出现这种情况:
1 | 天气插件:我需要 tools |
如果工具管理器后来被替换:1
2
3
4
5
6
7旧工具管理器卸载
↓
天气插件也暂时卸载
↓
新工具管理器启动
↓
天气插件重新启动,并连接新管理器
这就是 DeepSeek Harness 很特别的地方:
插件依赖不是只在启动时检查一次,而是一直被系统关注。
ctx 到底是什么
插件启动时会拿到一个 ctx:1
2
3export function apply(ctx) {
// 使用 ctx 做事
}
你可以把 ctx 理解成“公司总机”。
插件不知道其他插件住在哪里,只需要问总机:1
2
3
4
5ctx.tools // 工具管理器
ctx.llm // 模型管理器
ctx.sessions // 会话管理器
ctx.fs // 文件系统
ctx.sandbox // 沙箱
比如天气插件说:1
ctx.tools.register(weatherTool)
意思就是:
总机,请把我的天气能力登记到工具管理器里。
这样天气插件不需要自己找到 Agent Loop,也不需要直接修改模型提示词。工具管理器会负责把天气工具告诉模型。
插件卸载时,怎么避免留下垃圾
这是整个系统最值得理解的设计。
假设天气插件启动后做了三件事:1
2
31. 注册 weather 工具
2. 监听工具调用事件
3. 启动一个定时器
如果只是粗暴地删除插件代码,可能留下:
- 一个无法调用的 weather 工具;
- 一个永远存在的事件监听器;
- 一个一直运行的定时器。
DeepSeek Harness 要求插件在创建东西时,同时登记“怎么撤销”。
可以理解成:于是卸载插件时,系统会自动倒着清理:1
2
3
4
5
6
7
8创建 weather 工具
同时记下:卸载时删除 weather 工具
添加事件监听器
同时记下:卸载时移除监听器
启动定时器
同时记下:卸载时停止定时器所以热更新才比较可靠。1
2
3停止定时器
移除监听器
删除 weather 工具
修改天气插件代码后,系统可以:而不是让新旧两套东西同时残留。1
2
3
4
5完整清理旧天气插件
↓
重新加载新代码
↓
重新注册新天气工具
这个“创建时顺便记录撤销方法”的机制,技术上叫effect。
Fiber 又是什么
现在再引入 Fiber 就很容易了。
Fiber 可以理解成:
某个插件这一次运行的“工作档案”。
里面记录着:
- 这个插件是谁;
- 它在等哪些服务;
- 它现在有没有启动;
- 它注册过什么东西;
- 卸载时要清理什么;
- 它是否启动失败。
一个插件大致会经历对应的技术名称是:1
2
3
4
5
6
7
8
9等待条件
↓
正在启动
↓
正常运行
↓
正在清理
↓
已经卸载所以 Fiber 不是线程,也不是子进程。1
2
3
4
5PENDING
→ LOADING
→ ACTIVE
→ UNLOADING
→ DISPOSED
它只是插件的一份“生命周期档案”。
插件怎么插手系统流程
有些插件提供能力,比如文件系统。
另一些插件不提供新能力,只想在某个流程中插一脚。
例如权限插件想在执行 Bash 前检查命令:1
2
3
4
5
6模型请求执行 rm ...
↓
权限插件检查
↓
允许:继续执行
拒绝:直接返回错误
这个过程很像高速公路收费站:1
2
3
4
5
6请求
→ 审批站
→ 权限站
→ 超时站
→ 日志站
→ 真正执行
每一站都可以:
- 检查请求;
- 修改请求;
- 记录请求;
- 放行;
- 拒绝继续。
在代码里,放行通常是调用:不调用1
next()
next(),就表示到此为止。
这就是它所谓的waterfall。本质上就是一条可插拔的中间件链。
Bundle 和 Profile 又是什么
这里最容易被名词绕晕。
Plugin 是员工
真正执行工作的代码。
Bundle 是部门安装包
一个 Bundle 可能一次带来很多插件,例如:1
2
3
4
5
6Web Bundle
├── Web 服务器插件
├── 浏览器通信插件
├── 聊天界面插件
├── 设置页面插件
└── Web 启动插件
Profile 是公司组织方案
比如:1
2
3web Profile
├── 基础 Bundle
└── Web Bundle
而:1
2
3headless Profile
├── 基础 Bundle
└── 命令行 Bundle
因此:1
dsh --profile web
可以理解成:
按照 Web 这套组织方案,把对应部门和员工全部组装起来。
安装一个插件时发生什么
执行:1
dsh plugin --profile web add some-plugin
大致等于:1
2
3
4
51. 用 pnpm 下载 npm 包
2. 查看这个包有没有声明自己是 DSH Bundle
3. 找到它携带的插件配置
4. 把它加入 web Profile
5. 下次启动时加载其中的插件
大概是这么个逻辑