From 8a67b7b2f25e54283de588e11690755a003d0170 Mon Sep 17 00:00:00 2001 From: Travis Dent Date: Wed, 18 Jun 2025 09:52:38 -0700 Subject: [PATCH 1/2] Fix tests and response converter --- src/instrumentation/openai-agents/response.ts | 129 ++++++------------ tests/base.test.ts | 3 +- tests/openai-converters.test.ts | 22 ++- tests/registry.test.ts | 5 +- 4 files changed, 68 insertions(+), 91 deletions(-) diff --git a/src/instrumentation/openai-agents/response.ts b/src/instrumentation/openai-agents/response.ts index ae73027..87dffcb 100644 --- a/src/instrumentation/openai-agents/response.ts +++ b/src/instrumentation/openai-agents/response.ts @@ -26,6 +26,7 @@ import { import { extractAttributesFromMapping, extractAttributesFromArray, + extractAttributesFromMappingWithIndex, AttributeMap, IndexedAttributeMap } from '../../attributes'; @@ -100,108 +101,68 @@ const RESPONSE_INPUT_FUNCTION_CALL_ATTRIBUTES: IndexedAttributeMap = { * our centralized semantic convention constants and attribute mapping system. */ export function convertResponseSpan(data: ResponseSpanData): AttributeMap { - const attributes: AttributeMap = {}; - Object.assign(attributes, extractAttributesFromMapping(data, RESPONSE_ATTRIBUTES)); - - if (data._input && Array.isArray(data._input)) { - for (const item of data._input) { - switch (item.type) { - case 'message': // Input message - Object.assign(attributes, - extractAttributesFromMapping(item, RESPONSE_INPUT_ATTRIBUTES)); - break; - case 'function_call': // Input function call - Object.assign(attributes, - extractAttributesFromMapping(item, RESPONSE_INPUT_FUNCTION_CALL_ATTRIBUTES)); - debug('Extracted input function call:', item.name); - break; - case 'function_call_result': // Function call result - debug('Skipping function call result'); - break; - default: - debug('Unknown input item type:', item.type); - break; + const attrs: AttributeMap = {}; + Object.assign(attrs, extractAttributesFromMapping(data, RESPONSE_ATTRIBUTES)); + + if (Array.isArray(data._input)) { + data._input.forEach((item, i) => { + if (item.type === 'message') { + Object.assign( + attrs, + extractAttributesFromMappingWithIndex(item, RESPONSE_INPUT_ATTRIBUTES, i) + ); + } else if (item.type === 'function_call') { + Object.assign( + attrs, + extractAttributesFromMappingWithIndex( + item, + RESPONSE_INPUT_FUNCTION_CALL_ATTRIBUTES, + i + ) + ); } - } + }); } - // _response was added with https://github.com/openai/openai-agents-js/pull/85 if (data._response) { - Object.assign(attributes, - extractAttributesFromMapping(data._response, RESPONSE_MODEL_ATTRIBUTES)); - Object.assign(attributes, - extractAttributesFromMapping(data._response.usage, RESPONSE_USAGE_ATTRIBUTES)); + Object.assign(attrs, extractAttributesFromMapping(data._response, RESPONSE_MODEL_ATTRIBUTES)); + Object.assign(attrs, extractAttributesFromMapping(data._response.usage, RESPONSE_USAGE_ATTRIBUTES)); - const completions = []; + const completions: any[] = []; if (Array.isArray(data._response.output)) { for (const item of data._response.output) { - switch (item.type) { - case 'message': { // ResponseOutputMessage - for (const contentItem of item.content || []) { - switch (contentItem.type) { - case 'output_text': // ResponseOutputText - completions.push({ - role: item.role || 'assistant', - content: contentItem.text - }); - break; - case 'refusal': // ResponseOutputRefusal - completions.push({ - role: item.role || 'assistant', - content: contentItem.refusal - }); - break; - default: - debug('Unknown message content type:', contentItem.type); - break; + if (item.type === 'message') { + for (const c of item.content || []) { + if (c.type === 'output_text') { + completions.push({ role: item.role || 'assistant', content: c.text }); + } else if (c.type === 'refusal') { + completions.push({ role: item.role || 'assistant', content: c.refusal }); } } - break; - } - case 'reasoning': { // ResponseReasoningItem - const reasoningText = item.summary - ?.filter((item: any) => item.type === 'summary_text') - ?.map((item: any) => item.text) - ?.join('') || ''; - + } else if ( + item.type === 'function_call' || + item.type === 'file_search_call' || + item.type === 'web_search_call' || + item.type === 'computer_call' + ) { + Object.assign(attrs, extractAttributesFromMapping(item, RESPONSE_TOOL_CALL_ATTRIBUTES)); + } else if (item.type === 'reasoning') { + const reasoningText = (item.summary || []) + .filter((r: any) => r.type === 'summary_text') + .map((r: any) => r.text) + .join(''); if (reasoningText) { - completions.push({ - role: 'assistant', - content: reasoningText - }); + completions.push({ role: 'assistant', content: reasoningText }); } - break; - } - case 'function_call': // ResponseFunctionToolCall - case 'file_search_call': // ResponseFileSearchToolCall - case 'web_search_call': // ResponseFunctionWebSearch - case 'computer_call': { // ResponseComputerToolCall - Object.assign(attributes, - extractAttributesFromMapping(item, RESPONSE_TOOL_CALL_ATTRIBUTES)); - break; - } - case 'image_generation_call': // ResponseOutputItem.ImageGenerationCall - case 'code_interpreter_call': // ResponseCodeInterpreterToolCall - case 'local_shell_call': // ResponseOutputItem.LocalShellCall - case 'mcp_call': // ResponseOutputItem.McpCall - case 'mcp_list_tools': // ResponseOutputItem.McpListTools - case 'mcp_approval_request': { // ResponseOutputItem.McpApprovalRequest - debug('Unhandled output item type:', item.type); - break; - } - default: { - debug('Unknown output item type:', item.type); - break; } } } if (completions.length > 0) { - Object.assign(attributes, - extractAttributesFromArray(completions, RESPONSE_OUTPUT_ATTRIBUTES)); + Object.assign(attrs, extractAttributesFromArray(completions, RESPONSE_OUTPUT_ATTRIBUTES)); } } - return attributes; + return attrs; } diff --git a/tests/base.test.ts b/tests/base.test.ts index a9b5043..1cf760d 100644 --- a/tests/base.test.ts +++ b/tests/base.test.ts @@ -1,4 +1,5 @@ import { InstrumentationBase } from '../src/instrumentation/base'; +import { Client } from '../src/client'; class DummyInstrumentation extends InstrumentationBase { static readonly metadata = { @@ -27,7 +28,7 @@ describe('InstrumentationBase', () => { }); it('runtime targeting runs setup only once', () => { - const inst = new RuntimeInstrumentation('n','v',{}); + const inst = new RuntimeInstrumentation(new Client()); inst.setupRuntimeTargeting(); expect(inst.setup).toHaveBeenCalledTimes(1); inst.setupRuntimeTargeting(); diff --git a/tests/openai-converters.test.ts b/tests/openai-converters.test.ts index 01c06a5..29a25f5 100644 --- a/tests/openai-converters.test.ts +++ b/tests/openai-converters.test.ts @@ -1,7 +1,7 @@ import { convertGenerationSpan } from '../src/instrumentation/openai-agents/generation'; import { convertAgentSpan } from '../src/instrumentation/openai-agents/agent'; import { convertFunctionSpan } from '../src/instrumentation/openai-agents/function'; -import { convertResponseSpan, convertEnhancedResponseSpan, createEnhancedResponseSpanData } from '../src/instrumentation/openai-agents/response'; +import { convertResponseSpan } from '../src/instrumentation/openai-agents/response'; import { convertHandoffSpan } from '../src/instrumentation/openai-agents/handoff'; import { convertCustomSpan } from '../src/instrumentation/openai-agents/custom'; import { convertGuardrailSpan } from '../src/instrumentation/openai-agents/guardrail'; @@ -39,10 +39,24 @@ describe('OpenAI converters', () => { expect(convertResponseSpan({ type:'response', response_id:'r' } as any)['response.id']).toBe('r'); }); - it('enhances response data', () => { - const enhanced = createEnhancedResponseSpanData({ model:'m', input:[{type:'message', role:'user', content:'c'}] }, { responseId:'id', usage:{ inputTokens:1, outputTokens:2, totalTokens:3 } }); - const attrs = convertEnhancedResponseSpan(enhanced); + it('converts response data', () => { + const data = { + type: 'response', + response_id: 'id', + _input: [{ type: 'message', role: 'user', content: 'c' }], + _response: { + id: 'id', + model: 'm', + usage: { input_tokens: 1, output_tokens: 2, total_tokens: 3 }, + output: [ + { type: 'message', role: 'assistant', content: [{ type: 'output_text', text: 'o' }] } + ] + } + }; + const attrs = convertResponseSpan(data as any); + expect(attrs['response.id']).toBe('id'); expect(attrs['gen_ai.prompt.0.content']).toBe('c'); + expect(attrs['gen_ai.completion.0.content']).toBe('o'); expect(attrs['gen_ai.usage.total_tokens']).toBe('3'); }); diff --git a/tests/registry.test.ts b/tests/registry.test.ts index 3b6832f..869b8a0 100644 --- a/tests/registry.test.ts +++ b/tests/registry.test.ts @@ -1,4 +1,5 @@ import { InstrumentationBase } from '../src/instrumentation/base'; +import { Client } from '../src/client'; class RuntimeInst extends InstrumentationBase { static readonly metadata = { @@ -29,10 +30,10 @@ describe('InstrumentationRegistry', () => { AVAILABLE_INSTRUMENTORS: [RuntimeInst, SimpleInst] })); const { InstrumentationRegistry } = require('../src/instrumentation/registry'); - const registry = new InstrumentationRegistry(); + const registry = new InstrumentationRegistry(new Client()); registry.initialize(); expect(registry.getAvailable().length).toBe(2); - const active = registry.getActiveInstrumentors('svc'); + const active = registry.getActiveInstrumentors(); expect(active.some((i: any) => i instanceof RuntimeInst)).toBe(true); expect(active.some((i: any) => i instanceof SimpleInst)).toBe(true); }); From eb03f20ea6e6c121252709796ff5ab60a619181b Mon Sep 17 00:00:00 2001 From: Travis Dent Date: Wed, 18 Jun 2025 17:10:20 -0700 Subject: [PATCH 2/2] Add comment on indexed attributes --- src/instrumentation/openai-agents/response.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/instrumentation/openai-agents/response.ts b/src/instrumentation/openai-agents/response.ts index 87dffcb..16dc451 100644 --- a/src/instrumentation/openai-agents/response.ts +++ b/src/instrumentation/openai-agents/response.ts @@ -101,6 +101,7 @@ const RESPONSE_INPUT_FUNCTION_CALL_ATTRIBUTES: IndexedAttributeMap = { * our centralized semantic convention constants and attribute mapping system. */ export function convertResponseSpan(data: ResponseSpanData): AttributeMap { + // Include index in attribute keys so multiple prompts and completions are captured const attrs: AttributeMap = {}; Object.assign(attrs, extractAttributesFromMapping(data, RESPONSE_ATTRIBUTES));