aicc-py 0.1.0: initial SDK
- protocol.py: pydantic models for all AICC message types (envelope, session_init, tool_call, tool_result, event, error, heartbeat) - bridge.py: Bridge with @bridge.tool registration, capability checks, session management, serve_forever - client.py: AICCClient with single background reader (safe on concurrent transports like WebSocket), call_tool, events, manifest - tool.py: @tool decorator with schema generation from type hints - schema.py: JSON Schema generation (str/int/float/bool, list, dict, Optional, pydantic models) - transport: Transport protocol, InProcessTransport, WebSocket client+server - tests: 12 passing (integration, schema, websocket roundtrip) - examples: bridge_minimal.py + agent_minimal.py (verified end-to-end)
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
"""Tool registration and the @tool decorator."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import inspect
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Awaitable, Callable
|
||||
|
||||
from aicc.protocol import ToolClass
|
||||
from aicc.schema import function_schema, return_schema
|
||||
|
||||
|
||||
ToolImpl = Callable[..., Any | Awaitable[Any]]
|
||||
|
||||
|
||||
@dataclass
|
||||
class ToolDefinition:
|
||||
id: str
|
||||
cls: ToolClass
|
||||
description: str
|
||||
fn: ToolImpl
|
||||
input_schema: dict[str, Any]
|
||||
output_schema: dict[str, Any]
|
||||
requires_capability: str | None = None
|
||||
limits: dict[str, Any] | None = None
|
||||
strict_input: bool = False
|
||||
|
||||
|
||||
def tool(
|
||||
_fn: ToolImpl | None = None,
|
||||
*,
|
||||
id: str | None = None,
|
||||
cls: ToolClass | None = None,
|
||||
description: str | None = None,
|
||||
requires_capability: str | None = None,
|
||||
limits: dict[str, Any] | None = None,
|
||||
strict_input: bool = False,
|
||||
) -> Any:
|
||||
"""Mark a function as an AICC tool.
|
||||
|
||||
Use as @tool or @tool(cls=ToolClass.SENSOR, description=...).
|
||||
"""
|
||||
|
||||
def wrap(fn: ToolImpl) -> ToolDefinition:
|
||||
tool_id = id or fn.__name__
|
||||
if not tool_id.replace("_", "").isalnum() or tool_id[0].isdigit():
|
||||
raise ValueError(
|
||||
f"Tool id '{tool_id}' must match ^[a-z][a-z0-9_]*$ "
|
||||
"(lowercase letters, digits, underscores; cannot start with digit)."
|
||||
)
|
||||
tool_cls = cls or _infer_class(fn)
|
||||
if description:
|
||||
desc = description
|
||||
else:
|
||||
doc = inspect.getdoc(fn) or ""
|
||||
desc = doc.strip().splitlines()[0] if doc else ""
|
||||
if not desc:
|
||||
raise ValueError(
|
||||
f"Tool '{tool_id}' requires a description (set description= or add a docstring)."
|
||||
)
|
||||
return ToolDefinition(
|
||||
id=tool_id,
|
||||
cls=tool_cls,
|
||||
description=desc,
|
||||
fn=fn,
|
||||
input_schema=function_schema(fn),
|
||||
output_schema=return_schema(fn),
|
||||
requires_capability=requires_capability,
|
||||
limits=limits,
|
||||
strict_input=strict_input,
|
||||
)
|
||||
|
||||
if _fn is not None and callable(_fn):
|
||||
return wrap(_fn)
|
||||
return wrap
|
||||
|
||||
|
||||
def _infer_class(fn: ToolImpl) -> ToolClass:
|
||||
"""Default to sensor if the tool starts with get_/read_/observe_; otherwise actuator."""
|
||||
name = fn.__name__.lower()
|
||||
if any(name.startswith(p) for p in ("get_", "read_", "observe_", "inspect_", "see_", "hear_", "smell_", "touch_")):
|
||||
return ToolClass.SENSOR
|
||||
return ToolClass.ACTUATOR
|
||||
|
||||
|
||||
async def call_tool_impl(defn: ToolDefinition, input_data: dict[str, Any]) -> Any:
|
||||
"""Invoke a registered tool's implementation with the given input."""
|
||||
if defn.strict_input:
|
||||
from jsonschema import validate as _validate # type: ignore
|
||||
|
||||
_validate(instance=input_data, schema=defn.input_schema)
|
||||
result = defn.fn(**input_data) if input_data else defn.fn()
|
||||
if asyncio.iscoroutine(result) or inspect.isawaitable(result):
|
||||
result = await result # type: ignore[func-returns-value]
|
||||
return result
|
||||
Reference in New Issue
Block a user