UNPKG

newrelic

Version:
191 lines (169 loc) 5.02 kB
/* * Copyright 2024 New Relic Corporation. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ 'use strict' const { stringifyClaudeChunkedMessage } = require('./utils') /** * Parses an AWS invoke command instance into a re-usable entity. */ class BedrockCommand { #input #body #modelId /** * @param {object} input The `input` property from an InvokeModelCommand or * InvokeModelWithResponseStreamCommand instance that is used for the * conversation. */ constructor(input) { this.#input = input this.#body = JSON.parse(this.#input.body) this.#modelId = this.#input.modelId?.toLowerCase() ?? '' } /** * The maximum number of tokens allowed as defined by the user. * * @returns {number|undefined} */ get maxTokens() { let result if (this.isAi21() === true) { result = this.#body.maxTokens } else if (this.isClaude() === true) { result = this.#body.max_tokens_to_sample } else if (this.isClaude3() === true || this.isCohere() === true) { result = this.#body.max_tokens } else if (this.isLlama() === true) { result = this.#body.max_gen_length } else if (this.isTitan() === true) { result = this.#body.textGenerationConfig?.maxTokenCount } return result } /** * The model identifier for the command. * * @see https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids-arns.html * * @returns {string} */ get modelId() { return this.#modelId } /** * @returns {string} One of `embedding` or `completion`. */ get modelType() { // At the moment, this is a simple check. If Amazon ever introduces a // complex identifier, we can implement a more complicated check. return this.#modelId.toLowerCase().includes('embed') ? 'embedding' : 'completion' } /** * The question posed to the LLM. * * @returns {object[]} The array of context messages passed to the LLM (or a single user prompt for legacy "non-chat" models) */ get prompt() { if (this.isTitan() === true || this.isTitanEmbed() === true) { return [ { role: 'user', content: this.#body.inputText } ] } else if (this.isCohereEmbed() === true) { return [ { role: 'user', content: this.#body.texts.join(' ') } ] } else if ( this.isClaudeTextCompletionApi() === true || this.isAi21() === true || this.isCohere() === true || this.isLlama() === true ) { return [{ role: 'user', content: this.#body.prompt }] } else if (this.isClaudeMessagesApi() === true) { return normalizeClaude3Messages(this.#body?.messages) } return [] } /** * @returns {number|undefined} */ get temperature() { let result if (this.isTitan() === true) { result = this.#body.textGenerationConfig?.temperature } else if ( this.isClaude() === true || this.isClaude3() === true || this.isAi21() === true || this.isCohere() === true || this.isLlama() === true ) { result = this.#body.temperature } return result } isAi21() { return this.#modelId.startsWith('ai21.') } isClaude() { return this.#modelId.split('.').slice(-2).join('.').startsWith('anthropic.claude-v') } isClaude3() { return this.#modelId.split('.').slice(-2).join('.').startsWith('anthropic.claude-3') } isCohere() { return this.#modelId.startsWith('cohere.') && this.isCohereEmbed() === false } isCohereEmbed() { return this.#modelId.startsWith('cohere.embed') } isLlama() { return this.#modelId.startsWith('meta.llama') } isTitan() { return this.#modelId.startsWith('amazon.titan') && this.isTitanEmbed() === false } isTitanEmbed() { return this.#modelId.startsWith('amazon.titan-embed') } isClaudeMessagesApi() { return (this.isClaude3() === true || this.isClaude() === true) && 'messages' in this.#body } isClaudeTextCompletionApi() { return this.isClaude() === true && 'prompt' in this.#body } } /** * Claude v3 requests in Bedrock can have two different "chat" flavors. This function normalizes them into a consistent * format per the AIM agent spec * * @param messages - The raw array of messages passed to the invoke API * @returns {number|undefined} - The normalized messages */ function normalizeClaude3Messages(messages) { const result = [] for (const message of messages ?? []) { if (message == null) { continue } if (typeof message.content === 'string') { // Messages can be specified with plain string content result.push({ role: message.role, content: message.content }) } else if (Array.isArray(message.content)) { // Or in a "chunked" format for multi-modal support result.push({ role: message.role, content: stringifyClaudeChunkedMessage(message.content) }) } } return result } module.exports = BedrockCommand