@respan/instrumentation-pydantic-ai 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,815 @@
1
+ import { RespanLogType } from "@respan/respan-sdk";
2
+ import { GEN_AI_COMPLETION_PREFIX, GEN_AI_PROMPT_PREFIX, GEN_AI_PROVIDER_NAME, GEN_AI_REQUEST_MODEL, GEN_AI_SYSTEM, GEN_AI_USAGE_COMPLETION_TOKENS, GEN_AI_USAGE_INPUT_TOKENS, GEN_AI_USAGE_OUTPUT_TOKENS, GEN_AI_USAGE_PROMPT_TOKENS, LLM_REQUEST_FUNCTIONS, LLM_REQUEST_TYPE, LLM_USAGE_CACHE_READ_INPUT_TOKENS, LLM_USAGE_TOTAL_TOKENS, OFF_CONTRACT_ALIAS_ATTRS, OI_AGENT_NAME, OI_INPUT_VALUE, OI_LLM_INVOCATION_PARAMETERS, OI_LLM_MODEL_NAME, OI_LLM_PROVIDER, OI_LLM_SYSTEM, OI_LLM_TOKEN_COUNT_CACHE_READ, OI_LLM_TOKEN_COUNT_COMPLETION, OI_LLM_TOKEN_COUNT_PROMPT, OI_LLM_TOKEN_COUNT_TOTAL, OI_LLM_TOOLS, OI_OUTPUT_VALUE, OI_RAW_EXACT_ATTRS, OI_RAW_PREFIXES, OI_SPAN_KIND, OTEL_NOISE_EXACT_ATTRS, OTEL_NOISE_PREFIXES, PYDANTIC_AI_AGENT_NAME, PYDANTIC_AI_FINAL_RESULT, PYDANTIC_AI_INPUT_MESSAGES, PYDANTIC_AI_LEGACY_AGENT_NAME, PYDANTIC_AI_LEGACY_TOOL_ARGUMENTS, PYDANTIC_AI_LEGACY_TOOL_RESULT, PYDANTIC_AI_MODEL_NAME, PYDANTIC_AI_OPERATION_NAME, PYDANTIC_AI_OUTPUT_MESSAGES, PYDANTIC_AI_RAW_EXACT_ATTRS, PYDANTIC_AI_REQUEST_PARAMETERS, PYDANTIC_AI_RUNNING_TOOLS_SPAN_NAME, PYDANTIC_AI_SCOPE_MARKERS, PYDANTIC_AI_TOOL_CALL_ARGUMENTS, PYDANTIC_AI_TOOL_CALL_RESULT, PYDANTIC_AI_TOOL_DEFINITIONS, PYDANTIC_AI_TOOL_NAME, PYDANTIC_AI_TOOLS, PYDANTIC_AI_USAGE_TOTAL_TOKENS, RESPAN_LOG_METHOD, RESPAN_LOG_TYPE, TRACELOOP_ENTITY_INPUT, TRACELOOP_ENTITY_NAME, TRACELOOP_ENTITY_OUTPUT, TRACELOOP_ENTITY_PATH, TRACELOOP_SPAN_KIND, TRACELOOP_WORKFLOW_NAME, } from "./_constants.js";
3
+ const RESPAN_LOG_METHOD_TS_TRACING = "ts_tracing";
4
+ const PydanticAIOperationToLogType = {
5
+ chat: RespanLogType.CHAT,
6
+ response: RespanLogType.CHAT,
7
+ embedding: RespanLogType.EMBEDDING,
8
+ speech: RespanLogType.TEXT,
9
+ transcription: RespanLogType.TEXT,
10
+ };
11
+ const OIKindToLogType = {
12
+ LLM: RespanLogType.CHAT,
13
+ EMBEDDING: RespanLogType.EMBEDDING,
14
+ TOOL: RespanLogType.TOOL,
15
+ AGENT: RespanLogType.AGENT,
16
+ CHAIN: RespanLogType.WORKFLOW,
17
+ RETRIEVER: RespanLogType.TASK,
18
+ RERANKER: RespanLogType.TASK,
19
+ GUARDRAIL: RespanLogType.GUARDRAIL,
20
+ EVALUATOR: RespanLogType.TASK,
21
+ PROMPT: RespanLogType.TASK,
22
+ UNKNOWN: RespanLogType.TASK,
23
+ };
24
+ const LLM_LOG_TYPES = new Set([
25
+ RespanLogType.CHAT,
26
+ RespanLogType.TEXT,
27
+ RespanLogType.EMBEDDING,
28
+ ]);
29
+ const LLM_CANONICAL_EXACT_ATTRS = new Set([
30
+ GEN_AI_SYSTEM,
31
+ GEN_AI_PROVIDER_NAME,
32
+ GEN_AI_REQUEST_MODEL,
33
+ GEN_AI_USAGE_INPUT_TOKENS,
34
+ GEN_AI_USAGE_OUTPUT_TOKENS,
35
+ GEN_AI_USAGE_PROMPT_TOKENS,
36
+ GEN_AI_USAGE_COMPLETION_TOKENS,
37
+ PYDANTIC_AI_USAGE_TOTAL_TOKENS,
38
+ LLM_REQUEST_TYPE,
39
+ LLM_REQUEST_FUNCTIONS,
40
+ LLM_USAGE_TOTAL_TOKENS,
41
+ LLM_USAGE_CACHE_READ_INPUT_TOKENS,
42
+ ]);
43
+ const LLM_CANONICAL_PREFIXES = [
44
+ `${GEN_AI_PROMPT_PREFIX}.`,
45
+ `${GEN_AI_COMPLETION_PREFIX}.`,
46
+ ];
47
+ export class PydanticAISpanProcessor {
48
+ _options;
49
+ constructor(options = {}) {
50
+ this._options = {
51
+ includeNativeSpans: options.includeNativeSpans ?? true,
52
+ includeOpenInferenceSpans: options.includeOpenInferenceSpans ?? true,
53
+ };
54
+ }
55
+ onStart(_span, _parentContext) {
56
+ // Translation happens on ended spans so output and usage are complete.
57
+ }
58
+ onEnd(span) {
59
+ enrichPydanticAISpan(span, this._options);
60
+ }
61
+ shutdown() {
62
+ return Promise.resolve();
63
+ }
64
+ forceFlush() {
65
+ return Promise.resolve();
66
+ }
67
+ }
68
+ export function enrichPydanticAISpan(span, options = {}) {
69
+ const attrs = getMutableAttributes(span);
70
+ if (!attrs) {
71
+ return;
72
+ }
73
+ const resolvedOptions = {
74
+ includeNativeSpans: options.includeNativeSpans ?? true,
75
+ includeOpenInferenceSpans: options.includeOpenInferenceSpans ?? true,
76
+ };
77
+ const scopeName = getInstrumentationScopeName(span);
78
+ if (resolvedOptions.includeOpenInferenceSpans &&
79
+ isPydanticAIOpenInferenceSpan(span, attrs, scopeName)) {
80
+ const logType = enrichOpenInferenceSpan(span, attrs);
81
+ if (logType) {
82
+ replaceSpanAttributes(span, stripRawAttrs(attrs, logType, "openinference"));
83
+ }
84
+ return;
85
+ }
86
+ if (resolvedOptions.includeNativeSpans &&
87
+ isPydanticAINativeSpan(span, attrs, scopeName)) {
88
+ const logType = enrichNativePydanticAISpan(span, attrs);
89
+ if (logType) {
90
+ replaceSpanAttributes(span, stripRawAttrs(attrs, logType, "native"));
91
+ }
92
+ return;
93
+ }
94
+ }
95
+ export function isPydanticAISpan(span) {
96
+ const attrs = getMutableAttributes(span);
97
+ const scopeName = getInstrumentationScopeName(span);
98
+ return Boolean(attrs &&
99
+ (isPydanticAINativeSpan(span, attrs, scopeName) ||
100
+ isPydanticAIOpenInferenceSpan(span, attrs, scopeName)));
101
+ }
102
+ export function isPydanticAINativeSpan(span, attrs, scopeName = getInstrumentationScopeName(span)) {
103
+ if (scopeLooksLikePydanticAI(scopeName)) {
104
+ return true;
105
+ }
106
+ return Boolean(PYDANTIC_AI_REQUEST_PARAMETERS in attrs ||
107
+ PYDANTIC_AI_TOOL_DEFINITIONS in attrs ||
108
+ PYDANTIC_AI_INPUT_MESSAGES in attrs ||
109
+ PYDANTIC_AI_OUTPUT_MESSAGES in attrs ||
110
+ PYDANTIC_AI_AGENT_NAME in attrs ||
111
+ PYDANTIC_AI_LEGACY_AGENT_NAME in attrs ||
112
+ PYDANTIC_AI_LEGACY_TOOL_ARGUMENTS in attrs ||
113
+ PYDANTIC_AI_LEGACY_TOOL_RESULT in attrs ||
114
+ PYDANTIC_AI_FINAL_RESULT in attrs ||
115
+ (span.name === PYDANTIC_AI_RUNNING_TOOLS_SPAN_NAME &&
116
+ PYDANTIC_AI_TOOLS in attrs));
117
+ }
118
+ export function isPydanticAIOpenInferenceSpan(span, attrs, scopeName = getInstrumentationScopeName(span)) {
119
+ return Boolean(attrs[OI_SPAN_KIND] !== undefined &&
120
+ (scopeLooksLikePydanticAI(scopeName) ||
121
+ isPydanticAINativeSpan(span, attrs, scopeName)));
122
+ }
123
+ function enrichNativePydanticAISpan(span, attrs) {
124
+ const logType = extractNativeLogType(span, attrs);
125
+ if (!logType) {
126
+ return undefined;
127
+ }
128
+ switch (logType) {
129
+ case RespanLogType.CHAT:
130
+ enrichNativeChatSpan(span, attrs);
131
+ break;
132
+ case RespanLogType.EMBEDDING:
133
+ case RespanLogType.TEXT:
134
+ enrichNativeModelSpan(span, attrs, logType);
135
+ break;
136
+ case RespanLogType.TOOL:
137
+ enrichNativeToolSpan(span, attrs);
138
+ break;
139
+ case RespanLogType.AGENT:
140
+ enrichNativeAgentSpan(span, attrs);
141
+ break;
142
+ case RespanLogType.TASK:
143
+ enrichNativeTaskSpan(span, attrs);
144
+ break;
145
+ default:
146
+ return undefined;
147
+ }
148
+ delete attrs[TRACELOOP_SPAN_KIND];
149
+ return logType;
150
+ }
151
+ function enrichOpenInferenceSpan(span, attrs) {
152
+ const oiKind = String(attrs[OI_SPAN_KIND] ?? "").toUpperCase();
153
+ const logType = OIKindToLogType[oiKind] ?? RespanLogType.TASK;
154
+ const entityName = stringValue(attrs[OI_AGENT_NAME]) ?? span.name;
155
+ const entityPath = logType === RespanLogType.WORKFLOW ? "" : entityName;
156
+ setCommonAttrs(attrs, logType, entityName, entityPath);
157
+ if (attrs[OI_INPUT_VALUE] !== undefined) {
158
+ attrs[TRACELOOP_ENTITY_INPUT] = jsonString(attrs[OI_INPUT_VALUE]);
159
+ }
160
+ if (attrs[OI_OUTPUT_VALUE] !== undefined) {
161
+ attrs[TRACELOOP_ENTITY_OUTPUT] = jsonString(attrs[OI_OUTPUT_VALUE]);
162
+ }
163
+ const model = stringValue(attrs[OI_LLM_MODEL_NAME]);
164
+ if (model) {
165
+ attrs[GEN_AI_REQUEST_MODEL] = model;
166
+ }
167
+ const provider = stringValue(attrs[OI_LLM_PROVIDER] ?? attrs[OI_LLM_SYSTEM]);
168
+ if (provider) {
169
+ attrs[GEN_AI_SYSTEM] = normalizeProvider(provider);
170
+ attrs[GEN_AI_PROVIDER_NAME] = normalizeProvider(provider);
171
+ }
172
+ setUsageAttrs(attrs, {
173
+ promptTokens: coerceInteger(attrs[OI_LLM_TOKEN_COUNT_PROMPT]),
174
+ completionTokens: coerceInteger(attrs[OI_LLM_TOKEN_COUNT_COMPLETION]),
175
+ totalTokens: coerceInteger(attrs[OI_LLM_TOKEN_COUNT_TOTAL]),
176
+ cacheReadTokens: coerceInteger(attrs[OI_LLM_TOKEN_COUNT_CACHE_READ]),
177
+ });
178
+ if (logType === RespanLogType.CHAT || logType === RespanLogType.EMBEDDING) {
179
+ attrs[LLM_REQUEST_TYPE] =
180
+ logType === RespanLogType.EMBEDDING
181
+ ? RespanLogType.EMBEDDING
182
+ : RespanLogType.CHAT;
183
+ }
184
+ if (logType === RespanLogType.CHAT) {
185
+ oiMessagesToCanonical(attrs, "llm.input_messages.", GEN_AI_PROMPT_PREFIX);
186
+ oiMessagesToCanonical(attrs, "llm.output_messages.", GEN_AI_COMPLETION_PREFIX);
187
+ mapOpenInferenceInvocationParameters(attrs);
188
+ if (attrs[OI_LLM_TOOLS] !== undefined) {
189
+ attrs[LLM_REQUEST_FUNCTIONS] = jsonString(parseMaybeJson(attrs[OI_LLM_TOOLS]));
190
+ }
191
+ }
192
+ if (logType === RespanLogType.WORKFLOW) {
193
+ attrs[TRACELOOP_WORKFLOW_NAME] = entityName;
194
+ }
195
+ delete attrs[TRACELOOP_SPAN_KIND];
196
+ return logType;
197
+ }
198
+ function extractNativeLogType(span, attrs) {
199
+ if (PYDANTIC_AI_TOOL_NAME in attrs ||
200
+ PYDANTIC_AI_TOOL_CALL_ARGUMENTS in attrs ||
201
+ PYDANTIC_AI_TOOL_CALL_RESULT in attrs ||
202
+ PYDANTIC_AI_LEGACY_TOOL_ARGUMENTS in attrs ||
203
+ PYDANTIC_AI_LEGACY_TOOL_RESULT in attrs) {
204
+ return RespanLogType.TOOL;
205
+ }
206
+ const operationName = stringValue(attrs[PYDANTIC_AI_OPERATION_NAME]);
207
+ if (operationName) {
208
+ const mapped = PydanticAIOperationToLogType[operationName];
209
+ if (mapped) {
210
+ return mapped;
211
+ }
212
+ }
213
+ if (PYDANTIC_AI_AGENT_NAME in attrs ||
214
+ PYDANTIC_AI_LEGACY_AGENT_NAME in attrs) {
215
+ return RespanLogType.AGENT;
216
+ }
217
+ if (span.name === PYDANTIC_AI_RUNNING_TOOLS_SPAN_NAME) {
218
+ return RespanLogType.TASK;
219
+ }
220
+ if (PYDANTIC_AI_INPUT_MESSAGES in attrs ||
221
+ PYDANTIC_AI_OUTPUT_MESSAGES in attrs) {
222
+ return RespanLogType.CHAT;
223
+ }
224
+ return undefined;
225
+ }
226
+ function enrichNativeChatSpan(span, attrs) {
227
+ const entityName = span.name || "pydantic_ai.chat";
228
+ setCommonAttrs(attrs, RespanLogType.CHAT, entityName, entityName);
229
+ attrs[LLM_REQUEST_TYPE] = RespanLogType.CHAT;
230
+ setModelAndProviderAttrs(attrs);
231
+ const inputMessages = extractMessages(attrs, PYDANTIC_AI_INPUT_MESSAGES);
232
+ const outputMessages = extractMessages(attrs, PYDANTIC_AI_OUTPUT_MESSAGES);
233
+ if (inputMessages?.length) {
234
+ attrs[TRACELOOP_ENTITY_INPUT] = safeJson(inputMessages);
235
+ setIndexedMessages(attrs, GEN_AI_PROMPT_PREFIX, inputMessages, "user");
236
+ }
237
+ if (outputMessages?.length) {
238
+ attrs[TRACELOOP_ENTITY_OUTPUT] = safeJson(outputMessages);
239
+ setIndexedMessages(attrs, GEN_AI_COMPLETION_PREFIX, outputMessages, "assistant");
240
+ }
241
+ const tools = extractTools(attrs);
242
+ if (tools?.length) {
243
+ attrs[LLM_REQUEST_FUNCTIONS] = safeJson(tools);
244
+ }
245
+ setUsageAttrs(attrs, extractNativeUsage(attrs));
246
+ }
247
+ function enrichNativeModelSpan(span, attrs, logType) {
248
+ const entityName = span.name || `pydantic_ai.${logType}`;
249
+ setCommonAttrs(attrs, logType, entityName, entityName);
250
+ attrs[LLM_REQUEST_TYPE] = logType;
251
+ setModelAndProviderAttrs(attrs);
252
+ setUsageAttrs(attrs, extractNativeUsage(attrs));
253
+ }
254
+ function enrichNativeToolSpan(span, attrs) {
255
+ const toolName = stringValue(attrs[PYDANTIC_AI_TOOL_NAME]) ??
256
+ toolNameFromSpanName(span.name) ??
257
+ "pydantic_ai.tool";
258
+ setCommonAttrs(attrs, RespanLogType.TOOL, toolName, toolName);
259
+ const rawArguments = attrs[PYDANTIC_AI_TOOL_CALL_ARGUMENTS] ??
260
+ attrs[PYDANTIC_AI_LEGACY_TOOL_ARGUMENTS];
261
+ attrs[TRACELOOP_ENTITY_INPUT] = safeJson({
262
+ name: toolName,
263
+ arguments: toSerializableValue(parseMaybeJson(rawArguments ?? {})),
264
+ });
265
+ const rawResult = attrs[PYDANTIC_AI_TOOL_CALL_RESULT] ??
266
+ attrs[PYDANTIC_AI_LEGACY_TOOL_RESULT];
267
+ if (rawResult !== undefined) {
268
+ attrs[TRACELOOP_ENTITY_OUTPUT] = jsonString(parseMaybeJson(rawResult));
269
+ }
270
+ }
271
+ function enrichNativeAgentSpan(span, attrs) {
272
+ const agentName = stringValue(attrs[PYDANTIC_AI_AGENT_NAME]) ??
273
+ stringValue(attrs[PYDANTIC_AI_LEGACY_AGENT_NAME]) ??
274
+ (span.name || "pydantic_ai.agent");
275
+ setCommonAttrs(attrs, RespanLogType.AGENT, agentName, agentName);
276
+ attrs[TRACELOOP_WORKFLOW_NAME] = agentName;
277
+ const inputMessages = extractMessages(attrs, PYDANTIC_AI_INPUT_MESSAGES);
278
+ if (inputMessages?.length) {
279
+ attrs[TRACELOOP_ENTITY_INPUT] = safeJson(inputMessages);
280
+ }
281
+ const finalResult = attrs[PYDANTIC_AI_FINAL_RESULT];
282
+ const outputMessages = extractMessages(attrs, PYDANTIC_AI_OUTPUT_MESSAGES);
283
+ if (finalResult !== undefined) {
284
+ attrs[TRACELOOP_ENTITY_OUTPUT] = jsonString(parseMaybeJson(finalResult));
285
+ }
286
+ else if (outputMessages?.length) {
287
+ attrs[TRACELOOP_ENTITY_OUTPUT] = safeJson(outputMessages);
288
+ }
289
+ const tools = extractTools(attrs);
290
+ if (tools?.length) {
291
+ attrs[LLM_REQUEST_FUNCTIONS] = safeJson(tools);
292
+ }
293
+ }
294
+ function enrichNativeTaskSpan(span, attrs) {
295
+ const taskName = span.name === PYDANTIC_AI_RUNNING_TOOLS_SPAN_NAME
296
+ ? "running_tools"
297
+ : span.name || "pydantic_ai.task";
298
+ setCommonAttrs(attrs, RespanLogType.TASK, taskName, taskName);
299
+ const toolNames = extractToolNames(attrs);
300
+ if (toolNames?.length) {
301
+ attrs[TRACELOOP_ENTITY_INPUT] = safeJson({ tools: toolNames });
302
+ }
303
+ }
304
+ function setCommonAttrs(attrs, logType, entityName, entityPath) {
305
+ attrs[RESPAN_LOG_METHOD] = RESPAN_LOG_METHOD_TS_TRACING;
306
+ attrs[RESPAN_LOG_TYPE] = logType;
307
+ attrs[TRACELOOP_ENTITY_NAME] = entityName;
308
+ attrs[TRACELOOP_ENTITY_PATH] = entityPath;
309
+ }
310
+ function setModelAndProviderAttrs(attrs) {
311
+ const model = stringValue(attrs[GEN_AI_REQUEST_MODEL]) ??
312
+ stringValue(attrs[PYDANTIC_AI_MODEL_NAME]) ??
313
+ stringValue(extractRequestParameters(attrs)?.model);
314
+ if (model) {
315
+ attrs[GEN_AI_REQUEST_MODEL] = model;
316
+ }
317
+ const provider = stringValue(attrs[GEN_AI_PROVIDER_NAME] ?? attrs[GEN_AI_SYSTEM]);
318
+ if (provider) {
319
+ attrs[GEN_AI_SYSTEM] = normalizeProvider(provider);
320
+ attrs[GEN_AI_PROVIDER_NAME] = normalizeProvider(provider);
321
+ }
322
+ }
323
+ function extractNativeUsage(attrs) {
324
+ const promptTokens = coerceInteger(attrs[GEN_AI_USAGE_INPUT_TOKENS] ?? attrs[GEN_AI_USAGE_PROMPT_TOKENS]);
325
+ const completionTokens = coerceInteger(attrs[GEN_AI_USAGE_OUTPUT_TOKENS] ??
326
+ attrs[GEN_AI_USAGE_COMPLETION_TOKENS]);
327
+ return {
328
+ promptTokens,
329
+ completionTokens,
330
+ totalTokens: coerceInteger(attrs[PYDANTIC_AI_USAGE_TOTAL_TOKENS] ?? attrs[LLM_USAGE_TOTAL_TOKENS]),
331
+ cacheReadTokens: coerceInteger(attrs[LLM_USAGE_CACHE_READ_INPUT_TOKENS]),
332
+ };
333
+ }
334
+ function setUsageAttrs(attrs, usage) {
335
+ if (usage.promptTokens !== undefined) {
336
+ attrs[GEN_AI_USAGE_INPUT_TOKENS] = usage.promptTokens;
337
+ attrs[GEN_AI_USAGE_PROMPT_TOKENS] = usage.promptTokens;
338
+ }
339
+ if (usage.completionTokens !== undefined) {
340
+ attrs[GEN_AI_USAGE_OUTPUT_TOKENS] = usage.completionTokens;
341
+ attrs[GEN_AI_USAGE_COMPLETION_TOKENS] = usage.completionTokens;
342
+ }
343
+ const totalTokens = usage.totalTokens ??
344
+ (usage.promptTokens !== undefined || usage.completionTokens !== undefined
345
+ ? (usage.promptTokens ?? 0) + (usage.completionTokens ?? 0)
346
+ : undefined);
347
+ if (totalTokens !== undefined) {
348
+ attrs[LLM_USAGE_TOTAL_TOKENS] = totalTokens;
349
+ }
350
+ if (usage.cacheReadTokens !== undefined) {
351
+ attrs[LLM_USAGE_CACHE_READ_INPUT_TOKENS] = usage.cacheReadTokens;
352
+ }
353
+ }
354
+ function extractMessages(attrs, key) {
355
+ const rawMessages = parseMaybeJson(attrs[key]);
356
+ if (!Array.isArray(rawMessages)) {
357
+ return undefined;
358
+ }
359
+ const messages = rawMessages
360
+ .map((message) => normalizeMessage(message))
361
+ .filter((message) => message !== undefined);
362
+ return messages.length ? messages : undefined;
363
+ }
364
+ function normalizeMessage(value) {
365
+ const parsed = parseMaybeJson(value);
366
+ if (!isRecord(parsed)) {
367
+ const content = contentToValue(parsed);
368
+ return content === undefined ? undefined : { role: "user", content };
369
+ }
370
+ const role = stringValue(parsed.role) ?? "user";
371
+ const content = parsed.content !== undefined
372
+ ? contentToValue(parsed.content)
373
+ : parsed.parts !== undefined
374
+ ? contentToValue(parsed.parts)
375
+ : undefined;
376
+ const message = {
377
+ role,
378
+ content: content ?? "",
379
+ };
380
+ const toolCalls = normalizeToolCalls(parsed.tool_calls ?? parsed.toolCalls);
381
+ if (toolCalls.length) {
382
+ message.tool_calls = toolCalls;
383
+ }
384
+ return message;
385
+ }
386
+ function setIndexedMessages(attrs, prefix, messages, defaultRole) {
387
+ messages.forEach((message, index) => {
388
+ const base = `${prefix}.${index}`;
389
+ attrs[`${base}.role`] = stringValue(message.role) ?? defaultRole;
390
+ attrs[`${base}.content`] = messageContentAttrValue(message.content);
391
+ if (message.tool_calls !== undefined) {
392
+ attrs[`${base}.tool_calls`] = safeJson(message.tool_calls);
393
+ }
394
+ });
395
+ }
396
+ function extractTools(attrs) {
397
+ const directTools = normalizeToolDefinitions(parseMaybeJson(attrs[PYDANTIC_AI_TOOL_DEFINITIONS]));
398
+ if (directTools.length) {
399
+ return directTools;
400
+ }
401
+ const params = extractRequestParameters(attrs);
402
+ const paramTools = normalizeToolDefinitions([
403
+ ...(Array.isArray(params?.function_tools) ? params.function_tools : []),
404
+ ...(Array.isArray(params?.output_tools) ? params.output_tools : []),
405
+ ]);
406
+ return paramTools.length ? paramTools : undefined;
407
+ }
408
+ function extractToolNames(attrs) {
409
+ const tools = parseMaybeJson(attrs[PYDANTIC_AI_TOOLS]);
410
+ if (!Array.isArray(tools)) {
411
+ return undefined;
412
+ }
413
+ const names = tools
414
+ .map((tool) => stringValue(tool))
415
+ .filter((tool) => Boolean(tool));
416
+ return names.length ? names : undefined;
417
+ }
418
+ function normalizeToolDefinitions(value) {
419
+ const parsed = parseMaybeJson(value);
420
+ const items = Array.isArray(parsed)
421
+ ? parsed
422
+ : parsed === undefined || parsed === null
423
+ ? []
424
+ : [parsed];
425
+ const tools = [];
426
+ for (const item of items) {
427
+ const parsedItem = parseMaybeJson(item);
428
+ if (typeof parsedItem === "string" && parsedItem) {
429
+ tools.push({ type: "function", function: { name: parsedItem } });
430
+ continue;
431
+ }
432
+ if (!isRecord(parsedItem)) {
433
+ continue;
434
+ }
435
+ const existingFunction = isRecord(parsedItem.function)
436
+ ? parsedItem.function
437
+ : undefined;
438
+ const name = stringValue(existingFunction?.name ?? parsedItem.name);
439
+ if (!name) {
440
+ continue;
441
+ }
442
+ const functionPayload = { name };
443
+ const description = existingFunction?.description ?? parsedItem.description;
444
+ if (description !== undefined) {
445
+ functionPayload.description = String(description);
446
+ }
447
+ const parameters = existingFunction?.parameters ??
448
+ parsedItem.parameters ??
449
+ parsedItem.parameters_json_schema ??
450
+ parsedItem.input_schema;
451
+ if (parameters !== undefined) {
452
+ functionPayload.parameters = toSerializableValue(parameters);
453
+ }
454
+ const strict = existingFunction?.strict ?? parsedItem.strict;
455
+ if (strict !== undefined) {
456
+ functionPayload.strict = Boolean(strict);
457
+ }
458
+ tools.push({
459
+ type: stringValue(parsedItem.type) ?? "function",
460
+ function: functionPayload,
461
+ });
462
+ }
463
+ return tools;
464
+ }
465
+ function normalizeToolCalls(value) {
466
+ const parsed = parseMaybeJson(value);
467
+ const calls = Array.isArray(parsed)
468
+ ? parsed
469
+ : parsed === undefined || parsed === null
470
+ ? []
471
+ : [parsed];
472
+ return calls.flatMap((call, index) => {
473
+ const parsedCall = parseMaybeJson(call);
474
+ if (!isRecord(parsedCall)) {
475
+ return [];
476
+ }
477
+ const rawFunction = isRecord(parsedCall.function)
478
+ ? parsedCall.function
479
+ : {};
480
+ const name = stringValue(rawFunction.name ?? parsedCall.name ?? parsedCall.toolName);
481
+ if (!name) {
482
+ return [];
483
+ }
484
+ const args = rawFunction.arguments ??
485
+ parsedCall.arguments ??
486
+ parsedCall.args ??
487
+ parsedCall.input ??
488
+ {};
489
+ return [
490
+ {
491
+ id: stringValue(parsedCall.id) ?? `call_${index}`,
492
+ type: "function",
493
+ function: {
494
+ name,
495
+ arguments: typeof args === "string" ? args : safeJson(args),
496
+ },
497
+ },
498
+ ];
499
+ });
500
+ }
501
+ function extractRequestParameters(attrs) {
502
+ const parsed = parseMaybeJson(attrs[PYDANTIC_AI_REQUEST_PARAMETERS]);
503
+ return isRecord(parsed) ? parsed : undefined;
504
+ }
505
+ function mapOpenInferenceInvocationParameters(attrs) {
506
+ const params = parseMaybeJson(attrs[OI_LLM_INVOCATION_PARAMETERS]);
507
+ if (!isRecord(params)) {
508
+ return;
509
+ }
510
+ if (params.model !== undefined && attrs[GEN_AI_REQUEST_MODEL] === undefined) {
511
+ attrs[GEN_AI_REQUEST_MODEL] = stringValue(params.model);
512
+ }
513
+ }
514
+ function oiMessagesToCanonical(attrs, oiPrefix, genAiPrefix) {
515
+ const buckets = new Map();
516
+ for (const [key, value] of Object.entries(attrs)) {
517
+ if (!key.startsWith(oiPrefix)) {
518
+ continue;
519
+ }
520
+ const rest = key.slice(oiPrefix.length);
521
+ const dotIndex = rest.indexOf(".");
522
+ const indexText = dotIndex === -1 ? rest : rest.slice(0, dotIndex);
523
+ if (!/^\d+$/.test(indexText)) {
524
+ continue;
525
+ }
526
+ const index = Number.parseInt(indexText, 10);
527
+ const field = dotIndex === -1 ? "" : rest.slice(dotIndex + 1);
528
+ const bucket = buckets.get(index) ?? new Map();
529
+ bucket.set(field, value);
530
+ buckets.set(index, bucket);
531
+ }
532
+ for (const index of [...buckets.keys()].sort((left, right) => left - right)) {
533
+ const bucket = buckets.get(index);
534
+ const base = `${genAiPrefix}.${index}`;
535
+ const role = bucket.get("message.role");
536
+ if (role !== undefined) {
537
+ attrs[`${base}.role`] = String(role);
538
+ }
539
+ const content = bucket.get("message.content") ??
540
+ contentBlocksFromOpenInferenceBucket(bucket);
541
+ if (content !== undefined) {
542
+ attrs[`${base}.content`] = messageContentAttrValue(content);
543
+ }
544
+ const toolCalls = toolCallsFromOpenInferenceBucket(bucket);
545
+ if (toolCalls.length) {
546
+ attrs[`${base}.tool_calls`] = safeJson(toolCalls);
547
+ }
548
+ }
549
+ }
550
+ function contentBlocksFromOpenInferenceBucket(bucket) {
551
+ const contentBlocks = new Map();
552
+ for (const [field, value] of bucket) {
553
+ if (!field.startsWith("message.contents.")) {
554
+ continue;
555
+ }
556
+ const rest = field.slice("message.contents.".length);
557
+ const dotIndex = rest.indexOf(".");
558
+ if (dotIndex === -1) {
559
+ continue;
560
+ }
561
+ const indexText = rest.slice(0, dotIndex);
562
+ if (!/^\d+$/.test(indexText)) {
563
+ continue;
564
+ }
565
+ const blockIndex = Number.parseInt(indexText, 10);
566
+ const blockField = rest
567
+ .slice(dotIndex + 1)
568
+ .replace(/^message_content\./, "");
569
+ const block = contentBlocks.get(blockIndex) ?? new Map();
570
+ block.set(blockField, value);
571
+ contentBlocks.set(blockIndex, block);
572
+ }
573
+ if (!contentBlocks.size) {
574
+ return undefined;
575
+ }
576
+ const ordered = [...contentBlocks.keys()]
577
+ .sort((left, right) => left - right)
578
+ .map((index) => Object.fromEntries(contentBlocks.get(index)));
579
+ const text = ordered
580
+ .map((block) => stringValue(block.text))
581
+ .filter((block) => Boolean(block));
582
+ return text.length === ordered.length ? text.join("\n") : ordered;
583
+ }
584
+ function toolCallsFromOpenInferenceBucket(bucket) {
585
+ const toolCallBuckets = new Map();
586
+ for (const [field, value] of bucket) {
587
+ if (!field.startsWith("message.tool_calls.")) {
588
+ continue;
589
+ }
590
+ const rest = field.slice("message.tool_calls.".length);
591
+ const dotIndex = rest.indexOf(".");
592
+ if (dotIndex === -1) {
593
+ continue;
594
+ }
595
+ const indexText = rest.slice(0, dotIndex);
596
+ if (!/^\d+$/.test(indexText)) {
597
+ continue;
598
+ }
599
+ const index = Number.parseInt(indexText, 10);
600
+ const outputField = rest
601
+ .slice(dotIndex + 1)
602
+ .replace(/^tool_call\./, "");
603
+ const current = toolCallBuckets.get(index) ?? {};
604
+ current[outputField] = value;
605
+ toolCallBuckets.set(index, current);
606
+ }
607
+ return [...toolCallBuckets.keys()]
608
+ .sort((left, right) => left - right)
609
+ .map((index) => toolCallBuckets.get(index))
610
+ .map((call) => {
611
+ if (call.function?.name) {
612
+ return call;
613
+ }
614
+ return {
615
+ id: stringValue(call.id) ?? "",
616
+ type: stringValue(call.type) ?? "function",
617
+ function: {
618
+ name: stringValue(call["function.name"] ?? call.name) ?? "",
619
+ arguments: typeof call["function.arguments"] === "string"
620
+ ? call["function.arguments"]
621
+ : safeJson(call["function.arguments"] ?? call.arguments ?? {}),
622
+ },
623
+ };
624
+ })
625
+ .filter((call) => call.function.name);
626
+ }
627
+ function stripRawAttrs(attrs, logType, source) {
628
+ const stripped = {};
629
+ const rawExact = source === "native" ? PYDANTIC_AI_RAW_EXACT_ATTRS : OI_RAW_EXACT_ATTRS;
630
+ const rawPrefixes = source === "native" ? [] : OI_RAW_PREFIXES;
631
+ for (const [key, value] of Object.entries(attrs)) {
632
+ if (rawExact.has(key) || OFF_CONTRACT_ALIAS_ATTRS.has(key)) {
633
+ continue;
634
+ }
635
+ if (OTEL_NOISE_EXACT_ATTRS.has(key)) {
636
+ continue;
637
+ }
638
+ if (rawPrefixes.some((prefix) => key.startsWith(prefix))) {
639
+ continue;
640
+ }
641
+ if (OTEL_NOISE_PREFIXES.some((prefix) => key.startsWith(prefix))) {
642
+ continue;
643
+ }
644
+ if (!LLM_LOG_TYPES.has(logType) && isLlmOnlyCanonicalAttr(key)) {
645
+ continue;
646
+ }
647
+ stripped[key] = value;
648
+ }
649
+ return stripped;
650
+ }
651
+ function isLlmOnlyCanonicalAttr(key) {
652
+ return (LLM_CANONICAL_EXACT_ATTRS.has(key) ||
653
+ LLM_CANONICAL_PREFIXES.some((prefix) => key.startsWith(prefix)));
654
+ }
655
+ function getMutableAttributes(span) {
656
+ const spanAny = span;
657
+ if (spanAny.attributes && typeof spanAny.attributes === "object") {
658
+ return spanAny.attributes;
659
+ }
660
+ if (spanAny._attributes && typeof spanAny._attributes === "object") {
661
+ return spanAny._attributes;
662
+ }
663
+ return undefined;
664
+ }
665
+ function replaceSpanAttributes(span, attrs) {
666
+ const spanAny = span;
667
+ const target = getMutableAttributes(span);
668
+ if (target) {
669
+ for (const key of Object.keys(target)) {
670
+ delete target[key];
671
+ }
672
+ Object.assign(target, attrs);
673
+ }
674
+ if (spanAny.attributes && spanAny.attributes !== target) {
675
+ spanAny.attributes = attrs;
676
+ }
677
+ if (spanAny._attributes) {
678
+ spanAny._attributes = target ?? attrs;
679
+ }
680
+ }
681
+ function getInstrumentationScopeName(span) {
682
+ const spanAny = span;
683
+ return String(spanAny.instrumentationScope?.name ??
684
+ spanAny.instrumentationLibrary?.name ??
685
+ "");
686
+ }
687
+ function scopeLooksLikePydanticAI(scopeName) {
688
+ const normalized = scopeName.toLowerCase();
689
+ return PYDANTIC_AI_SCOPE_MARKERS.some((marker) => normalized.includes(marker));
690
+ }
691
+ function toolNameFromSpanName(spanName) {
692
+ const match = spanName.match(/(?:execute[_ ]tool|tool)[: ]+(.+)$/i);
693
+ return match?.[1]?.trim() || undefined;
694
+ }
695
+ function normalizeProvider(provider) {
696
+ return provider.toLowerCase().replace(/^@/, "").replace(/[^a-z0-9._-]/g, "_");
697
+ }
698
+ function parseMaybeJson(value) {
699
+ if (typeof value !== "string") {
700
+ return value;
701
+ }
702
+ try {
703
+ return JSON.parse(value);
704
+ }
705
+ catch {
706
+ return value;
707
+ }
708
+ }
709
+ function jsonString(value) {
710
+ if (value === undefined || value === null) {
711
+ return "";
712
+ }
713
+ if (typeof value === "string") {
714
+ return value;
715
+ }
716
+ return safeJson(value);
717
+ }
718
+ function safeJson(value) {
719
+ try {
720
+ return JSON.stringify(toSerializableValue(value));
721
+ }
722
+ catch {
723
+ return String(value);
724
+ }
725
+ }
726
+ function toSerializableValue(value) {
727
+ if (value === undefined || value === null) {
728
+ return value;
729
+ }
730
+ if (typeof value === "string" ||
731
+ typeof value === "number" ||
732
+ typeof value === "boolean") {
733
+ return value;
734
+ }
735
+ if (typeof value === "bigint") {
736
+ return value.toString();
737
+ }
738
+ if (value instanceof Date) {
739
+ return value.toISOString();
740
+ }
741
+ if (Array.isArray(value)) {
742
+ return value.map((item) => toSerializableValue(item));
743
+ }
744
+ if (isRecord(value)) {
745
+ if (typeof value.toJSON === "function") {
746
+ try {
747
+ return toSerializableValue(value.toJSON());
748
+ }
749
+ catch {
750
+ // Fall through to structural copy.
751
+ }
752
+ }
753
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
754
+ key,
755
+ toSerializableValue(item),
756
+ ]));
757
+ }
758
+ return String(value);
759
+ }
760
+ function contentToValue(value) {
761
+ const parsed = parseMaybeJson(value);
762
+ const text = extractText(parsed);
763
+ return text ?? toSerializableValue(parsed);
764
+ }
765
+ function messageContentAttrValue(value) {
766
+ const text = extractText(value);
767
+ return text ?? jsonString(value);
768
+ }
769
+ function extractText(value) {
770
+ const parsed = parseMaybeJson(value);
771
+ if (typeof parsed === "string") {
772
+ return parsed;
773
+ }
774
+ if (Array.isArray(parsed)) {
775
+ const parts = parsed.map((item) => extractText(item));
776
+ return parts.every((part) => part !== undefined)
777
+ ? parts.join("\n")
778
+ : undefined;
779
+ }
780
+ if (isRecord(parsed)) {
781
+ if (typeof parsed.text === "string") {
782
+ return parsed.text;
783
+ }
784
+ if (parsed.type === "text" && typeof parsed.content === "string") {
785
+ return parsed.content;
786
+ }
787
+ if (typeof parsed.content === "string") {
788
+ return parsed.content;
789
+ }
790
+ }
791
+ return undefined;
792
+ }
793
+ function coerceInteger(value) {
794
+ if (typeof value === "number" && Number.isFinite(value)) {
795
+ return Math.trunc(value);
796
+ }
797
+ if (typeof value === "string" && value.trim()) {
798
+ const parsed = Number.parseInt(value, 10);
799
+ return Number.isFinite(parsed) ? parsed : undefined;
800
+ }
801
+ return undefined;
802
+ }
803
+ function stringValue(value) {
804
+ if (typeof value === "string" && value.length) {
805
+ return value;
806
+ }
807
+ if (typeof value === "number" || typeof value === "boolean") {
808
+ return String(value);
809
+ }
810
+ return undefined;
811
+ }
812
+ function isRecord(value) {
813
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
814
+ }
815
+ //# sourceMappingURL=_processor.js.map