Avoid duplicate agent searches and resolve simple retrieved calculations

This commit is contained in:
Hamza Ayed
2026-10-03 17:31:30 +03:00
parent f896e04dce
commit 92deb41e6d
3 changed files with 212 additions and 18 deletions
+112 -6
View File
@@ -295,8 +295,45 @@ class AgentSkillTests(unittest.TestCase):
third_messages = model.await_args_list[2].args[0]["messages"]
self.assertEqual([message["role"] for message in third_messages[-2:]], ["assistant", "tool"])
def test_agent_reuses_duplicate_read_only_tool_result_and_finishes(self) -> None:
repeated_call = {
"id": "search-repeat",
"type": "function",
"function": {
"name": "search_workspace",
"arguments": '{"query":"same query"}',
},
}
completions = [
{"choices": [{"message": {"tool_calls": [repeated_call]}}]},
{"choices": [{"message": {"tool_calls": [{**repeated_call, "id": "search-repeat-2"}]}}]},
{"choices": [{"message": {"content": "وجدت المعلومة في الملف."}}]},
]
with (
patch(
"app.main.workspace.retrieve",
return_value=[("app/answer.py", "المعلومة المطلوبة")],
) as search,
patch("app.main.get_completion", new=AsyncMock(side_effect=completions)) as model,
):
result = asyncio.run(
_execute_agent(
AgentRequest(task="ابحث عن المعلومة", workspace_path=self.workspace)
)
)
search.assert_called_once_with("same query", Path(self.workspace).resolve())
self.assertEqual(model.await_count, 3)
self.assertNotIn("tools", model.await_args_list[2].args[0])
self.assertEqual(result["files"], ["app/answer.py"])
self.assertEqual(
[step["tool"] for step in result["steps"]],
["search_workspace", "search_workspace"],
)
self.assertEqual(result["result"], "وجدت المعلومة في الملف.")
def test_explicit_workspace_search_prefetches_before_followup_tool(self) -> None:
task = "ابحث في ملفات المشروع عن قيمة MAX_AGENT_TOOL_CALLS، ثم احسب القيمة مضروبة في 7."
task = "ابحث في ملفات المشروع عن كلمة privacy، ثم احسب 3 × 7."
completions = [
{"choices": [{"message": {"tool_calls": [{
"id": "calc-after-search",
@@ -305,7 +342,7 @@ class AgentSkillTests(unittest.TestCase):
{"choices": [{"message": {"content": "القيمة 3، والناتج 21 من app/main.py."}}]},
]
with (
patch("app.main.workspace.retrieve", return_value=[("app/main.py", "MAX_AGENT_TOOL_CALLS = 3")]) as search,
patch("app.main.workspace.retrieve", return_value=[("README.md", "Local workspace stays private.")]) as search,
patch("app.main.get_completion", new=AsyncMock(side_effect=completions)) as model,
):
result = asyncio.run(
@@ -320,23 +357,92 @@ class AgentSkillTests(unittest.TestCase):
search.assert_called_once_with(task, Path(self.workspace).resolve())
initial_payload = model.await_args_list[0].args[0]
self.assertIn(
self.assertNotIn(
"search_workspace",
[tool["function"]["name"] for tool in initial_payload["tools"]],
)
self.assertIn("MAX_AGENT_TOOL_CALLS = 3", initial_payload["messages"][1]["content"])
self.assertNotIn(
"search_knowledge",
[tool["function"]["name"] for tool in initial_payload["tools"]],
)
self.assertEqual(
[tool["function"]["name"] for tool in initial_payload["tools"]],
["calculator"],
)
system_message = initial_payload["messages"][0]["content"]
self.assertIn("بحث الخادم في مساحة العمل المسموحة مسبقًا", system_message)
self.assertNotIn("استخدم search_workspace", system_message)
self.assertIn("Local workspace stays private.", initial_payload["messages"][1]["content"])
self.assertEqual(
[step["tool"] for step in result["steps"]],
["search_workspace", "calculator"],
)
self.assertEqual(result["files"], ["README.md"])
self.assertIn("21", result["result"])
def test_explicit_search_multiplies_one_retrieved_numeric_constant_locally(self) -> None:
task = "ابحث في ملفات المشروع عن قيمة MAX_AGENT_TOOL_CALLS، ثم احسبها مضروبة في 7."
with (
patch(
"app.main.workspace.retrieve",
return_value=[("app/main.py", "MAX_AGENT_TOOL_CALLS = 3")],
) as search,
patch("app.main.get_completion", new=AsyncMock()) as model,
):
result = asyncio.run(
_execute_agent(
AgentRequest(
task=task,
workspace_path=self.workspace,
skill_id="code_explain",
)
)
)
search.assert_called_once_with(task, Path(self.workspace).resolve())
model.assert_not_awaited()
self.assertEqual(
[step["tool"] for step in result["steps"]],
["search_workspace", "calculator"],
)
self.assertEqual(result["files"], ["app/main.py"])
self.assertIn("21", result["result"])
self.assertIn("3 × 7 = 21", result["result"])
def test_explicit_workspace_search_rejects_repeating_prefetched_search(self) -> None:
task = "ابحث في ملفات المشروع عن قيمة MAX_AGENT_TOOL_CALLS."
completion = {
"choices": [{"message": {"tool_calls": [{
"id": "duplicate-prefetch",
"function": {
"name": "search_workspace",
"arguments": '{"query":"MAX_AGENT_TOOL_CALLS"}',
},
}]}}]
}
with (
patch("app.main.workspace.retrieve", return_value=[("app/main.py", "MAX_AGENT_TOOL_CALLS = 3")]) as search,
patch("app.main.get_completion", new=AsyncMock(return_value=completion)) as model,
):
with self.assertRaises(HTTPException) as error:
asyncio.run(
_execute_agent(
AgentRequest(task=task, workspace_path=self.workspace)
)
)
self.assertEqual(error.exception.status_code, 422)
search.assert_called_once_with(task, Path(self.workspace).resolve())
offered = model.await_args.args[0]["tools"]
self.assertNotIn("search_workspace", [item["function"]["name"] for item in offered])
def test_agent_caps_sequential_tools_and_forces_final_model_turn(self) -> None:
tool_calls = [
{"choices": [{"message": {"tool_calls": [{
"id": f"calc-{index}",
"function": {"name": "calculator", "arguments": '{"expression":"2 + 3"}'},
"function": {
"name": "calculator",
"arguments": json.dumps({"expression": f"2 + {index + 3}"}),
},
}]}}]}
for index in range(3)
]