(四)比LangChain更优雅?DeepSeek Harness的插件化Agent设计

项目地址:https://github.com/deepseek-ai/deepseek-harness
⭐ 88,000+ Stars | MIT License | 语言:TypeScript


开篇:当LangChain还在"链式调用"时,Harness已经"插件化"了

如果你用过LangChain,一定熟悉这样的代码:

from langchain import OpenAI, LLMChain, PromptTemplate
from langchain.chains import SimpleSequentialChain

# 定义链
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', new DeepSeekAdapter())

// 插件B消费服务
const llm = ctx.inject('llm')

对比LangChain

  • LangChain:组件直接实例化依赖,new Chain(dependency)
  • Harness:依赖通过Context注入,运行时动态绑定

2.2 Lifecycle(生命周期):插件的"出生到死亡"

Harness的每个插件都有完整的生命周期:

export default class MyPlugin extends Service {
  async setup() {
    // 初始化:注册事件监听、分配资源
    this.ctx.on('turn/start', this.handleTurnStart)
  }
  
  async start() {
    // 启动:开始处理业务
  }
  
  async stop() {
    // 停止:清理资源
    this.ctx.off('turn/start', this.handleTurnStart)
  }
  
  async dispose() {
    // 销毁:完全释放
  }
}

关键能力

  • 热加载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())
  return next(request)
})

对比LangChain

  • LangChain:通过回调函数(callbacks)实现拦截,但只能观察不能修改
  • Harness:waterfall事件允许完全拦截和修改数据流

三、实战对比:实现一个"日志记录"功能

3.1 LangChain方式

from langchain.callbacks import BaseCallbackHandler

class LoggingHandler(BaseCallbackHandler):
    def on_llm_start(self, serialized, prompts, **kwargs):
        print(f"[LOG] LLM调用开始: {prompts}")
    
    def on_llm_end(self, response, **kwargs):
        print(f"[LOG] LLM调用结束: {response}")

# 使用:每个Chain都要手动传入
chain = LLMChain(
    llm=llm, 
    prompt=prompt,
    callbacks=[LoggingHandler()]  # ← 每个链都要加
)

问题

  • 每个Chain都要手动注入callback
  • 无法修改数据,只能观察
  • 回调顺序不可控

3.2 Harness方式

export default class LoggingPlugin extends Service {
  setup() {
    // 拦截LLM请求事件
    this.ctx.on('llm/request', async (req, next) => {
      console.log('[LOG] LLM请求:', req.messages)
      return next(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', new LocalShellProvider())

现在你想把Agent部署到云端,但又不想改代码。Harness的做法:

// 换成云端执行,其他代码完全不变
ctx.provide('shell', new E2BCloudProvider())

Seam的三角色

  1. Service Definition:接口定义(interface IShell
  2. Provider:实现者(LocalShellProviderE2BCloudProvider
  3. Consumer:使用者(BashToolFileEditor等)

4.2 对比LangChain的"工具替换"

LangChain中替换工具:

# 原来
tools = [ShellTool(), FileTool()]

# 改成云端:需要重写每个Tool
class CloudShellTool(BaseTool):
    def _run(self, command):
        return call_e2b_api(command)  # 每个工具都要改

tools = [CloudShellTool(), CloudFileTool()]

Harness中替换:

// 只需要换Provider,所有Consumer自动切换
ctx.provide('shell', new E2BProvider())  // 一行代码

这就是"优雅"的差距


五、性能对比:谁更快?

指标 LangChain Harness
启动时间 快(直接实例化) 稍慢(需初始化插件系统)
运行时扩展 需重启 热插拔
内存占用 低(无运行时开销) 稍高(事件总线)
复杂场景性能 链式调用栈深 事件驱动扁平化

结论

  • 简单场景:LangChain更快
  • 复杂/动态场景:Harness架构更优

六、什么时候选LangChain?什么时候选Harness?

选LangChain,如果你:

  • 快速原型验证
  • 团队熟悉Python
  • 需求相对固定
  • 不想引入运行时复杂度

选Harness,如果你:

  • 需要高度可扩展
  • 需求经常变化
  • 想构建"Agent平台"而非单个Agent
  • 需要热加载/热卸载能力
  • 重视代码可维护性

七、Harness插件开发实战

7.1 最小插件示例

// plugins/hello.ts
import { Service, Context } from 'cordis'

export default class HelloPlugin extends Service {
  constructor(ctx: Context) {
    super(ctx, 'hello')
  }
  
  async setup() {
    // 注册一个命令
    this.ctx.command('hello')
      .action(() => 'Hello from plugin!')
  }
}

7.2 插件配置

# cordis.patch.yml
plugins:
  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篇,涵盖架构、源码、实战全流程。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