diff --git a/packages/llms/src/OpenAIClient.ts b/packages/llms/src/OpenAIClient.ts deleted file mode 100644 index 33ae97c..0000000 --- a/packages/llms/src/OpenAIClient.ts +++ /dev/null @@ -1,192 +0,0 @@ -/** - * OpenAI Client implementation - * @note This client is only for demonstrating how to implement a LLM client. - * @note Use OpenAILenientClient instead. - */ -import { InvokeError, InvokeErrorType } from './errors' -import type { InvokeResult, LLMClient, LLMConfig, Message, Tool } from './types' -import { modelPatch, zodToOpenAITool } from './utils' - -/** - * @deprecated Use OpenAILenientClient instead. - */ -export class OpenAIClient implements LLMClient { - config: LLMConfig - - constructor(config: LLMConfig) { - this.config = config - } - - async invoke( - messages: Message[], - tools: Record, - abortSignal?: AbortSignal - ): Promise { - // 1. Convert tools to OpenAI format - const openaiTools = Object.entries(tools).map(([name, tool]) => zodToOpenAITool(name, tool)) - - // 2. Call API - let response: Response - try { - response = await fetch(`${this.config.baseURL}/chat/completions`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${this.config.apiKey}`, - }, - body: JSON.stringify( - modelPatch({ - model: this.config.model, - temperature: this.config.temperature, - messages, - - tools: openaiTools, - // tool_choice: 'required', - tool_choice: { type: 'function', function: { name: 'AgentOutput' } }, - - // model specific params - - // reasoning_effort: 'minimal', - // verbosity: 'low', - parallel_tool_calls: false, - }) - ), - signal: abortSignal, - }) - } catch (error: unknown) { - // Network error - throw new InvokeError(InvokeErrorType.NETWORK_ERROR, 'Network request failed', error) - } - - // 3. Handle HTTP errors - if (!response.ok) { - const errorData = await response.json().catch() - const errorMessage = - (errorData as { error?: { message?: string } }).error?.message || response.statusText - - if (response.status === 401 || response.status === 403) { - throw new InvokeError( - InvokeErrorType.AUTH_ERROR, - `Authentication failed: ${errorMessage}`, - errorData - ) - } - if (response.status === 429) { - throw new InvokeError( - InvokeErrorType.RATE_LIMIT, - `Rate limit exceeded: ${errorMessage}`, - errorData - ) - } - if (response.status >= 500) { - throw new InvokeError( - InvokeErrorType.SERVER_ERROR, - `Server error: ${errorMessage}`, - errorData - ) - } - throw new InvokeError( - InvokeErrorType.UNKNOWN, - `HTTP ${response.status}: ${errorMessage}`, - errorData - ) - } - - const data = await response.json() - - // 4. Check finish_reason - const choice = data.choices?.[0] - if (!choice) { - throw new InvokeError(InvokeErrorType.UNKNOWN, 'No choices in response', data) - } - - switch (choice.finish_reason) { - case 'tool_calls': - // ✅ Normal - break - case 'length': - // ⚠️ Token limit reached - throw new InvokeError( - InvokeErrorType.CONTEXT_LENGTH, - 'Response truncated: max tokens reached', - data - ) - case 'content_filter': - // ❌ Content filtered - throw new InvokeError( - InvokeErrorType.CONTENT_FILTER, - 'Content filtered by safety system', - data - ) - case 'stop': - // ❌ Did not call tool (we require tool call) - throw new InvokeError(InvokeErrorType.NO_TOOL_CALL, 'Model did not call any tool', data) - default: - throw new InvokeError( - InvokeErrorType.UNKNOWN, - `Unexpected finish_reason: ${choice.finish_reason}`, - data - ) - } - - // 5. Parse tool call - const toolCall = choice.message?.tool_calls?.[0] - if (!toolCall) { - throw new InvokeError(InvokeErrorType.NO_TOOL_CALL, 'No tool call found in response', data) - } - - const toolName = toolCall.function.name - const tool = tools[toolName] - if (!tool) { - throw new InvokeError(InvokeErrorType.UNKNOWN, `Tool ${toolName} not found`, data) - } - - // 6. Parse and validate arguments - let toolArgs: unknown - try { - toolArgs = JSON.parse(toolCall.function.arguments) - } catch (e) { - throw new InvokeError(InvokeErrorType.INVALID_TOOL_ARGS, 'Invalid JSON in tool arguments', e) - } - - // Validate against zod schema - const validation = tool.inputSchema.safeParse(toolArgs) - if (!validation.success) { - throw new InvokeError( - InvokeErrorType.INVALID_TOOL_ARGS, - `Tool arguments validation failed: ${validation.error.message}`, - validation.error - ) - } - - // 7. Execute tool - let toolResult: unknown - try { - toolResult = await tool.execute(validation.data) - } catch (e) { - throw new InvokeError( - InvokeErrorType.TOOL_EXECUTION_ERROR, - `Tool execution failed: ${(e as Error).message}`, - e - ) - } - - // 8. Return result (including cache tokens) - return { - toolCall: { - // id: toolCall.id, - name: toolName, - args: validation.data as Record, - }, - toolResult, - usage: { - promptTokens: data.usage?.prompt_tokens ?? 0, - completionTokens: data.usage?.completion_tokens ?? 0, - totalTokens: data.usage?.total_tokens ?? 0, - cachedTokens: data.usage?.prompt_tokens_details?.cached_tokens, - reasoningTokens: data.usage?.completion_tokens_details?.reasoning_tokens, - }, - rawResponse: data, - } - } -} diff --git a/packages/llms/src/OpenAICompatibleClient.ts b/packages/llms/src/OpenAICompatibleClient.ts index d0c437b..4ad3051 100644 --- a/packages/llms/src/OpenAICompatibleClient.ts +++ b/packages/llms/src/OpenAICompatibleClient.ts @@ -90,7 +90,6 @@ export class OpenAIClient implements LLMClient { // 4. Parse and validate response const data = await response.json() - // Basic validation before normalize (these are structural issues, not format issues) const choice = data.choices?.[0] if (!choice) { throw new InvokeError(InvokeErrorType.UNKNOWN, 'No choices in response', data) @@ -116,7 +115,7 @@ export class OpenAIClient implements LLMClient { ) } - // Apply normalizeResponse if provided (for fixing format issues like wrong tool name) + // Apply normalizeResponse if provided (for fixing format issues automatically) const normalizedData = options?.normalizeResponse ? options.normalizeResponse(data) : data const normalizedChoice = (normalizedData as any).choices?.[0]