donobu
Version:
Create browser automations with an LLM agent and replay them as Playwright scripts.
355 lines • 15.6 kB
JavaScript
;
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