autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
194 lines (193 loc) • 8.54 kB
JavaScript
/**
* AiProviderManager — 统一 AI 提供商管理器(切面层)
*
* 设计目标:
* 1. 唯一权威: 当前 AI Provider 的唯一管理入口,所有读取/切换集中在此
* 2. AOP 切面: Token 追踪回调随 Provider 切换自动重新挂载,无需外部干预
* 3. 热切换: switchProvider() 一次调用 → Token AOP + Embedding fallback + DI 级联清理 + 事件通知
* 4. 模式查询: isMock / isReady 集中管理,消除散落的 name === 'mock' 判断
* 5. 事件驱动: 注册监听器,切换时自动回调(Realtime 广播、SearchEngine 重建等)
*
* 集成方式:
* - 由 AiModule.initialize() 创建并注入 DI 容器
* - ServiceContainer.reloadAiProvider() 委托 manager.switchProvider()
* - 消费者通过 container.get('aiProviderManager') 获取
* - DI 数据管道: switchProvider() 通过回调同步 singletons 中的 provider 引用
*/
import Logger from '#infra/logging/Logger.js';
// ── Manager ────────────────────────────────────────────
export class AiProviderManager {
#provider;
#embedProvider = null;
#tokenRecorder = null;
#listeners = new Set();
#logger = Logger.getInstance();
/** DI 容器注入: 清除 AI 依赖 singleton 的回调 */
#clearDependents = null;
/** DI 容器注入: Embedding fallback 初始化器 */
#embedFallbackInit = null;
/** DI 数据管道: 切换时同步 singletons 中的 provider 引用(供 DI 工厂函数读取) */
#syncToDi = null;
constructor(initialProvider) {
this.#provider = initialProvider;
this.#wireTokenTracking();
}
// ═══════════════════════════════════════════════════════
// 读取接口
// ═══════════════════════════════════════════════════════
/** 当前 AI Provider (只读) */
get provider() {
return this.#provider;
}
/** 当前 Embedding Provider (优先 fallback,回退到主 provider) */
get embedProvider() {
return this.#embedProvider ?? this.#provider;
}
/** 原始 Embedding fallback (可能为 null) */
get rawEmbedProvider() {
return this.#embedProvider;
}
/** 是否处于 Mock 模式 */
get isMock() {
return this.#provider.name === 'mock';
}
/** provider 是否可用于 AI 操作(非 mock) */
get isReady() {
return !!this.#provider && !this.isMock;
}
/** 当前 provider 名称 */
get name() {
return this.#provider.name;
}
/** 当前模型 */
get model() {
return this.#provider.model;
}
/** 结构化信息快照 */
get info() {
return {
name: this.#provider.name,
model: this.#provider.model,
isMock: this.isMock,
supportsEmbedding: typeof this.#provider.supportsEmbedding === 'function' &&
this.#provider.supportsEmbedding(),
};
}
// ═══════════════════════════════════════════════════════
// 热切换 — 唯一的全局切换入口
// ═══════════════════════════════════════════════════════
/**
* 切换 AI Provider — 原子操作
*
* 自动处理:
* 1. Token 追踪 AOP 重新挂载
* 2. Embedding fallback 重建
* 3. DI 数据管道同步(singletons.aiProvider)
* 4. DI 容器中的 AI 依赖 singleton 级联清除
* 5. 监听器回调通知
*/
switchProvider(newProvider) {
const prev = this.info;
// 1. 替换核心引用
this.#provider = newProvider;
// 2. AOP: 重新挂载 Token 追踪
this.#wireTokenTracking();
// 3. Embedding fallback 重建
this.#embedProvider = null;
if (this.#embedFallbackInit) {
this.#embedProvider = this.#embedFallbackInit(newProvider);
}
// 4. DI 数据管道同步
this.#syncToDi?.(this.#provider, this.#embedProvider);
// 5. 清除 DI 容器中的依赖 singleton
const clearedSingletons = this.#clearDependents?.() ?? [];
const result = {
previous: prev,
current: this.info,
clearedSingletons,
};
// 6. 通知监听器
for (const fn of this.#listeners) {
try {
fn(result);
}
catch {
/* listener should not break switching */
}
}
this.#logger.info('[AiProviderManager] Provider switched', {
from: `${prev.name}/${prev.model}`,
to: `${result.current.name}/${result.current.model}`,
mock: result.current.isMock,
cleared: clearedSingletons,
});
return result;
}
// ═══════════════════════════════════════════════════════
// Embedding 管理
// ═══════════════════════════════════════════════════════
/** 手动设置 Embedding fallback provider */
setEmbedProvider(ep) {
this.#embedProvider = ep;
}
// ═══════════════════════════════════════════════════════
// AOP: Token 追踪
// ═══════════════════════════════════════════════════════
/** 注入 TokenRecorder (延迟绑定,避免循环依赖) */
setTokenRecorder(recorder) {
this.#tokenRecorder = recorder;
this.#wireTokenTracking();
}
/**
* 在当前 provider 上安装 _onTokenUsage 回调
* 每次 provider.chat() / chatWithTools() 等调用后自动触发
*/
#wireTokenTracking() {
const p = this.#provider;
if (!p || typeof p !== 'object') {
return;
}
p._onTokenUsage = (usage) => {
if (!this.#tokenRecorder) {
return;
}
try {
this.#tokenRecorder.record({
source: usage.source || 'provider',
provider: p.name ?? undefined,
model: p.model ?? undefined,
inputTokens: usage.inputTokens || 0,
outputTokens: usage.outputTokens || 0,
});
}
catch {
/* token tracking never breaks execution */
}
};
}
// ═══════════════════════════════════════════════════════
// 事件
// ═══════════════════════════════════════════════════════
/** 注册切换监听器,返回取消注册函数 */
onSwitch(fn) {
this.#listeners.add(fn);
return () => {
this.#listeners.delete(fn);
};
}
// ═══════════════════════════════════════════════════════
// DI 绑定 (仅 ServiceContainer / AiModule 调用)
// ═══════════════════════════════════════════════════════
/** 注入 DI 容器的级联清理回调 */
_bindDependentClearer(fn) {
this.#clearDependents = fn;
}
/** 注入 Embedding Fallback 初始化器 */
_bindEmbedFallbackInit(fn) {
this.#embedFallbackInit = fn;
}
/** 注入 DI 数据管道同步回调(切换时更新 singletons 中的 provider 引用) */
_bindDiSync(fn) {
this.#syncToDi = fn;
}
}