Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,22 @@ def chat_completions_create_v_new(
capture_content = handler.should_capture_content()

def traced_method(wrapped, instance, args, kwargs):
# Materialize generator/map-backed messages/tools before they are
# consumed by either instrumentation or the SDK (fixes #825).
messages_raw = kwargs.get("messages")
if messages_raw is not None and not isinstance(messages_raw, (list, tuple)):
try:
messages_raw = list(messages_raw)
except TypeError:
pass
kwargs = {**kwargs, "messages": messages_raw}
tools_raw = kwargs.get("tools")
if tools_raw is not None and not isinstance(tools_raw, (list, tuple)):
try:
tools_raw = list(tools_raw)
except TypeError:
pass
kwargs = {**kwargs, "tools": tools_raw}
chat_invocation = create_chat_invocation(
handler, kwargs, instance, capture_content=capture_content
)
Expand Down Expand Up @@ -94,6 +110,22 @@ def async_chat_completions_create_v_new(
capture_content = handler.should_capture_content()

async def traced_method(wrapped, instance, args, kwargs):
# Materialize generator/map-backed messages/tools before they are
# consumed by either instrumentation or the SDK (fixes #825).
messages_raw = kwargs.get("messages")
if messages_raw is not None and not isinstance(messages_raw, (list, tuple)):
try:
messages_raw = list(messages_raw)
except TypeError:
pass
kwargs = {**kwargs, "messages": messages_raw}
tools_raw = kwargs.get("tools")
if tools_raw is not None and not isinstance(tools_raw, (list, tuple)):
try:
tools_raw = list(tools_raw)
except TypeError:
pass
kwargs = {**kwargs, "tools": tools_raw}
chat_invocation = create_chat_invocation(
handler, kwargs, instance, capture_content=capture_content
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""Regression test for issue #825: instrumented wrapper materializes generators."""
import os
os.environ.setdefault("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", "SPAN_ONLY")

from unittest.mock import MagicMock, AsyncMock, patch

# Import the instrumented wrappers (production code unchanged)
from opentelemetry.instrumentation.genai.openai.patch import (
chat_completions_create_v_new,
async_chat_completions_create_v_new,
)

def test_sync_wrapper_materializes_generator_messages():
mock_create = MagicMock(return_value={"choices": []})
handler = MagicMock()
handler.should_capture_content.return_value = False
wrapper = chat_completions_create_v_new(handler)
generator = (msg for msg in [{"role":"user","content":"hi"}])
wrapper(mock_create, None, [], {"messages": generator})
call_kwargs = mock_create.call_args.kwargs
assert isinstance(call_kwargs["messages"], list)
assert call_kwargs["messages"] == [{"role":"user","content":"hi"}]

def test_sync_wrapper_materializes_generator_tools():
mock_create = MagicMock(return_value={"choices": []})
handler = MagicMock()
handler.should_capture_content.return_value = False
wrapper = chat_completions_create_v_new(handler)
generator = (t for t in [{"type":"function","function":{"name":"f"}}])
wrapper(mock_create, None, [], {"messages": [{"role":"user","content":"hi"}], "tools": generator})
call_kwargs = mock_create.call_args.kwargs
assert isinstance(call_kwargs["tools"], list)
assert call_kwargs["tools"] == [{"type":"function","function":{"name":"f"}}]

def test_async_wrapper_materializes_generator_messages():
import asyncio
mock_create = AsyncMock(return_value={"choices": []})
handler = MagicMock()
handler.should_capture_content.return_value = False
wrapper = async_chat_completions_create_v_new(handler)
async def run():
generator = (msg for msg in [{"role":"user","content":"hi"}])
await wrapper(mock_create, None, [], {"messages": generator})
asyncio.run(run())
call_kwargs = mock_create.call_args.kwargs
assert isinstance(call_kwargs["messages"], list)
assert call_kwargs["messages"] == [{"role":"user","content":"hi"}]

def test_async_wrapper_materializes_generator_tools():
import asyncio
mock_create = AsyncMock(return_value={"choices": []})
handler = MagicMock()
handler.should_capture_content.return_value = False
wrapper = async_chat_completions_create_v_new(handler)
async def run():
generator = (t for t in [{"type":"function","function":{"name":"f"}}])
await wrapper(mock_create, None, [], {"messages": [{"role":"user","content":"hi"}], "tools": generator})
asyncio.run(run())
call_kwargs = mock_create.call_args.kwargs
assert isinstance(call_kwargs["tools"], list)
assert call_kwargs["tools"] == [{"type":"function","function":{"name":"f"}}]