[DeepSeek Harness插件内核-05]如何让同一服务的方法在插件关联的上下文中执行?

AI 时代程序员必备技能

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

Cordis利用动态创建子Context的方式构建具有树形层次化结构的Context Tree,进而为插件提供的独立的执行环境。貌似很完美地为插件解决了上下文隔离问题。但是还有一个问题得解决:由于Cordis总是将服务注册到根Context上,意味着以此为根的整棵Context Tree注册的插件使用的同名服务都是同一个实例,如何让服务执行的时候通过提取与插件关联的上下文信息,而不是使用与自身绑定的上下文

1. 有状态服务实例复用的困境

对于一个不维护自身状态,只提供单纯操作的服务对象来说,它可以毫无风险地很复用。但是一旦让服务实例拥有了状态,复用的时候就要小心了。以如下这个演示程序为例,我们创建了一个绑定了一个Context对象(对应ctx属性)的 SingletonService,我们定义了一个名为 foobar 的访问器属性从这个Context中提取foobar属性。

import { Context} from '@deepseek-ai/cordis'

class SingletonService{
    get foobar() {return this.ctx?.foobar ?? "N/A"}
    ctx: Context = new Context()
}

declare module '@deepseek-ai/cordis' {
  interface Context {
    service: SingletonService
    foobar: string,
  }
}

const context = new Context();
context.provide("service",new SingletonService())

const subContext1  = context.isolate("child1")
Object.defineProperty(subContext1, 'foobar', { get() { return "foo" } });
subContext1.plugin(ctx=>console.log(`[Plugin1] ctx.service.foobar = ${ctx.service.foobar }`));

const subContext2  = context.isolate("child2");
Object.defineProperty(subContext2, 'foobar', { get() { return "bar" } });
subContext2.plugin(ctx=>console.log(`[Plugin2] ctx.service.foobar = ${ctx.service.foobar }`));

我们调用provide方法将SingletonService对象注册到创建的根Context上。然后创建了两个子Context,并分别定义了foobar属性,对应返回值分别为 foobar。我们在这两个子Context上分别注册了插件,并让插件输出当前上下文的ctx.service.foobar的值。 由于SingletonService对象的ctx属性是一个完全独立的Context,所以它的foobar访问器属性应该永远返回 N/A,如下的输出也体现了这一点。

[Plugin1] ctx.service.foobar = N/A
[Plugin2] ctx.service.foobar = N/A

但是我们真实的需求就是希望SingletonService能够在调用它的插件提供的Context内执行,所以它的foobar访问器属性就应该在不同的插件调用下分别返回foobar。但是按照我们对上面程序的理解,这貌似是要完成一件无中生有的事情,感觉误解。实则不然,我们看看Cordis是如何解决这个问题的。

2. 基于代理的属性解析

让同一个对象的属性针对当前上下文返回不同的值,源于Context内部采用了基于代理的动态属性解析。从如下的代码可以看出, Context的构造函数返回的并不是this,而是一个将它作为目标对象的代理。这是 Cordis 最精妙的设计,这意味着所有对 Context 属性的访问都不会直接触发,而是经过这个 Proxy。至于这个Proxy究竟服务返回属性访问请求,全部实现在作为拦截处理器的ReflectService.handler静态属性上。不过这不是本篇文章关注的内容,我们会在本系列后续文章中对此进行详细介绍,在这里我们只需要知道:在整Cordis体系内针对Context属性成员的访问都由ReflectService.handler控制。

export class Context {
  constructor() {
    ...
    const self = new Proxy<this>(this, ReflectService.handler)
    ...
    return self
  }
}

3. symbols.tracker 和 Tracker 接口

Cordis针对这个问题的解决需要使用到如下这个名为Tracker的接口。如果注册到Context上的服务对象定义了一个由symbols.tracker标识的属性提供了这样一个对象,它会改变属性访问的解析策略。

export interface Tracker {
  associate?: string
  property?: string
  noShadow?: boolean
}

三个属性说明如下:

  • associate:用于开启跨命名空间的动态属性重定向;
  • property:用来指定当前Context绑定到对象的哪个属性上;
  • noShadow: 决定在依赖注入入口处,是否要 强行保留最外层的影子上下文

说到这里,你肯定还是很晕,在服务对象上定义一个由symbols.tracker标识的Tracker接口类型的属性就可以了吗?确实是的,我们可以通过实例演示的方式来证实这一点。

4. 利用Tracker.property 动态绑定上下文

对于我们演示实例来说,其实我们的目的明确:让SingletonService对象的ctx属性直接与当前的Context绑定在一起。所以我们需要按照如下的方式为SingletonService额外添加一个由symbols.tracker标识的属性,并将作为属性值的Tracker对象的property属性设置为当前上下文绑定的目标属性 ctx 就可以。再次运行,将看到我们希望的结果。

import { Context ,Tracker, symbols} from '@deepseek-ai/cordis'

class SingletonService{
    [symbols.tracker]:Tracker = {property : "ctx"}
    get foobar(){return this.ctx?.foobar??"N/A"}
    ctx: Context = new Context()
}

