From a154e8f37ef5956f17bb3cc9c6a0665ada0283ad Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 4 Feb 2025 16:50:34 -0800 Subject: [PATCH] Added anthropic tool calling functionality --- providers/anthropic/index.ts | 219 ++++++++++++++++++++++------------- 1 file changed, 139 insertions(+), 80 deletions(-) diff --git a/providers/anthropic/index.ts b/providers/anthropic/index.ts index 5e759818b..9a890f935 100644 --- a/providers/anthropic/index.ts +++ b/providers/anthropic/index.ts @@ -1,10 +1,9 @@ import { ProviderConfig, FunctionCallResponse, ProviderToolConfig, ProviderRequest, Message } from '../types' -import { ToolConfig } from '@/tools/types' export const anthropicProvider: ProviderConfig = { id: 'anthropic', name: 'Anthropic', - description: 'Anthropic\'s Claude models', + description: "Anthropic's Claude models", version: '1.0.0', models: ['claude-3-5-sonnet-20241022'], defaultModel: 'claude-3-5-sonnet-20241022', @@ -16,100 +15,160 @@ export const anthropicProvider: ProviderConfig = { 'anthropic-version': '2023-06-01' }), - createRequest: (request: ProviderRequest, functions?: any) => ({ - model: request.model || anthropicProvider.defaultModel, - messages: [ - ...(request.context ? [{ role: 'user', content: request.context }] : []) - ], - system: request.systemPrompt, - temperature: request.temperature, - max_tokens: request.maxTokens, - ...(functions && { - tools: functions - }) - }), - - extractResponse: (response: any) => { - const data = response.output || response - const textContent = data.content?.find((item: any) => item.type === 'text') - - return { - content: textContent?.text || '', - tokens: { - prompt: data.usage?.input_tokens, - completion: data.usage?.output_tokens, - total: data.usage?.input_tokens + data.usage?.output_tokens - } - } - }, - - handleToolCall: (response: any) => { - const data = response.output || response - const hasToolUse = data.content?.some((item: any) => item.type === 'tool_use') - - if (!hasToolUse) { - const textContent = data.content?.find((item: any) => item.type === 'text') - return { - hasFunctionCall: false, - content: textContent?.text || '' - } - } - - return { - hasFunctionCall: true - } - }, - - createToolCallMessage: (functionCall: FunctionCallResponse, result: any): Message => ({ - role: 'assistant', - content: [ - { - type: 'tool_use', - name: functionCall.name, - input: functionCall.arguments - } - ] - }), - transformToolsToFunctions: (tools: ProviderToolConfig[]) => { + if (!tools || tools.length === 0) { + return undefined + } + return tools.map(tool => ({ - type: 'function', name: tool.id, description: tool.description, - parameters: { + input_schema: { type: 'object', - properties: Object.entries(tool.params).reduce((acc, [key, config]) => { - acc[key] = { - type: config.type, - description: config.description, - ...(config.default && { default: config.default }) - } - return acc - }, {} as Record), - required: Object.entries(tool.params) - .filter(([_, config]) => config.required) - .map(([key]) => key) + properties: tool.parameters.properties, + required: tool.parameters.required } })) }, - transformFunctionCallResponse: (response: any): FunctionCallResponse => { - const content = response.output ? response.output.content : response.content - - if (!content || !Array.isArray(content)) { - throw new Error('Invalid response format: content is missing or not an array') + transformFunctionCallResponse: (response: any, tools?: ProviderToolConfig[]): FunctionCallResponse => { + const rawResponse = response?.output || response + if (!rawResponse?.content) { + throw new Error('No content found in response') } - const toolUse = content.find(item => item.type === 'tool_use') + const toolUse = rawResponse.content.find((item: any) => item.type === 'tool_use') if (!toolUse) { throw new Error('No tool use found in response') } + const tool = tools?.find(t => t.id === toolUse.name) + if (!tool) { + throw new Error(`Tool not found: ${toolUse.name}`) + } + + let input = toolUse.input + if (typeof input === 'string') { + try { + input = JSON.parse(input) + } catch (e) { + console.error('Failed to parse tool input:', e) + input = {} + } + } + return { name: toolUse.name, - arguments: typeof toolUse.input === 'string' - ? JSON.parse(toolUse.input) - : toolUse.input + arguments: { + ...tool.params, + ...input + } + } + }, + + transformRequest: (request: ProviderRequest, functions?: any) => { + // Transform messages to Anthropic format + const messages = request.messages?.map(msg => { + if (msg.role === 'function') { + return { + role: 'user', + content: [{ + type: 'tool_result', + tool_use_id: msg.name, + content: msg.content + }] + } + } + + if (msg.function_call) { + return { + role: 'assistant', + content: [{ + type: 'tool_use', + id: msg.function_call.name, + name: msg.function_call.name, + input: JSON.parse(msg.function_call.arguments) + }] + } + } + + return { + role: msg.role === 'assistant' ? 'assistant' : 'user', + content: msg.content ? [{ type: 'text', text: msg.content }] : [] + } + }) || [] + + // Add context if provided + if (request.context) { + messages.unshift({ + role: 'user', + content: [{ type: 'text', text: request.context }] + }) + } + + // Build the request payload exactly as Anthropic expects + const payload = { + model: request.model || 'claude-3-5-sonnet-20241022', + messages, + system: request.systemPrompt || '', + max_tokens: parseInt(String(request.maxTokens)) || 1024, + temperature: parseFloat(String(request.temperature ?? 0.7)), + ...(functions && { tools: functions }) + } + + return payload + }, + + transformResponse: (response: any) => { + try { + if (!response) { + console.warn('Received undefined response from Anthropic API') + return { content: '' } + } + + // Get the actual response content + const rawResponse = response.output || response + + // Extract text content from the message + let content = '' + const messageContent = rawResponse?.content || rawResponse?.message?.content + + if (Array.isArray(messageContent)) { + content = messageContent + .filter(item => item.type === 'text') + .map(item => item.text) + .join('\n') + } else if (typeof messageContent === 'string') { + content = messageContent + } + + const result = { + content, + model: rawResponse?.model || response?.model || 'claude-3-5-sonnet-20241022', + ...(rawResponse?.usage && { + tokens: { + prompt: rawResponse.usage.input_tokens, + completion: rawResponse.usage.output_tokens, + total: rawResponse.usage.input_tokens + rawResponse.usage.output_tokens + } + }) + } + + return result + } catch (error) { + console.error('Error in transformResponse:', error) + return { content: '' } + } + }, + + hasFunctionCall: (response: any) => { + try { + if (!response) return false + const rawResponse = response.output || response + return rawResponse?.content?.some((item: any) => item.type === 'tool_use') || false + } catch (error) { + console.error('Error in hasFunctionCall:', error) + return false } } -} \ No newline at end of file +} \ No newline at end of file