Initial commit
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from opc.layer3_agent.runtime_v2.runtime import NativeRuntimeV2
|
||||
from opc.layer4_tools.registry import ToolDefinition, ToolRegistry
|
||||
from opc.llm.provider import _parse_tool_arguments
|
||||
|
||||
|
||||
class ToolArgumentParsingTests(unittest.TestCase):
|
||||
def test_parse_tool_arguments_returns_error_context_for_invalid_json(self) -> None:
|
||||
parsed, raw, error = _parse_tool_arguments("shell_exec", '{"command": "echo hi"')
|
||||
self.assertEqual(parsed, '{"command": "echo hi"')
|
||||
self.assertEqual(raw, '{"command": "echo hi"')
|
||||
self.assertIsNotNone(error)
|
||||
self.assertIn("Invalid tool arguments JSON for `shell_exec`", error)
|
||||
|
||||
def test_runtime_v2_tool_argument_finalize_preserves_unicode(self) -> None:
|
||||
runtime = NativeRuntimeV2.__new__(NativeRuntimeV2)
|
||||
calls = runtime._finalize_tool_calls(
|
||||
{
|
||||
0: {
|
||||
"id": "call-1",
|
||||
"function": "todo_write",
|
||||
"arguments_chunks": [
|
||||
'{"todos":[{"id":"1","title":"梳理需求","status":"in_progress"}]}'
|
||||
],
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(calls[0]["function"], "todo_write")
|
||||
self.assertIsNone(calls[0]["arguments_parse_error"])
|
||||
self.assertEqual(calls[0]["arguments"]["todos"][0]["title"], "梳理需求")
|
||||
|
||||
|
||||
class ToolRegistryUnknownArgumentTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_unknown_argument_returns_structured_error_with_valid_params(self) -> None:
|
||||
"""Unknown tool arguments must surface as a tool-call error, not be
|
||||
silently dropped. The error message must list the valid parameters
|
||||
so the agent can retry with the right names instead of the LLM
|
||||
hallucination (e.g. `digest` vs `summary`)."""
|
||||
registry = ToolRegistry()
|
||||
|
||||
async def example_stub(summary: str = "", parent: str = "") -> dict[str, str]:
|
||||
return {"summary": summary, "parent": parent}
|
||||
|
||||
registry.register(
|
||||
ToolDefinition(
|
||||
name="example_tool",
|
||||
description="stub",
|
||||
parameters={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"summary": {"type": "string"},
|
||||
"parent": {"type": "string"},
|
||||
},
|
||||
},
|
||||
func=example_stub,
|
||||
)
|
||||
)
|
||||
|
||||
result = await registry.invoke(
|
||||
"example_tool",
|
||||
{"digest": "3 children done"},
|
||||
)
|
||||
|
||||
self.assertFalse(result.get("success", True))
|
||||
error = str(result.get("error", ""))
|
||||
self.assertIn("example_tool", error)
|
||||
self.assertIn("'digest'", error)
|
||||
# Error message must enumerate valid params so the agent can retry.
|
||||
self.assertIn("summary", error)
|
||||
self.assertIn("retry", error.lower())
|
||||
|
||||
async def test_known_arguments_still_execute_normally(self) -> None:
|
||||
"""Guardrail: making unknown-arg errors strict must not reject
|
||||
legitimate calls or the `task` auto-injection path."""
|
||||
registry = ToolRegistry()
|
||||
|
||||
async def echo_tool(message: str, task=None) -> dict[str, str]:
|
||||
return {"echoed": message, "task_injected": str(bool(task))}
|
||||
|
||||
registry.register(
|
||||
ToolDefinition(
|
||||
name="echo",
|
||||
description="stub",
|
||||
parameters={
|
||||
"type": "object",
|
||||
"properties": {"message": {"type": "string"}},
|
||||
},
|
||||
func=echo_tool,
|
||||
)
|
||||
)
|
||||
|
||||
result = await registry.invoke(
|
||||
"echo",
|
||||
{"message": "hello"},
|
||||
task=object(),
|
||||
)
|
||||
|
||||
self.assertTrue(result.get("success"))
|
||||
self.assertEqual(result["result"]["echoed"], "hello")
|
||||
self.assertEqual(result["result"]["task_injected"], "True")
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user