This commit is contained in:
Mery-Sanz 2025-04-11 13:49:06 +02:00
parent 5cd571976a
commit 6bafa0235f
2 changed files with 117 additions and 1 deletions

2
.gitignore vendored
View File

@ -146,4 +146,4 @@ cython_debug/
# CAI files
.cai/
.vscode/
cai_env/

View File

@ -174,3 +174,119 @@ async def test_get_response_with_tool_call(monkeypatch) -> None:
assert fn_call_item.name == "do_thing"
assert fn_call_item.arguments == "{'x':1}"
@pytest.mark.asyncio
async def test_fetch_response_non_stream(monkeypatch) -> None:
"""
Verify that `_fetch_response` builds the correct OpenAI API call when not
streaming and returns the ChatCompletion object directly. We supply a
dummy ChatCompletion through a stubbed OpenAI client and inspect the
captured kwargs.
"""
# Dummy completions to record kwargs
class DummyCompletions:
def __init__(self) -> None:
self.kwargs: dict[str, Any] = {}
async def create(self, **kwargs: Any) -> Any:
self.kwargs = kwargs
return chat
class DummyClient:
def __init__(self, completions: DummyCompletions) -> None:
self.chat = type("_Chat", (), {"completions": completions})()
self.base_url = httpx.URL("http://fake")
msg = ChatCompletionMessage(role="assistant", content="ignored")
choice = Choice(index=0, finish_reason="stop", message=msg)
chat = ChatCompletion(
id="resp-id",
created=0,
model="fake",
object="chat.completion",
choices=[choice],
)
completions = DummyCompletions()
dummy_client = DummyClient(completions)
model = OpenAIChatCompletionsModel(model=cai_model, openai_client=dummy_client) # type: ignore
# Execute the private fetch with a system instruction and simple string input.
with generation_span(disabled=True) as span:
result = await model._fetch_response(
system_instructions="sys",
input="hi",
model_settings=ModelSettings(),
tools=[],
output_schema=None,
handoffs=[],
span=span,
tracing=ModelTracing.DISABLED,
stream=False,
)
# Ensure expected args were passed through to OpenAI client.
kwargs = completions.kwargs
assert kwargs["stream"] is False
assert kwargs["store"] is True
assert kwargs["model"] == cai_model
assert kwargs["messages"][0]["role"] == "system"
assert kwargs["messages"][0]["content"] == "sys"
assert kwargs["messages"][1]["role"] == "user"
# Defaults for optional fields become the NOT_GIVEN sentinel
assert kwargs["tools"] is NOT_GIVEN
assert kwargs["tool_choice"] is NOT_GIVEN
assert kwargs["response_format"] is NOT_GIVEN
assert kwargs["stream_options"] is NOT_GIVEN
@pytest.mark.asyncio
async def test_fetch_response_stream(monkeypatch) -> None:
"""
When `stream=True`, `_fetch_response` should return a bare `Response`
object along with the underlying async stream. The OpenAI client call
should include `stream_options` to request usage-delimited chunks.
"""
os.environ['CAI_STREAM'] = 'true'
async def event_stream() -> AsyncIterator[ChatCompletionChunk]:
if False: # pragma: no cover
yield # pragma: no cover
class DummyCompletions:
def __init__(self) -> None:
self.kwargs: dict[str, Any] = {}
async def create(self, **kwargs: Any) -> Any:
self.kwargs = kwargs
return event_stream()
class DummyClient:
def __init__(self, completions: DummyCompletions) -> None:
self.chat = type("_Chat", (), {"completions": completions})()
self.base_url = httpx.URL("http://fake")
completions = DummyCompletions()
dummy_client = DummyClient(completions)
model = OpenAIChatCompletionsModel(model=cai_model, openai_client=dummy_client) # type: ignore
with generation_span(disabled=True) as span:
response, stream = await model._fetch_response(
system_instructions=None,
input="hi",
model_settings=ModelSettings(),
tools=[],
output_schema=None,
handoffs=[],
span=span,
tracing=ModelTracing.DISABLED,
stream=True,
)
# Check OpenAI client was called for streaming
assert completions.kwargs["stream"] is True
assert completions.kwargs["store"] is True
assert completions.kwargs["stream_options"] == {"include_usage": True}
# Response is a proper openai Response
assert isinstance(response, Response)
assert response.id == FAKE_RESPONSES_ID
assert response.model == cai_model
assert response.object == "response"
assert response.output == []
# We returned the async iterator produced by our dummy.
assert hasattr(stream, "__aiter__")