服务与依赖
掌握继承 Service 基类实现跨插件状态共享、声明式 using 依赖注入以及 TypeScript 强类型上下文扩展。
在复杂的多智能体与工具系统中,不同插件之间往往需要共享底层通用能力(例如向量数据库连接池、分布式缓存或统一审计管道)。
在 DeepSeek Harness 中,服务(Service) 是实现跨插件能力共享的官方单例抽象标准。配合 Cordis 微内核的声明式依赖注入(inject),系统能够自动解析有向无环图(DAG)加载拓扑,杜绝空指针调用与依赖混乱。
本文将以一个 “智能体长期记忆检索服务(Vector Memory Store Service)” 为例,系统讲解如何继承 Service 基类、利用 TypeScript 声明合并实现全链路类型安全,以及下游插件的消费注入。
概念基石:Harness 的微内核服务体系
在 Harness 中,核心基础设施均以命名服务的形式挂载在 Context 上:
ctx.tools // 工具注册中心:管理所有大模型可调用的 DSL Tool 实例
ctx.llm // 大模型推理中心:负责流式分片推流、多路由适配与 Token 统计
ctx.agents // 智能体调度中心:管理多轮会话状态、Trajectory 日志审计与规划循环
服务的四大核心特质:
- 全局单例性:在同一个微内核上下文树中,每个服务标识(如
vectorStore)全局唯一; - 声明式依赖解析:下游插件只需导出
inject: ['vectorStore'],微内核会自动保证加载时序; - TypeScript 强类型补全:借助模块声明合并(Module Augmentation),
ctx.vectorStore在全工程中拥有完美的 IDE 智能提示; - 生命周期自愈:服务实例本身也是一个插件,拥有独立的加载与销毁生命周期。
步骤一:编写自定义 Service 类与声明合并
创建 custom-plugins/memory-store/src/index.ts 文件:
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 中声明依赖名称:
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) 挂载,即可在不连接外部真实数据库的情况下完成纯内存单元测试。