@nomyx/assistant
Version:
A powerful assistant library and cli for your AI projects. works with Vertex AI (Claude and Gemini)
100 lines (88 loc) • 3.67 kB
text/typescript
import axios from 'axios';
import { ChatMessage, ChatOptions, ProviderResponse } from '../../../types/chat';
import { StandardizedToolCall } from '../../../types/tool';
import { AnthropicConfig } from './config';
import { AnthropicChatRequestBody, AnthropicChatResponse, AnthropicMessageContent, AnthropicToolUseContent, Tool } from './types';
import { convertToolSchema, convertMessages } from './tools';
export async function chat(
config: AnthropicConfig,
messages: ChatMessage[],
options: ChatOptions,
tools?: Tool[]
): Promise<ProviderResponse> {
const url = config.getApiUrl();
const convertedMessages = convertMessages(messages);
const requestBody: AnthropicChatRequestBody = {
model: config.model,
messages: convertedMessages.filter(msg => msg.role !== 'system'),
max_tokens: options.maxTokens || 4000,
temperature: options.temperature,
};
// Combine all system messages into a single system parameter
const systemMessages = convertedMessages.filter(msg => msg.role === 'system');
if (systemMessages.length > 0) {
requestBody.system = systemMessages.map(msg => msg.content).join('\n\n');
}
if (tools && tools.length > 0) {
const toolNames = tools.map(tool => tool.name).join(', ');
requestBody.system = (requestBody.system || '') + `\n\nYou have access to the following tools: ${toolNames}. Use them when necessary.`;
const convertedTools = tools.map(tool => convertToolSchema(tool.getSchema() as any));
requestBody.tools = convertedTools;
}
try {
const response = await axios.post<AnthropicChatResponse>(url, requestBody, {
headers: {
'Content-Type': 'application/json',
'x-api-key': config.apiKey,
'anthropic-version': '2023-06-01',
},
});
return await createProviderResponse(response.data, tools || []);
} catch (error) {
if (axios.isAxiosError(error) && error.response) {
throw new Error(`Anthropic API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`);
}
throw error;
}
}
async function createProviderResponse(data: AnthropicChatResponse, tools: Tool[]): Promise<ProviderResponse> {
const response: ProviderResponse = {
content: '',
toolCalls: [],
usage: {
promptTokens: data.usage?.input_tokens || 0,
completionTokens: data.usage?.output_tokens || 0,
totalTokens: (data.usage?.input_tokens || 0) + (data.usage?.output_tokens || 0)
},
};
for (const item of data.content) {
if (item.type === 'text') {
response.content += item.text;
} else if (item.type === 'tool_use') {
const toolUse = item as AnthropicToolUseContent;
const toolCall: StandardizedToolCall = {
name: toolUse.name,
arguments: toolUse.input,
};
response.toolCalls && response.toolCalls.push(toolCall);
const tool = tools.find(t => t.name === toolUse.name);
if (tool) {
try {
const result = await tool.execute(toolUse.input, {} as any);
response.content += `\n\nTool ${toolUse.name} result: ${typeof result === 'string' ? result : JSON.stringify(result)}`;
} catch (error) {
response.content += `\n\nError executing tool ${toolUse.name}: ${error}`;
}
} else {
response.content += `\n\nError: Tool not found: ${toolUse.name}`;
}
}
}
// Update usage tokens with the latest data from the response
response.usage = {
promptTokens: data.usage?.input_tokens || 0,
completionTokens: data.usage?.output_tokens || 0,
totalTokens: (data.usage?.input_tokens || 0) + (data.usage?.output_tokens || 0)
};
return response;
}