UNPKG

@nomyx/assistant

Version:

A powerful assistant library and cli for your AI projects. works with Vertex AI (Claude and Gemini)

147 lines (131 loc) 4.45 kB
import axios from 'axios'; import { ChatMessage, ChatOptions, ProviderResponse } from '../../../types/chat'; import { StandardizedToolCall, Tool } from '../../../types/tool'; import { OpenRouterConfig } from './config'; import { OpenRouterChatRequestBody, OpenRouterChatResponse, OpenRouterContentPart, OpenRouterMessage } from './types'; import { convertToolSchema, convertMessages, extractToolCalls} from './tools'; import { getMaxTokens, supportsTools } from './modelConfig'; export async function chat( config: OpenRouterConfig, messages: ChatMessage[], options: ChatOptions, tools?: Tool[] ): Promise<ProviderResponse> { const url = config.getApiUrl('chat/completions'); const model = config.model; const logger = config.getLogger(); const requestBody: OpenRouterChatRequestBody = { messages: convertMessages(messages).map(msg => ({ ...msg, content: convertContentToOpenRouterFormat(msg.content) })), model: model, max_tokens: options.maxTokens || getMaxTokens(model), temperature: options.temperature, stream: false }; const modelSupportsTools = supportsTools(model); if (tools && tools.length > 0) { if (modelSupportsTools) { requestBody.tools = tools.map(tool => convertToolSchema(tool.getSchema())); requestBody.tool_choice = 'auto'; } else { // Append instructions for using tools const toolInstructions = ` You have access to the following tools: ${tools.map(tool => `- ${tool.name}: ${tool.description}`).join('\n')} To use a tool, output a function call in a code block like this: \`\`\`function_call { "name": "tool_name", "arguments": { "arg1": "value1", "arg2": "value2" } } \`\`\` Replace "tool_name" with the actual tool name and provide the necessary arguments. For example, to use the "tool_name" tool with arguments "arg1" and "arg2": \`\`\`function_call { "name": "tool_name", "arguments": { "arg1": "value1", "arg2": "value2" } } \`\`\ `; requestBody.messages[requestBody.messages.length - 1].content += '\n\n' + toolInstructions; } } const headers = { ...config.getHeaders(), 'HTTP-Referer': config.httpReferer, 'X-Title': config.xTitle }; logger.debug('OpenRouter API request', { url, model, toolsCount: tools?.length }); try { const response = await axios.post<OpenRouterChatResponse>(url, requestBody, { headers }); logger.debug('OpenRouter API response received', { status: response.status }); return createProviderResponse(response.data, modelSupportsTools, logger); } catch (error) { if (axios.isAxiosError(error)) { logger.error('OpenRouter API error', { status: error.response?.status, statusText: error.response?.statusText, data: error.response?.data }); } else { logger.error('OpenRouter API error', { error }); } throw error; } } function createProviderResponse( data: OpenRouterChatResponse, modelSupportsTools: boolean, logger: ReturnType<OpenRouterConfig['getLogger']> ): ProviderResponse { const choice = data.choices[0]; if (!choice) { throw new Error('Unexpected response format from OpenRouter API'); } const message = choice.message || { role: 'assistant', content: '' }; let content = message.content || ''; let toolCalls: StandardizedToolCall[] = modelSupportsTools ? extractToolCalls(message as OpenRouterMessage) : []; if (!modelSupportsTools) { const functionCallRegex = /```function_call\n([\s\S]*?)```/g; let match; while ((match = functionCallRegex.exec(content)) !== null) { try { const functionCall = JSON.parse(match[1]); toolCalls.push({ name: functionCall.name, arguments: functionCall.arguments }); } catch (error) { logger.error('Error parsing function call', { error }); } } content = content.replace(functionCallRegex, ''); } return { content: content.trim(), toolCalls: toolCalls, usage: { promptTokens: data.usage?.prompt_tokens || 0, completionTokens: data.usage?.completion_tokens || 0, totalTokens: data.usage?.total_tokens || 0 }, }; } function convertContentToOpenRouterFormat(content: string | OpenRouterContentPart[]): any { if (typeof content === 'string') { return content; } return content.map(part => ({ type: part.type, content: (part as any)[part.type] })); }