[DeepSeek Harness插件内核-01]DeepSeek Harness三种插件形式

AI 时代程序员必备技能

Codex、Claude Code、Cursor、Hermes Agent、OpenClaw等工程化实战专栏 ,讲透 AI 如何接管脏活累活

DeepSeek Harness(dsh)是由 DeepSeek AI 开发的开源 agent harness。它采用一切皆插件的架构,并由 Cordis 驱动,其设计参见论文 A Programming Paradigm for Spatiotemporal Composability。Cordis 本质上是插件的的执行内核,Harness 则是基于 Cordis 的完整代理运行框架。理解 Cordis 就是理解 Harness 的底层机制:插件化、上下文共享、配置驱动、事件通信。这让开发者能灵活组合模型与工具,快速构建可扩展的 AI Agent 系统。在这个系列中,我将从源码为出发点,从各个角度Cordis的设计和实现。

1. 从Context说起

Cordis下的插件究竟是一个怎样的对象呢?要回答这个问题,就必须先了解Cordis整个架构体系一个最核心的对象,那就是作为上下文的Context对象。在 Cordis 框架下,Context 是它的核心抽象之一,可以理解为 插件运行的统一环境与依赖容器。它的作用和地位主要体现在以下几个方面:

  • 统一环境:所有插件都挂载在同一个 Context 上,形成共享的运行空间;
  • 服务解析:插件通过 Context 提供的服务解析器来访问其他插件暴露的功能(如日志、事件、注册表);
  • 事件总线:Context 内置事件系统,支持 ctx.onctx.emit 等方法,插件间通信通过它完成;
  • 依赖注入:插件可以通过 ctx.inject 获取其他服务,避免硬编码依赖;
  • 生命周期管理:Context 负责插件的加载、卸载、资源清理,保证运行时稳定。

Context在Cordis中被定义一个类,由于采用了基于代理的机制动态解析成员调用,让这个类型拥有了极强的扩展性,我们几乎可以将任意对象挂载到Context对象上。我们可以将Context视为一个依赖注入容器,或者说我们可以将整个Cordis视为一个建立在依赖注入容器上的插件系统。我们通过相应的服务注册API在容器上注册相应的服务,服务的消费者则从这个容器中提取依赖服务对象。为了解决多个同名服务并存的问题(针对同一个名称注册了不同的服务实例供不同的插件使用),Context采用树形层次化设计。

  • 顶层是根Context,包含日志、注册表、事件总线和服务接口;
  • 根Context 向下派生出多个 子 Context,每个子 Context 可以挂载不同的插件(模型适配器、工具插件、事件钩子等);
  • 所有子 Context 最终共享同一个 上下文空间,保证服务可继承、可覆盖,同时保持隔离性;

这种设计的优势在于:

  • 层次化:父子 Context 形成树状结构,支持继承与隔离;
  • 灵活性:不同子 Context 可以挂载不同插件,互不干扰;
  • 统一性:共享 Context 空间保证服务调用一致,避免碎片化;

针对Context的介绍会贯穿整个系列,这里我们简单看看定义了核心成员的Context接口的定义:

interface Context {
  [symbols.isolate]: Dict<symbol>
  [symbols.intercept]: Dict
  root: this
  baseUrl?: string

  events: EventsService
  logger: LoggerService
  reflect: ReflectService
  registry: RegistryService
}

定义在Context接口中的核心成员包括:

  • [symbols.isolate]:维护服务隔离映射表。当某个服务需要在不同作用域下拥有独立实例(例如不同模型或插件),上面介绍的同名服务在不同 Context 下可独立存在,互不干扰就通过它来实现;
  • [symbols.intercept]:存储服务的拦截配置。通过 intercept 方法,插件可以在加载时注入额外配置或修改服务行为。利用这个字典实现插件级的配置合并与行为定制,而不影响父级 Context;
  • root: 指向应用的根 Context。无论当前 Context 层级多深,都能通过 ctx.root 访问全局服务与配置,它保证所有子 Context 都能追溯到统一的根环境;
  • baseUrl: 定义插件或模块的基础路径。用于解析相对路径的模块加载或资源定位,它让插件能在不同运行环境下正确定位依赖;
  • events:事件总线服务。提供 ctx.onctx.emit 等方法,实现插件间通信,促成了Cordis松耦合的事件驱动架构;
  • logger:日志服务。通过 ctx.logger(name) 获取命名日志器,支持分级与上下文追踪,提供统一日志输出,方便调试与监控;
  • reflect:反射层服务。支撑上面提到过的基于代理机制的动态属性解析,它让 Context 能像对象一样直接访问服务;
  • registry:插件注册表。负责插件的加载、卸载与依赖管理。它形成 Cordis 的插件树结构,是整个系统的“调度中心”。

