from typing import Any from agr_assistent.llm.tools import Tool, ToolError, ToolRegistry, ToolResult, validate_arguments _SCHEMA = { "type": "object", "properties": { "level": {"type": "integer"}, "ratio": {"type": "number"}, "loud": {"type": "boolean"}, "mode": {"type": "string", "enum": ["on", "off"]}, }, "required": ["level"], } def test_arguments_are_converted_from_strings() -> None: arguments, errors = validate_arguments( _SCHEMA, {"level": "30", "ratio": "0,5", "loud": "true", "mode": "on", "extra": 1} ) assert errors == [] assert arguments == {"level": 30, "ratio": 0.5, "loud": True, "mode": "on"} def test_invalid_arguments_are_reported() -> None: _arguments, errors = validate_arguments(_SCHEMA, {"ratio": "много", "loud": 1, "mode": "auto"}) assert "не указан параметр level" in errors assert any(error.startswith("ratio:") for error in errors) assert any(error.startswith("loud:") for error in errors) assert any("недопустимое значение 'auto'" in error for error in errors) def _registry() -> ToolRegistry: def handler(arguments: dict[str, Any]) -> ToolResult: if arguments["level"] > 100: raise ToolError("слишком громко") if arguments["level"] < 0: raise RuntimeError("сломалось") return ToolResult(True, f"ok {arguments['level']}", "готово") return ToolRegistry([Tool("volume", "Громкость", _SCHEMA, handler)]) def test_registry_executes_tool_and_exposes_schema() -> None: registry = _registry() assert registry.schemas()[0]["function"]["name"] == "volume" assert registry.execute("volume", '{"level": 30}') == ToolResult(True, "ok 30", "готово") def test_registry_turns_problems_into_failed_results() -> None: registry = _registry() assert not registry.execute("missing", "{}").ok assert not registry.execute("volume", "{not json").ok assert not registry.execute("volume", "[1]").ok assert not registry.execute("volume", "{}").ok assert registry.execute("volume", '{"level": 101}') == ToolResult( False, "слишком громко", "слишком громко" ) failed = registry.execute("volume", '{"level": -1}') assert not failed.ok and "сломалось" in failed.content