Files
ftb-project-management/apps/server/src/modules/ai/providers/openai.provider.ts
2026-06-25 15:21:32 +08:00

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,
};
}
}