输出:

[Plugin1] ctx.service.foobar = foo
[Plugin2] ctx.service.foobar = bar

5. 利用Tracker.associate 关联属性成员

Trackerproperty解决的是当前Context绑定到服务对象的哪个属性成员上,而associate刚好相反,解决是服务对象如何与当前Context的某个属性成员进行关联。具体例子,当某个服务以名称 service 注册到Context上, 服务对象携带的Tracker对象将associate设置为 foo。假设当前Context通过变量ctx表示, 此时我们可以通过ctx.service得到这个服务对象的代理。如果我们访问ctx.service.bar,并且此时 ctx 具有一个名为 foo.bar 的属性成员,该成员的值将作为返回值。下面的程序很好的演示了这一点:

import { Context ,Tracker} from '@deepseek-ai/cordis'
class FooService {
    [symbols.tracker]:Tracker = {
        "associate": "foo"
    }
}
class BarService {
    [symbols.tracker]:Tracker = {
        "associate": "bar"
    }
}

declare module '@deepseek-ai/cordis' {
  interface Context {
    foo: {baz: string},
    bar: {baz: string},
  }
}

const context = new Context();
context.provide("foo", new FooService());
context.provide("bar", new BarService());
context.provide("foo.baz","ctx.foo.baz");
context.provide("bar.baz","ctx.bar.baz");

console.log(context.foo.baz);
console.log(context.bar.baz);

输出:

ctx.foo.baz
ctx.bar.baz

6. 关于 noShadow

对于Tracker的noShadow属性的作用和用法,我到目前没有看到明确的说明文档,只有从源代码去寻找答案。根据目前提供的源代码,我发现如下的方式是唯一能够验证noShadow属性分别设置成falsetrue的差异。如代码所示,我们定义了一个名为FoobarService的服务类型,我们会在构造函数中为当前对象添加一个Tracker对象并作为symbols.tracker标识的属性值。Trackerproperty设置成ctx,意味着将当前Context绑定到ctx属性上,noshadow则来源于指定的参数。

import { Context,  symbols, Tracker } from '@deepseek-ai/cordis'


declare module '@deepseek-ai/cordis' {
  interface Context {
    shadowService: FoobarService,
    noShadowService: FoobarService,
    foobar:string
  }
}

class FoobarService{
  invoke (){
    const tranker: Tracker = this[symbols.tracker];
    console.log(`\ntranker: property=${tranker.property}, noShadow=${tranker.noShadow}`);
    console.log(`this.ctx.foobar = ${this.ctx.foobar}`);
    const shadow:Context = this.ctx[symbols.shadow];
    if(shadow){
      console.log(`this.ctx[symbols.shadow].foobar = ${shadow.foobar}`);
    } else{
      console.log(`this.ctx[symbols.shadow] = ${shadow}`);
    }
  }    
  constructor (private ctx: Context, noShadow: boolean) {     
    this[symbols.tracker] = {property:"ctx", noShadow:noShadow}
  }
}

const context = new Context()
Object.defineProperty(context, "foobar", {value: "foo", writable: true});
(context as any)["shadowService"] = new FoobarService(context, false);
(context as any)["noShadowService"] = new FoobarService(context, true);

const subConext = context.extend();;
Object.defineProperty(subConext, "foobar", {value: "bar", writable: true});

subConext.shadowService.invoke()
subConext.noShadowService.invoke()

process.stdin.resume();

FoobarServiceinvoke方法会依次输入如下的内容:

  • 当前的Tracker
  • 当前Context(this.ctx)foobar属性值;
  • 如果symbols.shadow存在,则输出其foobar属性值,否则输出undefined

我们创建了一个根Context,并将其foobar属性设置为foo。然后基于这个Context对象和不同的noShadow值创建了FoobarService,并将它们作为当前Conext的shadowServicenoShadowService属性值。我们最终通过extend方法创建了一个子Context,并将foobar属性修改为bar。我们最终从子Context利用shadowServicenoShadowService属性提取FoobarService对象,并调用其invoke方法。

tranker: property=ctx, noShadow=false
this.ctx.foobar = bar
this.ctx[symbols.shadow].foobar = foo

tranker: property=ctx, noShadow=true
this.ctx.foobar = bar
this.ctx[symbols.shadow] = undefined

从上面的输出可以看出,两种情况下从当前Context提供相同的值,但是只有在默认noshadow=false的情况下,才能通过symbols.shadow得到服务对象真正绑定的Context。 这种情况下,目标方法(invoke)执行流程为:

  • Cordis会将服务实例的原始上下文,即构造函数中提供的Context对象(foobar=foo)提取出来,并将其作为影子上下文
  • 如果将影子上下文表示成shadow,接下Cordis会使用对象{[symbols.shadow]:shadow}作为参数调用extend方法扩展当前Context并生成一个子Context;
  • 为方法(invoke)调用的上下文 (即invoke方法中的thisctx.shadowService返回的FoobarSerivce的代理对象)创建一个代理(代理的代理)。通过提供的get处理器在利用此代理访问Tracker的property属性表示的属性时,直接返回上面创建的子Context,即将携带影子上下文的Context对象绑定到服务对象由Tracker.property指定的属性上;
  • 以新创建的代理作为上下文(this)调用方法(invoke)。

