docs: upload code files and config
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
# Step 09 — Phase 4.1: AI Provider 基础设施
|
||||
|
||||
**依赖**: Step 01(配置模块)
|
||||
**参考设计**: §5.2
|
||||
|
||||
## 目标
|
||||
|
||||
实现 AI Provider 策略模式基础设施:抽象基类、2 个 Provider 实现、工厂函数。
|
||||
|
||||
## 新建文件
|
||||
|
||||
| # | 文件 | 说明 |
|
||||
|---|------|------|
|
||||
| 1 | `src/ai/providers/base.ts` | `AIProvider` 抽象基类 + `ChatOptions` |
|
||||
| 2 | `src/ai/providers/deepseek.ts` | `DeepSeekProvider` |
|
||||
| 3 | `src/ai/providers/openai.ts` | `OpenAIProvider` |
|
||||
| 4 | `src/ai/factory.ts` | `createProvider()` 工厂 |
|
||||
|
||||
---
|
||||
|
||||
## 1. `src/ai/providers/base.ts`
|
||||
|
||||
```typescript
|
||||
export interface ChatOptions {
|
||||
model: string;
|
||||
temperature: number;
|
||||
timeoutMs: number;
|
||||
}
|
||||
|
||||
export abstract class AIProvider {
|
||||
abstract id: string;
|
||||
abstract name: string;
|
||||
|
||||
constructor(
|
||||
protected apiKey: string,
|
||||
protected endpoint: string
|
||||
) {}
|
||||
|
||||
abstract chat(
|
||||
systemPrompt: string,
|
||||
userPrompt: string,
|
||||
options: ChatOptions
|
||||
): Promise<string>;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. `src/ai/providers/deepseek.ts`
|
||||
|
||||
```typescript
|
||||
import { AIProvider, ChatOptions } from './base';
|
||||
|
||||
export class DeepSeekProvider extends AIProvider {
|
||||
id = 'deepseek';
|
||||
name = 'DeepSeek';
|
||||
|
||||
async chat(systemPrompt: string, userPrompt: string, options: ChatOptions): Promise<string> {
|
||||
const url = `${this.endpoint}/chat/completions`;
|
||||
|
||||
const body = JSON.stringify({
|
||||
model: options.model,
|
||||
temperature: options.temperature,
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: userPrompt },
|
||||
],
|
||||
});
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), options.timeoutMs);
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${this.apiKey}`,
|
||||
},
|
||||
body,
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
if (response.status === 401) {
|
||||
throw new Error('API Key 无效,请重新设置');
|
||||
}
|
||||
throw new Error(`API 请求失败 (${response.status}): ${errorText}`);
|
||||
}
|
||||
|
||||
const data = await response.json() as {
|
||||
choices: Array<{ message: { content: string } }>;
|
||||
};
|
||||
return data.choices[0]?.message?.content ?? '';
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. `src/ai/providers/openai.ts`
|
||||
|
||||
```typescript
|
||||
import { AIProvider, ChatOptions } from './base';
|
||||
|
||||
export class OpenAIProvider extends AIProvider {
|
||||
id = 'openai';
|
||||
name = 'OpenAI';
|
||||
|
||||
async chat(systemPrompt: string, userPrompt: string, options: ChatOptions): Promise<string> {
|
||||
const url = `${this.endpoint}/chat/completions`;
|
||||
|
||||
const body = JSON.stringify({
|
||||
model: options.model,
|
||||
temperature: options.temperature,
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: userPrompt },
|
||||
],
|
||||
});
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), options.timeoutMs);
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${this.apiKey}`,
|
||||
},
|
||||
body,
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
if (response.status === 401) {
|
||||
throw new Error('API Key 无效,请重新设置');
|
||||
}
|
||||
throw new Error(`API 请求失败 (${response.status}): ${errorText}`);
|
||||
}
|
||||
|
||||
const data = await response.json() as {
|
||||
choices: Array<{ message: { content: string } }>;
|
||||
};
|
||||
return data.choices[0]?.message?.content ?? '';
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. `src/ai/factory.ts`
|
||||
|
||||
```typescript
|
||||
import { AIProvider } from './providers/base';
|
||||
import { DeepSeekProvider } from './providers/deepseek';
|
||||
import { OpenAIProvider } from './providers/openai';
|
||||
|
||||
type ProviderConstructor = new (apiKey: string, endpoint: string) => AIProvider;
|
||||
|
||||
const registry: Record<string, ProviderConstructor> = {
|
||||
deepseek: DeepSeekProvider,
|
||||
openai: OpenAIProvider,
|
||||
};
|
||||
|
||||
export function createProvider(providerId: string, apiKey: string, endpoint: string): AIProvider {
|
||||
const Cls = registry[providerId];
|
||||
if (!Cls) {
|
||||
throw new Error(`未知的 Provider: ${providerId}`);
|
||||
}
|
||||
return new Cls(apiKey, endpoint);
|
||||
}
|
||||
|
||||
export function getProviderIds(): string[] {
|
||||
return Object.keys(registry);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 关键逻辑
|
||||
|
||||
- Provider 统一实现 `chat(systemPrompt, userPrompt, options): Promise<string>`
|
||||
- 支持 AbortController 超时控制
|
||||
- HTTP 401 → 抛出 "API Key 无效" 错误
|
||||
- 工厂函数通过注册表字符串查找,便于添加新的 Provider
|
||||
- 两个 Provider 实现几乎相同(都是 openai 兼容 API),可考虑后续合并
|
||||
|
||||
---
|
||||
|
||||
## 验收
|
||||
|
||||
- [ ] 4 个文件创建完成
|
||||
- [ ] `npm run compile` 通过
|
||||
- [ ] `npm run lint` 通过
|
||||
Reference in New Issue
Block a user