style: apply ruff's automatic fixes and formatter

Mechanical only, and separated from the judgment calls that follow so the
reviewable changes are not buried in a 98-file whitespace diff.

227 automatic fixes: 60 blank lines carrying whitespace, 60 unsorted import
blocks, 34 Optional[X] to X | None, 28 unused imports, 16 deprecated typing
imports, 12 datetime.timezone.utc to datetime.UTC, and assorted smaller
modernisations. Then `ruff format` over src and tests: 98 files reformatted,
35 already conforming.

No file among the unused-import findings defines __all__ or is an __init__.py,
so nothing here removes a re-export.

`make test`: 658 passed, unchanged from HEAD.

Two things observed while verifying, neither addressed here:

`pytest tests/` cannot collect — tests/e2e/test_orchestration_e2e.py uses an
`e2e` marker that is not registered, and the config is strict about markers.
This fails identically at HEAD, so it predates this change; `make test` passes
because it ignores tests/e2e, tests/integration and tests/contracts.

test_tatlock_tool_call_logging_calculator is flaky. It failed once in a full run
with these changes and passed on the next, passes in isolation with them, and
fails in isolation at HEAD. It is order- or timing-dependent, not a regression
from this commit — established by running the full suite both ways rather than
by reasoning about which change could have caused it.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-11 17:25:18 +02:00
co-authored by Claude
parent 57fa6c13fc
commit 78066fab1b
103 changed files with 1601 additions and 1749 deletions
+53 -66
View File
@@ -6,7 +6,9 @@ Tests:
- Stop sequence detection and enforcement
- Max tokens enforcement
"""
import json
import pytest
from fastapi.testclient import TestClient
from httpx import AsyncClient
@@ -15,11 +17,11 @@ from pydantic import ValidationError
from src.responses.schemas import ResponseRequest
from src.responses.streaming import StreamingCoordinator
# ============================================================================
# Parameter Validation Tests
# ============================================================================
@pytest.mark.unit
def test_temperature_validation():
"""Test temperature parameter validation."""
@@ -27,9 +29,7 @@ def test_temperature_validation():
valid_temps = [0.0, 0.5, 1.0, 1.5, 2.0]
for temp in valid_temps:
request = ResponseRequest(
model="lorem-tester",
input=[{"role": "user", "content": "Hello"}],
temperature=temp
model="lorem-tester", input=[{"role": "user", "content": "Hello"}], temperature=temp
)
assert request.temperature == temp
@@ -38,7 +38,7 @@ def test_temperature_validation():
ResponseRequest(
model="lorem-tester",
input=[{"role": "user", "content": "Hello"}],
temperature=-0.1 # Too low
temperature=-0.1, # Too low
)
assert "temperature" in str(exc_info.value).lower()
@@ -46,7 +46,7 @@ def test_temperature_validation():
ResponseRequest(
model="lorem-tester",
input=[{"role": "user", "content": "Hello"}],
temperature=2.1 # Too high
temperature=2.1, # Too high
)
assert "temperature" in str(exc_info.value).lower()
@@ -55,12 +55,12 @@ def test_temperature_validation():
def test_reasoning_effort_validation():
"""Test reasoning.effort parameter validation."""
# Valid effort levels
valid_efforts = ['none', 'minimal', 'low', 'medium', 'high', 'xhigh']
valid_efforts = ["none", "minimal", "low", "medium", "high", "xhigh"]
for effort in valid_efforts:
request = ResponseRequest(
model="lorem-tester",
input=[{"role": "user", "content": "Hello"}],
reasoning={"effort": effort, "summary": "auto"}
reasoning={"effort": effort, "summary": "auto"},
)
assert request.reasoning["effort"] == effort
@@ -69,7 +69,7 @@ def test_reasoning_effort_validation():
ResponseRequest(
model="lorem-tester",
input=[{"role": "user", "content": "Hello"}],
reasoning={"effort": "invalid", "summary": "auto"}
reasoning={"effort": "invalid", "summary": "auto"},
)
assert "reasoning.effort" in str(exc_info.value)
@@ -78,12 +78,12 @@ def test_reasoning_effort_validation():
def test_reasoning_summary_validation():
"""Test reasoning.summary parameter validation."""
# Valid summary values
valid_summaries = ['auto', 'off']
valid_summaries = ["auto", "off"]
for summary in valid_summaries:
request = ResponseRequest(
model="lorem-tester",
input=[{"role": "user", "content": "Hello"}],
reasoning={"effort": "medium", "summary": summary}
reasoning={"effort": "medium", "summary": summary},
)
assert request.reasoning["summary"] == summary
@@ -92,7 +92,7 @@ def test_reasoning_summary_validation():
ResponseRequest(
model="lorem-tester",
input=[{"role": "user", "content": "Hello"}],
reasoning={"effort": "medium", "summary": "invalid"}
reasoning={"effort": "medium", "summary": "invalid"},
)
assert "reasoning.summary" in str(exc_info.value)
@@ -102,26 +102,20 @@ def test_max_output_tokens_validation():
"""Test max_output_tokens parameter validation."""
# Valid values
request = ResponseRequest(
model="lorem-tester",
input=[{"role": "user", "content": "Hello"}],
max_output_tokens=100
model="lorem-tester", input=[{"role": "user", "content": "Hello"}], max_output_tokens=100
)
assert request.max_output_tokens == 100
# None is valid (unlimited)
request = ResponseRequest(
model="lorem-tester",
input=[{"role": "user", "content": "Hello"}],
max_output_tokens=None
model="lorem-tester", input=[{"role": "user", "content": "Hello"}], max_output_tokens=None
)
assert request.max_output_tokens is None
# Invalid: zero or negative
with pytest.raises(ValidationError) as exc_info:
ResponseRequest(
model="lorem-tester",
input=[{"role": "user", "content": "Hello"}],
max_output_tokens=0
model="lorem-tester", input=[{"role": "user", "content": "Hello"}], max_output_tokens=0
)
assert "max_output_tokens" in str(exc_info.value)
@@ -129,7 +123,7 @@ def test_max_output_tokens_validation():
ResponseRequest(
model="lorem-tester",
input=[{"role": "user", "content": "Hello"}],
max_output_tokens=-10
max_output_tokens=-10,
)
assert "max_output_tokens" in str(exc_info.value)
@@ -141,9 +135,7 @@ def test_stop_sequences_validation():
for num_seqs in range(1, 5):
stop_seqs = [f"stop{i}" for i in range(num_seqs)]
request = ResponseRequest(
model="lorem-tester",
input=[{"role": "user", "content": "Hello"}],
stop=stop_seqs
model="lorem-tester", input=[{"role": "user", "content": "Hello"}], stop=stop_seqs
)
assert request.stop == stop_seqs
@@ -152,7 +144,7 @@ def test_stop_sequences_validation():
ResponseRequest(
model="lorem-tester",
input=[{"role": "user", "content": "Hello"}],
stop=["stop1", "stop2", "stop3", "stop4", "stop5"] # 5 sequences
stop=["stop1", "stop2", "stop3", "stop4", "stop5"], # 5 sequences
)
assert "4 stop sequences" in str(exc_info.value)
@@ -161,7 +153,7 @@ def test_stop_sequences_validation():
ResponseRequest(
model="lorem-tester",
input=[{"role": "user", "content": "Hello"}],
stop=["stop1", ""] # Empty string
stop=["stop1", ""], # Empty string
)
assert "non-empty" in str(exc_info.value).lower()
@@ -170,6 +162,7 @@ def test_stop_sequences_validation():
# Stop Sequence Enforcement Tests
# ============================================================================
@pytest.mark.unit
def test_stop_sequence_detection_helper():
"""Test stop sequence detection helper method."""
@@ -181,26 +174,17 @@ def test_stop_sequence_detection_helper():
assert text == "Hello world"
# Stop sequence not present
found, text = coordinator._check_stop_sequence(
"Hello world",
["STOP", "END"]
)
found, text = coordinator._check_stop_sequence("Hello world", ["STOP", "END"])
assert found is False
assert text == "Hello world"
# Stop sequence found
found, text = coordinator._check_stop_sequence(
"Hello STOP this should not appear",
["STOP"]
)
found, text = coordinator._check_stop_sequence("Hello STOP this should not appear", ["STOP"])
assert found is True
assert text == "Hello "
# Multiple stop sequences, first one wins
found, text = coordinator._check_stop_sequence(
"Hello STOP this END that",
["STOP", "END"]
)
found, text = coordinator._check_stop_sequence("Hello STOP this END that", ["STOP", "END"])
assert found is True
assert text == "Hello "
@@ -216,7 +200,7 @@ async def test_stop_sequence_in_streaming(async_client: AsyncClient):
"model": "lorem-tester",
"input": [{"role": "user", "content": "Generate long text"}],
"stop": ["dolor"], # Common word in lorem ipsum
"stream": True
"stream": True,
}
chunks_received = []
@@ -251,6 +235,7 @@ async def test_stop_sequence_in_streaming(async_client: AsyncClient):
# Max Tokens Enforcement Tests
# ============================================================================
@pytest.mark.unit
def test_max_tokens_check_helper():
"""Test max tokens check helper method."""
@@ -296,7 +281,7 @@ async def test_max_tokens_in_streaming(async_client: AsyncClient):
"model": "lorem-tester",
"input": [{"role": "user", "content": "Generate text"}],
"max_output_tokens": 5, # Very low limit
"stream": True
"stream": True,
}
chunks_received = []
@@ -340,6 +325,7 @@ async def test_max_tokens_in_streaming(async_client: AsyncClient):
# Combined Features Test
# ============================================================================
@pytest.mark.unit
def test_combined_validation(client: TestClient):
"""Test combined parameter validation in actual request."""
@@ -351,7 +337,7 @@ def test_combined_validation(client: TestClient):
"max_output_tokens": 100,
"stop": ["STOP", "END"],
"reasoning": {"effort": "high", "summary": "auto"},
"stream": False
"stream": False,
}
response = client.post("/v1/responses", json=request_data)
@@ -368,7 +354,7 @@ def test_invalid_combined_parameters(client: TestClient):
"model": "lorem-tester",
"input": [{"role": "user", "content": "Hello"}],
"temperature": 3.0, # Too high
"stream": False
"stream": False,
}
response = client.post("/v1/responses", json=request_data)
@@ -382,6 +368,7 @@ def test_invalid_combined_parameters(client: TestClient):
# Streaming Delta Calculation Tests (No Duplication)
# ============================================================================
@pytest.mark.unit
@pytest.mark.asyncio
async def test_streaming_delta_calculation_no_duplication():
@@ -392,8 +379,10 @@ async def test_streaming_delta_calculation_no_duplication():
This test prevents the duplication bug where the same text was
streamed multiple times because we weren't computing deltas correctly.
"""
from collections.abc import AsyncGenerator
from typing import Any
from src.agents.base import AgentInterface, OutputItem
from typing import AsyncGenerator, Any
# Create a mock agent that simulates PydanticAI's behavior
# (yielding accumulated text, not deltas)
@@ -406,7 +395,7 @@ async def test_streaming_delta_calculation_no_duplication():
temperature: float = 1.0,
max_tokens: int | None = None,
stop: list[str] | None = None,
**kwargs: Any
**kwargs: Any,
) -> AsyncGenerator[OutputItem, None]:
"""
Simulate PydanticAI streaming behavior:
@@ -430,12 +419,8 @@ async def test_streaming_delta_calculation_no_duplication():
type="message",
id=msg_id,
role="assistant",
content=[{
"type": "output_text",
"text": text,
"annotations": []
}],
status="in_progress"
content=[{"type": "output_text", "text": text, "annotations": []}],
status="in_progress",
)
# Final message
@@ -443,12 +428,10 @@ async def test_streaming_delta_calculation_no_duplication():
type="message",
id=msg_id,
role="assistant",
content=[{
"type": "output_text",
"text": "Hello world how are you",
"annotations": []
}],
status="completed"
content=[
{"type": "output_text", "text": "Hello world how are you", "annotations": []}
],
status="completed",
)
async def supports_tools(self) -> bool:
@@ -462,7 +445,9 @@ async def test_streaming_delta_calculation_no_duplication():
# Register the mock agent
import time
from src.agents.registry import ModelRegistry
ModelRegistry.MODELS["mock-streaming"] = {
"agent_class": MockStreamingAgent,
"description": "Mock streaming agent for testing",
@@ -473,9 +458,7 @@ async def test_streaming_delta_calculation_no_duplication():
try:
# Create a test request
request = ResponseRequest(
model="mock-streaming",
input=[{"role": "user", "content": "Test"}],
stream=True
model="mock-streaming", input=[{"role": "user", "content": "Test"}], stream=True
)
# Stream the response
@@ -512,8 +495,10 @@ async def test_streaming_with_multiple_message_items():
Test that coordinator handles multiple message OutputItems correctly,
only streaming the delta between each one.
"""
from collections.abc import AsyncGenerator
from typing import Any
from src.agents.base import AgentInterface, OutputItem
from typing import AsyncGenerator, Any
class MockMultiMessageAgent(AgentInterface):
async def generate_response(
@@ -524,7 +509,7 @@ async def test_streaming_with_multiple_message_items():
temperature: float = 1.0,
max_tokens: int | None = None,
stop: list[str] | None = None,
**kwargs: Any
**kwargs: Any,
) -> AsyncGenerator[OutputItem, None]:
"""Yield multiple in_progress messages with accumulated text."""
# First chunk
@@ -533,7 +518,7 @@ async def test_streaming_with_multiple_message_items():
id="msg_1",
role="assistant",
content=[{"type": "output_text", "text": "The answer is", "annotations": []}],
status="in_progress"
status="in_progress",
)
# Second chunk (more text accumulated)
@@ -542,7 +527,7 @@ async def test_streaming_with_multiple_message_items():
id="msg_1",
role="assistant",
content=[{"type": "output_text", "text": "The answer is 42", "annotations": []}],
status="in_progress"
status="in_progress",
)
# Final chunk
@@ -551,7 +536,7 @@ async def test_streaming_with_multiple_message_items():
id="msg_1",
role="assistant",
content=[{"type": "output_text", "text": "The answer is 42", "annotations": []}],
status="completed"
status="completed",
)
async def supports_tools(self) -> bool:
@@ -565,7 +550,9 @@ async def test_streaming_with_multiple_message_items():
# Register mock agent
import time
from src.agents.registry import ModelRegistry
ModelRegistry.MODELS["mock-multi"] = {
"agent_class": MockMultiMessageAgent,
"description": "Mock multi-message agent for testing",
@@ -577,7 +564,7 @@ async def test_streaming_with_multiple_message_items():
request = ResponseRequest(
model="mock-multi",
input=[{"role": "user", "content": "What is the answer?"}],
stream=True
stream=True,
)
coordinator = StreamingCoordinator()
+24 -52
View File
@@ -1,7 +1,9 @@
"""
Tests for error handling in Responses API.
"""
import json
import pytest
from fastapi.testclient import TestClient
from httpx import AsyncClient
@@ -12,10 +14,8 @@ def test_model_not_found_error(client: TestClient) -> None:
"""Test response when model doesn't exist."""
request_data = {
"model": "nonexistent-model-12345",
"input": [
{"role": "user", "content": "Hello"}
],
"stream": False
"input": [{"role": "user", "content": "Hello"}],
"stream": False,
}
response = client.post("/v1/responses", json=request_data)
@@ -29,11 +29,7 @@ def test_model_not_found_error(client: TestClient) -> None:
@pytest.mark.unit
def test_validation_error_missing_model(client: TestClient) -> None:
"""Test validation error when model field is missing."""
request_data = {
"input": [
{"role": "user", "content": "Hello"}
]
}
request_data = {"input": [{"role": "user", "content": "Hello"}]}
response = client.post("/v1/responses", json=request_data)
@@ -45,10 +41,8 @@ def test_validation_error_invalid_temperature(client: TestClient) -> None:
"""Test validation error for out-of-range temperature."""
request_data = {
"model": "lorem-tester",
"input": [
{"role": "user", "content": "Hello"}
],
"temperature": 3.0 # Max is 2.0
"input": [{"role": "user", "content": "Hello"}],
"temperature": 3.0, # Max is 2.0
}
response = client.post("/v1/responses", json=request_data)
@@ -61,10 +55,8 @@ def test_rate_limit_error(client: TestClient) -> None:
"""Test rate limit error trigger."""
request_data = {
"model": "lorem-tester",
"input": [
{"role": "user", "content": "trigger_rate_limit"}
],
"stream": False
"input": [{"role": "user", "content": "trigger_rate_limit"}],
"stream": False,
}
response = client.post("/v1/responses", json=request_data)
@@ -78,10 +70,8 @@ def test_context_overflow_error(client: TestClient) -> None:
"""Test context length overflow error trigger."""
request_data = {
"model": "lorem-tester",
"input": [
{"role": "user", "content": "trigger_context_overflow"}
],
"stream": False
"input": [{"role": "user", "content": "trigger_context_overflow"}],
"stream": False,
}
response = client.post("/v1/responses", json=request_data)
@@ -96,10 +86,8 @@ async def test_streaming_model_not_found(async_client: AsyncClient) -> None:
"""Test streaming response with nonexistent model."""
request_data = {
"model": "nonexistent-streaming-model",
"input": [
{"role": "user", "content": "Hello"}
],
"stream": True
"input": [{"role": "user", "content": "Hello"}],
"stream": True,
}
async with async_client.stream(
@@ -133,10 +121,8 @@ async def test_streaming_rate_limit_error(async_client: AsyncClient) -> None:
"""Test streaming with rate limit error."""
request_data = {
"model": "lorem-tester",
"input": [
{"role": "user", "content": "trigger_rate_limit"}
],
"stream": True
"input": [{"role": "user", "content": "trigger_rate_limit"}],
"stream": True,
}
async with async_client.stream(
@@ -177,10 +163,8 @@ async def test_streaming_context_overflow_error(async_client: AsyncClient) -> No
"""Test streaming with context overflow error."""
request_data = {
"model": "lorem-tester",
"input": [
{"role": "user", "content": "trigger_context_overflow"}
],
"stream": True
"input": [{"role": "user", "content": "trigger_context_overflow"}],
"stream": True,
}
async with async_client.stream(
@@ -215,17 +199,9 @@ def test_function_call_output_item(client: TestClient) -> None:
"""Test response with function call items."""
request_data = {
"model": "lorem-tester",
"input": [
{"role": "user", "content": "Use the search tool"}
],
"tools": [
{
"name": "search",
"description": "Search for information",
"parameters": {}
}
],
"stream": False
"input": [{"role": "user", "content": "Use the search tool"}],
"tools": [{"name": "search", "description": "Search for information", "parameters": {}}],
"stream": False,
}
response = client.post("/v1/responses", json=request_data)
@@ -248,10 +224,8 @@ def test_pipeline_prefix_stripping(client: TestClient) -> None:
"""Test that pipeline prefixes are stripped from model names."""
request_data = {
"model": "some_pipeline.lorem-tester",
"input": [
{"role": "user", "content": "Hello"}
],
"stream": False
"input": [{"role": "user", "content": "Hello"}],
"stream": False,
}
response = client.post("/v1/responses", json=request_data)
@@ -267,10 +241,8 @@ def test_multiple_pipeline_prefixes(client: TestClient) -> None:
"""Test multiple dots in model name (only first is prefix)."""
request_data = {
"model": "pipeline.sub.lorem-tester",
"input": [
{"role": "user", "content": "Hello"}
],
"stream": False
"input": [{"role": "user", "content": "Hello"}],
"stream": False,
}
response = client.post("/v1/responses", json=request_data)
+29 -66
View File
@@ -1,13 +1,14 @@
"""
Tests for conversation history management.
"""
import pytest
from fastapi.testclient import TestClient
from src.responses.history import ConversationHistory
from src.responses.context import ContextWindow
from src.responses.schemas import ResponseRequest
from src.responses import service
from src.responses.context import ContextWindow
from src.responses.history import ConversationHistory
from src.responses.schemas import ResponseRequest
@pytest.mark.unit
@@ -19,7 +20,7 @@ async def test_conversation_id_from_metadata():
request = ResponseRequest(
model="lorem-tester",
input=[{"role": "user", "content": "Hello"}],
metadata={"conversation_id": "conv_123"}
metadata={"conversation_id": "conv_123"},
)
conv_id = await history.get_conversation_id(request)
@@ -34,7 +35,7 @@ async def test_conversation_id_generation():
request = ResponseRequest(
model="lorem-tester",
input=[{"role": "user", "content": "Hello"}]
input=[{"role": "user", "content": "Hello"}],
# No metadata provided
)
@@ -43,10 +44,7 @@ async def test_conversation_id_generation():
assert len(conv_id) == 16 # 16 character hex
# Same first message should generate same ID
request2 = ResponseRequest(
model="lorem-tester",
input=[{"role": "user", "content": "Hello"}]
)
request2 = ResponseRequest(model="lorem-tester", input=[{"role": "user", "content": "Hello"}])
conv_id2 = await history.get_conversation_id(request2)
assert conv_id == conv_id2
@@ -59,7 +57,7 @@ def test_conversation_history_tracking(client: TestClient):
"model": "lorem-tester",
"input": [{"role": "user", "content": "Hello"}],
"metadata": {"conversation_id": "test_conv_001"},
"stream": False
"stream": False,
}
response = client.post("/v1/responses", json=request_data)
@@ -71,10 +69,10 @@ def test_conversation_history_tracking(client: TestClient):
"input": [
{"role": "user", "content": "Hello"},
{"role": "assistant", "content": "Hi there!"},
{"role": "user", "content": "How are you?"}
{"role": "user", "content": "How are you?"},
],
"metadata": {"conversation_id": "test_conv_001"},
"stream": False
"stream": False,
}
response2 = client.post("/v1/responses", json=request_data2)
@@ -94,7 +92,7 @@ async def test_conversation_history_retrieval():
request = ResponseRequest(
model="lorem-tester",
input=[{"role": "user", "content": "Test"}],
metadata={"conversation_id": "test_retrieve"}
metadata={"conversation_id": "test_retrieve"},
)
conv_id = await history.get_conversation_id(request)
@@ -104,24 +102,15 @@ async def test_conversation_history_retrieval():
assert len(retrieved) == 0
# Add mock response
from src.responses.schemas import Response, ResponseUsage, MessageOutputItem, OutputTextContent
from src.responses.schemas import MessageOutputItem, OutputTextContent, Response, ResponseUsage
mock_response = Response(
id="resp_123",
created_at=1234567890,
model="lorem-tester",
status="completed",
output=[
MessageOutputItem(
id="msg_1",
content=[OutputTextContent(text="Test response")]
)
],
usage=ResponseUsage(
input_tokens=10,
output_tokens=5,
reasoning_tokens=0,
total_tokens=15
)
output=[MessageOutputItem(id="msg_1", content=[OutputTextContent(text="Test response")])],
usage=ResponseUsage(input_tokens=10, output_tokens=5, reasoning_tokens=0, total_tokens=15),
)
await history.add_response(conv_id, mock_response)
@@ -138,7 +127,7 @@ async def test_conversation_history_trimming():
"""Test that history is trimmed to max_turns."""
history = ConversationHistory(max_turns=3)
from src.responses.schemas import Response, ResponseUsage, MessageOutputItem, OutputTextContent
from src.responses.schemas import MessageOutputItem, OutputTextContent, Response, ResponseUsage
conv_id = "test_trim"
@@ -150,17 +139,11 @@ async def test_conversation_history_trimming():
model="lorem-tester",
status="completed",
output=[
MessageOutputItem(
id=f"msg_{i}",
content=[OutputTextContent(text=f"Response {i}")]
)
MessageOutputItem(id=f"msg_{i}", content=[OutputTextContent(text=f"Response {i}")])
],
usage=ResponseUsage(
input_tokens=10,
output_tokens=5,
reasoning_tokens=0,
total_tokens=15
)
input_tokens=10, output_tokens=5, reasoning_tokens=0, total_tokens=15
),
)
await history.add_response(conv_id, response)
@@ -178,7 +161,7 @@ async def test_clear_conversation():
"""Test clearing conversation history."""
history = ConversationHistory()
from src.responses.schemas import Response, ResponseUsage, MessageOutputItem, OutputTextContent
from src.responses.schemas import MessageOutputItem, OutputTextContent, Response, ResponseUsage
conv_id = "test_clear"
@@ -188,18 +171,8 @@ async def test_clear_conversation():
created_at=1234567890,
model="lorem-tester",
status="completed",
output=[
MessageOutputItem(
id="msg_1",
content=[OutputTextContent(text="Test")]
)
],
usage=ResponseUsage(
input_tokens=10,
output_tokens=5,
reasoning_tokens=0,
total_tokens=15
)
output=[MessageOutputItem(id="msg_1", content=[OutputTextContent(text="Test")])],
usage=ResponseUsage(input_tokens=10, output_tokens=5, reasoning_tokens=0, total_tokens=15),
)
await history.add_response(conv_id, response)
@@ -227,7 +200,7 @@ async def test_conversation_count():
initial_count = await history.get_conversation_count()
# Add responses to 3 different conversations
from src.responses.schemas import Response, ResponseUsage, MessageOutputItem, OutputTextContent
from src.responses.schemas import MessageOutputItem, OutputTextContent, Response, ResponseUsage
for i in range(3):
response = Response(
@@ -235,18 +208,10 @@ async def test_conversation_count():
created_at=1234567890,
model="lorem-tester",
status="completed",
output=[
MessageOutputItem(
id=f"msg_{i}",
content=[OutputTextContent(text="Test")]
)
],
output=[MessageOutputItem(id=f"msg_{i}", content=[OutputTextContent(text="Test")])],
usage=ResponseUsage(
input_tokens=10,
output_tokens=5,
reasoning_tokens=0,
total_tokens=15
)
input_tokens=10, output_tokens=5, reasoning_tokens=0, total_tokens=15
),
)
await history.add_response(f"conv_{i}", response)
@@ -281,8 +246,8 @@ async def test_context_window_trimming():
# Create items that exceed limit
items = [
"This is a long message " * 20, # ~480 chars = ~120 tokens
"Another message " * 10, # ~160 chars = ~40 tokens
"Short message" # ~13 chars = ~3 tokens
"Another message " * 10, # ~160 chars = ~40 tokens
"Short message", # ~13 chars = ~3 tokens
]
# Trim with 10 token reserve
@@ -355,14 +320,12 @@ async def test_service_conversation_helpers():
@pytest.mark.asyncio
async def test_streaming_tracks_history(async_client):
"""Test that streaming responses also track conversation history."""
from httpx import AsyncClient
import json
request_data = {
"model": "lorem-tester",
"input": [{"role": "user", "content": "Hello"}],
"metadata": {"conversation_id": "stream_test_001"},
"stream": True
"stream": True,
}
async with async_client.stream(
+21 -50
View File
@@ -3,6 +3,7 @@ Tests for Responses API router.
"""
import json
import pytest
from fastapi.testclient import TestClient
from httpx import AsyncClient
@@ -13,10 +14,8 @@ def test_create_response_non_streaming(client: TestClient) -> None:
"""Test non-streaming response creation."""
request_data = {
"model": "lorem-tester",
"input": [
{"role": "user", "content": "Hello, how are you?"}
],
"stream": False
"input": [{"role": "user", "content": "Hello, how are you?"}],
"stream": False,
}
response = client.post("/v1/responses", json=request_data)
@@ -48,14 +47,9 @@ def test_create_response_with_reasoning(client: TestClient) -> None:
"""Test response with reasoning enabled."""
request_data = {
"model": "lorem-tester",
"input": [
{"role": "user", "content": "Explain something"}
],
"reasoning": {
"effort": "medium",
"summary": "auto"
},
"stream": False
"input": [{"role": "user", "content": "Explain something"}],
"reasoning": {"effort": "medium", "summary": "auto"},
"stream": False,
}
response = client.post("/v1/responses", json=request_data)
@@ -88,17 +82,11 @@ def test_create_response_with_tools(client: TestClient) -> None:
"""Test response with tools available."""
request_data = {
"model": "lorem-tester",
"input": [
{"role": "user", "content": "Use a tool to help"}
],
"input": [{"role": "user", "content": "Use a tool to help"}],
"tools": [
{
"name": "search_knowledge",
"description": "Search knowledge base",
"parameters": {}
}
{"name": "search_knowledge", "description": "Search knowledge base", "parameters": {}}
],
"stream": False
"stream": False,
}
response = client.post("/v1/responses", json=request_data)
@@ -117,10 +105,8 @@ def test_create_response_invalid_model(client: TestClient) -> None:
"""Test response with non-existent model."""
request_data = {
"model": "nonexistent-model",
"input": [
{"role": "user", "content": "Hello"}
],
"stream": False
"input": [{"role": "user", "content": "Hello"}],
"stream": False,
}
response = client.post("/v1/responses", json=request_data)
@@ -134,11 +120,7 @@ def test_create_response_invalid_model(client: TestClient) -> None:
def test_create_response_validation_error(client: TestClient) -> None:
"""Test response with invalid request data."""
# Missing required 'model' field
request_data = {
"input": [
{"role": "user", "content": "Hello"}
]
}
request_data = {"input": [{"role": "user", "content": "Hello"}]}
response = client.post("/v1/responses", json=request_data)
@@ -151,10 +133,8 @@ async def test_create_response_streaming(async_client: AsyncClient) -> None:
"""Test streaming response creation."""
request_data = {
"model": "lorem-tester",
"input": [
{"role": "user", "content": "Hello"}
],
"stream": True
"input": [{"role": "user", "content": "Hello"}],
"stream": True,
}
async with async_client.stream(
@@ -200,14 +180,9 @@ async def test_create_response_streaming_with_reasoning(async_client: AsyncClien
"""Test streaming response with reasoning."""
request_data = {
"model": "lorem-tester",
"input": [
{"role": "user", "content": "Explain"}
],
"reasoning": {
"effort": "medium",
"summary": "auto"
},
"stream": True
"input": [{"role": "user", "content": "Explain"}],
"reasoning": {"effort": "medium", "summary": "auto"},
"stream": True,
}
async with async_client.stream(
@@ -238,16 +213,12 @@ async def test_create_response_streaming_with_reasoning(async_client: AsyncClien
# Should have reasoning events
reasoning_events = [
e for e in events
if e["event"] == "response.reasoning_summary_text.delta"
e for e in events if e["event"] == "response.reasoning_summary_text.delta"
]
assert len(reasoning_events) > 0
# Should have output text events
output_events = [
e for e in events
if e["event"] == "response.output_text.delta"
]
output_events = [e for e in events if e["event"] == "response.output_text.delta"]
assert len(output_events) > 0
@@ -258,7 +229,7 @@ def test_create_response_temperature_parameter(client: TestClient) -> None:
"model": "lorem-tester",
"input": [{"role": "user", "content": "Test"}],
"temperature": 0.5,
"stream": False
"stream": False,
}
response = client.post("/v1/responses", json=request_data)
@@ -273,7 +244,7 @@ def test_create_response_max_tokens_parameter(client: TestClient) -> None:
"model": "lorem-tester",
"input": [{"role": "user", "content": "Test"}],
"max_output_tokens": 100,
"stream": False
"stream": False,
}
response = client.post("/v1/responses", json=request_data)
+7 -15
View File
@@ -53,9 +53,7 @@ class TestStreamDirectDelegation:
) as mock_delegate:
gen = coordinator._stream_direct_delegation(
user_message="Search for Docker info",
recommendation=SimpleNamespace(
recommended_capabilities=["librarian"]
),
recommendation=SimpleNamespace(recommended_capabilities=["librarian"]),
tracker=tracker,
conversation_id="conv_1",
conversation_history=HISTORY,
@@ -66,9 +64,9 @@ class TestStreamDirectDelegation:
first = await gen.__anext__()
assert isinstance(first, ReasoningSummaryDelta)
assert first.delta.strip() != ""
assert mock_delegate.await_count == 0, (
"start think message must stream before the expert runs"
)
assert (
mock_delegate.await_count == 0
), "start think message must stream before the expert runs"
second = await gen.__anext__()
assert isinstance(second, ReasoningSummaryDone)
@@ -101,9 +99,7 @@ class TestStreamDirectDelegation:
event
async for event in coordinator._stream_direct_delegation(
user_message="And what services does it list?",
recommendation=SimpleNamespace(
recommended_capabilities=["librarian"]
),
recommendation=SimpleNamespace(recommended_capabilities=["librarian"]),
tracker=tracker,
conversation_id="conv_1",
conversation_history=HISTORY,
@@ -139,9 +135,7 @@ class TestStreamDirectDelegation:
event
async for event in coordinator._stream_direct_delegation(
user_message="Search for Docker info",
recommendation=SimpleNamespace(
recommended_capabilities=["librarian"]
),
recommendation=SimpleNamespace(recommended_capabilities=["librarian"]),
tracker=tracker,
conversation_id="conv_1",
results=results,
@@ -172,9 +166,7 @@ class TestServiceDelegationContext:
) as mock_delegate:
results = await _direct_delegation_with_results(
user_message="And what services does it list?",
recommendation=SimpleNamespace(
recommended_capabilities=["librarian"]
),
recommendation=SimpleNamespace(recommended_capabilities=["librarian"]),
tracker=tracker,
conversation_id="conv_1",
conversation_history=HISTORY,