Private
Public Access
0
0
Files
manual_slop/tests/test_openai_schemas.py
T
ed 04d723e420 feat(openai): add src/openai_schemas.py + refactor openai_compatible.py (t2_1-t2_7)
Phase 2 of any_type_componentization_20260621. Promotes NormalizedResponse
+ OpenAICompatibleRequest from src/openai_compatible.py to typed
dataclasses. The 17 Any sites become 5 dataclasses:

NEW src/openai_schemas.py (138 lines):
- ToolCallFunction dataclass (name, arguments)
- ToolCall dataclass (id, function: ToolCallFunction, type='function')
- ChatMessage dataclass (role, content, tool_calls, tool_call_id, name)
- UsageStats dataclass (input_tokens, output_tokens, cache_read_*, cache_creation_*)
- NormalizedResponse dataclass (text, tool_calls: tuple, usage, raw_response: Any)
- OpenAICompatibleRequest dataclass (messages: list[ChatMessage], model, ...)

NEW tests/test_openai_schemas.py (19 tests, all pass):
- ToolCallFunction, ToolCall, ChatMessage round-trips
- UsageStats field access + frozen=True semantics
- NormalizedResponse.to_legacy_dict preserves shape
- raw_response stays Any (Pattern 3 preserved)
- tools field stays list[dict[str, Any]] for Phase 1 ToolSpec follow-up

MODIFIED src/openai_compatible.py:
- Removed inline NormalizedResponse + OpenAICompatibleRequest definitions
- Re-imported from src.openai_schemas
- _send_blocking: tool_calls -> tuple[ToolCall, ...]; usage_*_tokens -> UsageStats
- _send_streaming: same migration
- send_openai_compatible: messages_dicts = [m.to_dict() for m in request.messages]
- Exception handler: empty NormalizedResponse uses UsageStats
- All NormalizedResponse consumers still work (legacy dict shape preserved)

Verified:
  uv run pytest tests/test_openai_schemas.py tests/test_mcp_tool_specs.py tests/test_audit_dataclass_coverage.py tests/test_type_aliases.py tests/test_mcp_client_beads.py tests/test_mcp_client_paths.py tests/test_arch_boundary_phase2.py --timeout=60
    64 passed in 6.28s
2026-06-22 00:59:42 -04:00

206 lines
6.5 KiB
Python

