Files
prompt-optimizer/docs/architecture/llm-refactor.md
linshen 5de5614664 refactor(core): 重构LLM服务为Adapter模式架构
重大架构升级:
- 引入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包构建成功
2025-10-04 01:01:08 +08:00

585 lines
15 KiB
Markdown

# LLM服务架构重构文档
## 概述
本文档说明Prompt Optimizer的LLM服务架构重构,从单体Service转变为**Provider-Adapter-Registry**三层架构,实现了更高的模块化、可扩展性和可维护性。
## 重构目标
1. **Provider抽象层**: 统一各LLM提供商的元数据定义(能力、参数等)
2. **Adapter统一接口**: 规范不同SDK的调用方式,隔离SDK细节
3. **配置自包含**: 配置包含完整元数据,不依赖运行时查找
4. **向后兼容**: 传统配置自动转换,保持API签名不变
## 架构设计
### 三层架构图
```mermaid
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元数据)
```typescript
interface TextProvider {
id: string; // 'openai' | 'gemini' | 'anthropic'
name: string; // 'OpenAI' | 'Google Gemini' | 'Anthropic'
description: string;
defaultBaseURL?: string;
connectionSchema: ConnectionSchema; // 连接参数定义
}
```
**职责**: 定义Provider的基本信息和连接要求
#### 2. TextModel (Model元数据)
```typescript
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 (自包含配置)
```typescript
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接口)
```typescript
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 (注册表)
```typescript
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)
## 配置迁移流程
### 传统配置 → 新配置
```mermaid
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)
```typescript
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()`初始化时:
```typescript
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
```typescript
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封装
- 错误堆栈保留
### 工厂函数
```typescript
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实现
```typescript
// 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
```typescript
// 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. 更新配置转换逻辑 (如需要)
```typescript
// 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**:
1. **解耦SDK**: 不同SDK的调用逻辑分离,易于维护和测试
2. **统一接口**: 所有Provider遵循相同接口,简化Service层逻辑
3. **扩展性**: 添加新Provider只需实现Adapter,无需修改Service
4. **可测试性**: Adapter可独立Mock和测试
### Q2: 传统配置如何迁移?
**A**: 自动迁移,无需手动操作:
1. ModelManager初始化时检测传统配置
2. 自动调用`convertLegacyToTextModelConfigWithRegistry()`
3. 转换后保存到Storage
4. 下次加载直接识别为新格式
### Q3: TextModelConfig为什么要嵌入元数据?
**A**:
1. **自包含**: 运行时无需查找Provider/Model元数据
2. **类型安全**: 完整类型定义,IDE智能提示
3. **性能**: 避免运行时查找,直接访问
4. **可追溯**: 配置包含完整历史信息
### Q4: 如何处理SDK错误?
**A**: Adapter必须保留原始错误堆栈:
```typescript
try {
const response = await sdk.call();
} catch (error: any) {
// 直接throw,不要包装,保留原始堆栈
throw error;
}
```
### Q5: 如何支持动态模型获取?
**A**: 实现`getModelsAsync()`方法:
```typescript
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` → OpenAIAdapter
- `deepseek` → OpenAIAdapter (OpenAI兼容)
- `zhipu` → OpenAIAdapter (OpenAI兼容)
- `siliconflow` → OpenAIAdapter (OpenAI兼容)
- `custom` → OpenAIAdapter (OpenAI兼容)
## 测试策略
### 单元测试
- **Adapter测试**: Mock SDK,测试数据转换和错误处理
- **Registry测试**: 测试Adapter注册、查找、缓存逻辑
- **转换测试**: 测试传统配置 → 新配置映射正确性
### 集成测试
- **Adapter集成**: 使用真实API密钥测试SDK调用
- **迁移集成**: 测试配置自动转换和持久化
- **回归测试**: 验证API签名和行为不变
## 相关链接
- [需求文档](../../.spec-workflow/specs/text-model-provider-refactor/requirements.md)
- [设计文档](../../.spec-workflow/specs/text-model-provider-refactor/design.md)
- [任务文档](../../.spec-workflow/specs/text-model-provider-refactor/tasks.md)
- [LLM Service源码](../../packages/core/src/services/llm/service.ts)
- [Adapter目录](../../packages/core/src/services/llm/adapters/)