Skip to content

05 · Tools

A Tool is a capability a participant can invoke mid-turn. The harness runs a uniform tool loop; each family parses its own native call format (Hermes/Qwen <tool_call> JSON, Gemma ```tool_code, Llama <|python_tag|>). Tools hold live callables, so they are not serializable — templates store tool names and resolve them against a ToolRegistry at build time (mirroring how models resolve from ids).

Define and register a tool

from interlens import Tool, DEFAULT_REGISTRY

class Calculator(Tool):
    name = "calculator"

    @property
    def schema(self) -> dict:
        return {
            "type": "function",
            "function": {
                "name": "calculator",
                "description": "Evaluate a basic arithmetic expression and return the result.",
                "parameters": {
                    "type": "object",
                    "properties": {"expression": {"type": "string", "description": "e.g. '2 * (3 + 4)'"}},
                    "required": ["expression"],
                },
            },
        }

    def __call__(self, expression: str) -> str:
        return str(eval(expression, {"__builtins__": {}}))   # sandbox eval for the example only

DEFAULT_REGISTRY.register(Calculator())   # now resolvable by the name "calculator"

The schema is passed straight to apply_chat_template(tools=...), so use the standard function-calling shape.

Give a participant tools

Live participant

from interlens import AutoModelParticipant
solver = AutoModelParticipant.from_pretrained(
    "Qwen/Qwen2.5-7B-Instruct", name="solver",
    tools=(Calculator(),),     # the tool objects
    max_tool_iters=4,          # bound the call→result→call loop
)

On a participant

from interlens import Conversation, AutoModelParticipant
solver = AutoModelParticipant.from_pretrained("Qwen/Qwen2.5-7B-Instruct", name="solver",
                                              tools=(Calculator(),), max_tool_iters=4)
conv = Conversation(participants=[solver], shared_context="Compute 17 * 23 using your tool, then explain.")
conv.run(turns=2)

Tools are resolved by name from DEFAULT_REGISTRY only when a saved conversation is reloaded (Conversation.load) — save stores each participant's tools by name; pass registry= there for a scoped registry.

What the loop does

For each turn the model may emit tool calls; the harness executes each, appends the call + result to a private working view, and lets the model react — up to max_tool_iters times. Only the final natural-language message reaches the shared transcript; the full call/result trail is kept in msg.metadata["tool_trail"]. Tool exceptions are returned to the model as error: … results (data, not crashes), so a failing tool doesn't kill the run.

msg = conv.transcript[-1]
for step in msg.metadata.get("tool_trail", []):
    print(step)   # {"name": "calculator", "arguments": {...}, "output": "391", "error": False}

Use a scoped registry instead of the global one by passing registry= to Conversation.load.

Next: hooks.