2. 插件的表现形式

Context相当于Cordis这个插件内核的执行上下文,它提供了注册、执行和释放插件所需的运行时信息和服务对象,所以插件本质上可以视为针对Context上下文的一项操作。由于我们在定义插件的时候,一般会提供相应的配置来控制其执行行为,所以作为插件操作的输入还应该包括配置。除此之外,插件还需要一些额外的元数据信息。从这个角度去理解Cordis的插件类型就很容易了。如下面的代码所示,表示插件的Plugin是由三个泛型接口组成的联合,类型参数T表示的是插件对应配置的类型。

export type Plugin<T = any> =
  | Plugin.Function<T>
  | Plugin.Constructor<T>
  | Plugin.Object<T>

表示三种插件形态的接口都是针对如下这个Base接口的扩展,该接口定义了一个插件的基本结构。由于Base接口定义的所有成员都是可缺省的,所以具体的插件类型可以针对性地提供:

export namespace Plugin {
  export interface Base<T = any> {
    name?: string
    Config?: StandardSchemaV1<any, T>
    inject?: Inject
    provide?: string | string[]
    intercept?: Dict<boolean>
  }
}

Base接口定义的成员说明如下:

  • name:插件的名称标识。用于在注册表中区分不同插件,便于调试和依赖管理。保证插件在 Context 中有唯一可识别的身份;
  • Config:插件的配置模式(Schema)。定义插件可接受的配置结构和类型,确保配置合法性。提供类型安全和配置验证,避免运行时错误。很明显这里命名错误,应该是config
  • inject:依赖注入声明。指定插件运行所需的外部服务或其他插件。通过 Context 自动解析依赖,避免硬编码耦合。
  • provide:插件对外提供的服务名称。声明插件能暴露哪些功能接口,供其他插件使用。形成服务契约,实现插件间的协作。
  • intercept:拦截器配置。声明插件是否拦截某些服务调用或事件。允许插件在运行时修改或增强其他服务的行为。

2.1 函数形式

函数形式作为简单明了,只需要提供一个将Context和配置对象作为输入的函数即可,对应与如下这个Plugin.Function<T>接口。

export namespace Plugin {
  export interface Function<T = any> extends Base<T> {
    (ctx: Context, config: T): any
  }
}

在如下的演示程序中,我们注册的插件体现为greet函数。该函数具有上述的签名,作为配置的类型需要利用timeOfDay提供表示输出问候语的时间部分(morning、afternoon和evening)。整个插件的目的就是在控制台输出一条由提供配置格式化的问候语。

import { Context } from '@deepseek-ai/cordis'
import * as readline from "readline"
function greet (context: Context, config: {timeOfDay:string}){
    console.log(`Good ${config.timeOfDay}`)
}

async function main() {
  const ctx = new Context();
  const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout});

  const loop = function(){
     rl.question("Enter time of day or exit: ", (answer) => {
      if(answer == "exit"){
        rl.close()
      } else{
        ctx.registry.plugin(greet, {timeOfDay: answer});
        setTimeout(()=>loop(),1000);
      }
     });
    }
    loop();
}

main();

我们在main函数中我们创建了一个Context对象,并构建了一个循环,利用输入来指定greet插件的配置。我们在注册插件的时候,利用Context的registry属性得到表示注册服务的RegistryService对象,并调用其plugin方法完成插件的注册。执行这个程序,以插件配置的形式输入不同的时间,注册的插件会输出相应的问候语。

Enter time of day or exit: morning
Good morning
Enter time of day or exit: afternoon
Good afternoon
Enter time of day or exit: evening
Good evening

2.2 类形式

插件的第二种形式体现为如下这个Plugin.Constructor<T>接口,它包含一个具有与上述函数相同签名的构造函数。

export namespace Plugin {
  export interface Constructor<T = any> extends Base<T> {
    new (ctx: Context, config: T): any
  }
}

这意味着我们可以将插件定义成一个具有指定构造函数的类。所谓上面演示的通过函数greet定义的插件,也可以通过如下这个greeter类来定义。

import { Context } from '@deepseek-ai/cordis'
import * as readline from "readline"

