Record actual served model as response model for Azure OpenAI

This commit is contained in:
Tao Chen
2026-05-17 19:50:21 -07:00
Unverified
parent da308f5f1e
commit 3f65aa055f
4 changed files with 390 additions and 39 deletions
@@ -101,6 +101,12 @@ INNER_RESPONSE_TELEMETRY_CAPTURED_FIELDS: Final[contextvars.ContextVar[set[str]
INNER_RESPONSE_ID_CAPTURED_FIELD: Final[str] = "response_id"
INNER_USAGE_CAPTURED_FIELD: Final[str] = "usage"
# Response header set by Azure OpenAI naming the model that actually served the
# request (which can differ from the deployment alias the caller sent). Chat
# clients may surface this on ``ChatResponse.additional_properties`` so the
# telemetry layer can promote it to ``gen_ai.response.model``.
AZURE_OPENAI_SERVED_MODEL_HEADER: Final[str] = "x-ms-served-model"
# Tracks accumulated token usage from all inner chat completion spans within an agent invoke.
INNER_ACCUMULATED_USAGE: Final[contextvars.ContextVar[UsageDetails | None]] = contextvars.ContextVar(
"inner_accumulated_usage", default=None
@@ -2125,6 +2131,14 @@ def _get_response_attributes(
attributes[OtelAttr.FINISH_REASONS] = json.dumps([finish_reason])
if model := getattr(response, "model", None):
attributes[OtelAttr.RESPONSE_MODEL] = model
# If the underlying provider reports the actually served model via the
# ``x-ms-served-model`` response header (Azure OpenAI), prefer it over the
# model reported on the response body for the response model attribute.
additional_properties = getattr(response, "additional_properties", None)
if isinstance(additional_properties, Mapping):
candidate = cast("Mapping[str, Any]", additional_properties).get(AZURE_OPENAI_SERVED_MODEL_HEADER)
if isinstance(candidate, str) and candidate:
attributes[OtelAttr.RESPONSE_MODEL] = candidate
if capture_usage and (usage := response.usage_details):
input_tokens = usage.get("input_token_count")
if input_tokens:
@@ -1739,6 +1739,78 @@ def test_get_response_attributes_capture_response_id_false():
assert OtelAttr.RESPONSE_ID not in result
def test_get_response_attributes_served_model_overrides_response_model():
"""When the response carries the Azure ``x-ms-served-model`` header, it should override RESPONSE_MODEL."""
from unittest.mock import Mock
from agent_framework.observability import (
AZURE_OPENAI_SERVED_MODEL_HEADER,
OtelAttr,
_get_response_attributes,
)
response = Mock()
response.response_id = None
response.finish_reason = None
response.raw_representation = None
response.usage_details = None
response.model = "gpt-4"
response.additional_properties = {AZURE_OPENAI_SERVED_MODEL_HEADER: "gpt-4o-2024-08-06"}
attrs = {OtelAttr.REQUEST_MODEL: "my-deployment-alias"}
result = _get_response_attributes(attrs, response)
# REQUEST_MODEL is left untouched; RESPONSE_MODEL is overridden by the served-model header.
assert result[OtelAttr.REQUEST_MODEL] == "my-deployment-alias"
assert result[OtelAttr.RESPONSE_MODEL] == "gpt-4o-2024-08-06"
def test_get_response_attributes_no_served_model_keeps_response_model():
"""Without the served-model header RESPONSE_MODEL should reflect the response's reported model."""
from unittest.mock import Mock
from agent_framework.observability import OtelAttr, _get_response_attributes
response = Mock()
response.response_id = None
response.finish_reason = None
response.raw_representation = None
response.usage_details = None
response.model = "gpt-4"
response.additional_properties = {}
attrs = {OtelAttr.REQUEST_MODEL: "my-deployment-alias"}
result = _get_response_attributes(attrs, response)
assert result[OtelAttr.REQUEST_MODEL] == "my-deployment-alias"
assert result[OtelAttr.RESPONSE_MODEL] == "gpt-4"
def test_get_response_attributes_ignores_non_string_served_model():
"""A non-string / empty value in the served-model header should not override RESPONSE_MODEL."""
from unittest.mock import Mock
from agent_framework.observability import (
AZURE_OPENAI_SERVED_MODEL_HEADER,
OtelAttr,
_get_response_attributes,
)
response = Mock()
response.response_id = None
response.finish_reason = None
response.raw_representation = None
response.usage_details = None
response.model = "gpt-4"
response.additional_properties = {AZURE_OPENAI_SERVED_MODEL_HEADER: ""}
attrs = {OtelAttr.REQUEST_MODEL: "my-deployment-alias"}
result = _get_response_attributes(attrs, response)
assert result[OtelAttr.REQUEST_MODEL] == "my-deployment-alias"
assert result[OtelAttr.RESPONSE_MODEL] == "gpt-4"
# region Test _get_exporters_from_env
@@ -2481,6 +2553,28 @@ def test_capture_response(span_exporter: InMemorySpanExporter):
assert spans[0].attributes.get(OtelAttr.OUTPUT_TOKENS) == 50
def test_capture_response_does_not_update_span_name_with_request_model(span_exporter: InMemorySpanExporter):
"""_capture_response should not rename the span even when REQUEST_MODEL is set."""
from agent_framework.observability import OtelAttr, _capture_response, get_tracer
span_exporter.clear()
tracer = get_tracer()
attrs = {
OtelAttr.OPERATION: "chat",
OtelAttr.REQUEST_MODEL: "my-deployment-alias",
OtelAttr.RESPONSE_MODEL: "gpt-4o-2024-08-06",
}
with tracer.start_as_current_span("chat my-deployment-alias") as span:
_capture_response(span=span, attributes=attrs)
spans = span_exporter.get_finished_spans()
assert len(spans) == 1
assert spans[0].name == "chat my-deployment-alias"
assert spans[0].attributes.get(OtelAttr.RESPONSE_MODEL) == "gpt-4o-2024-08-06"
async def test_layer_ordering_span_sequence_with_function_calling(span_exporter: InMemorySpanExporter):
"""Test that with correct layer ordering, spans appear in the expected sequence.
@@ -62,8 +62,9 @@ from agent_framework.exceptions import (
ChatClientException,
ChatClientInvalidRequestException,
)
from agent_framework.observability import ChatTelemetryLayer
from agent_framework.observability import AZURE_OPENAI_SERVED_MODEL_HEADER, ChatTelemetryLayer
from openai import AsyncAzureOpenAI, AsyncOpenAI, BadRequestError
from openai.lib._parsing._responses import type_to_text_format_param as _type_to_text_format_param
from openai.types.responses import FunctionShellTool
from openai.types.responses.file_search_tool_param import FileSearchToolParam
from openai.types.responses.function_tool_param import FunctionToolParam
@@ -359,6 +360,11 @@ class RawOpenAIChatClient( # type: ignore[misc]
STORES_BY_DEFAULT: ClassVar[bool] = True # type: ignore[reportIncompatibleVariableOverride, misc]
SUPPORTS_RICH_FUNCTION_OUTPUT: ClassVar[bool] = True
# Azure OpenAI may include this header in responses for the actual model that served the request,
# instead of the deployment name sent in the request. Surface it on the ChatResponse when present
# for better observability.
SERVED_MODEL_HEADER: ClassVar[str] = AZURE_OPENAI_SERVED_MODEL_HEADER
FILE_SEARCH_MAX_RESULTS: int = 50
@overload
@@ -614,17 +620,24 @@ class RawOpenAIChatClient( # type: ignore[misc]
client = self.client
validated_options = await self._validate_options(options)
try:
stream_response = await client.responses.retrieve(
raw_stream_response = await client.responses.with_raw_response.retrieve(
continuation_token["response_id"],
stream=True,
)
served_model = self._extract_served_model(raw_stream_response.headers)
stream_response = raw_stream_response.parse()
async for chunk in stream_response:
yield self._parse_chunk_from_openai(
update = self._parse_chunk_from_openai(
chunk,
options=validated_options,
function_call_ids=function_call_ids,
seen_reasoning_delta_item_ids=seen_reasoning_delta_item_ids,
)
if served_model is not None:
if update.additional_properties is None:
update.additional_properties = {}
update.additional_properties[self.SERVED_MODEL_HEADER] = served_model
yield update
except Exception as ex:
self._handle_request_error(ex)
else:
@@ -633,24 +646,35 @@ class RawOpenAIChatClient( # type: ignore[misc]
run_options,
validated_options,
) = await self._prepare_request(messages, options)
# Translate `text_format` (Pydantic model) into `text.format` JSON-schema
# config so we can always go through `responses.create(stream=True, ...)` —
# the only path that exposes raw HTTP headers via `with_raw_response`.
# The SDK's `responses.stream(...)` helper does the same translation
# internally but does not surface headers, so we replicate it here.
text_format = run_options.pop("text_format", None)
if text_format is not None:
text_cfg = dict(run_options.get("text") or {})
if "format" in text_cfg:
raise ChatClientInvalidRequestException("Cannot mix and match text.format with text_format")
text_cfg["format"] = _type_to_text_format_param(text_format)
run_options["text"] = text_cfg
try:
if "text_format" in run_options:
async with client.responses.stream(**run_options) as response:
async for chunk in response:
yield self._parse_chunk_from_openai(
chunk,
options=validated_options,
function_call_ids=function_call_ids,
seen_reasoning_delta_item_ids=seen_reasoning_delta_item_ids,
)
else:
async for chunk in await client.responses.create(stream=True, **run_options):
yield self._parse_chunk_from_openai(
chunk,
options=validated_options,
function_call_ids=function_call_ids,
seen_reasoning_delta_item_ids=seen_reasoning_delta_item_ids,
)
raw_create_response = await client.responses.with_raw_response.create(
stream=True, **run_options
)
served_model = self._extract_served_model(raw_create_response.headers)
async for chunk in raw_create_response.parse():
update = self._parse_chunk_from_openai(
chunk,
options=validated_options,
function_call_ids=function_call_ids,
seen_reasoning_delta_item_ids=seen_reasoning_delta_item_ids,
)
if served_model is not None:
if update.additional_properties is None:
update.additional_properties = {}
update.additional_properties[self.SERVED_MODEL_HEADER] = served_model
yield update
except Exception as ex:
self._handle_request_error(ex)
@@ -664,10 +688,12 @@ class RawOpenAIChatClient( # type: ignore[misc]
client = self.client
validated_options = await self._validate_options(options)
try:
response = await client.responses.retrieve(continuation_token["response_id"])
raw_response = await client.responses.with_raw_response.retrieve(continuation_token["response_id"])
response = raw_response.parse()
except Exception as ex:
self._handle_request_error(ex)
chat_response = self._parse_response_from_openai(response, options=validated_options)
self._attach_served_model_header(chat_response, raw_response.headers)
# Once the background response completes, drop the continuation_token from
# the caller's options dict. FunctionInvocationLayer reuses the same dict
# across tool-loop iterations, so leaving it in place makes the next iteration
@@ -680,15 +706,33 @@ class RawOpenAIChatClient( # type: ignore[misc]
client, run_options, validated_options = await self._prepare_request(messages, options)
try:
if "text_format" in run_options:
response = await client.responses.parse(stream=False, **run_options)
raw_response = await client.responses.with_raw_response.parse(stream=False, **run_options)
else:
response = await client.responses.create(stream=False, **run_options)
raw_response = await client.responses.with_raw_response.create(stream=False, **run_options)
response = raw_response.parse()
except Exception as ex:
self._handle_request_error(ex)
return self._parse_response_from_openai(response, options=validated_options)
chat_response = self._parse_response_from_openai(response, options=validated_options)
self._attach_served_model_header(chat_response, raw_response.headers)
return chat_response
return _get_response()
@classmethod
def _attach_served_model_header(cls, chat_response: ChatResponse, headers: Any) -> None:
"""Surface the ``x-ms-served-model`` response header on the ChatResponse when present."""
served_model = cls._extract_served_model(headers)
if served_model is None:
return
chat_response.additional_properties[cls.SERVED_MODEL_HEADER] = served_model
@classmethod
def _extract_served_model(cls, headers: Any) -> str | None:
"""Return the ``x-ms-served-model`` response header value when present."""
if headers is None:
return None
return headers.get(cls.SERVED_MODEL_HEADER)
def _prepare_response_and_text_format(
self,
*,
@@ -1429,9 +1473,10 @@ class RawOpenAIChatClient( # type: ignore[misc]
props = content.additional_properties or {}
# Local-shell variant serializes as `local_shell_call` carrying a server-issued id;
# plain function_call_output pairs by call_id and is safe under storage.
if (
props.get(OPENAI_SHELL_OUTPUT_TYPE_KEY) == OPENAI_SHELL_OUTPUT_TYPE_LOCAL_SHELL_CALL
and props.get(OPENAI_LOCAL_SHELL_CALL_ITEM_ID_KEY)
if props.get(
OPENAI_SHELL_OUTPUT_TYPE_KEY
) == OPENAI_SHELL_OUTPUT_TYPE_LOCAL_SHELL_CALL and props.get(
OPENAI_LOCAL_SHELL_CALL_ITEM_ID_KEY
):
continue
new_args: dict[str, Any] = {}
@@ -72,9 +72,10 @@ class OutputStruct(BaseModel):
class _FakeAsyncEventStream:
def __init__(self, events: list[object]) -> None:
def __init__(self, events: list[object], headers: dict[str, str] | None = None) -> None:
self._events = events
self._iterator = iter(())
self._headers = headers or {}
def __aiter__(self) -> "_FakeAsyncEventStream":
self._iterator = iter(self._events)
@@ -86,6 +87,32 @@ class _FakeAsyncEventStream:
except StopIteration as exc:
raise StopAsyncIteration from exc
# The chat client now consumes the streaming response via ``with_raw_response``,
# which returns a wrapper exposing ``.parse()`` (the underlying iterable) and
# ``.headers``. Mimic that interface so test mocks remain a single object.
def parse(self) -> "_FakeAsyncEventStream":
return self
@property
def headers(self) -> dict[str, str]:
return self._headers
def _as_raw(mock_response: MagicMock) -> MagicMock:
"""Make ``mock_response`` look like an OpenAI ``with_raw_response`` wrapper.
The chat client now calls ``responses.with_raw_response.{create,parse,retrieve}``
and then ``.parse()`` on the returned wrapper to get the actual response payload,
plus ``.headers`` to surface the ``x-ms-served-model`` Azure header. Tests still
patch the underlying ``responses.{create,parse,retrieve}`` methods (the SDK's
raw-response wrapper internally delegates to these), so the patched return value
is what our code unwraps. Setting ``mock_response.parse`` to return the mock
itself lets the existing assertions on ``mock_response.id`` etc. continue to work.
"""
mock_response.parse = MagicMock(return_value=mock_response)
mock_response.headers = {}
return mock_response
class _FakeAsyncEventStreamContext(_FakeAsyncEventStream):
async def __aenter__(self) -> "_FakeAsyncEventStreamContext":
@@ -477,7 +504,7 @@ async def test_response_format_parse_path() -> None:
mock_parsed_response.finish_reason = None
mock_parsed_response.conversation = None # No conversation object
with patch.object(client.client.responses, "parse", return_value=mock_parsed_response):
with patch.object(client.client.responses, "parse", return_value=_as_raw(mock_parsed_response)):
response = await client.get_response(
messages=[Message(role="user", contents=["Test message"])],
options={"response_format": OutputStruct, "store": True},
@@ -504,7 +531,7 @@ async def test_response_format_parse_path_with_conversation_id() -> None:
mock_parsed_response.conversation = MagicMock()
mock_parsed_response.conversation.id = "conversation_456"
with patch.object(client.client.responses, "parse", return_value=mock_parsed_response):
with patch.object(client.client.responses, "parse", return_value=_as_raw(mock_parsed_response)):
response = await client.get_response(
messages=[Message(role="user", contents=["Test message"])],
options={"response_format": OutputStruct, "store": True},
@@ -542,7 +569,7 @@ async def test_response_format_dict_parse_path() -> None:
mock_message_item.content = [mock_message_content]
mock_response.output = [mock_message_item]
with patch.object(client.client.responses, "create", return_value=mock_response):
with patch.object(client.client.responses, "create", return_value=_as_raw(mock_response)):
response = await client.get_response(
messages=[Message(role="user", contents=["Test message"])],
options={"response_format": response_format},
@@ -554,6 +581,171 @@ async def test_response_format_dict_parse_path() -> None:
assert response.value["answer"] == "Parsed"
async def test_served_model_header_captured_on_response() -> None:
"""The ``x-ms-served-model`` Azure response header should be surfaced on ChatResponse.additional_properties."""
from agent_framework.observability import AZURE_OPENAI_SERVED_MODEL_HEADER
client = OpenAIChatClient(model="test-model", api_key="test-key")
mock_response = MagicMock()
mock_response.id = "response_123"
mock_response.model = "test-model"
mock_response.created_at = 1000000000
mock_response.metadata = {}
mock_response.output_parsed = None
mock_response.output = []
mock_response.usage = None
mock_response.finish_reason = None
mock_response.conversation = None
mock_response.status = "completed"
raw = _as_raw(mock_response)
raw.headers = {AZURE_OPENAI_SERVED_MODEL_HEADER: "gpt-4o-2024-08-06"}
with patch.object(client.client.responses, "create", return_value=raw):
response = await client.get_response(
messages=[Message(role="user", contents=["Test message"])],
)
assert response.additional_properties.get(AZURE_OPENAI_SERVED_MODEL_HEADER) == "gpt-4o-2024-08-06"
async def test_served_model_header_absent_does_not_set_property() -> None:
"""When the served-model header is missing the key should not appear on additional_properties."""
from agent_framework.observability import AZURE_OPENAI_SERVED_MODEL_HEADER
client = OpenAIChatClient(model="test-model", api_key="test-key")
mock_response = MagicMock()
mock_response.id = "response_123"
mock_response.model = "test-model"
mock_response.created_at = 1000000000
mock_response.metadata = {}
mock_response.output_parsed = None
mock_response.output = []
mock_response.usage = None
mock_response.finish_reason = None
mock_response.conversation = None
mock_response.status = "completed"
# _as_raw sets headers to {} by default — i.e. no x-ms-served-model.
with patch.object(client.client.responses, "create", return_value=_as_raw(mock_response)):
response = await client.get_response(
messages=[Message(role="user", contents=["Test message"])],
)
assert AZURE_OPENAI_SERVED_MODEL_HEADER not in response.additional_properties
async def test_served_model_header_captured_on_parse_path() -> None:
"""The served-model header should also be captured on the structured-output (parse) path."""
from agent_framework.observability import AZURE_OPENAI_SERVED_MODEL_HEADER
client = OpenAIChatClient(model="test-model", api_key="test-key")
mock_parsed_response = MagicMock()
mock_parsed_response.id = "parsed_response_123"
mock_parsed_response.text = "Parsed response"
mock_parsed_response.model = "test-model"
mock_parsed_response.created_at = 1000000000
mock_parsed_response.metadata = {}
mock_parsed_response.output_parsed = None
mock_parsed_response.usage = None
mock_parsed_response.finish_reason = None
mock_parsed_response.conversation = None
raw = _as_raw(mock_parsed_response)
raw.headers = {AZURE_OPENAI_SERVED_MODEL_HEADER: "gpt-4o-2024-08-06"}
with patch.object(client.client.responses, "parse", return_value=raw):
response = await client.get_response(
messages=[Message(role="user", contents=["Test message"])],
options={"response_format": OutputStruct, "store": True},
)
assert response.additional_properties.get(AZURE_OPENAI_SERVED_MODEL_HEADER) == "gpt-4o-2024-08-06"
async def test_served_model_header_propagated_to_streaming_updates() -> None:
"""In streaming mode the served-model header should be set on every ChatResponseUpdate."""
from agent_framework.observability import AZURE_OPENAI_SERVED_MODEL_HEADER
client = OpenAIChatClient(model="test-model", api_key="test-key")
events = [
ResponseTextDeltaEvent(
type="response.output_text.delta",
content_index=0,
item_id="text_item",
output_index=0,
sequence_number=1,
logprobs=[],
delta="Hello",
),
ResponseTextDeltaEvent(
type="response.output_text.delta",
content_index=0,
item_id="text_item",
output_index=0,
sequence_number=2,
logprobs=[],
delta=" world",
),
]
fake_stream = _FakeAsyncEventStream(
events,
headers={AZURE_OPENAI_SERVED_MODEL_HEADER: "gpt-4o-2024-08-06"},
)
with (
patch.object(client, "_prepare_request", new=AsyncMock(return_value=(client.client, {}, {}))),
patch.object(client.client.responses, "create", new=AsyncMock(return_value=fake_stream)),
patch.object(client, "_get_metadata_from_response", return_value={}),
):
stream = client._inner_get_response(messages=[Message(role="user", contents=["Hi"])], options={}, stream=True)
updates = [update async for update in stream]
assert updates, "Expected at least one streaming update"
for update in updates:
assert update.additional_properties is not None
assert update.additional_properties.get(AZURE_OPENAI_SERVED_MODEL_HEADER) == "gpt-4o-2024-08-06"
async def test_served_model_header_absent_in_streaming_updates() -> None:
"""When the header is missing in streaming mode it should not be added to update properties."""
from agent_framework.observability import AZURE_OPENAI_SERVED_MODEL_HEADER
client = OpenAIChatClient(model="test-model", api_key="test-key")
events = [
ResponseTextDeltaEvent(
type="response.output_text.delta",
content_index=0,
item_id="text_item",
output_index=0,
sequence_number=1,
logprobs=[],
delta="Hello",
),
]
fake_stream = _FakeAsyncEventStream(events) # default empty headers
with (
patch.object(client, "_prepare_request", new=AsyncMock(return_value=(client.client, {}, {}))),
patch.object(client.client.responses, "create", new=AsyncMock(return_value=fake_stream)),
patch.object(client, "_get_metadata_from_response", return_value={}),
):
stream = client._inner_get_response(messages=[Message(role="user", contents=["Hi"])], options={}, stream=True)
updates = [update async for update in stream]
assert updates, "Expected at least one streaming update"
for update in updates:
if update.additional_properties is not None:
assert AZURE_OPENAI_SERVED_MODEL_HEADER not in update.additional_properties
async def test_bad_request_error_non_content_filter() -> None:
"""Test get_response BadRequestError without content_filter."""
client = OpenAIChatClient(model="test-model", api_key="test-key")
@@ -953,7 +1145,9 @@ async def test_local_shell_tool_is_invoked_in_function_loop() -> None:
mock_text_item.content = [mock_text_content]
mock_response2.output = [mock_text_item]
with patch.object(client.client.responses, "create", side_effect=[mock_response1, mock_response2]) as mock_create:
with patch.object(
client.client.responses, "create", side_effect=[_as_raw(mock_response1), _as_raw(mock_response2)]
) as mock_create:
await client.get_response(
messages=[Message(role="user", contents=["What Python version is available?"])],
options={"tools": [local_shell_tool]},
@@ -1026,7 +1220,9 @@ async def test_shell_call_is_invoked_as_local_shell_function_loop() -> None:
mock_text_item.content = [mock_text_content]
mock_response2.output = [mock_text_item]
with patch.object(client.client.responses, "create", side_effect=[mock_response1, mock_response2]) as mock_create:
with patch.object(
client.client.responses, "create", side_effect=[_as_raw(mock_response1), _as_raw(mock_response2)]
) as mock_create:
await client.get_response(
messages=[Message(role="user", contents=["What Python version is available?"])],
options={"tools": [local_shell_tool]},
@@ -1097,7 +1293,9 @@ async def test_tool_loop_store_false_omits_reasoning_items_from_second_request()
mock_text_item.content = [mock_text_content]
mock_response2.output = [mock_text_item]
with patch.object(client.client.responses, "create", side_effect=[mock_response1, mock_response2]) as mock_create:
with patch.object(
client.client.responses, "create", side_effect=[_as_raw(mock_response1), _as_raw(mock_response2)]
) as mock_create:
response = await client.get_response(
messages=[Message(role="user", contents=["What's the weather in Amsterdam?"])],
options={
@@ -2810,7 +3008,9 @@ async def test_end_to_end_mcp_approval_flow(span_exporter) -> None:
mock_response2.output = [mock_text_item]
# Patch the create call to return the two mocked responses in sequence
with patch.object(client.client.responses, "create", side_effect=[mock_response1, mock_response2]) as mock_create:
with patch.object(
client.client.responses, "create", side_effect=[_as_raw(mock_response1), _as_raw(mock_response2)]
) as mock_create:
# First call: get the approval request
response = await client.get_response(messages=[Message(role="user", contents=["Trigger approval"])])
assert response.messages[0].contents[0].type == "function_approval_request"
@@ -3235,7 +3435,7 @@ async def test_inner_get_response_streaming_with_response_format_tracks_reasonin
"_prepare_request",
new=AsyncMock(return_value=(client.client, {"text_format": OutputStruct}, {})),
),
patch.object(client.client.responses, "stream", return_value=_FakeAsyncEventStreamContext(events)),
patch.object(client.client.responses, "create", new=AsyncMock(return_value=_FakeAsyncEventStream(events))),
patch.object(client, "_get_metadata_from_response", return_value={}),
):
stream = client._inner_get_response(messages=messages, options={}, stream=True)
@@ -4120,9 +4320,7 @@ async def test_prepare_options_with_conversation_id_strips_server_items_for_mixe
types = [item.get("type") for item in options["input"]]
assert "reasoning" not in types
assert "function_call" not in types
output_call_ids = {
item["call_id"] for item in options["input"] if item.get("type") == "function_call_output"
}
output_call_ids = {item["call_id"] for item in options["input"] if item.get("type") == "function_call_output"}
assert output_call_ids == {"call_history", "call_live"}
assert options["previous_response_id"] == "resp_prev123"