UNPKG

adk-typescript

Version:

TypeScript port of Google's Agent Development Kit (ADK)

378 lines (377 loc) 14.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Claude = void 0; const BaseLlm_1 = require("./BaseLlm"); const LlmResponse_1 = require("./LlmResponse"); const sdk_1 = __importDefault(require("@anthropic-ai/sdk")); /** * Maximum tokens for Claude model responses */ const MAX_TOKEN = 1024; /** * AnthropicVertex client for Claude models */ class AnthropicVertexClient { /** * Constructor * @param projectId GCP project ID * @param region GCP region */ constructor(projectId, region) { this.isConfigured = false; /** * Messages API for Anthropic */ this.messages = { /** * Create a message using the Anthropic API * * @param options Message creation options * @returns A promise resolving to the message */ create: async (options) => { if (!this.isConfigured) { throw new Error('Anthropic client not properly configured.'); } try { // For Vertex AI, we need to use the correct endpoint based on the requested model // We could rebuild the client for each request, but for now we'll use the existing client // Ideally we would use a model-specific endpoint // Convert our options to Anthropic SDK format const createParams = { model: options.model, max_tokens: options.max_tokens || MAX_TOKEN, system: options.system, messages: options.messages, }; // Add tools if provided if (options.tools && options.tools.length > 0) { createParams.tools = options.tools; } // Add tool choice if provided if (options.tool_choice) { createParams.tool_choice = { type: options.tool_choice.type, // Map additional parameters ...(options.tool_choice.disable_parallel_tool_use !== undefined ? { disable_parallel_tool_use: options.tool_choice.disable_parallel_tool_use } : {}) }; } // Call the Anthropic API const response = await this.client.messages.create(createParams); // Return parsed response return { id: response.id, type: response.type, role: response.role, content: response.content, model: response.model, stop_reason: response.stop_reason, stop_sequence: response.stop_sequence, usage: { input_tokens: response.usage.input_tokens, output_tokens: response.usage.output_tokens } }; } catch (error) { console.error("Error calling Anthropic API:", error); throw error; } } }; this.projectId = projectId; this.region = region; try { // Initialize the Anthropic client specifically for Vertex AI integration this.client = new sdk_1.default({ apiKey: 'vertex-ai', // Special value to indicate Vertex AI integration baseURL: this.getVertexEndpoint(), defaultHeaders: { 'x-goog-user-project': this.projectId, 'x-vertex-ai-region': this.region, } }); this.isConfigured = true; } catch (error) { console.error("Error initializing Anthropic client:", error); this.client = new sdk_1.default(); // Fallback empty client this.isConfigured = false; } } /** * Get the Vertex AI endpoint for Anthropic * @param model The model name to use * @returns The endpoint URL */ getVertexEndpoint(model = 'claude-3-5-sonnet-v2@20241022') { return `https://${this.region}-aiplatform.googleapis.com/v1/projects/${this.projectId}/locations/${this.region}/publishers/anthropic/models/${model}:predict`; } } /** * Convert genai role to Claude role * @param role Role to convert * @returns Claude role */ function toClaudeRole(role) { if (role === 'model' || role === 'assistant') { return 'assistant'; } return 'user'; } /** * Convert a Part to a Claude content block * @param part The part to convert * @returns A Claude content block */ function partToMessageBlock(part) { if (part.text) { return { type: 'text', text: part.text }; } if (part.functionCall) { if (!part.functionCall.name) { throw new Error('Function call must have a name'); } return { type: 'tool_use', id: part.functionCall.id || '', name: part.functionCall.name, input: part.functionCall.args }; } if (part.functionResponse) { let content = ''; if (part.functionResponse.response && 'result' in part.functionResponse.response && part.functionResponse.response['result']) { // Convert complex response to string to avoid issues with the Anthropic API content = String(part.functionResponse.response['result']); } return { type: 'tool_result', tool_use_id: part.functionResponse.id || '', content: content, is_error: false }; } if (part.inlineData && part.inlineData.mimeType.startsWith('image/')) { return { type: 'image', source: { type: 'base64', media_type: part.inlineData.mimeType, data: part.inlineData.data } }; } throw new Error('Unsupported part type'); } /** * Convert Content to a Claude MessageParam * @param content Content to convert * @returns MessageParam for Claude API */ function contentToMessageParam(content) { return { role: toClaudeRole(content.role), content: content.parts.map(partToMessageBlock) }; } /** * Convert a Claude ContentBlock to a Part * @param contentBlock Block to convert * @returns Converted Part */ function contentBlockToPart(contentBlock) { if (contentBlock.type === 'text') { const textBlock = contentBlock; return { text: textBlock.text }; } if (contentBlock.type === 'tool_use') { const toolUseBlock = contentBlock; const part = { functionCall: { name: toolUseBlock.name, args: toolUseBlock.input, id: toolUseBlock.id } }; return part; } throw new Error('Unsupported content block type'); } /** * Convert a Claude message to an LlmResponse * @param message Claude message * @returns LlmResponse */ function messageToGenerateContentResponse(message) { const response = new LlmResponse_1.LlmResponse(); response.content = { role: 'model', parts: message.content.map(contentBlockToPart) }; // Could also set finish_reason and usage_metadata if needed return response; } /** * Convert a FunctionDeclaration to a Claude ToolParam * @param functionDeclaration Function declaration to convert * @returns Claude ToolParam */ function functionDeclarationToToolParam(functionDeclaration) { if (!functionDeclaration.name) { throw new Error('Function declaration must have a name'); } const properties = {}; if (functionDeclaration.parameters && functionDeclaration.parameters.properties) { for (const [key, value] of Object.entries(functionDeclaration.parameters.properties)) { // Safely copy properties const valueDict = {}; // Use type assertion to help TypeScript understand this is a safe operation const safeValue = value; // Copy each property individually if (safeValue) { for (const propKey in safeValue) { if (Object.prototype.hasOwnProperty.call(safeValue, propKey)) { valueDict[propKey] = safeValue[propKey]; } } } if ('type' in valueDict) { valueDict.type = typeof valueDict.type === 'string' ? valueDict.type.toLowerCase() : valueDict.type; } properties[key] = valueDict; } } return { name: functionDeclaration.name, description: functionDeclaration.description || '', input_schema: { type: 'object', properties: properties, } }; } /** * Claude class - wrapper around Anthropic's Claude API */ class Claude extends BaseLlm_1.BaseLlm { /** * Constructor * @param model Model name, defaults to 'claude-3-5-sonnet-v2@20241022' */ constructor(model = 'claude-3-5-sonnet-v2@20241022') { super(model); this.anthropicClient = null; } /** * Create and return the Anthropic client * Lazy initialization to avoid creating the client until needed * @returns AnthropicVertexClient */ get client() { if (!this.anthropicClient) { const projectId = process.env.GOOGLE_CLOUD_PROJECT; const region = process.env.GOOGLE_CLOUD_LOCATION; if (!projectId || !region) { throw new Error('GOOGLE_CLOUD_PROJECT and GOOGLE_CLOUD_LOCATION must be set for using Anthropic on Vertex.'); } this.anthropicClient = new AnthropicVertexClient(projectId, region); } return this.anthropicClient; } /** * Generate content asynchronously * @param llmRequest The request * @param stream Whether to stream (currently not supported for Claude) * @returns AsyncGenerator yielding responses */ async *generateContentAsync(llmRequest, stream = false) { // Convert contents to Claude format const messages = (llmRequest.contents || []).map(contentToMessageParam); // Process tools if present let tools = undefined; if (llmRequest.config && llmRequest.config.tools && llmRequest.config.tools.length > 0 && llmRequest.config.tools[0].functionDeclarations && llmRequest.config.tools[0].functionDeclarations.length > 0) { tools = llmRequest.config.tools[0].functionDeclarations.map(functionDeclarationToToolParam); } // Process tool choice const toolChoice = (llmRequest.config && llmRequest.config.tools && llmRequest.config.tools.length > 0) ? { type: 'auto', disable_parallel_tool_use: true } : undefined; try { if (stream) { // Log that streaming is not fully supported yet but we're falling back to non-streaming console.warn('Streaming for Claude is not fully supported in this implementation. Falling back to non-streaming.'); // Implement basic streaming simulation by yielding the entire response at once // In a full implementation, you would use the Anthropic streaming API const message = await this.client.messages.create({ model: this.model, system: llmRequest.config.systemInstruction, messages: messages, tools: tools, tool_choice: toolChoice, max_tokens: MAX_TOKEN }); // Log the response (would use proper logging in real implementation) console.log('Claude response (stream mode):', JSON.stringify(message, null, 2)); // Convert and yield the response // In a real streaming implementation, we would process chunks as they arrive yield messageToGenerateContentResponse(message); } else { // Standard non-streaming implementation const message = await this.client.messages.create({ model: this.model, system: llmRequest.config.systemInstruction, messages: messages, tools: tools, tool_choice: toolChoice, max_tokens: MAX_TOKEN }); // Log the response (would use proper logging in real implementation) console.log('Claude response:', JSON.stringify(message, null, 2)); // Convert and yield the response yield messageToGenerateContentResponse(message); } } catch (error) { console.error('Error during Claude API call:', error); // Create an error response const errorResponse = new LlmResponse_1.LlmResponse(); errorResponse.errorCode = 'CLAUDE_API_ERROR'; errorResponse.errorMessage = String(error); yield errorResponse; } } /** * List of supported models * @returns Regular expressions for supported model names */ static supportedModels() { return [ "claude-3-5-haiku@\\d+", "claude-3-5-sonnet-v2@\\d+", "claude-3-5-sonnet@\\d+", "claude-3-haiku@\\d+", "claude-3-opus@\\d+", "claude-3-sonnet@\\d+" ]; } } exports.Claude = Claude;