class geeter{
    constructor(context: Context, config: {timeOfDay:string}){
       console.log(`Good ${config.timeOfDay}`) ;
    }
}

async function main() {
  const ctx = new Context();
  const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout});

  const loop = function(){
     rl.question("Enter time of day or exit: ", (answer) => {
      if(answer == "exit"){
        rl.close()
      } else{
        ctx.registry.plugin(geeter, {timeOfDay: answer});
        setTimeout(()=>loop(),1000);
      }
     });
    }
    loop();
}

main();

2.3 对象形式

插件的第三种形式体现为如下所示的Plugin.Object<T>接口,该接口必须包含一个名为apply的方法,该函数同样具有与上面一致的签名。

export namespace Plugin {
  export interface Object<T = any> extends Base<T> {
    apply(ctx: Context, config: T): any
  }
}

如果采用对象形式,上面演示程序针对插件的注册可以改成如下的形式。

import { Context } from '@deepseek-ai/cordis'
import * as readline from "readline"
function greet (context: Context, config: {timeOfDay:string}){
    console.log(`Good ${config.timeOfDay}`)
}

async function main() {
  const ctx = new Context();
  const rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout});

  const loop = function(){
     rl.question("Enter time of day or exit: ", (answer) => {
      if(answer == "exit"){
        rl.close()
      } else{
        ctx.registry.plugin({apply : greet}, {timeOfDay: answer});
        setTimeout(()=>loop(),1000);
      }
     });
    }
    loop();
}

main();

2.4 插件的最终表现形式

虽然我们可以采用上述三种方式来定义和注册插件,但是对于Cordis来说,它所谓的插件就是一个单纯的函数。两者之间的转换体现在如下这个resolve函数中:

  • 如果原始形态就是函数,直接使用该函数;
  • 对于其它两种形式,取其apply成员(构造函数的apply成员或者插件对象的apply方法)。
resolve(plugin: Plugin): Function | undefined {
try {
    if (typeof plugin === 'function') return plugin
    if (isApplicable(plugin)) return plugin.apply
} catch {}
}

function isApplicable(object: Plugin) {
  return object && typeof object === 'object' && typeof object.apply === 'function'
}

3. 插件函数的返回值

我们再来看看函数和对象形式的插件接口Plugin.Function<T>Plugin.Object<T>的定义,可以前者和后者的apply方法对返回类型都没有限制。由于插件是可拔插的组件,我们希望当插件被卸载的时候,它引用的资源能够全部释放掉。在这种情况下,我们可以让函数返回一个执行清理工作的函数。

export namespace Plugin {
  export interface Function<T = any> extends Base<T> {
    (ctx: Context, config: T): any
  }  
  export interface Object<T = any> extends Base<T> {
    apply(ctx: Context, config: T): any
  }
}

由于插件的生命周期管理由其关联的Fiber对象来负责,实际上RegistryServiceplugin方法就返回这么一个对象。具体可以参考我的文章DeepSeek Harness插件内核-03:利用Fiber管理Context和插件的生命周期。在如下演示程序中,我们分别以函数和对象形式注册了两个插件,并得到对应的Fiber对象。插件函数都返回了一个函数输出一段相应的执行性文本。在插件注册,我们等待一秒让插件正常执行,在先后调用两个Fiber对象的dispose方法。从输出的结果可以看出,插件函数返回的函数会因Fiber对象的释放被调用。

import { Context } from '@deepseek-ai/cordis'
const context = new Context();
const fiber1 = context.registry.plugin(ctx =>{
   console.log("Plugin1 executes...");
   return ()=>console.log("Plugin1 is disposed...");
})
const fiber2 = context.registry.plugin({
  apply(ctx:Context){
    console.log("Plugin2 executes...");
    return ()=>console.log("Plugin2 is disposed...");
  }
})

setTimeout(() => {
  console.log("Fiber1 is disposed...")
  fiber1.dispose();
  console.log("Fiber2 is disposed...")
  fiber2.dispose();
}, 1000);

输出:

Plugin1 executes...
Plugin2 executes...
Fiber1 is disposed...
Fiber2 is disposed...
Plugin1 is disposed...
Plugin2 is disposed...

4. 针对类形式插件的特殊处理

再来看看表示类形式插件的Plugin.Constructor<T>接口,它仅仅要求提供一个指定签名的构造函数。构造函数最终的使命是创建对象,有没有想过生成的这个对象对插件本身有何作用?

