30c8b26381
Phase 2 deferred t2_6: update src/ai_client.py _send_grok + _send_minimax +
_send_llama + _send_gemini_cli (4 functions) to use the new
dataclass API after NormalizedResponse was refactored to
(text, tool_calls: tuple[ToolCall, ...], usage: UsageStats, raw_response).
These 4 callers were left with the old keyword args
(usage_input_tokens, usage_output_tokens, ...) which broke at
runtime: ai_client.send() raised
TypeError: NormalizedResponse.__init__() got an unexpected keyword
argument 'usage_input_tokens'.
FIXES:
- src/ai_client.py L2054: gemini_cli 'adapter unavailable' branch
- src/ai_client.py L2088: gemini_cli normal response branch
- Added: from src.openai_schemas import UsageStats (module level)
- Added backward-compat in src/openai_compatible.py:
messages_dicts = [m.to_dict() if hasattr(m, 'to_dict') else m for m in request.messages]
(accepts both ChatMessage dataclass and dict for backward compat
with existing tests that pass raw dicts)
TEST FIXES:
- tests/test_ai_client_tool_loop.py: _make_normalized_response helper
uses UsageStats instead of usage_*_tokens kwargs
- tests/test_ai_client_tool_loop_builder.py: same
- tests/test_ai_client_tool_loop_send_func.py: same
- tests/test_openai_compatible.py: NormalizedResponse(text=..., usage=UsageStats(...))
+ tool_calls[0].function.name (attribute access) instead of ['function']['name']
- tests/test_auto_whitelist.py: use update_session_metadata() instead of
dict subscript assignment (Session dataclass doesn't support item assignment)
VERIFIED:
uv run pytest tests/test_ai_client_*.py tests/test_openai_*.py \
tests/test_auto_whitelist.py --timeout=30
56 passed in 4.49s (19 previously failing tests now pass)
uv run python scripts/audit_weak_types.py --strict
STRICT OK: 115 weak sites <= baseline 115
uv run python scripts/audit_dataclass_coverage.py --strict
STRICT OK: 200 weak sites <= baseline 207
This commit closes the t2_6 deferred task. The 41-site Phase 3 call-site
migration remains deferred (separate provider_state_migration track).
43 lines
2.0 KiB
Python
43 lines
2.0 KiB
Python
"""Verify run_with_tool_loop supports a per-round request_builder callback.
|
|
|
|
Vendors that mutate their history list (e.g. MiniMax) need to rebuild
|
|
the messages on each round so the API sees the latest tool results.
|
|
run_with_tool_loop accepts a callable as the 2nd arg to enable this.
|
|
"""
|
|
from __future__ import annotations
|
|
from typing import Any
|
|
from unittest.mock import MagicMock, patch
|
|
from src.openai_compatible import NormalizedResponse, OpenAICompatibleRequest
|
|
from src.ai_client import run_with_tool_loop
|
|
from src.result_types import Result
|
|
from src.vendor_capabilities import VendorCapabilities
|
|
|
|
def _make_normalized_response(text: str = "ok", tool_calls: list[dict[str, Any]] | None = None) -> NormalizedResponse:
|
|
from src.openai_schemas import UsageStats
|
|
return NormalizedResponse(
|
|
text=text, tool_calls=tool_calls or (),
|
|
usage=UsageStats(input_tokens=10, output_tokens=5),
|
|
raw_response=None,
|
|
)
|
|
|
|
def test_run_with_tool_loop_calls_request_builder_each_round() -> None:
|
|
caps = VendorCapabilities(vendor="test", model="test-model", tool_calling=True, context_window=8192)
|
|
client = MagicMock()
|
|
tool_response = _make_normalized_response(
|
|
"first", tool_calls=[{"id": "c1", "type": "function", "function": {"name": "noop", "arguments": "{}"}}]
|
|
)
|
|
final = _make_normalized_response("done")
|
|
builder_calls: list[int] = []
|
|
def builder(round_idx: int) -> OpenAICompatibleRequest:
|
|
builder_calls.append(round_idx)
|
|
return OpenAICompatibleRequest(messages=[{"role": "user", "content": f"round={round_idx}"}], model="m")
|
|
with patch("src.openai_compatible.send_openai_compatible", side_effect=[Result(data=tool_response), Result(data=final)]), \
|
|
patch("src.ai_client._execute_tool_calls_concurrently", return_value=[("noop", "c1", "r", "")]):
|
|
result = run_with_tool_loop(
|
|
client, builder, capabilities=caps,
|
|
pre_tool_callback=None, qa_callback=None, patch_callback=None,
|
|
base_dir=".", vendor_name="test", history_lock=None, history=None,
|
|
)
|
|
assert result == "done"
|
|
assert len(builder_calls) >= 2
|