(四)比LangChain更优雅?DeepSeek Harness的插件化Agent设计
项目地址:https://github.com/deepseek-ai/deepseek-harness
⭐ 88,000+ Stars | MIT License | 语言:TypeScript
开篇:当LangChain还在"链式调用"时,Harness已经"插件化"了
如果你用过LangChain,一定熟悉这样的代码:
fromlangchainimportOpenAI,LLMChain,PromptTemplatefromlangchain.chainsimportSimpleSequentialChain# 定义链llm=OpenAI(temperature=0.7)template=PromptTemplate(...)chain=LLMChain(llm=llm,prompt=template)# 串起来overall_chain=SimpleSequentialChain(chains=[chain1,chain2,chain3])result=overall_chain.run(input)链式调用——这是LangChain的核心抽象。但问题是:一旦业务复杂起来,链会变成一团乱麻。你想替换其中一个环节?可能要重写整个链。
DeepSeek Harness的做法完全不同:没有链,只有插件。
一、两种设计哲学的根本差异
1.1 LangChain:链式组合
Input → [Prompt] → [LLM] → [OutputParser] → [Tool] → Output ↑___________链式调用_____________↑特点:
- 组件通过"链"硬编码组合
- 数据流向固定
- 扩展需要继承/重写Chain类
1.2 Harness:插件化事件驱动
Event: turn/start ↓ Plugin A (拦截/修改) ↓ Plugin B (处理) ↓ Event: turn/end特点:
- 组件通过"事件"松耦合
- 数据流由事件驱动,动态可配
- 扩展只需注册新插件
二、Harness插件系统的三大核心机制
2.1 Context(上下文):插件的"共享内存"
Harness中,插件不直接调用彼此,而是通过Context交换数据:
// 插件A提供服务ctx.provide('llm',newDeepSeekAdapter())// 插件B消费服务constllm=ctx.inject('llm')对比LangChain:
- LangChain:组件直接实例化依赖,
new Chain(dependency) - Harness:依赖通过Context注入,运行时动态绑定
2.2 Lifecycle(生命周期):插件的"出生到死亡"
Harness的每个插件都有完整的生命周期:
exportdefaultclassMyPluginextendsService{asyncsetup(){// 初始化:注册事件监听、分配资源this.ctx.on('turn/start',this.handleTurnStart)}asyncstart(){// 启动:开始处理业务}asyncstop(){// 停止:清理资源this.ctx.off('turn/start',this.handleTurnStart)}asyncdispose(){// 销毁:完全释放}}关键能力:
- 热加载:
scope.plugin(MyPlugin)立即生效 - 热卸载:
scope.dispose()完全清理,无内存泄漏 - 可逆性:卸载后状态回到加载前
对比LangChain:
- LangChain:链一旦构建,无法动态增删组件
- Harness:运行时随时插拔插件
2.3 Event(事件):插件的"通信协议"
Harness使用事件总线实现插件间通信:
// 发布事件ctx.emit('agent/message',{role:'user',content:'Hello'})// 订阅事件ctx.on('agent/message',async(msg)=>{console.log('收到消息:',msg.content)})四种事件模式:
| 模式 | 说明 | 用途 |
|---|---|---|
emit | 广播,不等待 | 日志、通知 |
parallel | 并行执行 | 独立副作用 |
serial | 串行执行 | 有序处理 |
bail | 返回首个非空 | 策略选择 |
| waterfall | 可拦截修改 | 中间件 |
waterfall是Harness的杀手锏:
// 拦截并修改LLM请求ctx.on('llm/request',async(request,next)=>{// 修改请求参数request.temperature=0.5// 继续传播(或调用next())returnnext(request)})对比LangChain:
- LangChain:通过回调函数(callbacks)实现拦截,但只能观察不能修改
- Harness:waterfall事件允许完全拦截和修改数据流
三、实战对比:实现一个"日志记录"功能
3.1 LangChain方式
fromlangchain.callbacksimportBaseCallbackHandlerclassLoggingHandler(BaseCallbackHandler):defon_llm_start(self,serialized,prompts,**kwargs):print(f"[LOG] LLM调用开始:{prompts}")defon_llm_end(self,response,**kwargs):print(f"[LOG] LLM调用结束:{response}")# 使用:每个Chain都要手动传入chain=LLMChain(llm=llm,prompt=prompt,callbacks=[LoggingHandler()]# ← 每个链都要加)问题:
- 每个Chain都要手动注入callback
- 无法修改数据,只能观察
- 回调顺序不可控
3.2 Harness方式
exportdefaultclassLoggingPluginextendsService{setup(){// 拦截LLM请求事件this.ctx.on('llm/request',async(req,next)=>{console.log('[LOG] LLM请求:',req.messages)returnnext(req)// 继续执行})// 拦截LLM响应事件this.ctx.on('llm/response',async(res)=>{console.log('[LOG] LLM响应:',res.content)})}}// 使用:注册一次,全局生效ctx.plugin(LoggingPlugin)优势:
- 注册一次,所有Agent生效
- 可以修改请求/响应
- 通过事件优先级控制顺序
四、Harness插件的"超能力":Seam交换
这是Harness独有的设计——能力边界(Capability Seam)。
4.1 什么是Seam?
想象你在开发一个Agent,需要执行Shell命令:
// 默认:本地执行ctx.provide('shell',newLocalShellProvider())现在你想把Agent部署到云端,但又不想改代码。Harness的做法:
// 换成云端执行,其他代码完全不变ctx.provide('shell',newE2BCloudProvider())Seam的三角色:
- Service Definition:接口定义(
interface IShell) - Provider:实现者(
LocalShellProvider、E2BCloudProvider) - Consumer:使用者(
BashTool、FileEditor等)
4.2 对比LangChain的"工具替换"
LangChain中替换工具:
# 原来tools=[ShellTool(),FileTool()]# 改成云端:需要重写每个ToolclassCloudShellTool(BaseTool):def_run(self,command):returncall_e2b_api(command)# 每个工具都要改tools=[CloudShellTool(),CloudFileTool()]Harness中替换:
// 只需要换Provider,所有Consumer自动切换ctx.provide('shell',newE2BProvider())// 一行代码这就是"优雅"的差距。
五、性能对比:谁更快?
| 指标 | LangChain | Harness |
|---|---|---|
| 启动时间 | 快(直接实例化) | 稍慢(需初始化插件系统) |
| 运行时扩展 | 需重启 | 热插拔 |
| 内存占用 | 低(无运行时开销) | 稍高(事件总线) |
| 复杂场景性能 | 链式调用栈深 | 事件驱动扁平化 |
结论:
- 简单场景:LangChain更快
- 复杂/动态场景:Harness架构更优
六、什么时候选LangChain?什么时候选Harness?
选LangChain,如果你:
- 快速原型验证
- 团队熟悉Python
- 需求相对固定
- 不想引入运行时复杂度
选Harness,如果你:
- 需要高度可扩展
- 需求经常变化
- 想构建"Agent平台"而非单个Agent
- 需要热加载/热卸载能力
- 重视代码可维护性
七、Harness插件开发实战
7.1 最小插件示例
// plugins/hello.tsimport{Service,Context}from'cordis'exportdefaultclassHelloPluginextendsService{constructor(ctx:Context){super(ctx,'hello')}asyncsetup(){// 注册一个命令this.ctx.command('hello').action(()=>'Hello from plugin!')}}7.2 插件配置
# cordis.patch.ymlplugins:hello:# 插件配置greeting:'Hi there!'7.3 加载插件
npx @deepseek-ai/dsh web--patch./cordis.patch.yml结语:插件化是Agent框架的未来吗?
LangChain的链式调用是命令式编程——你告诉计算机"先做这个,再做那个"。
Harness的插件化事件驱动是声明式编程——你定义"当这个发生时,做那个",具体怎么流转,由框架决定。
在简单场景下,命令式更直观。但在复杂场景下,声明式的可组合性、可扩展性、可维护性优势明显。
DeepSeek Harness用88K Stars证明:插件化不是过度设计,而是Agent框架的进化方向。
下一篇预告:
(五)DeepSeek Harness来了!一个让你自己「拼」出AI Agent的开源神器
我们将进入实战环节,手把手教你用Harness搭建第一个Agent。
本文是「DeepSeek Harness源码分析」系列第4篇,系列共100篇,涵盖架构、源码、实战全流程。