feat: harden cache reuse semantics and expand protocol regressions
Stabilize cross-protocol ask-mode/streaming behavior and reduce session-reuse branch collisions, then add focused docs/tests for multimodal normalization and pool/stats/config paths. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -15,9 +15,10 @@ class _FakeSessionCache:
|
||||
self.put_calls: list[tuple[str, str, str]] = []
|
||||
self.invalidate_calls: list[str] = []
|
||||
|
||||
def build_key(self, api_key: str, messages: list[dict], *, tool_config=None) -> str:
|
||||
def build_key(self, api_key: str, messages: list[dict], *, tool_config=None, branch_context=None) -> str:
|
||||
marker = "with_tool" if tool_config is not None else "no_tool"
|
||||
key = f"{api_key}:{len(messages)}:{marker}"
|
||||
branch_marker = branch_context or "-"
|
||||
key = f"{api_key}:{len(messages)}:{marker}:branch={branch_marker}"
|
||||
self.keys.append(key)
|
||||
return key
|
||||
|
||||
@@ -635,6 +636,93 @@ class ToolCallBridgeTests(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertEqual(fake_cache.put_calls, [])
|
||||
|
||||
|
||||
async def test_openai_session_reuse_lookup_key_separates_branches(self) -> None:
|
||||
fake_cache = _FakeSessionCache()
|
||||
fake_client = _FakeClient(
|
||||
stream_events=[],
|
||||
complete_result={"text": "ok", "toolEvents": [], "sessionId": "sess-branch"},
|
||||
)
|
||||
|
||||
req_a = ChatCompletionsRequest(
|
||||
model="org_auto",
|
||||
messages=[
|
||||
{"role": "system", "content": "S"},
|
||||
{"role": "user", "content": "U"},
|
||||
{"role": "assistant", "content": "A1"},
|
||||
{"role": "user", "content": "next"},
|
||||
],
|
||||
stream=False,
|
||||
)
|
||||
req_b = ChatCompletionsRequest(
|
||||
model="org_auto",
|
||||
messages=[
|
||||
{"role": "system", "content": "S"},
|
||||
{"role": "user", "content": "U"},
|
||||
{"role": "assistant", "content": "A2"},
|
||||
{"role": "user", "content": "next"},
|
||||
],
|
||||
stream=False,
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(main, "session_cache", fake_cache),
|
||||
patch.object(main, "pool", _FakePool(_FakeInstance(fake_client))),
|
||||
patch.object(main, "chat_guard", _FakeGuard()),
|
||||
patch.object(main, "_ensure_instance_logged_in", AsyncMock(return_value={"id": "u"})),
|
||||
patch.object(main.stats_collector, "record_chat", AsyncMock(return_value=None)),
|
||||
_SettingsPatch(default_ask_mode="chat", tool_forward_enabled=False),
|
||||
):
|
||||
await main.v1_chat_completions(req_a, _make_request("/v1/chat/completions"))
|
||||
await main.v1_chat_completions(req_b, _make_request("/v1/chat/completions"))
|
||||
|
||||
self.assertGreaterEqual(len(fake_cache.get_calls), 4)
|
||||
self.assertNotEqual(fake_cache.get_calls[0], fake_cache.get_calls[2])
|
||||
self.assertEqual(fake_cache.get_calls[1], fake_cache.get_calls[3])
|
||||
|
||||
async def test_openai_and_anthropic_resolve_same_default_ask_mode_without_tooling(self) -> None:
|
||||
openai_spy = _SpyClient(stream_events=[], complete_result={"text": "ok", "toolEvents": []})
|
||||
anthropic_spy = _SpyClient(stream_events=[], complete_result={"text": "ok", "toolEvents": []})
|
||||
|
||||
openai_req = ChatCompletionsRequest(
|
||||
model="org_auto",
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
stream=False,
|
||||
)
|
||||
anthropic_req = AnthropicMessagesRequest(
|
||||
model="claude-3-5-sonnet-20241022",
|
||||
max_tokens=128,
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
stream=False,
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(main, "pool", _FakePool(_FakeInstance(openai_spy))),
|
||||
patch.object(main, "chat_guard", _FakeGuard()),
|
||||
patch.object(main, "_ensure_instance_logged_in", AsyncMock(return_value={"id": "u"})),
|
||||
patch.object(main.stats_collector, "record_chat", AsyncMock(return_value=None)),
|
||||
_SettingsPatch(default_ask_mode="chat", tool_forward_enabled=False),
|
||||
):
|
||||
await main.v1_chat_completions(openai_req, _make_request("/v1/chat/completions"))
|
||||
|
||||
with (
|
||||
patch.object(main, "pool", _FakePool(_FakeInstance(anthropic_spy))),
|
||||
patch.object(main, "chat_guard", _FakeGuard()),
|
||||
patch.object(main, "_ensure_instance_logged_in", AsyncMock(return_value={"id": "u"})),
|
||||
patch.object(main.stats_collector, "record_chat", AsyncMock(return_value=None)),
|
||||
patch.object(main.settings, "api_keys", ["test-key"]),
|
||||
_SettingsPatch(default_ask_mode="chat", tool_forward_enabled=False),
|
||||
):
|
||||
await main.v1_messages(
|
||||
anthropic_req,
|
||||
_make_request(
|
||||
"/v1/messages",
|
||||
headers={"x-api-key": "test-key", "anthropic-version": "2023-06-01"},
|
||||
),
|
||||
)
|
||||
|
||||
self.assertEqual(openai_spy.last_complete_args[2], "chat")
|
||||
self.assertEqual(anthropic_spy.last_complete_args[2], "chat")
|
||||
|
||||
async def test_anthropic_non_stream_with_tools_uses_agent_mode(self) -> None:
|
||||
spy_client = _SpyClient(stream_events=[], complete_result={"text": "ok", "toolEvents": []})
|
||||
req = AnthropicMessagesRequest(
|
||||
|
||||
Reference in New Issue
Block a user