07-tool-using-agent / src /executor.py
FlyingNunchucks's picture
Sync from GitHub via hub-sync
041303e verified
Raw History Blame Contribute Delete
4.91 kB
from datetime import datetime, timezone
from time import perf_counter
from typing import Any, Dict
from src.schemas import ToolAuthorization, ToolCall, ToolResult
from src.tool_registry import TOOL_REGISTRY, get_tool
def _validate_arguments(
tool_name: str,
arguments: Dict[str, Any],
) -> None:
"""Validate arguments against the application-owned tool schema."""
if tool_name not in TOOL_REGISTRY:
raise ValueError(
f"Tool '{tool_name}' is not registered."
)
schema = TOOL_REGISTRY[tool_name]["parameters"]
properties = schema.get("properties", {})
required = schema.get("required", [])
allow_additional = schema.get(
"additionalProperties",
True,
)
for required_argument in required:
if required_argument not in arguments:
raise ValueError(
f"Missing required argument "
f"'{required_argument}' for tool "
f"'{tool_name}'."
)
if not allow_additional:
unknown_arguments = (
set(arguments.keys()) - set(properties.keys())
)
if unknown_arguments:
unknown = ", ".join(
sorted(unknown_arguments)
)
raise ValueError(
f"Unexpected argument(s) for tool "
f"'{tool_name}': {unknown}"
)
python_types = {
"string": str,
"integer": int,
"number": (int, float),
"boolean": bool,
"object": dict,
"array": list,
}
for argument_name, argument_value in arguments.items():
parameter_schema = properties.get(argument_name)
if not parameter_schema:
continue
expected_type_name = parameter_schema.get("type")
if not expected_type_name:
continue
expected_python_type = python_types.get(
expected_type_name
)
if expected_python_type is None:
continue
if not isinstance(
argument_value,
expected_python_type,
):
raise TypeError(
f"Argument '{argument_name}' for tool "
f"'{tool_name}' must be of type "
f"'{expected_type_name}'."
)
def authorize_tool_call(call: ToolCall) -> ToolAuthorization:
"""Decide whether a model-proposed tool call may execute."""
try:
_validate_arguments(
tool_name=call.tool_name,
arguments=call.arguments,
)
except Exception as exc:
return ToolAuthorization(
call_id=call.call_id,
tool_name=call.tool_name,
status="blocked",
reason=f"{type(exc).__name__}: {exc}",
)
return ToolAuthorization(
call_id=call.call_id,
tool_name=call.tool_name,
status="approved",
reason=(
"Tool is registered and arguments match the approved schema."
),
)
def execute_authorized_tool(
call: ToolCall,
authorization: ToolAuthorization,
) -> ToolResult:
"""Execute only a previously authorized call and normalize its result."""
started_at = datetime.now(timezone.utc)
timer_start = perf_counter()
if authorization.call_id != call.call_id:
raise ValueError("Authorization call_id does not match tool call.")
if authorization.tool_name != call.tool_name:
raise ValueError("Authorization tool_name does not match tool call.")
if authorization.status == "blocked":
completed_at = datetime.now(timezone.utc)
duration_ms = (perf_counter() - timer_start) * 1000
return ToolResult(
call_id=call.call_id,
tool_name=call.tool_name,
status="blocked",
output=None,
error=authorization.reason,
started_at=started_at,
completed_at=completed_at,
duration_ms=round(duration_ms, 3),
)
try:
tool = get_tool(call.tool_name)
output = tool(**call.arguments)
status = "success"
error = None
except Exception as exc:
output = None
status = "error"
error = f"{type(exc).__name__}: {exc}"
completed_at = datetime.now(timezone.utc)
duration_ms = (perf_counter() - timer_start) * 1000
return ToolResult(
call_id=call.call_id,
tool_name=call.tool_name,
status=status,
output=output,
error=error,
started_at=started_at,
completed_at=completed_at,
duration_ms=round(duration_ms, 3),
)
def execute_tool(call: ToolCall) -> ToolResult:
"""Authorize and execute one tool call through the controlled boundary."""
authorization = authorize_tool_call(call)
return execute_authorized_tool(
call=call,
authorization=authorization,
)