"""Tests for src/openai_schemas.py
Phase 2 of any_type_componentization_20260621. Verifies:
- ToolCall + ToolCallFunction round-trip via to_dict
- ChatMessage round-trip for all 4 roles
- UsageStats field access
- NormalizedResponse legacy dict preservation
- OpenAICompatibleRequest typed messages
- raw_response remains Any (Pattern 3 preserved)
- tools field stays list[dict[str, Any]] for cross-phase Phase 1 ToolSpec
(deferred to follow-up track per spec 3.4)
CONVENTION: 1-space indentation. NO COMMENTS.
"""
from __future__ import annotations
import json
import pytest
from src import openai_schemas
def test_tool_call_function_construction() -> None:
tcf = openai_schemas.ToolCallFunction(name="get_weather", arguments='{"city": "sf"}')
assert tcf.name == "get_weather"
assert tcf.arguments == '{"city": "sf"}'
def test_tool_call_to_dict_round_trip() -> None:
tc = openai_schemas.ToolCall(
id="call_123",
type="function",
function=openai_schemas.ToolCallFunction(name="read_file", arguments='{"path": "/x.py"}'),
)
d = tc.to_dict()
assert d["id"] == "call_123"
assert d["type"] == "function"
assert d["function"]["name"] == "read_file"
assert d["function"]["arguments"] == '{"path": "/x.py"}'
def test_tool_call_defaults() -> None:
tc = openai_schemas.ToolCall(
id="call_x",
function=openai_schemas.ToolCallFunction(name="noop", arguments="{}"),
)
assert tc.type == "function"
def test_tool_call_is_frozen() -> None:
tc = openai_schemas.ToolCall(
id="call_y",
function=openai_schemas.ToolCallFunction(name="noop", arguments="{}"),
)
with pytest.raises(Exception):
tc.id = "mutated"
def test_chat_message_system_role() -> None:
msg = openai_schemas.ChatMessage(role="system", content="You are a helper.")
d = msg.to_dict()
assert d["role"] == "system"
assert d["content"] == "You are a helper."
assert "tool_calls" not in d
assert "tool_call_id" not in d
def test_chat_message_user_role() -> None:
msg = openai_schemas.ChatMessage(role="user", content="Hello")
d = msg.to_dict()
assert d["role"] == "user"
assert d["content"] == "Hello"
def test_chat_message_assistant_with_tool_calls() -> None:
tc = openai_schemas.ToolCall(
id="call_a",
function=openai_schemas.ToolCallFunction(name="read_file", arguments='{"path": "/x"}'),
)
msg = openai_schemas.ChatMessage(role="assistant", content="", tool_calls=(tc,))
d = msg.to_dict()
assert d["role"] == "assistant"
assert d["content"] == ""
assert len(d["tool_calls"]) == 1
assert d["tool_calls"][0]["function"]["name"] == "read_file"
def test_chat_message_tool_role() -> None:
msg = openai_schemas.ChatMessage(
role="tool", content='{"result": "ok"}', tool_call_id="call_a"
)
d = msg.to_dict()
assert d["role"] == "tool"
assert d["tool_call_id"] == "call_a"
def test_chat_message_is_frozen() -> None:
msg = openai_schemas.ChatMessage(role="user", content="hi")
with pytest.raises(Exception):
msg.role = "mutated"
def test_usage_stats_construction() -> None:
u = openai_schemas.UsageStats(input_tokens=100, output_tokens=50)
assert u.input_tokens == 100
assert u.output_tokens == 50
assert u.cache_read_tokens == 0
assert u.cache_creation_tokens == 0
def test_usage_stats_with_cache() -> None:
u = openai_schemas.UsageStats(
input_tokens=100,
output_tokens=50,
cache_read_tokens=80,
cache_creation_tokens=20,
)
assert u.cache_read_tokens == 80
assert u.cache_creation_tokens == 20
def test_usage_stats_is_frozen() -> None:
u = openai_schemas.UsageStats(input_tokens=1, output_tokens=1)
with pytest.raises(Exception):
u.input_tokens = 999
def test_normalized_response_construction() -> None:
tc = openai_schemas.ToolCall(
id="call_z",
function=openai_schemas.ToolCallFunction(name="noop", arguments="{}"),
)
usage = openai_schemas.UsageStats(input_tokens=10, output_tokens=20)
resp = openai_schemas.NormalizedResponse(
text="hello", tool_calls=(tc,), usage=usage, raw_response=None
)
assert resp.text == "hello"
assert len(resp.tool_calls) == 1
assert resp.usage.input_tokens == 10
assert resp.raw_response is None
def test_normalized_response_raw_can_be_any_type() -> None:
"""Pattern 3: raw_response is intentionally Any (SDK-specific)."""
usage = openai_schemas.UsageStats(input_tokens=0, output_tokens=0)
resp = openai_schemas.NormalizedResponse(
text="", tool_calls=(), usage=usage, raw_response={"vendor_specific": True}
)
assert resp.raw_response == {"vendor_specific": True}
def test_normalized_response_to_legacy_dict_preserves_shape() -> None:
tc = openai_schemas.ToolCall(
id="call_q",
function=openai_schemas.ToolCallFunction(name="x", arguments="{}"),
)
usage = openai_schemas.UsageStats(
input_tokens=10, output_tokens=20, cache_read_tokens=5, cache_creation_tokens=3
)
resp = openai_schemas.NormalizedResponse(
text="hello", tool_calls=(tc,), usage=usage, raw_response="sdk_obj"
)
d = resp.to_legacy_dict()
assert d["text"] == "hello"
assert d["tool_calls"][0]["id"] == "call_q"
assert d["usage"]["input_tokens"] == 10
assert d["usage"]["cache_read_tokens"] == 5
assert d["raw_response"] == "sdk_obj"
def test_openai_compatible_request_defaults() -> None:
msg = openai_schemas.ChatMessage(role="user", content="hi")
req = openai_schemas.OpenAICompatibleRequest(messages=[msg], model="gpt-4")
assert req.messages == [msg]
assert req.model == "gpt-4"
assert req.temperature == 0.0
assert req.top_p == 1.0
assert req.max_tokens == 8192
assert req.tools is None
assert req.tool_choice == "auto"
assert req.stream is False
assert req.stream_callback is None
assert req.extra_body is None
def test_openai_compatible_request_tools_field_stays_dict_list() -> None:
"""Cross-phase coupling (deferred): Phase 1 ToolSpec migration is a
follow-up track per spec 3.4. The tools field stays list[dict[str, Any]]
for now."""
msg = openai_schemas.ChatMessage(role="user", content="hi")
tools = [{"type": "function", "function": {"name": "x"}}]
req = openai_schemas.OpenAICompatibleRequest(messages=[msg], model="gpt-4", tools=tools)
assert req.tools == tools
def test_chat_message_to_dict_handles_optional_fields() -> None:
msg = openai_schemas.ChatMessage(role="assistant", content="", name=None, tool_call_id=None)
d = msg.to_dict()
assert "name" not in d
assert "tool_call_id" not in d
def test_normalized_response_is_frozen() -> None:
usage = openai_schemas.UsageStats(input_tokens=0, output_tokens=0)
resp = openai_schemas.NormalizedResponse(text="x", tool_calls=(), usage=usage, raw_response=None)
with pytest.raises(Exception):
resp.text = "mutated"