UNPKG

@anthropic-ai/sdk

Version:
158 lines • 7.84 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.recordStreamedMessageContent = recordStreamedMessageContent; exports.endAnthropicApiCallSpanWhenStreamEnds = endAnthropicApiCallSpanWhenStreamEnds; const api_1 = require("@opentelemetry/api"); const error_1 = require("../../../core/error.js"); const streaming_1 = require("../../../core/streaming.js"); const tracing_1 = require("../../../internal/tracing.js"); const log_1 = require("../../../internal/utils/log.js"); const values_1 = require("../../../internal/utils/values.js"); const attributes_1 = require("./attributes.js"); const content_1 = require("./content.js"); const response_1 = require("./response.js"); /** * In content mode, records `message`, which a stream helper accumulated, as the response content * of `call`, under the same size limit as a response that is not streamed. */ function recordStreamedMessageContent(client, call, body, message) { if (!call.span.isRecording() || client.openTelemetry.traces.contentMode !== 'content') { return; } try { (0, content_1.setMessageContentAttributes)(call, body, message.content, client.openTelemetry.traces.maxContentBytes); } catch (problem) { (0, log_1.loggerFor)(client).debug('could not record the streamed message on its span', problem); } } /** * Ends `span` when the application's read of `stream` ends. Only the stream of `messages.create` * has events that the span records. */ function endAnthropicApiCallSpanWhenStreamEnds(client, span, stream, props) { if (props.options.__span?.name === 'anthropic.messages.create' && stream instanceof streaming_1.Stream) { endMessagesCreateSpanWhenStreamEnds(client, span, stream, props); } else { (0, tracing_1.endApiCallSpanWhenStreamEnds)(span, stream, props); } } /** * Ends the span of a `messages.create` API call when the application's read of `stream` ends, with * the attributes of the Message that the events reconstruct, as far as it was read. The content is * that of a stream helper's Message: the helper records it at `message_stop` * (`recordStreamedMessageContent`), and a stream that ends earlier records it here. */ function endMessagesCreateSpanWhenStreamEnds(client, span, stream, props) { if (!span.isRecording()) { return; } span.setAttribute(tracing_1.ATTR_HTTP_RESPONSE_STATUS_CODE, props.response.status); const call = props.options.__span; const startTime = call?.startTime; let message; let usage; let sawDelta = false; let sawMessageDelta = false; let sawStop = false; (0, tracing_1.observeStream)(stream, props.controller.signal, { onItem(event) { try { switch (event.type) { case 'message_start': { const { id, model, stop_reason } = event.message; message = { id, model, stop_reason }; usage = { ...event.message.usage }; break; } case 'content_block_start': // A fallback block names the model that produces the rest of the Message. if (message && event.content_block.type === 'fallback') { message.model = event.content_block.to.model; } break; case 'message_delta': if (message && usage) { // Totals for the whole Message, null when they do not apply, so one that is present // replaces the earlier value. usage.output_tokens = event.usage.output_tokens; if (event.usage.input_tokens != null) { usage.input_tokens = event.usage.input_tokens; } if (event.usage.cache_creation_input_tokens != null) { usage.cache_creation_input_tokens = event.usage.cache_creation_input_tokens; } if (event.usage.cache_read_input_tokens != null) { usage.cache_read_input_tokens = event.usage.cache_read_input_tokens; } if (event.usage.output_tokens_details != null) { usage.output_tokens_details = event.usage.output_tokens_details; } if ('iterations' in event.usage && event.usage.iterations != null) { usage.iterations = event.usage.iterations; } if (event.delta.stop_reason != null) { message.stop_reason = event.delta.stop_reason; } sawMessageDelta = true; } break; case 'message_stop': sawStop = true; break; case 'content_block_delta': // The first chunk is the first delta, of any type, not an earlier event. if (!sawDelta && startTime !== undefined) { span.setAttribute(attributes_1.ATTR_GEN_AI_RESPONSE_TIME_TO_FIRST_CHUNK, (Date.now() - startTime) / 1000); } sawDelta = true; break; case 'content_block_stop': break; default: (0, values_1.checkNever)(event); } } catch (problem) { (0, log_1.loggerFor)(client).debug('could not record the stream on its span', problem); } }, onEnd(end) { try { if (message && usage) { // `message_delta` has the stop reason and the final output usage, so a stream cut short // after it still has them. (0, response_1.setResponseAttributes)(span, message, usage, sawMessageDelta || sawStop); } } catch (problem) { (0, log_1.loggerFor)(client).debug('could not record the stream on its span', problem); } const failure = call?.failure; // A helper that failed on an event did not build the Message that the stream sent. if (!sawStop && call?.streamed && failure === undefined) { recordStreamedMessageContent(client, call, call.streamed.body, call.streamed.message); } if (end.reason === 'failed') { (0, tracing_1.setErrorStatus)(span, end.error); } else if (failure !== undefined) { (0, tracing_1.setErrorStatus)(span, failure, 'client_error'); } else if (end.reason === 'completed') { if (sawStop) { span.setStatus({ code: api_1.SpanStatusCode.OK }); } else { // The application sees the stream end without an error, so only the span fails. (0, tracing_1.setErrorStatus)(span, new error_1.AnthropicError('The stream ended before message_stop.'), 'api_error'); } } // Otherwise the application closed the stream before the API did, which leaves the status // unset, even after `message_stop`. (0, tracing_1.guardTracing)(client, () => span.end()); }, }); } //# sourceMappingURL=streaming.js.map