mirror of
https://github.com/linshenkx/prompt-optimizer.git
synced 2026-05-07 22:18:23 +08:00
重大架构升级: - 引入Adapter模式,实现Provider解耦 - 新增7个Provider适配器(OpenAI、Gemini、Anthropic等) - 精简LLMService从2000+行到300+行 - 引入TextModelConfig新类型系统,支持向后兼容 - 完善类型定义和测试覆盖 架构改进: - 抽象基类AbstractTextProviderAdapter提供模板方法 - TextAdapterRegistry统一管理所有适配器 - 支持动态模型获取(OpenAI兼容API) - 提供配置转换器实现平滑升级 新增文件: - packages/core/src/services/llm/adapters/ (8个文件) - packages/core/src/services/model/converter.ts - packages/core/src/services/model/text-model-defaults.ts - docs/architecture/llm-refactor.md - 9个新增测试文件 修改文件: - packages/core/src/services/llm/service.ts - packages/core/src/services/model/types.ts - packages/core/src/index.ts - 31个测试文件更新 - UI组件和composables适配新类型 BREAKING CHANGE: 模型配置结构从ModelConfig升级为TextModelConfig (已提供converter.ts确保向后兼容和自动转换) 测试结果: ✅ TypeScript类型检查通过 (core + ui) ✅ 563个测试通过,90个跳过 ✅ Core包构建成功
15 KiB
15 KiB
LLM服务架构重构文档
概述
本文档说明Prompt Optimizer的LLM服务架构重构,从单体Service转变为Provider-Adapter-Registry三层架构,实现了更高的模块化、可扩展性和可维护性。
重构目标
- Provider抽象层: 统一各LLM提供商的元数据定义(能力、参数等)
- Adapter统一接口: 规范不同SDK的调用方式,隔离SDK细节
- 配置自包含: 配置包含完整元数据,不依赖运行时查找
- 向后兼容: 传统配置自动转换,保持API签名不变
架构设计
三层架构图
graph TB
subgraph "应用层"
UI[UI组件]
LLMService[LLMService]
end
subgraph "配置层"
TextModelConfig[TextModelConfig<br/>自包含配置]
ModelManager[ModelManager<br/>配置管理]
end
subgraph "Provider层"
TextProvider[TextProvider<br/>提供商元数据]
TextModel[TextModel<br/>模型元数据]
end
subgraph "Adapter层"
Registry[TextAdapterRegistry<br/>适配器注册表]
OpenAIAdapter[OpenAIAdapter]
GeminiAdapter[GeminiAdapter]
AnthropicAdapter[AnthropicAdapter]
end
subgraph "SDK层"
OpenAISDK[OpenAI SDK]
GeminiSDK[Google Generative AI]
AnthropicSDK[Anthropic SDK]
end
UI --> LLMService
LLMService --> ModelManager
LLMService --> Registry
ModelManager --> TextModelConfig
TextModelConfig --> TextProvider
TextModelConfig --> TextModel
Registry --> OpenAIAdapter
Registry --> GeminiAdapter
Registry --> AnthropicAdapter
OpenAIAdapter --> OpenAISDK
GeminiAdapter --> GeminiSDK
AnthropicAdapter --> AnthropicSDK
核心组件
1. TextProvider (Provider元数据)
interface TextProvider {
id: string; // 'openai' | 'gemini' | 'anthropic'
name: string; // 'OpenAI' | 'Google Gemini' | 'Anthropic'
description: string;
defaultBaseURL?: string;
connectionSchema: ConnectionSchema; // 连接参数定义
}
职责: 定义Provider的基本信息和连接要求
2. TextModel (Model元数据)
interface TextModel {
id: string; // 'gpt-4o-mini' | 'gemini-2.0-flash-exp'
name: string;
description?: string;
providerId: string; // 归属Provider
capabilities: {
supportsStreaming: boolean;
supportsTools: boolean;
supportsReasoning: boolean;
maxContextLength: number;
};
parameterDefinitions: ParameterDefinition[];
defaultParameterValues: Record<string, any>;
}
职责: 定义Model的能力和参数
3. TextModelConfig (自包含配置)
interface TextModelConfig {
id: string;
name: string;
enabled: boolean;
providerMeta: TextProvider; // 嵌入Provider元数据
modelMeta: TextModel; // 嵌入Model元数据
connectionConfig: ConnectionConfig; // 连接配置(apiKey, baseURL)
paramOverrides: Record<string, any>; // 参数覆盖
}
特点:
- 自包含: 包含运行时所需的全部信息
- 类型安全: 完整TypeScript类型定义
- 元数据嵌入: 无需运行时查找Provider/Model
4. ITextProviderAdapter (Adapter接口)
interface ITextProviderAdapter {
getProvider(): TextProvider;
getModels(): TextModel[];
getModelsAsync?(config: TextModelConfig): Promise<TextModel[]>;
buildDefaultModel(modelId: string): TextModel;
sendMessage(messages: Message[], config: TextModelConfig): Promise<LLMResponse>;
sendMessageStream(messages: Message[], config: TextModelConfig, handlers: StreamHandlers): Promise<void>;
sendMessageStreamWithTools?(messages: Message[], config: TextModelConfig, tools: ToolDefinition[], handlers: StreamHandlers): Promise<void>;
}
职责:
- 提供Provider和Model元数据
- 封装SDK调用逻辑
- 处理消息格式转换
- 保留错误堆栈
5. TextAdapterRegistry (注册表)
class TextAdapterRegistry implements ITextAdapterRegistry {
private adapters: Map<string, ITextProviderAdapter>;
private staticModelsCache: Map<string, TextModel[]>;
getAdapter(providerId: string): ITextProviderAdapter;
getAllProviders(): TextProvider[];
getStaticModels(providerId: string): TextModel[];
getDynamicModels(providerId: string, config: TextModelConfig): Promise<TextModel[]>;
getModels(providerId: string, config?: TextModelConfig): Promise<TextModel[]>;
}
职责:
- 注册和管理所有Adapter实例
- 提供统一的Adapter查找接口
- 缓存静态模型列表
- 支持动态模型获取(OpenAI)
配置迁移流程
传统配置 → 新配置
flowchart LR
A[传统ModelConfig] --> B{检测格式}
B -->|Legacy| C[convertLegacyToTextModelConfigWithRegistry]
B -->|New| D[直接使用]
C --> E[获取Registry]
E --> F[getAdapter<br/>providerId]
F --> G[获取Provider元数据]
F --> H[获取Model元数据]
H -->|找到| I[使用静态模型]
H -->|未找到| J[buildDefaultModel]
I --> K[构建TextModelConfig]
J --> K
K --> L[保存到Storage]
D --> L
转换逻辑 (converter.ts)
export async function convertLegacyToTextModelConfigWithRegistry(
key: string,
legacy: ModelConfig,
registry: ITextAdapterRegistry
): Promise<TextModelConfig> {
// 1. Provider映射
const providerId = mapProviderToAdapterId(legacy.provider);
// 2. 获取Adapter
const adapter = registry.getAdapter(providerId);
// 3. 获取Provider元数据
const providerMeta = adapter.getProvider();
// 4. 获取Model元数据
let modelMeta = adapter.getModels().find(m => m.id === legacy.defaultModel);
if (!modelMeta) {
modelMeta = adapter.buildDefaultModel(legacy.defaultModel);
}
// 5. 构建TextModelConfig
return {
id: key,
name: legacy.name,
enabled: legacy.enabled,
providerMeta,
modelMeta,
connectionConfig: {
apiKey: legacy.apiKey,
baseURL: legacy.baseURL
},
paramOverrides: legacy.llmParams || {}
};
}
Provider映射规则:
gemini→gemini(GeminiAdapter)anthropic→anthropic(AnthropicAdapter)openai|deepseek|zhipu|siliconflow|custom→openai(OpenAIAdapter)
自动转换时机
在ModelManager.init()初始化时:
async init(): Promise<void> {
const existingModels = await this.getModelsFromStorage();
for (const [key, existingModel] of Object.entries(existingModels)) {
if (isLegacyConfig(existingModel)) {
try {
// 优先使用Registry转换
const registry = await this.getRegistry();
const convertedModel = await convertLegacyToTextModelConfigWithRegistry(
key,
existingModel,
registry
);
updatedModels[key] = convertedModel;
hasUpdates = true;
} catch (error) {
// Fallback到硬编码转换
const convertedModel = convertLegacyToTextModelConfig(key, existingModel);
updatedModels[key] = convertedModel;
}
}
}
// 保存转换后的配置
if (hasUpdates) {
await this.saveModelsToStorage(updatedModels);
}
}
Service层集成
LLMService使用Registry
export class LLMService implements ILLMService {
constructor(
private modelManager: ModelManager,
private registry: ITextAdapterRegistry
) {}
async sendMessage(messages: Message[], provider: string): Promise<string> {
// 1. 获取配置
const config = await this.modelManager.getModel(provider) as TextModelConfig;
// 2. 获取Adapter
const adapter = this.registry.getAdapter(config.providerMeta.id);
// 3. 调用Adapter
const response = await adapter.sendMessage(messages, config);
return response.content;
}
}
关键特性:
- 通过
config.providerMeta.id获取正确的Adapter - 无需switch/case Provider类型
- SDK调用完全由Adapter封装
- 错误堆栈保留
工厂函数
export function createLLMService(modelManager: ModelManager): ILLMService {
if (isRunningInElectron()) {
return new ElectronLLMProxy();
}
// 创建Registry实例
const registry = new TextAdapterRegistry();
// 注入Registry到Service
return new LLMService(modelManager, registry);
}
开发者指南
如何添加新Provider
1. 创建Adapter实现
// packages/core/src/services/llm/adapters/example-adapter.ts
import { AbstractTextProviderAdapter } from './abstract-adapter';
import type { TextProvider, TextModel, TextModelConfig, LLMResponse, Message, StreamHandlers } from '../types';
export class ExampleAdapter extends AbstractTextProviderAdapter {
getProvider(): TextProvider {
return {
id: 'example',
name: 'Example Provider',
description: 'Example LLM Provider',
defaultBaseURL: 'https://api.example.com/v1',
connectionSchema: {
required: ['apiKey'],
optional: ['baseURL'],
fieldTypes: {
apiKey: 'string',
baseURL: 'url'
}
}
};
}
getModels(): TextModel[] {
return [
{
id: 'example-model-v1',
name: 'Example Model V1',
description: 'Fast and efficient model',
providerId: 'example',
capabilities: {
supportsStreaming: true,
supportsTools: false,
supportsReasoning: false,
maxContextLength: 8000
},
parameterDefinitions: [
{
name: 'temperature',
type: 'number',
description: 'Sampling temperature',
min: 0,
max: 2,
default: 0.7
}
],
defaultParameterValues: {
temperature: 0.7
}
}
];
}
protected async doSendMessage(
messages: Message[],
config: TextModelConfig
): Promise<LLMResponse> {
// 实现SDK调用逻辑
const client = new ExampleSDK({
apiKey: config.connectionConfig.apiKey,
baseURL: config.connectionConfig.baseURL || this.getProvider().defaultBaseURL
});
try {
const response = await client.chat.completions.create({
model: config.modelMeta.id,
messages: messages,
...config.paramOverrides
});
return {
content: response.choices[0].message.content || '',
reasoning: undefined,
metadata: {
model: config.modelMeta.id,
usage: response.usage
}
};
} catch (error: any) {
// 保留原始错误堆栈
throw error;
}
}
protected async doSendMessageStream(
messages: Message[],
config: TextModelConfig,
handlers: StreamHandlers
): Promise<void> {
// 实现流式调用逻辑
const client = new ExampleSDK({
apiKey: config.connectionConfig.apiKey,
baseURL: config.connectionConfig.baseURL
});
try {
const stream = await client.chat.completions.create({
model: config.modelMeta.id,
messages: messages,
stream: true,
...config.paramOverrides
});
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content || '';
if (content && handlers.onToken) {
handlers.onToken(content);
}
}
if (handlers.onComplete) {
handlers.onComplete({ content: '', metadata: {} });
}
} catch (error: any) {
if (handlers.onError) {
handlers.onError(error);
}
throw error;
}
}
}
2. 注册到Registry
// packages/core/src/services/llm/adapters/registry.ts
import { ExampleAdapter } from './example-adapter';
export class TextAdapterRegistry implements ITextAdapterRegistry {
private adapters: Map<string, ITextProviderAdapter>;
constructor() {
this.adapters = new Map();
this.staticModelsCache = new Map();
// 注册所有Adapter
this.adapters.set('openai', new OpenAIAdapter());
this.adapters.set('gemini', new GeminiAdapter());
this.adapters.set('anthropic', new AnthropicAdapter());
this.adapters.set('example', new ExampleAdapter()); // 新增
}
}
3. 更新配置转换逻辑 (如需要)
// packages/core/src/services/model/converter.ts
function mapProviderToAdapterId(provider: string): string {
switch (provider) {
case 'gemini':
return 'gemini';
case 'anthropic':
return 'anthropic';
case 'example': // 新增
return 'example';
case 'openai':
case 'deepseek':
case 'zhipu':
case 'siliconflow':
case 'custom':
default:
return 'openai';
}
}
常见问题 (FAQ)
Q1: 为什么要重构为Adapter模式?
A:
- 解耦SDK: 不同SDK的调用逻辑分离,易于维护和测试
- 统一接口: 所有Provider遵循相同接口,简化Service层逻辑
- 扩展性: 添加新Provider只需实现Adapter,无需修改Service
- 可测试性: Adapter可独立Mock和测试
Q2: 传统配置如何迁移?
A: 自动迁移,无需手动操作:
- ModelManager初始化时检测传统配置
- 自动调用
convertLegacyToTextModelConfigWithRegistry() - 转换后保存到Storage
- 下次加载直接识别为新格式
Q3: TextModelConfig为什么要嵌入元数据?
A:
- 自包含: 运行时无需查找Provider/Model元数据
- 类型安全: 完整类型定义,IDE智能提示
- 性能: 避免运行时查找,直接访问
- 可追溯: 配置包含完整历史信息
Q4: 如何处理SDK错误?
A: Adapter必须保留原始错误堆栈:
try {
const response = await sdk.call();
} catch (error: any) {
// 直接throw,不要包装,保留原始堆栈
throw error;
}
Q5: 如何支持动态模型获取?
A: 实现getModelsAsync()方法:
async getModelsAsync(config: TextModelConfig): Promise<TextModel[]> {
const client = new OpenAI({
apiKey: config.connectionConfig.apiKey,
baseURL: config.connectionConfig.baseURL
});
const response = await client.models.list();
return response.data.map(model => ({
id: model.id,
name: model.id,
description: '',
providerId: 'openai',
capabilities: { /*...*/ },
parameterDefinitions: [],
defaultParameterValues: {}
}));
}
Registry会自动fallback到静态模型。
Q6: Provider映射规则是什么?
A:
- Gemini:
gemini→ GeminiAdapter (Google SDK) - Anthropic:
anthropic→ AnthropicAdapter (Anthropic SDK) - OpenAI及兼容:
openai→ OpenAIAdapterdeepseek→ OpenAIAdapter (OpenAI兼容)zhipu→ OpenAIAdapter (OpenAI兼容)siliconflow→ OpenAIAdapter (OpenAI兼容)custom→ OpenAIAdapter (OpenAI兼容)
测试策略
单元测试
- Adapter测试: Mock SDK,测试数据转换和错误处理
- Registry测试: 测试Adapter注册、查找、缓存逻辑
- 转换测试: 测试传统配置 → 新配置映射正确性
集成测试
- Adapter集成: 使用真实API密钥测试SDK调用
- 迁移集成: 测试配置自动转换和持久化
- 回归测试: 验证API签名和行为不变