UNPKG

newrelic

Version:
208 lines (184 loc) 5.99 kB
/* * Copyright 2024 New Relic Corporation. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ 'use strict' const { stringifyClaudeChunkedMessage, stringifyConverseChunkedMessage } = require('./stringify-message') /** * @typedef {object} AwsBedrockMiddlewareResponse * @property {object} response Has a `body` property that is an IncomingMessage, * a `headers` property that are the response headers, a `reason` property that * indicates the status code reason, and a `statusCode` property. * @property {object} output Has a `$metadata` property that includes the * `requestId`, and a `body` property that is a Uint8Array representation * of the response payload. */ /** * Represents a response from the Bedrock API, handling both standard and Converse API responses. * This object provides an abstraction that normalizes responses into a known interface and * simplifies accessing desired fields, accommodating the varied shapes of Bedrock API responses. */ class BedrockResponse { #originalResponse #innerResponse #innerOutput #parsedBody #output #command #completions = [] #id #isConverse /** * @param {object} params params object * @param {AwsBedrockMiddlewareResponse} params.response AWS Bedrock middleware response * @param {BedrockCommand} params.bedrockCommand AWS Bedrock command * @param {boolean} params.isError is there an error */ constructor({ response, bedrockCommand, isError = false }) { this.#originalResponse = response this.#innerResponse = isError ? response.$response : response.response this.#command = bedrockCommand this.isError = isError this.#isConverse = this.#command.isConverse ?? false if (this.isError) { return } if (this.#isConverse) { this.#innerOutput = response.output.output this.#output = this.#innerOutput.message } else { this.#innerOutput = response.output const json = new TextDecoder().decode(this.#innerOutput.body) this.#parsedBody = JSON.parse(json) const cmd = this.#command const body = this.#parsedBody this.#extractCompletionsAndId(cmd, body) } } /** * The parsed body of the response. * * @returns {object|undefined} */ get parsedBody() { return this.#parsedBody } get inputTokenCount() { if (this.#isConverse) { return parseInt(this.#originalResponse?.output?.usage?.inputTokens || 0, 10) } return parseInt(this?.headers?.['x-amzn-bedrock-input-token-count'] || 0, 10) } get outputTokenCount() { if (this.#isConverse) { return parseInt(this.#originalResponse?.output?.usage?.outputTokens || 0, 10) } return parseInt(this?.headers?.['x-amzn-bedrock-output-token-count'] || 0, 10) } get totalTokenCount() { if (this.#isConverse) { return parseInt(this.#originalResponse?.output?.usage?.totalTokens || 0, 10) } return this.inputTokenCount + this.outputTokenCount } /** * The prompt responses returned by the model. * * @returns {string[]|*[]} Should be an array of string responses to the * prompt. */ get completions() { if (this.#isConverse === false) { return this.#completions } const content = this.#output?.content if (!content) return [] return [typeof content === 'string' ? content : stringifyConverseChunkedMessage(content)] } /** * The reason the model has given for finishing the response. * * @returns {string|undefined} */ get finishReason() { if (this.isError) { return undefined } if (this.#isConverse) { return this.#originalResponse.output.stopReason } const cmd = this.#command if (cmd.isClaude() === true || cmd.isClaude3() === true) { return this.#parsedBody.stop_reason } if (cmd.isCohere() === true) { return this.#parsedBody.generations?.find((r) => r.finish_reason !== null)?.finish_reason } if (cmd.isLlama() === true) { return this.#parsedBody.stop_reason } if (cmd.isTitan() === true) { return this.#parsedBody.results?.find((r) => r.completionReason !== null)?.completionReason } return undefined } /** * HTTP headers provided in the API response. * * @returns {object} Typical key-value set of HTTP headers. */ get headers() { return this.#innerResponse.headers } /** * Retrieve the response identifier provided by some model responses. * * @returns {string|undefined} */ get id() { // For Converse API, the requestId serves as the id. return this.#isConverse ? this.requestId : this.#id } /** * UUID assigned to the initial request as returned by the API. * * @returns {string} */ get requestId() { return this.headers?.['x-amzn-requestid'] } /** * The HTTP status code of the response. * * @returns {number} */ get statusCode() { return this.#innerResponse.statusCode } /** * Extracts and sets #completions and #id from the InvokeModel response body. * @param {BedrockCommand} cmd AWS Bedrock Command * @param {*} body InvokeModel response body */ #extractCompletionsAndId(cmd, body) { if (cmd.isClaude() === true) { body.completion && this.#completions.push(body.completion) } else if (cmd.isClaude3() === true) { if (body?.type === 'message_stop') { // Streamed response this.#completions.push(body.completions) } else { this.#completions = [stringifyClaudeChunkedMessage(body?.content)] } this.#id = body.id } else if (cmd.isCohere() === true) { this.#completions = body.generations?.map((g) => g.text) ?? [] this.#id = body.id } else if (cmd.isLlama() === true) { body.generation && this.#completions.push(body.generation) } else if (cmd.isTitan() === true) { this.#completions = body.results?.map((r) => r.outputText) ?? [] } } } module.exports = BedrockResponse