框架能力

服务与依赖

掌握继承 Service 基类实现跨插件状态共享、声明式 using 依赖注入以及 TypeScript 强类型上下文扩展。

在复杂的多智能体与工具系统中,不同插件之间往往需要共享底层通用能力(例如向量数据库连接池、分布式缓存或统一审计管道)。

在 DeepSeek Harness 中,服务(Service) 是实现跨插件能力共享的官方单例抽象标准。配合 Cordis 微内核的声明式依赖注入(inject),系统能够自动解析有向无环图(DAG)加载拓扑,杜绝空指针调用与依赖混乱。

本文将以一个 “智能体长期记忆检索服务(Vector Memory Store Service)” 为例,系统讲解如何继承 Service 基类、利用 TypeScript 声明合并实现全链路类型安全,以及下游插件的消费注入。


概念基石:Harness 的微内核服务体系

在 Harness 中,核心基础设施均以命名服务的形式挂载在 Context 上:

typescript
ctx.tools    // 工具注册中心:管理所有大模型可调用的 DSL Tool 实例
ctx.llm      // 大模型推理中心:负责流式分片推流、多路由适配与 Token 统计
ctx.agents   // 智能体调度中心:管理多轮会话状态、Trajectory 日志审计与规划循环

服务的四大核心特质:

  1. 全局单例性:在同一个微内核上下文树中,每个服务标识(如 vectorStore)全局唯一;
  2. 声明式依赖解析:下游插件只需导出 inject: ['vectorStore'],微内核会自动保证加载时序;
  3. TypeScript 强类型补全:借助模块声明合并(Module Augmentation),ctx.vectorStore 在全工程中拥有完美的 IDE 智能提示;
  4. 生命周期自愈:服务实例本身也是一个插件,拥有独立的加载与销毁生命周期。

步骤一:编写自定义 Service 类与声明合并

创建 custom-plugins/memory-store/src/index.ts 文件:

typescript
import { Service, type Context } from '@deepseek-ai/cordis';

// 1. 词汇表类型定义 (Vocabulary Types)
export interface MemoryDocument {
    id: string;
    text: string;
    embedding?: number[];
    metadata?: Record<string, any>;
}

// 2. TypeScript 模块声明合并:为 Context 接口注入类型定义
declare module '@deepseek-ai/cordis' {
    interface Context {
        vectorStore: VectorStoreService;
    }
}

// 3. 继承 Cordis 的 Service 单例基类
export default class VectorStoreService extends Service {
    // 声明当前服务所依赖的其他上游服务
    static inject = ['llm'];

    private memoryCache: Map<string, MemoryDocument> = new Map();

    constructor(ctx: Context) {
        // super(ctx, 'vectorStore') 将当前实例挂载到 ctx.vectorStore
        super(ctx, 'vectorStore');
    }

    // 暴露核心业务方法:保存记忆切片
    public async storeMemory(doc: MemoryDocument): Promise<void> {
        this.memoryCache.set(doc.id, doc);
        console.log(`[MemoryStore]  已持久化记忆切片: ${doc.id} (当前总计: ${this.memoryCache.size})`);
    }

    // 暴露核心业务方法:语义相似度检索
    public async searchSimilar(query: string, topK: number = 3): Promise<MemoryDocument[]> {
        console.log(`[MemoryStore]  正在检索与 "${query}" 相关的记忆片段...`);
        return Array.from(this.memoryCache.values()).slice(0, topK);
    }
}

步骤二:在下游插件中声明并消费服务

当下游的业务插件或模型工具需要使用向量存储能力时,只需在 inject 中声明依赖名称:

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

export const name = 'agent-memory-retriever';

// 声明依赖 vectorStore 与 tools 服务
export const inject = ['vectorStore', 'tools'];

export function apply(ctx: Context) {
    // 微内核保证:在 apply 执行时,ctx.vectorStore 必定已经处于就绪状态
    ctx.on('session/before-turn', async (session) => {
        const historyDocs = await ctx.vectorStore.searchSimilar(session?.userPrompt ?? '', 2);
        console.log(`[Retriever] 为当前轮次检索到 ${historyDocs.length} 条历史上下文记忆`);
    });
}

微内核 DAG 拓扑解析与依赖反转

┌─────────────────────────────────────────────────────────────┐
│                 Cordis 微内核服务依赖拓扑图                 │
├─────────────────────────────────────────────────────────────┤
│         [ ctx.llm ] (基础大模型推理基础设施)                 │
│                          ▲                                  │
│                          │ (static inject = ['llm'])        │
│         [ ctx.vectorStore ] (向量长期记忆服务)               │
│                          ▲                                  │
│                          │ (inject = ['vectorStore'])       │
│         [ agent-memory-retriever ] (智能体业务消费方)       │
└─────────────────────────────────────────────────────────────┘

微内核在启动时会自动构建有向无环图,自底向上依次初始化服务。如果检测到循环依赖(如服务 A 与服务 B 互相强依赖),微内核会在启动阶段直接抛出明确的拓扑环路径报错。


常见问题解答 (FAQ)

Q1: 为什么在构造函数中执行了 super(ctx, 'vectorStore') 还需要写 declare module

解答super(ctx, 'vectorStore') 是 JavaScript 运行时层面的动作,用于将服务实例实际挂载到 ctx 属性上;而 declare module '@deepseek-ai/cordis' 是 TypeScript 编译期的类型合并,让开发者在编写代码时能够获得精准的代码补全与静态检查。两者相辅相成。

Q2: 如果下游插件没有在 inject 中声明依赖,直接访问 ctx.vectorStore 会怎样?

解答:如果该服务恰好已经提前加载完毕,运行时可能能够正常执行;但在服务重启、异步延迟加载或跨模块热重载时,未声明 inject 会直接引发 TypeError: Cannot read properties of undefined。因此必须显式声明所有使用到的服务依赖

Q3: 自定义 Service 如何在自身被注销时释放资源?

解答Service 本身是 Cordis 的高级插件形态。你可以在 Service 的构造函数内直接调用 this.ctx.effect(() => { return () => cleanup(); }) 来注册数据库连接池或文件缓存的销毁回调。

Q4: 如何在单元测试中对 ctx.vectorStore 进行 Mock 隔离?

解答:只需编写一个继承自 VectorStoreService 的 Mock 类,并在测试上下文中通过 testCtx.plugin(MockVectorStoreService) 挂载,即可在不连接外部真实数据库的情况下完成纯内存单元测试。