80 lines
2.3 KiB
TypeScript
80 lines
2.3 KiB
TypeScript
import OpenAI from 'openai';
|
|
import type { AiProvider, PingRequest, PingResponse, ToolCallRequest, ToolCallResponse } from './provider.interface';
|
|
|
|
/**
|
|
* OpenAI 格式提供商(兼容 OpenAI Chat Completions 风格的中转站)
|
|
*/
|
|
export class OpenAIProvider implements AiProvider {
|
|
readonly format = 'openai' as const;
|
|
private client: OpenAI;
|
|
|
|
constructor(apiKey: string, baseURL: string) {
|
|
this.client = new OpenAI({
|
|
apiKey,
|
|
baseURL,
|
|
});
|
|
}
|
|
|
|
async callTool(req: ToolCallRequest): Promise<ToolCallResponse> {
|
|
const response = await this.client.chat.completions.create({
|
|
model: req.model,
|
|
max_tokens: req.maxTokens,
|
|
messages: [
|
|
{ role: 'system', content: req.systemPrompt },
|
|
{ role: 'user', content: req.userPrompt },
|
|
],
|
|
tools: [
|
|
{
|
|
type: 'function',
|
|
function: {
|
|
name: req.tool.name,
|
|
description: req.tool.description,
|
|
parameters: req.tool.inputSchema,
|
|
},
|
|
},
|
|
],
|
|
tool_choice: req.forceTool
|
|
? { type: 'function', function: { name: req.tool.name } }
|
|
: 'auto',
|
|
});
|
|
|
|
const choice = response.choices[0];
|
|
if (!choice?.message?.tool_calls?.length) {
|
|
throw new Error('OpenAI 未通过 tool_calls 返回结果');
|
|
}
|
|
|
|
const tc = choice.message.tool_calls[0];
|
|
if (tc.type !== 'function') {
|
|
throw new Error('OpenAI 返回的 tool_call 类型非 function');
|
|
}
|
|
|
|
let parsed: Record<string, any>;
|
|
try {
|
|
parsed = JSON.parse(tc.function.arguments || '{}');
|
|
} catch (e) {
|
|
throw new Error(`OpenAI tool_call arguments 不是合法 JSON: ${(e as Error).message}`);
|
|
}
|
|
|
|
return {
|
|
toolName: tc.function.name,
|
|
toolInput: parsed,
|
|
inputTokens: response.usage?.prompt_tokens ?? 0,
|
|
outputTokens: response.usage?.completion_tokens ?? 0,
|
|
rawModel: response.model,
|
|
};
|
|
}
|
|
|
|
async ping(req: PingRequest): Promise<PingResponse> {
|
|
const resp = await this.client.chat.completions.create({
|
|
model: req.model,
|
|
max_tokens: 16,
|
|
messages: [{ role: 'user', content: 'ping' }],
|
|
});
|
|
return {
|
|
ok: true,
|
|
inputTokens: resp.usage?.prompt_tokens ?? 0,
|
|
outputTokens: resp.usage?.completion_tokens ?? 0,
|
|
};
|
|
}
|
|
}
|