UNPKG

donobu

Version:

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

307 lines 12.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AnthropicGptClient = 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"); /** * A GPT client implemented using the Anthropic API * @see https://docs.anthropic.com/en/api/messages */ class AnthropicGptClient extends GptClient_1.GptClient { /** * Create a new instance. */ constructor(anthropicConfig) { super(anthropicConfig); this.anthropicConfig = anthropicConfig; this.headers = new Headers({ [AnthropicGptClient.API_KEY_HEADER_NAME]: anthropicConfig.apiKey, [AnthropicGptClient.API_VERSION_HEADER_NAME]: AnthropicGptClient.API_VERSION_HEADER_VALUE, 'Content-Type': AnthropicGptClient.CONTENT_TYPE_HEADER_VALUE, }); } async ping() { const resp = await this.makeRequest(`/v1/models/${this.anthropicConfig.modelName}`); if (resp.status === 404) { throw new GptModelNotFoundException_1.GptModelNotFoundException(this.config.type, this.anthropicConfig.modelName); } else if (resp.status !== 200) { throw await this.mapErrorResponseToDonobuException(resp); } } async getMessage(messages) { const systemPrompts = messages .filter((msg) => msg.type === 'system') .map(AnthropicGptClient.chatRequestMessageFromGptMessage); const body = { model: this.anthropicConfig.modelName, max_tokens: AnthropicGptClient.MAX_TOKENS, temperature: 0.0, system: systemPrompts, messages: messages .filter((msg) => msg.type !== 'system') .map(AnthropicGptClient.chatRequestMessageFromGptMessage), }; AnthropicGptClient.shenanigansUserMessageMerge(body.messages); const resp = await this.makeRequest('/v1/messages', 'POST', body); if (resp.status !== 200) { throw await this.mapErrorResponseToDonobuException(resp); } const data = await resp.json(); const text = data.content[0].text; const promptTokensUsed = data.usage.input_tokens; const completionTokensUsed = data.usage.output_tokens; return { type: 'assistant', text, promptTokensUsed, completionTokensUsed, }; } async getStructuredOutput(messages, jsonSchema) { const systemPrompts = messages .filter((msg) => msg.type === 'system') .map(AnthropicGptClient.chatRequestMessageFromGptMessage); const body = { model: this.anthropicConfig.modelName, max_tokens: AnthropicGptClient.MAX_TOKENS, temperature: 0.0, system: systemPrompts, messages: messages .filter((msg) => msg.type !== 'system') .map(AnthropicGptClient.chatRequestMessageFromGptMessage), 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, }, }; AnthropicGptClient.shenanigansUserMessageMerge(body.messages); const resp = await this.makeRequest('/v1/messages', 'POST', body); if (resp.status !== 200) { throw await this.mapErrorResponseToDonobuException(resp); } const data = await resp.json(); const item = data.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 = data.usage.input_tokens; const completionTokensUsed = data.usage.output_tokens; return { type: 'structured_output', output: respObj, promptTokensUsed, completionTokensUsed, }; } async getToolCalls(messages, tools) { const systemPrompts = messages .filter((msg) => msg.type === 'system') .map(AnthropicGptClient.chatRequestMessageFromGptMessage); if (systemPrompts.length > 0) { const lastPrompt = systemPrompts[systemPrompts.length - 1]; lastPrompt.cache_control = { type: 'ephemeral' }; } const body = { model: this.anthropicConfig.modelName, max_tokens: AnthropicGptClient.MAX_TOKENS, temperature: 0.0, tool_choice: { type: 'any' }, tools: tools.length ? tools.map(AnthropicGptClient.toolChoiceFromTool) : undefined, system: systemPrompts, messages: messages .filter((msg) => msg.type !== 'system') .map(AnthropicGptClient.chatRequestMessageFromGptMessage), }; AnthropicGptClient.shenanigansUserMessageMerge(body.messages); // Note the special header for this endpoint const headers = new Headers(this.headers); headers.append('anthropic-beta', 'prompt-caching-2024-07-31'); const resp = await this.makeRequest('/v1/messages', 'POST', body, headers); if (resp.status !== 200) { throw await this.mapErrorResponseToDonobuException(resp); } const data = await resp.json(); const proposedToolCalls = data.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 = data.usage.input_tokens; const completionTokensUsed = data.usage.output_tokens; return { type: 'proposed_tool_calls', proposedToolCalls, promptTokensUsed, completionTokensUsed, }; } async mapErrorResponseToDonobuException(error) { try { const errorData = await error.json(); Logger_1.appLogger.error(`Anthropic error response: ${JSON.stringify(JsonUtils_1.JsonUtils.objectToJson(errorData))}`); if (errorData.error?.code === 'authentication_error') { return new GptPlatformAuthenticationFailedException_1.GptPlatformAuthenticationFailedException(this.config.type); } return new GptPlatformInternalErrorException_1.GptPlatformInternalErrorException(errorData.error?.message || `HTTP ${error.status}: ${error.statusText}`); } catch (_) { Logger_1.appLogger.error(`Failed to parse Anthropic error response: HTTP ${error.status}: ${error.statusText}`); return new GptPlatformInternalErrorException_1.GptPlatformInternalErrorException(`HTTP ${error.status}: ${error.statusText}`); } } async makeRequest(endpoint, method = 'GET', body, customHeaders) { try { return await fetch(`${AnthropicGptClient.API_URL}${endpoint}`, { method, headers: customHeaders || this.headers, body: body ? JSON.stringify(body) : undefined, signal: AbortSignal.timeout(AnthropicGptClient.REQUEST_TIMEOUT_MILLISECONDS), }); } catch (error) { if (error instanceof TypeError) { throw new GptPlatformNotReachableException_1.GptPlatformNotReachableException(this.config.type); } else { throw 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: gptMessage.text, }; } if (gptMessage.type === 'structured_output') { const output = gptMessage.output; return { role: 'assistant', content: 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 === 'system') { return { type: 'text', text: gptMessage.text, }; } 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.AnthropicGptClient = AnthropicGptClient; AnthropicGptClient.API_URL = 'https://api.anthropic.com'; AnthropicGptClient.REQUEST_TIMEOUT_MILLISECONDS = 120000; AnthropicGptClient.API_KEY_HEADER_NAME = 'x-api-key'; AnthropicGptClient.API_VERSION_HEADER_NAME = 'anthropic-version'; AnthropicGptClient.API_VERSION_HEADER_VALUE = '2023-06-01'; AnthropicGptClient.CONTENT_TYPE_HEADER_VALUE = 'application/json'; AnthropicGptClient.MAX_TOKENS = 8192; //# sourceMappingURL=AnthropicGptClient.js.map