所示当noshadow=falseinvoke方法中this.ctx属性得到的是携带影子上下文的Context对象,如果noshadow=true,该属性返回的就是当前上下文。从这个意义上讲:

  • 影子上下文指的是目标服务对象绑定的原始Context,我原来以为当前提供的Context才是影子;
  • noShadow的意思是不要构建影子上下文(shadow翻译为名词影子),或者不用影子上下文来遮挡当前上下文(shadow翻译成动词遮挡)。我原以为服务希望使用原始上下文,不希望被当前上下文遮挡。

但是让我疑惑的,如果invoke方法在两种情况下直接使用this.ctx去提取上下文信息,还是会得到相同的上下文信息,针对要求开发者利用symbols.shadow去提取影子上下文。虽然Cordis存在一些类似于下面的代理,但是我发现除非我手工创建一个包含影子的Cotext,否则这些分支都走不到。鉴于noshadow这个开关与我原有认知存在错位,我目前也没有看到官方文档对这个开关的作用和用法又什么确定的描述,上面的内容仅仅我通过代码反推的结果,仅当参考。

if (ctx[symbols.shadow] && !tracker.noShadow) {
  ctx = Object.getPrototypeOf(ctx)
}

let fiber = (ctx[symbols.shadow] as Context ?? ctx).fiber

AI 时代程序员必备技能

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

【内容概要】概述 该数据集提供了印度主要股票基准指数的精心策划的端到端历史记录。与仅限标准价格的数据集不同,该资源将主要交易所数据与先进的技术指标和基本估值比率相结合,提供了印度市场生态系统的360度视图。 涵盖的指数 该数据集包括以下13个主要指标的每日数据: SENSEX(BSE 30) 漂亮50 第九银行(银行业) 第九届IT(信息技术) 九药集团(制药) NIFTY快速消费品(快速消费品) 第九金属(金属与采矿) 九号汽车 NIFTY NEXT 50(大盘股出现) 漂亮的中帽150 漂亮的小帽250 漂亮100 漂亮500 数据来源和方法 基础数据(OHLCV):来源于雅虎财经(yfinance),提供可靠、高频调整的每日定价。 估值数据:通过nsepy库进行增强,直接抓取NSE印度官方数据,提供准确的市盈率(市盈率)、市盈率(市净率)和股息收益率(如有历史数据)。 合并策略:来自两个来源的数据点在每日时间戳上智能合并,以最大限度地扩大覆盖范围并填补任何空白。 时间范围 开始日期:2000年1月1日 结束日期:当前交易日(动态更新为现在)。 特征分解(按指数) 该数据集远远超出了简单的价格历史,包含30多个工程列: 基础市场数据: 日期(每日时间戳)、开盘、高、低、收盘、调整收盘、成交量。 估值与基本面: 市盈率(PE)、市盈率(PB)、股息收益率(股息收益率%)。 趋势和移动平均线: MA_50、MA_200、价格_MA50_比率、价格_MA200_比率。 动量和振荡器: RSI_14(相对强弱指数)、MACD、MACD_Signal、MACD_Histogram。 波动性和风险: 波动率_20d(20天滚动标准偏差),ATR_14(平均真实范围),日变化率_%。 价格行动: Daily_Return_%、Log_Return__%、Gap_%(开盘价与上一...
【单相单级并网逆变器】模拟了一个具有最大功率点追踪(MPPT)功能的单相单级脉宽调制(PWM)光伏逆变器,并且支持并网运行(Simulink仿真实现)内容概要:本文针对传统三电平并网逆变器存在的谐波含量高、对电网不平衡工况适应性差及动态响应慢等问题,研究了一种基于有源中点箝位(ANPC)结构的单相单级并网逆变器,并提出融合双极性倍频脉宽调制(DPWMA)、正负序分离锁相与电网电压前馈控制的复合控制策略。通过Simulink搭建仿真模型,验证了该系统在稳态运行、电网电压不平衡和动态扰动工况下的优越性能,结果表明所提方法能显著降低并网电流谐波畸变率,提升锁相精度与系统抗扰能力,实现高质量、高稳定性的并网运行。; 适合人群:具备电力电子与电力系统基础知识,从事新能源并网、逆变器控制或相关领域研究的研发人员及研究生。; 使用场景及目标:①用于光伏发电、储能系统等新能源并网场景中高性能逆变器的设计与优化;②为解决电网电压不平衡、谐波干扰等复杂工况下的并网稳定性问题提供技术参考与仿真验证方案; 阅读建议:建议结合Simulink仿真模型同步学习,重点关注DPWMA调制实现、正负序分离锁相算法设计与前馈控制环节的协同机制,深入理解控制策略对电能质量和动态性能的提升作用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值