* Instrument a method with Sentry spans * Following Sentry AI Agents Manual Instrumentation conventions * @see https://docs.sentry.io/platforms/javascript/guides/node/tracing/instrumentation/ai-agents-module/#manual-instrumentation
( originalMethod: (...args: T) => Promise<R>, methodPath: string, instrumentedMethod: InstrumentedMethodEntry, context: unknown, options: OpenAiOptions, )
| 144 | * @see https://docs.sentry.io/platforms/javascript/guides/node/tracing/instrumentation/ai-agents-module/#manual-instrumentation |
| 145 | */ |
| 146 | function instrumentMethod<T extends unknown[], R>( |
| 147 | originalMethod: (...args: T) => Promise<R>, |
| 148 | methodPath: string, |
| 149 | instrumentedMethod: InstrumentedMethodEntry, |
| 150 | context: unknown, |
| 151 | options: OpenAiOptions, |
| 152 | ): (...args: T) => Promise<R> { |
| 153 | return function instrumentedCall(...args: T): Promise<R> { |
| 154 | const operationName = instrumentedMethod.operation || 'unknown'; |
| 155 | const requestAttributes = extractRequestAttributes(args, operationName); |
| 156 | const model = (requestAttributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE] as string) || 'unknown'; |
| 157 | |
| 158 | const params = args[0] as Record<string, unknown> | undefined; |
| 159 | const isStreamRequested = params && typeof params === 'object' && params.stream === true; |
| 160 | |
| 161 | const spanConfig = { |
| 162 | name: `${operationName} ${model}`, |
| 163 | op: `gen_ai.${operationName}`, |
| 164 | attributes: requestAttributes as Record<string, SpanAttributeValue>, |
| 165 | }; |
| 166 | |
| 167 | if (isStreamRequested) { |
| 168 | let originalResult!: Promise<R>; |
| 169 | |
| 170 | const instrumentedPromise = startSpanManual(spanConfig, (span: Span) => { |
| 171 | originalResult = originalMethod.apply(context, args); |
| 172 | |
| 173 | if (options.recordInputs && params) { |
| 174 | addRequestAttributes(span, params, operationName, shouldEnableTruncation(options.enableTruncation)); |
| 175 | } |
| 176 | |
| 177 | // Return async processing |
| 178 | return (async () => { |
| 179 | try { |
| 180 | const result = await originalResult; |
| 181 | return instrumentStream( |
| 182 | result as OpenAIStream<ChatCompletionChunk | ResponseStreamingEvent>, |
| 183 | span, |
| 184 | options.recordOutputs ?? false, |
| 185 | ) as unknown as R; |
| 186 | } catch (error) { |
| 187 | span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); |
| 188 | captureException(error, { |
| 189 | mechanism: { |
| 190 | handled: false, |
| 191 | type: 'auto.ai.openai.stream', |
| 192 | data: { function: methodPath }, |
| 193 | }, |
| 194 | }); |
| 195 | span.end(); |
| 196 | throw error; |
| 197 | } |
| 198 | })(); |
| 199 | }); |
| 200 | |
| 201 | return wrapPromiseWithMethods(originalResult, instrumentedPromise, 'auto.ai.openai'); |
| 202 | } |
| 203 |
no test coverage detected