learning_ai_common_plat/packages/llm/src/providers/openai.ts

82 lines
2.3 KiB
TypeScript

/**
* OpenAI direct LLM provider.
*
* Uses OpenAI REST API with Bearer token authentication.
* Reads config from OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL.
*/
import type { ChatCompletionRequest, ChatCompletionResponse, LLMProvider } from '../types.js';
export interface OpenAIConfig {
apiKey: string;
baseUrl?: string;
model?: string;
}
export class OpenAIProvider implements LLMProvider {
private config: OpenAIConfig;
constructor(config?: Partial<OpenAIConfig>) {
this.config = {
apiKey: config?.apiKey || process.env.OPENAI_API_KEY || '',
baseUrl: config?.baseUrl || process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1',
model: config?.model || process.env.OPENAI_MODEL || 'gpt-4o-mini',
};
}
isConfigured(): boolean {
return Boolean(this.config.apiKey);
}
async chatCompletion(req: ChatCompletionRequest): Promise<ChatCompletionResponse> {
if (!this.isConfigured()) {
throw new Error('OpenAI is not configured (missing OPENAI_API_KEY)');
}
const base = this.config.baseUrl!.replace(/\/+$/, '');
const url = `${base}/chat/completions`;
const body = {
model: req.model || this.config.model,
messages: req.messages,
temperature: req.temperature,
max_tokens: req.maxTokens,
top_p: req.topP,
stop: req.stop,
response_format: req.responseFormat,
};
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.config.apiKey}`,
},
body: JSON.stringify(body),
});
if (!response.ok) {
const text = await response.text();
throw new Error(`OpenAI error ${response.status}: ${text}`);
}
const data = (await response.json()) as {
choices: Array<{ message: { content: string }; finish_reason: string }>;
model: string;
usage: { prompt_tokens: number; completion_tokens: number; total_tokens: number };
};
return {
content: data.choices[0]?.message?.content ?? '',
model: data.model,
finishReason:
(data.choices[0]?.finish_reason as ChatCompletionResponse['finishReason']) ?? null,
usage: {
promptTokens: data.usage.prompt_tokens,
completionTokens: data.usage.completion_tokens,
totalTokens: data.usage.total_tokens,
},
};
}
}