export namespace Plugin {
  export interface Constructor<T = any> extends Base<T> {
    new (ctx: Context, config: T): any
  }
}

其实我们可以使用两个预定义的Symbol为通过生成的对象设置两个特殊的成员,一个利用symbols.initHooks定义一个属性用来提供一组会在初始化自动执行的构造函数,另一个则是利用symbols.init定义的属性来提供插件的返回值,作为插件卸载时执行清理工作的处理函数。具体实现体现在如下的演示程序中:

class Plugin{
    [symbols.initHooks] = [
        ()=>console.log("Initial hook 1 execute..."),
        ()=>console.log("Initial hook 2 execute...")];
    [symbols.init] = ()=>console.log("Plugin is disposed...");
    
    constructor(ctx: Context){
        console.log("Plugin initialize...");
    }
}
const context = new Context();
const fiber = context.registry.plugin(Plugin);
setTimeout(() => {
  console.log("Fiber is disposed...")
  fiber.dispose();
}, 1000);
process.stdin.resume();

输出:

Plugin initialize...
Initial hook 1 execute...
Initial hook 2 execute...
Plugin is disposed...
Fiber is disposed...

AI 时代程序员必备技能

Codex、Claude Code、Cursor、Hermes Agent、OpenClaw等工程化实战专栏 ,讲透 AI 如何接管脏活累活

内容概要:本文研究基于CNN-BiLSTM-Attention的负荷预测方法,提出一种融合卷积神经网络(CNN)、双向长短期记忆网络(BiLSTM)和注意力机制(Attention)的深度学习模型,旨在实现高精度的电力负荷预测。该模型通过CNN有效提取输入数据中的局部空间特征,利用BiLSTM充分捕捉时间序列的长期依赖关系与前后向时序信息,并引入Attention机制动态加权关键时间步的输出,增强模型对重要时刻的敏感性,从而显著提升预测准确性。文中提供了完整的Python代码实现流程,涵盖数据预处理、特征工程、模型构建、训练优化、结果评估及可视化等环节,并通过对比实验证明该模型在预测精度、收敛速度和泛化能力方面优于传统预测方法。; 适合人群:具备一定Python编程能力和深度学习理论基础,从事电力系统分析、能源管理、智能电网或时间序列预测等相关领域的科研人员、工程技术人员及高校研究生。; 使用场景及目标:①应用于城市电网、工业园区、商业楼宇等场景下的短期电力负荷预测;②为电力调度、需求响应、能源优化配置和电网安全运行提供精准数据支持;③深入理解并掌握CNN、BiLSTM与Attention融合模型的架构设计、实现细节及其在时序预测任务中的协同机制。; 阅读建议:建议读者结合所提供的Python代码进行动手实践,重点剖析Attention机制对不同时间步特征的权重分配过程,理解各模块之间的数据流动与功能衔接,同时可在自有数据集上迁移应用并调整超参数,以进一步提升模型性能和实际适用性。
内容概要:本文对下垂控制与虚拟同步机(VSG)两种并网型(grid-forming)控制策略进行了系统性的性能对比研究,并基于Simulink仿真平台构建了完整的系统模型,开展动态响应分析。研究深入探讨了两种控制策略在频率调节、有功/无功功率分配、电压支撑能力以及应对负载突变、电网扰动等非理想工况下的控制特性差异。内容涵盖控制原理建模、关键参数设计、稳定性分析及仿真验证全过程,重点剖析了VSG在惯性和阻尼模拟方面的优势,以及下垂控制在简单性与可扩展性上的特点,旨在为构网型逆变器在微电网、新能源并网及新型电力系统中的工程应用提供理论支撑与选型依据。; 适合人群:具备电力电子、自动控制理论及新能源并网技术基础知识的研究生、科研人员及相关领域的工程技术人员。; 使用场景及目标:①深入理解下垂控制与虚拟同步机的核心控制原理及其在Simulink中的实现方法;②掌握并对比两者在动态响应速度、系统稳定性、抗扰动能力及工况适应性等方面的性能差异;③为微电网、储能系统、分布式电源等场景下的控制器设计、参数优化与技术路线选择提供仿真验证手段与决策支持; 阅读建议:建议结合提供的Simulink仿真模型进行动手实践,重点关注控制器参数(如VSG的转动惯量、阻尼系数,下垂系数等)对系统性能的影响,通过对比不同扰动工况下的仿真波形,深刻领会两种策略各自的适用边界与优缺点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值