feat: 重构对话功能
This commit is contained in:
@@ -1,4 +1,12 @@
|
||||
export interface ChatOptions {
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export interface GatewayChatMessage {
|
||||
role: 'system' | 'user' | 'assistant' | 'tool';
|
||||
content: string;
|
||||
}
|
||||
|
||||
export abstract class BaseProvider {
|
||||
abstract chat(messages: DialogueMessageProps[], modelName: string): Promise<AsyncIterable<UniversalChunk>>
|
||||
abstract chat(messages: GatewayChatMessage[], modelName: string, options?: ChatOptions): Promise<AsyncIterable<UniversalChunk>>
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { BaseProvider } from "./BaseProvider";
|
||||
import { BaseProvider, ChatOptions, GatewayChatMessage } from "./BaseProvider";
|
||||
|
||||
import OpenAI from "openai";
|
||||
import logManager from "@electron/service/logger"
|
||||
|
||||
|
||||
function _transformChunk(chunk: OpenAI.Chat.Completions.ChatCompletionChunk): UniversalChunk {
|
||||
const choice = chunk.choices[0];
|
||||
return {
|
||||
isEnd: choice?.finish_reason === 'stop',
|
||||
isEnd: choice?.finish_reason != null,
|
||||
result: choice?.delta?.content ?? '',
|
||||
}
|
||||
}
|
||||
@@ -15,12 +14,12 @@ function _transformChunk(chunk: OpenAI.Chat.Completions.ChatCompletionChunk): Un
|
||||
export class OpenAIProvider extends BaseProvider {
|
||||
private client: OpenAI;
|
||||
|
||||
constructor(apiKey: string, baseURL: string) {
|
||||
constructor(apiKey: string, baseURL: string, headers?: Record<string, string>) {
|
||||
super();
|
||||
this.client = new OpenAI({ apiKey, baseURL });
|
||||
this.client = new OpenAI({ apiKey, baseURL, defaultHeaders: headers });
|
||||
}
|
||||
|
||||
async chat(messages: DialogueMessageProps[], model: string): Promise<AsyncIterable<UniversalChunk>> {
|
||||
async chat(messages: GatewayChatMessage[], model: string, options?: ChatOptions): Promise<AsyncIterable<UniversalChunk>> {
|
||||
const startTime = Date.now();
|
||||
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
@@ -34,17 +33,25 @@ export class OpenAIProvider extends BaseProvider {
|
||||
try {
|
||||
const chunks = await this.client.chat.completions.create({
|
||||
model,
|
||||
messages,
|
||||
messages: messages as any,
|
||||
stream: true,
|
||||
}, {
|
||||
signal: options?.signal,
|
||||
});
|
||||
|
||||
const responseTime = Date.now() - startTime;
|
||||
logManager.logApiResponse('chat.completions.create', { success: true }, 200, responseTime);
|
||||
// return chunk;
|
||||
return {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
for await (const chunk of chunks) {
|
||||
yield _transformChunk(chunk);
|
||||
try {
|
||||
for await (const chunk of chunks) {
|
||||
if (options?.signal?.aborted) break;
|
||||
yield _transformChunk(chunk);
|
||||
}
|
||||
const responseTime = Date.now() - startTime;
|
||||
logManager.logApiResponse('chat.completions.create', { success: true }, 200, responseTime);
|
||||
} catch (error) {
|
||||
const responseTime = Date.now() - startTime;
|
||||
logManager.logApiResponse('chat.completions.create', { error: error instanceof Error ? error.message : String(error) }, 500, responseTime);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,123 +1,34 @@
|
||||
import type { Provider } from "@lib/types"
|
||||
import { OpenAIProvider } from "./OpenAIProvider"
|
||||
import { parseOpenAISetting } from '@lib/utils'
|
||||
import { decode } from 'js-base64'
|
||||
import { configManager } from '@electron/service/config-service'
|
||||
import { logManager } from '@electron/service/logger'
|
||||
import { CONFIG_KEYS } from "@lib/constants"
|
||||
import { BaseProvider } from "./BaseProvider";
|
||||
import { OpenAIProvider } from "./OpenAIProvider";
|
||||
import { providerApiService } from '@electron/service/provider-api-service';
|
||||
import { getProviderTypeInfo } from '@lib/providers';
|
||||
|
||||
const providers = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'bigmodel',
|
||||
title: '智谱AI',
|
||||
models: ['glm-4.5-flash'],
|
||||
openAISetting: {
|
||||
baseURL: 'https://open.bigmodel.cn/api/paas/v4',
|
||||
apiKey: process.env.BIGMODEL_API_KEY || '',
|
||||
},
|
||||
createdAt: new Date().getTime(),
|
||||
updatedAt: new Date().getTime()
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'deepseek',
|
||||
title: '深度求索 (DeepSeek)',
|
||||
models: ['deepseek-chat'],
|
||||
openAISetting: {
|
||||
baseURL: 'https://api.deepseek.com/v1',
|
||||
apiKey: process.env.DEEPSEEK_API_KEY || '',
|
||||
},
|
||||
createdAt: new Date().getTime(),
|
||||
updatedAt: new Date().getTime()
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'siliconflow',
|
||||
title: '硅基流动',
|
||||
models: ['Qwen/Qwen3-8B', 'deepseek-ai/DeepSeek-R1-0528-Qwen3-8B'],
|
||||
openAISetting: {
|
||||
baseURL: 'https://api.siliconflow.cn/v1',
|
||||
apiKey: process.env.SILICONFLOW_API_KEY || '',
|
||||
},
|
||||
createdAt: new Date().getTime(),
|
||||
updatedAt: new Date().getTime()
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'qianfan',
|
||||
title: '百度千帆',
|
||||
models: ['ernie-speed-128k', 'ernie-4.0-8k', 'ernie-3.5-8k'],
|
||||
openAISetting: {
|
||||
baseURL: 'https://qianfan.baidubce.com/v2',
|
||||
apiKey: process.env.QIANFAN_API_KEY || '',
|
||||
},
|
||||
createdAt: new Date().getTime(),
|
||||
updatedAt: new Date().getTime()
|
||||
},
|
||||
];
|
||||
|
||||
interface _Provider extends Omit<Provider, 'openAISetting'> {
|
||||
openAISetting?: {
|
||||
apiKey: string,
|
||||
baseURL: string,
|
||||
};
|
||||
}
|
||||
|
||||
const _parseProvider = () => {
|
||||
let result: Provider[] = [];
|
||||
let isBase64Parsed = false;
|
||||
const providerConfig = configManager.get(CONFIG_KEYS.PROVIDER);
|
||||
|
||||
const mapCallback = (provider: Provider) => ({
|
||||
...provider,
|
||||
openAISetting: typeof provider.openAISetting === 'string'
|
||||
? parseOpenAISetting(provider.openAISetting ?? '')
|
||||
: provider.openAISetting,
|
||||
})
|
||||
|
||||
try {
|
||||
result = JSON.parse(decode(providerConfig)) as Provider[];
|
||||
isBase64Parsed = true;
|
||||
} catch (error) {
|
||||
logManager.error(`parse base64 provider failed: ${error}`);
|
||||
export function createProvider(accountId: string): BaseProvider {
|
||||
const account = providerApiService.getAccounts().find((a) => a.id === accountId);
|
||||
if (!account) {
|
||||
throw new Error(`Provider account ${accountId} not found`);
|
||||
}
|
||||
|
||||
if (!isBase64Parsed) try {
|
||||
result = JSON.parse(providerConfig) as Provider[]
|
||||
} catch (error) {
|
||||
logManager.error(`parse provider failed: ${error}`);
|
||||
const apiKeyResult = providerApiService.getApiKey(accountId);
|
||||
const apiKey = apiKeyResult.apiKey;
|
||||
if (!apiKey) {
|
||||
throw new Error(`API key for account ${accountId} not found`);
|
||||
}
|
||||
|
||||
if (!result.length) return;
|
||||
const baseURL = account.baseUrl || getProviderTypeInfo(account.vendorId)?.defaultBaseUrl;
|
||||
if (!baseURL) {
|
||||
throw new Error(`Base URL for account ${accountId} not found`);
|
||||
}
|
||||
|
||||
return result.map(mapCallback) as _Provider[]
|
||||
}
|
||||
|
||||
const getProviderConfig = () => {
|
||||
try {
|
||||
return _parseProvider();
|
||||
} catch (error) {
|
||||
logManager.error(`get provider config failed: ${error}`);
|
||||
return null;
|
||||
switch (account.apiProtocol) {
|
||||
case 'anthropic-messages':
|
||||
throw new Error('Anthropic provider not yet implemented');
|
||||
case 'openai-completions':
|
||||
case 'openai-responses':
|
||||
default:
|
||||
return new OpenAIProvider(apiKey, baseURL, account.headers);
|
||||
}
|
||||
}
|
||||
|
||||
export function createProvider(name: string) {
|
||||
const providers = getProviderConfig();
|
||||
|
||||
if (!providers) {
|
||||
throw new Error('provider config not found');
|
||||
}
|
||||
|
||||
for (const provider of providers) {
|
||||
if (provider.name === name) {
|
||||
if (!provider.openAISetting?.apiKey || !provider.openAISetting?.baseURL) {
|
||||
throw new Error('apiKey or baseURL not found');
|
||||
}
|
||||
// TODO: visible
|
||||
|
||||
return new OpenAIProvider(provider.openAISetting.apiKey, provider.openAISetting.baseURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
export * from './BaseProvider';
|
||||
export { OpenAIProvider };
|
||||
|
||||
Reference in New Issue
Block a user