fix: close forced tool-choice with structured fallback
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -224,6 +224,42 @@ class ToolCallBridgeTests(unittest.IsolatedAsyncioTestCase):
|
||||
{"query": "gateway"},
|
||||
)
|
||||
|
||||
async def test_openai_non_stream_fallbacks_to_structured_tool_call_for_forced_tool(self) -> None:
|
||||
fake_client = _FakeClient(
|
||||
stream_events=[],
|
||||
complete_result={
|
||||
"text": "```json\n{\"arguments\": {\"query\": \"gateway\"}}\n```",
|
||||
"toolEvents": [],
|
||||
"sessionId": "sess-fallback-openai",
|
||||
},
|
||||
)
|
||||
req = ChatCompletionsRequest(
|
||||
model="org_auto",
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
stream=False,
|
||||
tools=[{"type": "function", "function": {"name": "lookup", "parameters": {}}}],
|
||||
tool_choice={"type": "function", "function": {"name": "lookup"}},
|
||||
)
|
||||
|
||||
with (
|
||||
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)),
|
||||
):
|
||||
response = await main.v1_chat_completions(req, _make_request("/v1/chat/completions"))
|
||||
|
||||
payload = json.loads(response.body)
|
||||
message = payload["choices"][0]["message"]
|
||||
self.assertEqual(payload["choices"][0]["finish_reason"], "tool_calls")
|
||||
self.assertEqual(message["content"], "")
|
||||
self.assertIsInstance(message["tool_calls"], list)
|
||||
self.assertEqual(message["tool_calls"][0]["function"]["name"], "lookup")
|
||||
self.assertEqual(
|
||||
json.loads(message["tool_calls"][0]["function"]["arguments"]),
|
||||
{"query": "gateway"},
|
||||
)
|
||||
|
||||
async def test_openai_stream_bridges_tool_and_text_events(self) -> None:
|
||||
fake_client = _FakeClient(
|
||||
stream_events=[
|
||||
@@ -306,6 +342,46 @@ class ToolCallBridgeTests(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertEqual(payload["content"][1]["name"], "lookup")
|
||||
self.assertEqual(payload["content"][2]["tool_use_id"], "toolu_1")
|
||||
|
||||
async def test_anthropic_non_stream_fallbacks_to_structured_tool_blocks_for_forced_tool(self) -> None:
|
||||
fake_client = _FakeClient(
|
||||
stream_events=[],
|
||||
complete_result={
|
||||
"text": "{\"input\": {\"k\": \"v\"}, \"result\": {\"value\": 1}}",
|
||||
"toolEvents": [],
|
||||
"sessionId": "sess-fallback-anthropic",
|
||||
},
|
||||
)
|
||||
req = AnthropicMessagesRequest(
|
||||
model="claude-3-5-sonnet-20241022",
|
||||
max_tokens=256,
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
stream=False,
|
||||
tools=[{"name": "lookup", "input_schema": {"type": "object", "properties": {}}}],
|
||||
tool_choice={"type": "tool", "name": "lookup"},
|
||||
)
|
||||
|
||||
with (
|
||||
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)),
|
||||
patch.object(main.settings, "api_keys", ["test-key"]),
|
||||
):
|
||||
response = await main.v1_messages(
|
||||
req,
|
||||
_make_request(
|
||||
"/v1/messages",
|
||||
headers={"x-api-key": "test-key", "anthropic-version": "2023-06-01"},
|
||||
),
|
||||
)
|
||||
|
||||
payload = json.loads(response.body)
|
||||
types = [item["type"] for item in payload["content"]]
|
||||
self.assertEqual(types, ["tool_use", "tool_result"])
|
||||
self.assertEqual(payload["stop_reason"], "end_turn")
|
||||
self.assertEqual(payload["content"][0]["name"], "lookup")
|
||||
self.assertEqual(payload["content"][1]["tool_use_id"], "toolu_fallback_0")
|
||||
|
||||
async def test_openai_stream_tool_call_indices_are_stable(self) -> None:
|
||||
fake_client = _FakeClient(
|
||||
stream_events=[
|
||||
|
||||
Reference in New Issue
Block a user