Skip to content

Commit 8913d11

Browse files
committed
openai_v2: improve readability of responses.py
1 parent 456aafc commit 8913d11

File tree

1 file changed

+82
-162
lines changed
  • instrumentation-genai/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2

1 file changed

+82
-162
lines changed

instrumentation-genai/opentelemetry-instrumentation-openai-v2/src/opentelemetry/instrumentation/openai_v2/responses.py

Lines changed: 82 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -65,42 +65,13 @@
6565
_InputHandler = Callable[[Any, bool], _InputHandlerResult]
6666

6767

68-
def _select_input_handler(item_type: Any) -> _InputHandler:
69-
"""
70-
Select an input handler for a Responses input item.
71-
72-
We avoid enumerating every OpenAI `type` value. Instead we:
73-
- Handle a few semantically special types explicitly.
74-
- Route tool requests/results by naming patterns (`*_call`, `*_call_output`, etc.).
75-
- Fall back to `_handle_generic_input` for unknown/future types.
76-
"""
77-
handler: _InputHandler = _handle_generic_input
78-
if not isinstance(item_type, str) or not item_type:
79-
return handler
80-
81-
# Explicit "special" types where we want stable, structured bodies.
82-
if item_type == "message":
83-
handler = _handle_message_input
84-
elif item_type == "function_call":
85-
handler = _handle_function_tool_call
86-
elif item_type == "mcp_call":
87-
handler = _handle_mcp_call
88-
elif item_type == "reasoning":
89-
handler = _handle_reasoning_item
90-
elif item_type == "image_generation_call":
91-
handler = _handle_image_generation_call
92-
# Tool call results sent back to the model.
93-
elif (
94-
item_type.endswith("_call_output")
95-
or item_type.endswith("_output")
96-
or item_type.endswith("_response")
97-
):
98-
handler = _handle_tool_call_output_input
99-
# Tool calls requested by the model.
100-
elif item_type.endswith("_call") or item_type.startswith("mcp_"):
101-
handler = _handle_tool_call_input
102-
103-
return handler
68+
def _put(body: dict[str, Any], key: str, value: Any) -> None:
69+
"""Set `body[key] = value` only when value is meaningfully present."""
70+
if value is None:
71+
return
72+
if isinstance(value, str) and not value:
73+
return
74+
body[key] = value
10475

10576

