UNPKG

donobu

Version:

Create browser automations with an LLM agent and replay them as Playwright scripts.

355 lines 15.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AnthropicAwsBedrockGptClient = void 0; const Logger_1 = require("../utils/Logger"); const JsonUtils_1 = require("../utils/JsonUtils"); const GptClient_1 = require("./GptClient"); const GptModelNotFoundException_1 = require("../exceptions/GptModelNotFoundException"); const GptPlatformAuthenticationFailedException_1 = require("../exceptions/GptPlatformAuthenticationFailedException"); const GptPlatformInternalErrorException_1 = require("../exceptions/GptPlatformInternalErrorException"); const GptPlatformNotReachableException_1 = require("../exceptions/GptPlatformNotReachableException"); const client_bedrock_runtime_1 = require("@aws-sdk/client-bedrock-runtime"); const GptPlatformRateLimitedException_1 = require("../exceptions/GptPlatformRateLimitedException"); /** * A GPT client implemented using AWS Bedrock for Anthropic models. * @see https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude.html */ class AnthropicAwsBedrockGptClient extends GptClient_1.GptClient { /** * Create a new instance. */ constructor(anthropicAwsBedrockConfig) { super(anthropicAwsBedrockConfig); this.anthropicAwsBedrockConfig = anthropicAwsBedrockConfig; this.bedrockClient = new client_bedrock_runtime_1.BedrockRuntimeClient({ region: anthropicAwsBedrockConfig.region, credentials: anthropicAwsBedrockConfig.accessKeyId ? { accessKeyId: anthropicAwsBedrockConfig.accessKeyId, secretAccessKey: anthropicAwsBedrockConfig.secretAccessKey, } : undefined, }); } async ping() { try { // Build a minimal request to check if the model exists and credentials are valid. const command = new client_bedrock_runtime_1.InvokeModelCommand({ modelId: this.anthropicAwsBedrockConfig.modelName, contentType: 'application/json', accept: 'application/json', body: JSON.stringify({ anthropic_version: 'bedrock-2023-05-31', max_tokens: 1, messages: [ { role: 'user', content: [{ type: 'text', text: 'ping' }] }, ], }), }); await this.bedrockClient.send(command); } catch (error) { if (error.name === 'ResourceNotFoundException' || (error.message && error.message.includes('model not found'))) { throw new GptModelNotFoundException_1.GptModelNotFoundException(this.anthropicAwsBedrockConfig.type, this.anthropicAwsBedrockConfig.modelName); } else if (error.name === 'AccessDeniedException' || error.name === 'UnrecognizedClientException' || error.name === 'InvalidSignatureException') { throw new GptPlatformAuthenticationFailedException_1.GptPlatformAuthenticationFailedException(this.anthropicAwsBedrockConfig.type); } else if (error.name === 'ServiceUnavailableException') { throw new GptPlatformNotReachableException_1.GptPlatformNotReachableException(this.anthropicAwsBedrockConfig.type); } else { throw new GptPlatformInternalErrorException_1.GptPlatformInternalErrorException(error.message || 'Unknown Bedrock error'); } } } async getMessage(messages) { const systemPrompt = this.extractSystemPrompt(messages); const nonSystemMessages = messages .filter((msg) => msg.type !== 'system') .map(AnthropicAwsBedrockGptClient.chatRequestMessageFromGptMessage); const bedrockRequest = { anthropic_version: 'bedrock-2023-05-31', max_tokens: AnthropicAwsBedrockGptClient.MAX_TOKENS, temperature: 0.0, system: systemPrompt, messages: nonSystemMessages, }; try { const command = new client_bedrock_runtime_1.InvokeModelCommand({ modelId: this.anthropicAwsBedrockConfig.modelName, contentType: 'application/json', accept: 'application/json', body: JSON.stringify(bedrockRequest), }); const response = await this.bedrockClient.send(command); const responseBody = JSON.parse(Buffer.from(response.body).toString('utf-8')); const text = responseBody.content[0].text; // Bedrock currently doesn't provide token usage in the same format as Anthropic direct API // We would need to adapt this if/when Bedrock adds token usage metrics const promptTokensUsed = responseBody.usage?.input_tokens || 0; const completionTokensUsed = responseBody.usage?.output_tokens || 0; return { type: 'assistant', text, promptTokensUsed, completionTokensUsed, }; } catch (error) { throw await this.mapErrorToDonobuException(error); } } async getStructuredOutput(messages, jsonSchema) { const systemPrompt = this.extractSystemPrompt(messages); const nonSystemMessages = messages .filter((msg) => msg.type !== 'system') .map(AnthropicAwsBedrockGptClient.chatRequestMessageFromGptMessage); const bedrockRequest = { anthropic_version: 'bedrock-2023-05-31', max_tokens: AnthropicAwsBedrockGptClient.MAX_TOKENS, temperature: 0.0, system: systemPrompt, messages: nonSystemMessages, tools: [ { name: 'StructuredOutputTool', description: 'Call this tool with the described parameters', input_schema: jsonSchema, }, ], tool_choice: { name: 'StructuredOutputTool', type: 'tool', disable_parallel_tool_use: true, }, }; try { const command = new client_bedrock_runtime_1.InvokeModelCommand({ modelId: this.anthropicAwsBedrockConfig.modelName, contentType: 'application/json', accept: 'application/json', body: JSON.stringify(bedrockRequest), }); const response = await this.bedrockClient.send(command); const responseBody = JSON.parse(Buffer.from(response.body).toString('utf-8')); const item = responseBody.content[0]; const contentType = item.type; let respObj; if (contentType === 'tool_use') { respObj = item.input; } else if (contentType === 'text') { throw new Error('Unsupported content type: text'); } else { throw new Error(`Unexpected content type: ${contentType}`); } const promptTokensUsed = responseBody.usage?.input_tokens || 0; const completionTokensUsed = responseBody.usage?.output_tokens || 0; return { type: 'structured_output', output: respObj, promptTokensUsed, completionTokensUsed, }; } catch (error) { throw await this.mapErrorToDonobuException(error); } } async getToolCalls(messages, tools) { const systemPrompt = this.extractSystemPrompt(messages); const nonSystemMessages = messages .filter((msg) => msg.type !== 'system') .map(AnthropicAwsBedrockGptClient.chatRequestMessageFromGptMessage); // Apply user message merging for compatibility with Anthropic's expectations AnthropicAwsBedrockGptClient.shenanigansUserMessageMerge(nonSystemMessages); const bedrockRequest = { anthropic_version: 'bedrock-2023-05-31', max_tokens: AnthropicAwsBedrockGptClient.MAX_TOKENS, temperature: 0.0, system: systemPrompt, messages: nonSystemMessages, tool_choice: { type: 'any' }, tools: tools.length ? tools.map(AnthropicAwsBedrockGptClient.toolChoiceFromTool) : undefined, }; try { const command = new client_bedrock_runtime_1.InvokeModelCommand({ modelId: this.anthropicAwsBedrockConfig.modelName, contentType: 'application/json', accept: 'application/json', body: JSON.stringify(bedrockRequest), }); const response = await this.bedrockClient.send(command); const responseBody = JSON.parse(Buffer.from(response.body).toString('utf-8')); const proposedToolCalls = responseBody.content.map((item) => { const contentType = item.type; if (contentType === 'tool_use') { const tool = tools.find((t) => t.name === item.name); if (!tool) { throw new Error('Unable to find matching tool for tool call'); } return { name: item.name, parameters: item.input, toolCallId: item.id, }; } else if (contentType === 'text') { throw new Error('Unsupported content type: text'); } else { throw new Error(`Unexpected content type: ${contentType}`); } }); const promptTokensUsed = responseBody.usage?.input_tokens || 0; const completionTokensUsed = responseBody.usage?.output_tokens || 0; return { type: 'proposed_tool_calls', proposedToolCalls, promptTokensUsed, completionTokensUsed, }; } catch (error) { throw await this.mapErrorToDonobuException(error); } } /** * Extract system prompt from messages */ extractSystemPrompt(messages) { const systemMessages = messages.filter((msg) => msg.type === 'system'); if (systemMessages.length === 0) { return ''; } // Concatenate all system prompts return systemMessages.map((msg) => msg.text).join('\n\n'); } /** * Maps AWS SDK errors to our application-specific exceptions */ async mapErrorToDonobuException(error) { Logger_1.appLogger.error(`Bedrock error: ${JSON.stringify(JsonUtils_1.JsonUtils.objectToJson(error))}`); if (error.name === 'ResourceNotFoundException' || (error.message && error.message.includes('model not found'))) { return new GptModelNotFoundException_1.GptModelNotFoundException(this.anthropicAwsBedrockConfig.type, this.anthropicAwsBedrockConfig.modelName); } else if (error.name === 'AccessDeniedException' || error.name === 'UnrecognizedClientException' || error.name === 'InvalidSignatureException') { return new GptPlatformAuthenticationFailedException_1.GptPlatformAuthenticationFailedException(this.anthropicAwsBedrockConfig.type); } else if (error.name === 'ServiceUnavailableException') { return new GptPlatformNotReachableException_1.GptPlatformNotReachableException(this.anthropicAwsBedrockConfig.type); } else if (error.name === 'ThrottlingException') { return new GptPlatformRateLimitedException_1.GptPlatformRateLimitedException(this.anthropicAwsBedrockConfig.type); } else { return new GptPlatformInternalErrorException_1.GptPlatformInternalErrorException(error.message || 'Unknown Bedrock error'); } } /** * Merges adjacent user messages because Anthropic will reject requests that do not delicately * flip-flop between "user" and "assistant" roles. */ static shenanigansUserMessageMerge(messages) { for (let i = messages.length - 1; i > 0; i--) { const message = messages[i]; const adjacentMessage = messages[i - 1]; if (message.role === 'user' && adjacentMessage.role === 'user') { adjacentMessage.content.push(...message.content); messages.splice(i, 1); } } } static chatRequestMessageFromGptMessage(gptMessage) { if (gptMessage.type === 'assistant') { return { role: 'assistant', content: [ { type: 'text', text: gptMessage.text, }, ], }; } if (gptMessage.type === 'structured_output') { const output = gptMessage.output; return { role: 'assistant', content: [ { type: 'text', text: JSON.stringify(JsonUtils_1.JsonUtils.objectToJson(output), null, 2), }, ], }; } if (gptMessage.type === 'proposed_tool_calls') { return { role: 'assistant', content: gptMessage.proposedToolCalls.map((tc) => ({ type: 'tool_use', id: tc.toolCallId, name: tc.name, input: JsonUtils_1.JsonUtils.objectToJson(tc.parameters), })), }; } if (gptMessage.type === 'user') { return { role: 'user', content: gptMessage.items.map((item) => { if (item.type === 'png') { return { type: 'image', source: { type: 'base64', media_type: 'image/png', data: Buffer.from(item.bytes).toString('base64'), }, }; } else { return { type: 'text', text: item.text, }; } }), }; } if (gptMessage.type === 'tool_call_result') { return { role: 'user', content: [ { type: 'tool_result', tool_use_id: gptMessage.toolCallId, content: gptMessage.data, }, ], }; } throw new Error(`Unsupported message type: ${JsonUtils_1.JsonUtils.objectToJson(gptMessage)}`); } static toolChoiceFromTool(tool) { return { name: tool.name, description: tool.description, input_schema: tool.inputSchema, }; } } exports.AnthropicAwsBedrockGptClient = AnthropicAwsBedrockGptClient; AnthropicAwsBedrockGptClient.MAX_TOKENS = 8192; //# sourceMappingURL=AnthropicAwsBedrockGptClient.js.map