Private
Public Access
0
0

fix(ai_client): move openai_compatible imports to local scope; fix startup_speedup invariant

The follow-up track's tool-loop refactor moved
'from src.openai_compatible import send_openai_compatible,
 OpenAICompatibleRequest, NormalizedResponse' to MODULE level
in src/ai_client.py. This violates the startup_speedup_20260606
invariant: heavy SDKs must not be loaded at module level because
ai_client.py is on the main thread's import chain.

src/openai_compatible.py line 5 does 'from openai import
OpenAIError, ...', so any import from it triggers the openai SDK
to load. test_ai_client_does_not_import_openai_at_module_level
guards this invariant and was failing.

Fix: move the imports back to local scope inside the function
bodies that need them:
- _default_send closure inside run_with_tool_loop
  (imports send_openai_compatible)
- _send_grok (imports OpenAICompatibleRequest)
- _send_minimax (imports OpenAICompatibleRequest)
- _send_llama (imports OpenAICompatibleRequest)
- _send_gemini_cli (imports OpenAICompatibleRequest + NormalizedResponse)

Test patches: tests that previously patched
'src.ai_client.send_openai_compatible' now patch
'src.openai_compatible.send_openai_compatible' (the actual
import source). _execute_tool_calls_concurrently patches
unchanged (it's defined in src/ai_client.py itself).

Green confirmed: 62 vendor + tool + import-isolation tests
pass. 0 regressions.
This commit is contained in:
2026-06-11 16:15:49 -04:00
parent 4748d13490
commit 9ddfa98133
3 changed files with 22 additions and 16 deletions
+5 -5
View File
@@ -34,7 +34,7 @@ def _make_normalized_response(text: str = "ok", tool_calls: list[dict[str, Any]]
def test_run_with_tool_loop_no_tool_calls_returns_immediately(caps: VendorCapabilities) -> None:
client = MagicMock()
with patch("src.ai_client.send_openai_compatible", return_value=_make_normalized_response("hello")) as call:
with patch("src.openai_compatible.send_openai_compatible", return_value=_make_normalized_response("hello")) as call:
result = run_with_tool_loop(
client, OpenAICompatibleRequest(messages=[{"role": "user", "content": "x"}], model="m"),
capabilities=caps,
@@ -50,7 +50,7 @@ def test_run_with_tool_loop_dispatches_tool_calls(caps: VendorCapabilities) -> N
"first response", tool_calls=[{"id": "c1", "type": "function", "function": {"name": "read_file", "arguments": "{}"}}]
)
final_response = _make_normalized_response("after tool")
with patch("src.ai_client.send_openai_compatible", side_effect=[tool_response, final_response]) as call, \
with patch("src.openai_compatible.send_openai_compatible", side_effect=[tool_response, final_response]) as call, \
patch("src.ai_client._execute_tool_calls_concurrently", return_value=[("read_file", "c1", "result", "")]) as dispatch:
result = run_with_tool_loop(
client, OpenAICompatibleRequest(messages=[{"role": "user", "content": "x"}], model="m"),
@@ -67,7 +67,7 @@ def test_run_with_tool_loop_respects_max_rounds(caps: VendorCapabilities) -> Non
infinite_tool_response = _make_normalized_response(
"loop", tool_calls=[{"id": "c1", "type": "function", "function": {"name": "noop", "arguments": "{}"}}]
)
with patch("src.ai_client.send_openai_compatible", return_value=infinite_tool_response), \
with patch("src.openai_compatible.send_openai_compatible", return_value=infinite_tool_response), \
patch("src.ai_client._execute_tool_calls_concurrently", return_value=[("noop", "c1", "result", "")]):
result = run_with_tool_loop(
client, OpenAICompatibleRequest(messages=[{"role": "user", "content": "x"}], model="m"),
@@ -83,7 +83,7 @@ def test_run_with_tool_loop_appends_to_history(caps: VendorCapabilities) -> None
history_lock = MagicMock()
history_lock.__enter__ = MagicMock(return_value=history_lock)
history_lock.__exit__ = MagicMock(return_value=False)
with patch("src.ai_client.send_openai_compatible", return_value=_make_normalized_response("hi")):
with patch("src.openai_compatible.send_openai_compatible", return_value=_make_normalized_response("hi")):
run_with_tool_loop(
client, OpenAICompatibleRequest(messages=[{"role": "user", "content": "x"}], model="m"),
capabilities=caps,
@@ -98,7 +98,7 @@ def test_run_with_tool_loop_does_not_crash_on_tool_error(caps: VendorCapabilitie
"err", tool_calls=[{"id": "c1", "type": "function", "function": {"name": "fail", "arguments": "{}"}}]
)
final_response = _make_normalized_response("recovered")
with patch("src.ai_client.send_openai_compatible", side_effect=[tool_response, final_response]), \
with patch("src.openai_compatible.send_openai_compatible", side_effect=[tool_response, final_response]), \
patch("src.ai_client._execute_tool_calls_concurrently", return_value=[("fail", "c1", "", "ToolExecutionError")]):
result = run_with_tool_loop(
client, OpenAICompatibleRequest(messages=[{"role": "user", "content": "x"}], model="m"),