10677
def _create_log_record(
@@ -113,17 +84,26 @@ def _create_log_record(
11384
)
11485

11586

87+
def _tool_item_id(value: Any) -> Any:
88+
return get_property_value(value, "call_id") or get_property_value(value, "id")
89+
90+
91+
def _tool_item_base_body(
92+
value: Any, *, include_name: bool = False, include_type: bool = False
93+
) -> _InputBody:
94+
body: _InputBody = {}
95+
_put(body, "id", _tool_item_id(value))
96+
if include_name:
97+
_put(body, "name", get_property_value(value, "name"))
98+
if include_type:
99+
_put(body, "type", get_property_value(value, "type"))
100+
return body
101+
102+
116103
def responses_input_to_event(
117104
input_value: str | ResponseInputItemParam, capture_content: bool
118105
) -> LogRecord | None:
119-
"""
120-
Convert a single Responses API input item to an input log event.
121-
122-
Flow:
123-
- Determine the item "type"
124-
- Dispatch to a handler that extracts a minimal body + role
125-
- Build a `gen_ai.<role>.input` `LogRecord`
126-
"""
106+
"""Convert one Responses input item into a `gen_ai.<role>.input` `LogRecord`."""
127107
if isinstance(input_value, str):
128108
body = {"content": input_value} if capture_content else {}
129109
return _create_log_record(
@@ -132,11 +112,44 @@ def responses_input_to_event(
132112
)
133113

134114
item_type = get_property_value(input_value, "type")
135-
handler = _select_input_handler(item_type)
115+
116+
# Pattern-based routing avoids having to enumerate every OpenAI `type` string.
117+
handler: _InputHandler = _input_generic
118+
if isinstance(item_type, str) and item_type:
119+
if item_type == "message":
120+
handler = _input_message
121+
elif item_type == "function_call":
122+
handler = _input_function_tool_call
123+
elif item_type == "mcp_call":
124+
handler = _input_mcp_call
125+
elif item_type == "reasoning":
126+
handler = _input_reasoning
127+
elif item_type == "image_generation_call":
128+
handler = _input_image_generation_call
129+
elif (
130+
item_type.endswith("_call_output")
131+
or item_type.endswith("_output")
132+
or item_type.endswith("_response")
133+
):
134+
handler = _input_tool_call_output
135+
elif item_type.endswith("_call") or item_type.startswith("mcp_"):
136+
handler = _input_tool_call
137+
136138
role, body = handler(input_value, capture_content)
137139
return _create_log_record(_EVENT_NAME_INPUT_FMT.format(role=role), body)
138140

139141

142+
def output_to_event(
143+
output: ResponseOutputItem, capture_content: bool
144+
) -> LogRecord | None:
145+
"""Convert one Responses output item into a `gen_ai.output` `LogRecord`."""
146+
body = _base_output_body(output)
147+
output_type = getattr(output, "type", None)
148+
handler = _OUTPUT_TYPE_HANDLERS.get(output_type, _output_unknown_body)
149+
body.update(handler(output, capture_content))
150+
return _create_log_record(_EVENT_NAME_OUTPUT, body)
151+
152+
140153
def _base_output_body(output: ResponseOutputItem) -> dict[str, Any]:
141154
body: dict[str, Any] = {}
142155

@@ -187,9 +200,7 @@ def _output_reasoning_body(
187200
) -> dict[str, Any]:
188201
body: dict[str, Any] = {"type": "reasoning"}
189202

190-
item_id = getattr(output, "id", None)
191-
if item_id:
192-
body["id"] = item_id
203+
_put(body, "id", getattr(output, "id", None))
193204

194205
if capture_content:
195206
summary = getattr(output, "summary", None)
@@ -207,12 +218,8 @@ def _output_tool_call_body(
207218
body: dict[str, Any] = {"type": output_type}
208219

209220
call_id = getattr(output, "call_id", None) or getattr(output, "id", None)
210-
if call_id:
211-
body["id"] = call_id
212-
213-
name = getattr(output, "name", None)
214-
if name:
215-
body["name"] = name
221+
_put(body, "id", call_id)
222+
_put(body, "name", getattr(output, "name", None))
216223

217224
return body
218225

@@ -228,35 +235,12 @@ def _output_unknown_body(
228235
"message": _output_message_body,
229236
"function_call": _output_function_call_body,
230237
"reasoning": _output_reasoning_body,
238+
**{t: _output_tool_call_body for t in _TOOL_CALL_OUTPUT_TYPES},
231239
}
232240

233-
234-
def output_to_event(
235-
output: ResponseOutputItem, capture_content: bool
236-
) -> LogRecord | None:
237-
"""
238-
Convert a single Responses API output item to an output log event.
239-
240-
Flow:
241-
- Extract common output fields (`index`, `finish_reason`)
242-
- Dispatch by `output.type` to a small body builder
243-
- Build a `gen_ai.output` `LogRecord`
244-
"""
245-
body = _base_output_body(output)
246-
output_type = getattr(output, "type", None)
247-
if output_type in _TOOL_CALL_OUTPUT_TYPES:
248-
body.update(_output_tool_call_body(output, capture_content))
249-
else:
250-
handler = _OUTPUT_TYPE_HANDLERS.get(output_type, _output_unknown_body)
251-
body.update(handler(output, capture_content))
252-
253-
return _create_log_record(_EVENT_NAME_OUTPUT, body)
254-
255-
256-
def _handle_message_input(
241+
def _input_message(
257242
input_value: Any, capture_content: bool
258243
) -> _InputHandlerResult:
259-
"""Message: { content: ContentPart[], role, type: "message" }"""
260244
role = get_property_value(input_value, "role") or "user"
261245
body: _InputBody = {}
262246

@@ -270,24 +254,13 @@ def _handle_message_input(
270254
return role, body
271255

272256

273-
def _handle_mcp_call(
257+
def _input_mcp_call(
274258
input_value: Any, capture_content: bool
275259
) -> _InputHandlerResult:
276-
"""McpCall: { id, name, arguments, output?, type: "mcp_call" }"""
277-
body: _InputBody = {}
278-
279-
item_id = get_property_value(input_value, "id")
280-
if item_id:
281-
body["id"] = item_id
282-
283-
name = get_property_value(input_value, "name")
284-
if name:
285-
body["name"] = name
260+
body = _tool_item_base_body(input_value, include_name=True)
286261

287262
if capture_content:
288-
arguments = get_property_value(input_value, "arguments")
289-
if arguments:
290-
body["arguments"] = arguments
263+
_put(body, "arguments", get_property_value(input_value, "arguments"))
291264

292265
output = get_property_value(input_value, "output")
293266
if output and isinstance(output, str):
@@ -296,10 +269,9 @@ def _handle_mcp_call(
296269
return "tool", body
297270

298271

299-
def _handle_function_tool_call(
272+
def _input_function_tool_call(
300273
input_value: Any, capture_content: bool
301274
) -> _InputHandlerResult:
302-
"""FunctionToolCall: { call_id, name, arguments, type: "function_call" }"""
303275
body: _InputBody = {}
304276

305277
call_id = get_property_value(input_value, "call_id")
@@ -321,15 +293,11 @@ def _handle_function_tool_call(
321293
return "assistant", body
322294

323295

324-
def _handle_reasoning_item(
296+
def _input_reasoning(
325297
input_value: Any, capture_content: bool
326298
) -> _InputHandlerResult:
327-
"""ReasoningItem: { id, summary: ContentPart[], type: "reasoning" }"""
328299
body: _InputBody = {}
329-
330-
item_id = get_property_value(input_value, "id")
331-
if item_id:
332-
body["id"] = item_id
300+
_put(body, "id", get_property_value(input_value, "id"))
333301

334302
if capture_content:
335303
summary = get_property_value(input_value, "summary")
@@ -341,53 +309,23 @@ def _handle_reasoning_item(
341309
return "assistant", body
342310

343311

344-
def _handle_tool_call_input(
312+
def _input_tool_call(
345313
input_value: Any, capture_content: bool
346314
) -> _InputHandlerResult:
347-
"""Generic handler for tool call inputs (file_search, web_search, computer, code_interpreter, etc.)."""
348-
body: _InputBody = {}
349-
350-
call_id = get_property_value(input_value, "call_id") or get_property_value(
351-
input_value, "id"
352-
)
353-
if call_id:
354-
body["id"] = call_id
355-
356-
name = get_property_value(input_value, "name")
357-
if name:
358-
body["name"] = name
359-
360-
item_type = get_property_value(input_value, "type")
361-
if item_type:
362-
body["type"] = item_type
315+
body = _tool_item_base_body(input_value, include_name=True, include_type=True)
363316

364317
if capture_content:
365-
query = get_property_value(input_value, "query")
366-
if query:
367-
body["query"] = query
368-
369-
arguments = get_property_value(input_value, "arguments")
370-
if arguments:
371-
body["arguments"] = arguments
372-
373-
code = get_property_value(input_value, "code")
374-
if code:
375-
body["content"] = code
318+
_put(body, "query", get_property_value(input_value, "query"))
319+
_put(body, "arguments", get_property_value(input_value, "arguments"))
320+
_put(body, "content", get_property_value(input_value, "code"))
376321

377322
return "assistant", body
378323

379324

380-
def _handle_tool_call_output_input(
325+
def _input_tool_call_output(
381326
input_value: Any, capture_content: bool
382327
) -> _InputHandlerResult:
383-
"""Generic handler for tool call output inputs (tool results sent back to the model)."""
384-
body: _InputBody = {}
385-
386-
call_id = get_property_value(input_value, "call_id") or get_property_value(
387-
input_value, "id"
388-
)
389-
if call_id:
390-
body["id"] = call_id
328+
body = _tool_item_base_body(input_value)
391329

392330
if capture_content:
393331
output = get_property_value(input_value, "output")
@@ -406,19 +344,12 @@ def _handle_tool_call_output_input(
406344
return "tool", body
407345

408346

409-
def _handle_image_generation_call(
347+
def _input_image_generation_call(
410348
input_value: Any, capture_content: bool
411349
) -> _InputHandlerResult:
412-
"""ImageGenerationCall: { id, result, status, type: "image_generation_call" }"""
413350
body: _InputBody = {}
414-
415-
item_id = get_property_value(input_value, "id")
416-
if item_id:
417-
body["id"] = item_id
418-
419-
status = get_property_value(input_value, "status")
420-
if status:
421-
body["status"] = status
351+
_put(body, "id", get_property_value(input_value, "id"))
352+
_put(body, "status", get_property_value(input_value, "status"))
422353

423354
if capture_content:
424355
result = get_property_value(input_value, "result")
@@ -428,22 +359,13 @@ def _handle_image_generation_call(
428359
return "assistant", body
429360

430361

431-
def _handle_generic_input(
362+
def _input_generic(
432363
input_value: Any, capture_content: bool
433364
) -> _InputHandlerResult:
434-
"""Fallback handler for unknown input types."""
435365
role = get_property_value(input_value, "role") or "user"
436366
body: _InputBody = {}
437-
438-
item_id = get_property_value(input_value, "id") or get_property_value(
439-
input_value, "call_id"
440-
)
441-
if item_id:
442-
body["id"] = item_id
443-
444-
item_type = get_property_value(input_value, "type")
445-
if item_type:
446-
body["type"] = item_type
367+
_put(body, "id", get_property_value(input_value, "id") or get_property_value(input_value, "call_id"))
368+
_put(body, "type", get_property_value(input_value, "type"))
447369

448370
if capture_content:
449371
content = get_property_value(input_value, "content")
@@ -459,14 +381,12 @@ def _handle_generic_input(
459381

460382

461383
def _extract_text_from_content_parts(content_parts) -> str | None:
462-
"""Extract text content from a list of content parts."""
463384
if not content_parts:
464385
return None
465386

466387
if isinstance(content_parts, str):
467388
return content_parts
468389

469-
# Handle non-iterable types gracefully
470390
if not isinstance(content_parts, (list, tuple)):
471391
return None
472392

0 commit comments

Comments
 (0)