Files
OpenJarvis/docs/user-guide/tools.md
T
Jon Saad-FalconandClaude Opus 4.6 f75afefcfb Add MkDocs Material documentation site with 40 pages and auto-generated API reference
Sets up a complete documentation website with 7 navigable sections (Home, Getting
Started, User Guide, Architecture, API Reference, Deployment, Development), light/dark
mode, search, code copy, and Mermaid diagram support. API reference pages use
mkdocstrings to auto-generate docs from source docstrings. GitHub Actions workflow
deploys to GitHub Pages on push to main.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 06:09:36 +00:00

392 lines
14 KiB
Markdown

# Tools
The tool system enables agents to perform actions beyond text generation -- calculations, memory lookups, file reading, and sub-model calls. Tools follow a spec-driven design with a central dispatch engine and OpenAI function-calling format support.
## Architecture
```
Agent --> Engine (with tool defs) --> tool_calls response --> ToolExecutor --> Tool.execute()
^ |
| v
+------------------------------- ToolResult <--------------------------------------------+
```
---
## BaseTool ABC
All tools implement the `BaseTool` abstract base class.
```python
from abc import ABC, abstractmethod
from openjarvis.tools._stubs import ToolSpec
from openjarvis.core.types import ToolResult
class BaseTool(ABC):
tool_id: str
@property
@abstractmethod
def spec(self) -> ToolSpec:
"""Return the tool specification."""
@abstractmethod
def execute(self, **params) -> ToolResult:
"""Execute the tool with the given parameters."""
def to_openai_function(self) -> dict:
"""Convert to OpenAI function-calling format."""
```
The `to_openai_function()` method is provided by the base class and converts the tool's spec into the format expected by OpenAI-compatible APIs:
```json
{
"type": "function",
"function": {
"name": "calculator",
"description": "Evaluate a mathematical expression safely.",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Math expression to evaluate"
}
},
"required": ["expression"]
}
}
}
```
---
## ToolSpec
The `ToolSpec` dataclass describes a tool's interface and characteristics.
| Field | Type | Default | Description |
|------------------------|------------------|---------|----------------------------------------------------|
| `name` | `str` | -- | Unique tool identifier |
| `description` | `str` | -- | Human-readable description (sent to the model) |
| `parameters` | `dict[str, Any]` | `{}` | JSON Schema for the tool's parameters |
| `category` | `str` | `""` | Tool category (e.g., `math`, `memory`, `reasoning`) |
| `cost_estimate` | `float` | `0.0` | Estimated cost per invocation |
| `latency_estimate` | `float` | `0.0` | Estimated latency per invocation |
| `requires_confirmation`| `bool` | `False` | Whether the tool requires user confirmation |
| `metadata` | `dict[str, Any]` | `{}` | Additional metadata |
---
## ToolResult
The `ToolResult` dataclass holds the result of a tool execution.
| Field | Type | Default | Description |
|-------------------|------------------|---------|------------------------------------------|
| `tool_name` | `str` | -- | Name of the tool that was called |
| `content` | `str` | -- | The tool's output (text) |
| `success` | `bool` | `True` | Whether the execution succeeded |
| `usage` | `dict[str, Any]` | `{}` | Token usage (for LLM tool) |
| `cost_usd` | `float` | `0.0` | Actual cost of the invocation |
| `latency_seconds` | `float` | `0.0` | Measured execution latency |
| `metadata` | `dict[str, Any]` | `{}` | Additional metadata |
---
## ToolExecutor
The `ToolExecutor` is the central dispatch engine for tool calls. It manages a set of tool instances, parses JSON arguments, measures execution latency, and publishes events on the event bus.
```python
from openjarvis.tools._stubs import ToolExecutor
executor = ToolExecutor(tools=[calculator, think_tool], bus=event_bus)
# Get OpenAI-format tool definitions
openai_tools = executor.get_openai_tools()
# Execute a tool call
from openjarvis.core.types import ToolCall
tc = ToolCall(id="call_1", name="calculator", arguments='{"expression": "2+2"}')
result = executor.execute(tc)
print(result.content) # "4"
```
### Execution Flow
1. **Parse arguments:** The `arguments` JSON string from the `ToolCall` is deserialized.
2. **Publish start event:** `TOOL_CALL_START` is emitted on the event bus with tool name and arguments.
3. **Execute:** The tool's `execute()` method is called with the parsed parameters.
4. **Measure latency:** Execution time is recorded in `result.latency_seconds`.
5. **Publish end event:** `TOOL_CALL_END` is emitted with success status and latency.
6. **Return result:** The `ToolResult` is returned to the caller.
If the tool name is unknown, a `ToolResult` with `success=False` is returned. If JSON parsing fails or the tool raises an exception, the error is captured and returned as a failed `ToolResult`.
### Methods
| Method | Returns | Description |
|---------------------|------------------------|--------------------------------------------|
| `execute(tool_call)`| `ToolResult` | Parse args, dispatch, measure, emit events |
| `available_tools()` | `list[ToolSpec]` | Return specs for all registered tools |
| `get_openai_tools()`| `list[dict]` | Return tools in OpenAI function format |
---
## Built-in Tools
### Calculator
**Registry key:** `calculator` | **Category:** `math`
Evaluates mathematical expressions safely using Python's `ast` module. No arbitrary code execution -- only whitelisted operations are allowed.
**Parameters:**
| Parameter | Type | Required | Description |
|--------------|--------|----------|------------------------------------------|
| `expression` | string | Yes | Math expression (e.g., `"2+3*4"`, `"sqrt(16)"`) |
**Supported operations:**
| Category | Operations |
|--------------|---------------------------------------------------------------|
| Arithmetic | `+`, `-`, `*`, `/`, `//` (floor div), `%` (mod), `**` (power) |
| Functions | `abs`, `round`, `min`, `max`, `sqrt`, `log`, `log10`, `log2` |
| Trigonometry | `sin`, `cos`, `tan` |
| Rounding | `ceil`, `floor` |
| Constants | `pi`, `e` |
**Example:**
```python
from openjarvis.tools.calculator import CalculatorTool
calc = CalculatorTool()
result = calc.execute(expression="sqrt(144) + 3**2")
print(result.content) # "21.0"
print(result.success) # True
```
### Think
**Registry key:** `think` | **Category:** `reasoning`
A zero-cost reasoning scratchpad. The input is echoed back as the output, allowing the model to "think out loud" during a tool-calling loop. This enables chain-of-thought reasoning within the agent workflow.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|--------|----------|------------------------------------------|
| `thought` | string | Yes | The reasoning or thought process |
**Example:**
```python
from openjarvis.tools.think import ThinkTool
think = ThinkTool()
result = think.execute(thought="Let me break this problem into steps...")
print(result.content) # "Let me break this problem into steps..."
print(result.success) # True
```
!!! info "Cost and Latency"
The Think tool has zero cost and near-zero latency, making it ideal for structured reasoning without consuming additional resources.
### Retrieval
**Registry key:** `retrieval` | **Category:** `memory`
Searches the memory backend for relevant context and returns formatted results with source attribution.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|---------|----------|------------------------------------------|
| `query` | string | Yes | Search query |
| `top_k` | integer | No | Number of results (default: 5) |
**Constructor parameters:**
| Parameter | Type | Default | Description |
|-----------|-----------------|---------|--------------------------------|
| `backend` | `MemoryBackend` | `None` | Memory backend to search |
| `top_k` | `int` | `5` | Default number of results |
**Example:**
```python
from openjarvis.tools.retrieval import RetrievalTool
from openjarvis.memory.sqlite import SQLiteMemory
backend = SQLiteMemory(db_path="./memory.db")
retrieval = RetrievalTool(backend=backend)
result = retrieval.execute(query="machine learning")
print(result.content) # Formatted context with source tags
```
### LLM
**Registry key:** `llm` | **Category:** `inference`
Delegates a sub-query to an inference engine. Useful for summarization, sub-questions, or generating structured output within an agent workflow.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|--------|----------|------------------------------------------|
| `prompt` | string | Yes | The prompt to send to the model |
| `system` | string | No | Optional system message for context |
**Constructor parameters:**
| Parameter | Type | Default | Description |
|-----------|-------------------|---------|--------------------------------|
| `engine` | `InferenceEngine` | `None` | Inference engine to use |
| `model` | `str` | `""` | Model identifier |
**Example:**
```python
from openjarvis.tools.llm_tool import LLMTool
llm = LLMTool(engine=my_engine, model="qwen3:8b")
result = llm.execute(
prompt="Summarize: AI is transforming industries...",
system="You are a concise summarizer.",
)
print(result.content)
```
### FileRead
**Registry key:** `file_read` | **Category:** `filesystem`
Reads file contents with safety validations. Supports optional directory restrictions, file size limits (1 MB max), and line count limiting.
**Parameters:**
| Parameter | Type | Required | Description |
|-------------|---------|----------|------------------------------------------|
| `path` | string | Yes | Path to the file to read |
| `max_lines` | integer | No | Maximum lines to return (default: all) |
**Constructor parameters:**
| Parameter | Type | Default | Description |
|----------------|--------------|---------|-----------------------------------------------|
| `allowed_dirs` | `list[str]` | `None` | Restrict file access to these directories |
**Safety features:**
- Path validation against allowed directories (when configured)
- Maximum file size: 1 MB
- UTF-8 encoding required (rejects binary files)
- Existence and file-type checks
**Example:**
```python
from openjarvis.tools.file_read import FileReadTool
reader = FileReadTool(allowed_dirs=["/home/user/projects"])
result = reader.execute(path="/home/user/projects/README.md", max_lines=50)
print(result.content)
print(result.metadata) # {"path": "/home/user/projects/README.md", "size_bytes": 1234}
```
---
## Tool Registration
Tools are registered via the `@ToolRegistry.register()` decorator, making them discoverable by name at runtime.
```python
from openjarvis.core.registry import ToolRegistry
from openjarvis.tools._stubs import BaseTool, ToolSpec
from openjarvis.core.types import ToolResult
@ToolRegistry.register("my_tool")
class MyTool(BaseTool):
tool_id = "my_tool"
@property
def spec(self) -> ToolSpec:
return ToolSpec(
name="my_tool",
description="A custom tool that does something useful.",
parameters={
"type": "object",
"properties": {
"input": {
"type": "string",
"description": "The input to process.",
},
},
"required": ["input"],
},
category="custom",
)
def execute(self, **params) -> ToolResult:
value = params.get("input", "")
return ToolResult(
tool_name="my_tool",
content=f"Processed: {value}",
success=True,
)
```
After registration, use the tool with an agent:
```bash
jarvis ask --agent orchestrator --tools my_tool "Process this data"
```
---
## Using Tools with Agents
### Via CLI
Tools are specified as a comma-separated list with the `--tools` flag. An agent (typically `orchestrator`) must be selected:
```bash
# Single tool
jarvis ask --agent orchestrator --tools calculator "What is 15% of 340?"
# Multiple tools
jarvis ask --agent orchestrator --tools calculator,think "Solve: 2x + 5 = 13"
# All available tools (list them)
jarvis ask --agent orchestrator --tools calculator,think,retrieval,file_read "..."
```
### Via Python SDK
Tools are passed as a list of name strings:
```python
from openjarvis import Jarvis
j = Jarvis()
# Use calculator and think tools
result = j.ask_full(
"What is the area of a circle with radius 7?",
agent="orchestrator",
tools=["calculator", "think"],
)
for tr in result["tool_results"]:
print(f" {tr['tool_name']}: {tr['content']} (success={tr['success']})")
j.close()
```
The SDK automatically instantiates tool objects with appropriate dependencies. For example, the `retrieval` tool receives the configured memory backend, and the `llm` tool receives the active engine and model.