Add email_file and write_press_releases to tool docs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>cora-start
parent
8f6e218221
commit
274163508f
|
|
@ -0,0 +1,557 @@
|
|||
# CheddahBot
|
||||
|
||||
A personal AI assistant built in Python with a Gradio web UI. CheddahBot supports multiple LLM providers (hot-swappable at runtime), a 4-layer memory system, 15+ built-in tools, a task scheduler with heartbeat, voice chat, and the ability for the agent to create new tools and skills on the fly.
|
||||
|
||||
The UI runs as a Progressive Web App (PWA), so you can install it on your phone and use it like a native app.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Features](#features)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Configuration](#configuration)
|
||||
- [Provider Setup](#provider-setup)
|
||||
- [Architecture](#architecture)
|
||||
- [Memory System](#memory-system)
|
||||
- [Tools Reference](#tools-reference)
|
||||
- [Meta-Tools: Runtime Tool and Skill Creation](#meta-tools-runtime-tool-and-skill-creation)
|
||||
- [Scheduler and Heartbeat](#scheduler-and-heartbeat)
|
||||
- [Voice Chat](#voice-chat)
|
||||
- [Identity System](#identity-system)
|
||||
- [Known Issues and Limitations](#known-issues-and-limitations)
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **Multi-model support** -- Claude (via Claude Code CLI), OpenRouter (GPT-4o, Gemini, Mistral, Llama, and more), Ollama (local), LM Studio (local). All hot-swappable from the UI dropdown at any time.
|
||||
- **Gradio web UI** -- Clean chat interface with model switcher, conversation history, file uploads, microphone input, and camera. Launches as a PWA for mobile use.
|
||||
- **4-layer memory** -- Identity files (SOUL.md, USER.md), long-term memory (MEMORY.md), daily logs (YYYY-MM-DD.md), and semantic search over all memory via sentence-transformer embeddings.
|
||||
- **15+ built-in tools** -- File operations, shell commands, web search, URL fetching, Python code execution, image analysis, CSV/JSON processing, memory management, task scheduling.
|
||||
- **Meta-tools** -- The agent can create entirely new tools and multi-step skills at runtime. New tools are written as Python modules and hot-loaded without restarting.
|
||||
- **Task scheduler** -- Cron-based recurring tasks and one-time scheduled prompts. Includes a heartbeat system that periodically runs a proactive checklist.
|
||||
- **Voice chat** -- Speech-to-text via Whisper (local or API) and text-to-speech via edge-tts. Record audio, get a spoken response.
|
||||
- **Persistent storage** -- SQLite database for conversations, messages, scheduled tasks, and key-value storage. All conversations are saved and browsable.
|
||||
- **Streaming responses** -- Responses stream token-by-token in the chat UI for all OpenAI-compatible providers.
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Python 3.11 or later
|
||||
- (Optional) Node.js / npm -- only needed if using the Claude Code CLI provider
|
||||
- (Optional) Ollama or LM Studio -- for local model inference
|
||||
- (Optional) ffmpeg -- for video frame extraction
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone <your-repo-url> CheddahBot
|
||||
cd CheddahBot
|
||||
|
||||
# Create a virtual environment (recommended)
|
||||
python -m venv .venv
|
||||
.venv\Scripts\activate # Windows
|
||||
# source .venv/bin/activate # macOS/Linux
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Configure
|
||||
|
||||
Copy or edit the `.env` file in the project root:
|
||||
|
||||
```env
|
||||
# Required for OpenRouter (recommended primary provider)
|
||||
OPENROUTER_API_KEY=your-key-here
|
||||
|
||||
# Optional overrides
|
||||
# CHEDDAH_DEFAULT_MODEL=claude-sonnet-4-20250514
|
||||
# CHEDDAH_HOST=0.0.0.0
|
||||
# CHEDDAH_PORT=7860
|
||||
```
|
||||
|
||||
Get an OpenRouter API key at https://openrouter.ai/keys.
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
python -m cheddahbot
|
||||
```
|
||||
|
||||
The Gradio UI will launch at `http://localhost:7860` by default. On your local network it will also be accessible at `http://<your-ip>:7860`. The PWA can be installed from the browser on mobile devices.
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
CheddahBot loads configuration in this priority order: environment variables (highest), then `config.yaml`, then built-in defaults.
|
||||
|
||||
### config.yaml
|
||||
|
||||
Located at the project root. Controls server settings, memory parameters, scheduler timing, local model endpoints, and shell safety settings.
|
||||
|
||||
```yaml
|
||||
# Default model to use on startup
|
||||
default_model: "claude-sonnet-4-20250514"
|
||||
|
||||
# Gradio server settings
|
||||
host: "0.0.0.0"
|
||||
port: 7860
|
||||
|
||||
# Memory settings
|
||||
memory:
|
||||
max_context_messages: 50 # Messages kept in the LLM context window
|
||||
flush_threshold: 40 # Auto-summarize when message count exceeds this
|
||||
embedding_model: "all-MiniLM-L6-v2" # Sentence-transformer model for semantic search
|
||||
search_top_k: 5 # Number of semantic search results returned
|
||||
|
||||
# Scheduler settings
|
||||
scheduler:
|
||||
heartbeat_interval_minutes: 30
|
||||
poll_interval_seconds: 60
|
||||
|
||||
# Local model endpoints (auto-detected)
|
||||
ollama_url: "http://localhost:11434"
|
||||
lmstudio_url: "http://localhost:1234"
|
||||
|
||||
# Safety settings
|
||||
shell:
|
||||
blocked_commands:
|
||||
- "rm -rf /"
|
||||
- "format"
|
||||
- ":(){:|:&};:"
|
||||
require_approval: false # If true, shell commands need user confirmation
|
||||
```
|
||||
|
||||
### .env
|
||||
|
||||
Environment variables with the `CHEDDAH_` prefix override `config.yaml` values:
|
||||
|
||||
| Variable | Description |
|
||||
|---|---|
|
||||
| `OPENROUTER_API_KEY` | Your OpenRouter API key (recommended) |
|
||||
| `CHEDDAH_DEFAULT_MODEL` | Override the default model ID |
|
||||
| `CHEDDAH_HOST` | Override the Gradio server host |
|
||||
| `CHEDDAH_PORT` | Override the Gradio server port |
|
||||
| `GMAIL_USERNAME` | Gmail address for sending emails (enables email tool) |
|
||||
| `GMAIL_APP_PASSWORD` | Gmail app password ([create one here](https://myaccount.google.com/apppasswords)) |
|
||||
| `EMAIL_DEFAULT_TO` | Default recipient for the `email_file` tool |
|
||||
|
||||
### Identity Files
|
||||
|
||||
See the [Identity System](#identity-system) section below.
|
||||
|
||||
---
|
||||
|
||||
## Provider Setup
|
||||
|
||||
CheddahBot routes model requests to different backends based on the selected model ID. You can switch models at any time from the dropdown in the UI.
|
||||
|
||||
### OpenRouter (Recommended)
|
||||
|
||||
OpenRouter is the recommended primary provider. It gives full control over system prompts, supports tool/function calling, and provides access to a wide range of models through a single API key -- including Claude, GPT-4o, Gemini, Mistral, Llama, and many others.
|
||||
|
||||
1. Sign up at https://openrouter.ai and create an API key.
|
||||
2. Set `OPENROUTER_API_KEY` in your `.env` file.
|
||||
3. Select any OpenRouter model from the UI dropdown.
|
||||
|
||||
Pre-configured OpenRouter models:
|
||||
|
||||
| Model ID | Display Name |
|
||||
|---|---|
|
||||
| `openai/gpt-4o` | GPT-4o |
|
||||
| `openai/gpt-4o-mini` | GPT-4o Mini |
|
||||
| `google/gemini-2.0-flash-001` | Gemini 2.0 Flash |
|
||||
| `google/gemini-2.5-pro-preview` | Gemini 2.5 Pro |
|
||||
| `mistralai/mistral-large` | Mistral Large |
|
||||
| `meta-llama/llama-3.3-70b-instruct` | Llama 3.3 70B |
|
||||
|
||||
You can use any model ID supported by OpenRouter -- the ones above are just the pre-populated dropdown entries.
|
||||
|
||||
### Ollama (Local, Free)
|
||||
|
||||
Ollama is fully supported for running local models with no API key required.
|
||||
|
||||
1. Install Ollama from https://ollama.com.
|
||||
2. Pull a model: `ollama pull llama3.1` (or any model you want).
|
||||
3. Start Ollama (it runs on `http://localhost:11434` by default).
|
||||
4. Click the **Refresh** button in the CheddahBot UI. Your Ollama models will appear in the dropdown with a `[Ollama]` prefix.
|
||||
|
||||
Model IDs follow the format `local/ollama/<model-name>` (e.g., `local/ollama/llama3.1`).
|
||||
|
||||
### LM Studio (Local)
|
||||
|
||||
LM Studio provides a local OpenAI-compatible API.
|
||||
|
||||
1. Install LM Studio from https://lmstudio.ai.
|
||||
2. Load a model and start the local server (default: `http://localhost:1234`).
|
||||
3. Click **Refresh** in the CheddahBot UI. Your LM Studio models appear with a `[LM Studio]` prefix.
|
||||
|
||||
Model IDs follow the format `local/lmstudio/<model-id>`.
|
||||
|
||||
### Claude Code CLI
|
||||
|
||||
Claude models (Sonnet, Opus, Haiku) are routed through the Claude Code CLI (`claude -p`), which uses your Anthropic Max subscription.
|
||||
|
||||
1. Install Claude Code: `npm install -g @anthropic-ai/claude-code`
|
||||
2. Make sure `claude` is available in your PATH.
|
||||
3. Claude models will appear in the dropdown by default.
|
||||
|
||||
**Important caveat:** The Claude Code CLI is designed as a coding assistant. When invoked via `claude -p`, it does not fully respect custom system prompts -- it applies its own internal system prompt on top of whatever you provide. This means the personality defined in `SOUL.md` and the tool-use instructions may not be followed reliably when using Claude via this path. This is a known limitation of the CLI integration.
|
||||
|
||||
**Recommendation:** If you want full control over system prompts and behavior (which is important for the identity system, memory injection, and tool calling to work properly), use Claude models through OpenRouter instead. OpenRouter supports Claude models with standard OpenAI-compatible API semantics, giving you complete control over the system prompt.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
CheddahBot/
|
||||
config.yaml # Main configuration file
|
||||
.env # API keys and environment overrides
|
||||
requirements.txt # Python dependencies
|
||||
identity/
|
||||
SOUL.md # Agent personality definition
|
||||
USER.md # User profile (filled in by you)
|
||||
HEARTBEAT.md # Proactive checklist for heartbeat cycle
|
||||
memory/ # Runtime memory files (gitignored)
|
||||
MEMORY.md # Long-term learned facts
|
||||
YYYY-MM-DD.md # Daily logs
|
||||
embeddings.db # Vector embeddings for semantic search
|
||||
data/
|
||||
cheddahbot.db # SQLite database (conversations, tasks, KV store)
|
||||
uploads/ # User-uploaded files
|
||||
generated/ # Agent-generated files (TTS output, etc.)
|
||||
skills/ # User/agent-created skill modules
|
||||
cheddahbot/
|
||||
__main__.py # Entry point (python -m cheddahbot)
|
||||
config.py # Configuration loader
|
||||
db.py # SQLite persistence layer
|
||||
llm.py # Model-agnostic LLM adapter
|
||||
router.py # System prompt builder and message formatter
|
||||
agent.py # Core agent loop (LLM + tools + memory)
|
||||
memory.py # 4-layer memory system
|
||||
ui.py # Gradio web interface
|
||||
scheduler.py # Task scheduler and heartbeat
|
||||
media.py # Audio/video processing (STT, TTS, video frames)
|
||||
providers/ # Reserved for future custom providers
|
||||
tools/
|
||||
__init__.py # Tool registry, @tool decorator, auto-discovery
|
||||
file_ops.py # File read/write/edit/search tools
|
||||
shell.py # Shell command execution
|
||||
web.py # Web search and URL fetching
|
||||
code_exec.py # Python code execution (sandboxed subprocess)
|
||||
calendar_tool.py # Memory and scheduling tools
|
||||
image.py # Image analysis via vision-capable LLM
|
||||
data_proc.py # CSV and JSON processing
|
||||
build_tool.py # Meta-tool: create new tools at runtime
|
||||
build_skill.py # Meta-tool: create new skills at runtime
|
||||
skills/
|
||||
__init__.py # Skill registry, @skill decorator, dynamic loader
|
||||
```
|
||||
|
||||
### Module Responsibilities
|
||||
|
||||
**`__main__.py`** -- Application entry point. Initializes configuration, database, LLM adapter, agent, memory system, tool system, and scheduler in sequence, then launches the Gradio UI.
|
||||
|
||||
**`config.py`** -- Loads configuration from `.env`, `config.yaml`, and built-in defaults using a layered override approach. Defines dataclasses for `Config`, `MemoryConfig`, `SchedulerConfig`, and `ShellConfig`. Creates required data directories on startup.
|
||||
|
||||
**`db.py`** -- SQLite persistence layer using WAL mode for concurrent access. Manages conversations, messages (with tool call metadata), scheduled tasks, task run logs, and a general-purpose key-value store. Thread-safe via `threading.local()`.
|
||||
|
||||
**`llm.py`** -- Model-agnostic LLM adapter that routes requests to the appropriate backend based on the model ID. Claude models go through the Claude Code CLI subprocess. All other models (OpenRouter, Ollama, LM Studio) go through the OpenAI Python SDK against the appropriate base URL. Handles streaming, tool call accumulation, and model discovery for local providers.
|
||||
|
||||
**`router.py`** -- Builds the system prompt by concatenating identity files (SOUL.md, USER.md), memory context, tool descriptions, and core instructions. Also handles formatting conversation history into the LLM message format.
|
||||
|
||||
**`agent.py`** -- The core agent loop. On each user message: stores the message, builds the system prompt with memory context, calls the LLM, checks for tool calls, executes tools, feeds results back to the LLM, and repeats (up to 10 iterations). Handles streaming output to the UI. Triggers memory auto-flush when conversation length exceeds the configured threshold.
|
||||
|
||||
**`memory.py`** -- Implements the 4-layer memory system (see [Memory System](#memory-system) below). Manages long-term memory files, daily logs, embedding-based semantic search, conversation summarization, and reindexing.
|
||||
|
||||
**`ui.py`** -- Gradio interface with a chat panel, model dropdown with refresh, new chat button, multimodal input (text, file upload, microphone), voice chat accordion, conversation history browser, and settings section. Supports streaming responses.
|
||||
|
||||
**`scheduler.py`** -- Background thread that polls for due scheduled tasks (cron-based or one-time) and executes them by sending prompts to the agent. Includes a separate heartbeat thread that periodically reads `HEARTBEAT.md` and asks the agent to act on any items that need attention.
|
||||
|
||||
**`media.py`** -- Audio and video processing. Speech-to-text via local Whisper or OpenAI Whisper API. Text-to-speech via edge-tts (free, no API key). Video frame extraction via ffmpeg.
|
||||
|
||||
**`tools/__init__.py`** -- Tool registry with a `@tool` decorator for registering functions, automatic parameter schema extraction from type hints, OpenAI function-calling schema generation, auto-discovery of tool modules via `pkgutil`, and runtime execution with context injection.
|
||||
|
||||
**`skills/__init__.py`** -- Skill registry with a `@skill` decorator, dynamic loading from `.py` files in the `skills/` directory, and runtime execution.
|
||||
|
||||
---
|
||||
|
||||
## Memory System
|
||||
|
||||
CheddahBot uses a 4-layer memory architecture that gives the agent both persistent knowledge and contextual awareness.
|
||||
|
||||
### Layer 1: Identity (SOUL.md + USER.md)
|
||||
|
||||
Static files in `identity/` that define who the agent is and who the user is. These are loaded into the system prompt on every request. See [Identity System](#identity-system).
|
||||
|
||||
### Layer 2: Long-Term Memory (MEMORY.md)
|
||||
|
||||
A Markdown file at `memory/MEMORY.md` containing timestamped facts, preferences, and instructions the agent has learned. The agent writes to this file using the `remember_this` tool. The most recent 2000 characters are injected into the system prompt.
|
||||
|
||||
Example entries:
|
||||
```
|
||||
- [2025-06-15 14:30] User prefers tabs over spaces
|
||||
- [2025-06-15 15:00] User's project deadline is June 30th
|
||||
```
|
||||
|
||||
### Layer 3: Daily Logs (YYYY-MM-DD.md)
|
||||
|
||||
Date-stamped Markdown files in `memory/` that capture timestamped notes, conversation summaries, and heartbeat actions for each day. The agent writes to these using the `log_note` tool. Today's log (up to 1500 characters) is injected into the system prompt.
|
||||
|
||||
When conversation length exceeds the configured `flush_threshold` (default 40 messages), older messages are automatically summarized and moved to the daily log.
|
||||
|
||||
### Layer 4: Semantic Search (Embeddings)
|
||||
|
||||
All memory entries are indexed using sentence-transformer embeddings (`all-MiniLM-L6-v2` by default) and stored in `memory/embeddings.db`. On each user message, a semantic search is performed against the index, and the top-k most relevant memory fragments are injected into the system prompt.
|
||||
|
||||
If `sentence-transformers` is not installed, the system falls back to a keyword-based search over the Markdown files.
|
||||
|
||||
The `reindex_all()` method rebuilds the entire embedding index from all memory files.
|
||||
|
||||
---
|
||||
|
||||
## Tools Reference
|
||||
|
||||
Tools are registered using the `@tool` decorator and auto-discovered at startup. They are exposed to the LLM via OpenAI-compatible function-calling schema. The agent can chain multiple tool calls in a single response (up to 10 iterations).
|
||||
|
||||
### Files
|
||||
|
||||
| Tool | Description |
|
||||
|---|---|
|
||||
| `read_file(path)` | Read the contents of a file (up to 50K chars) |
|
||||
| `write_file(path, content)` | Write content to a file (creates or overwrites) |
|
||||
| `edit_file(path, old_text, new_text)` | Replace the first occurrence of text in a file |
|
||||
| `list_directory(path)` | List files and folders with sizes |
|
||||
| `search_files(pattern, directory)` | Search for files matching a glob pattern |
|
||||
| `search_in_files(query, directory, extension)` | Search for text content across files |
|
||||
|
||||
### Shell
|
||||
|
||||
| Tool | Description |
|
||||
|---|---|
|
||||
| `run_command(command, timeout)` | Execute a shell command (with safety checks, max 120s) |
|
||||
|
||||
Blocked patterns include `rm -rf /`, `format c:`, fork bombs, `dd if=/dev/zero`, `mkfs.`, and writes to `/dev/sda`.
|
||||
|
||||
### Web
|
||||
|
||||
| Tool | Description |
|
||||
|---|---|
|
||||
| `web_search(query, max_results)` | Search the web via DuckDuckGo (no API key needed) |
|
||||
| `fetch_url(url)` | Fetch and extract text content from a URL (HTML parsed, scripts/nav stripped) |
|
||||
|
||||
### Code
|
||||
|
||||
| Tool | Description |
|
||||
|---|---|
|
||||
| `run_python(code, timeout)` | Execute Python code in a subprocess (max 60s) |
|
||||
|
||||
### Memory
|
||||
|
||||
| Tool | Description |
|
||||
|---|---|
|
||||
| `remember_this(text)` | Save a fact or instruction to long-term memory (MEMORY.md) |
|
||||
| `search_memory(query)` | Semantic search through saved memories |
|
||||
| `log_note(text)` | Add a timestamped note to today's daily log |
|
||||
|
||||
### Scheduling
|
||||
|
||||
| Tool | Description |
|
||||
|---|---|
|
||||
| `schedule_task(name, prompt, schedule)` | Schedule a recurring (cron) or one-time (`once:YYYY-MM-DDTHH:MM`) task |
|
||||
| `list_tasks()` | List all scheduled tasks with status |
|
||||
|
||||
### Media
|
||||
|
||||
| Tool | Description |
|
||||
|---|---|
|
||||
| `analyze_image(path, question)` | Analyze an image using the current vision-capable LLM |
|
||||
|
||||
### Data
|
||||
|
||||
| Tool | Description |
|
||||
|---|---|
|
||||
| `read_csv(path, max_rows)` | Read a CSV file and display as a formatted table |
|
||||
| `read_json(path)` | Read and pretty-print a JSON file |
|
||||
| `query_json(path, json_path)` | Extract data from JSON using dot-notation (`data.users.0.name`) |
|
||||
|
||||
### Content
|
||||
|
||||
| Tool | Description |
|
||||
|---|---|
|
||||
| `write_press_releases(topic, company_name, ...)` | Full autonomous PR pipeline: generates headlines, writes 2 press releases with JSON-LD schemas, saves `.txt` + `.docx` files |
|
||||
|
||||
### Delivery
|
||||
|
||||
| Tool | Description |
|
||||
|---|---|
|
||||
| `email_file(file_path, to, subject)` | Email a file as an attachment via Gmail SMTP. Auto-converts `.txt` to `.docx` before sending |
|
||||
|
||||
### Meta
|
||||
|
||||
| Tool | Description |
|
||||
|---|---|
|
||||
| `build_tool(name, description, code)` | Create a new tool module at runtime (see below) |
|
||||
| `build_skill(name, description, steps)` | Create a new multi-step skill at runtime (see below) |
|
||||
|
||||
---
|
||||
|
||||
## Meta-Tools: Runtime Tool and Skill Creation
|
||||
|
||||
One of CheddahBot's distinctive features is that the agent can extend its own capabilities at runtime by writing new tools and skills.
|
||||
|
||||
### build_tool
|
||||
|
||||
The `build_tool` meta-tool allows the agent to create a new tool by writing Python code with the `@tool` decorator. The code is saved as a new module in the `cheddahbot/tools/` directory and hot-loaded immediately -- no restart required.
|
||||
|
||||
Example: if you ask "create a tool that counts words in a file", the agent will:
|
||||
|
||||
1. Write a Python function with the `@tool` decorator.
|
||||
2. Save it to `cheddahbot/tools/word_counter.py`.
|
||||
3. Import and register it at runtime.
|
||||
4. The new tool is immediately available for use.
|
||||
|
||||
The generated module includes the necessary imports automatically. Tool names must be valid Python identifiers and cannot overwrite existing modules.
|
||||
|
||||
### build_skill
|
||||
|
||||
The `build_skill` meta-tool creates multi-step skills -- higher-level operations that combine multiple actions. Skills are saved to the `skills/` directory and loaded via the skill registry.
|
||||
|
||||
Skills use the `@skill` decorator from the skills module and can orchestrate complex workflows.
|
||||
|
||||
---
|
||||
|
||||
## Scheduler and Heartbeat
|
||||
|
||||
### Scheduled Tasks
|
||||
|
||||
The scheduler runs as a background thread that polls the database for due tasks every 60 seconds (configurable via `scheduler.poll_interval_seconds`).
|
||||
|
||||
Tasks can be created by the agent using the `schedule_task` tool:
|
||||
|
||||
- **Cron schedule** -- Standard cron expressions (e.g., `0 9 * * *` for daily at 9 AM). The next run time is calculated after each execution.
|
||||
- **One-time** -- Use the format `once:YYYY-MM-DDTHH:MM`. The task is automatically disabled after it runs.
|
||||
|
||||
When a task fires, its prompt is sent to the agent via `respond_to_prompt`, and the result is logged to the `task_run_logs` table.
|
||||
|
||||
### Heartbeat
|
||||
|
||||
The heartbeat is a separate background thread that runs on a configurable interval (default: every 30 minutes). On each cycle, it:
|
||||
|
||||
1. Reads `identity/HEARTBEAT.md` -- a checklist of things to proactively check.
|
||||
2. Sends the checklist to the agent as a prompt.
|
||||
3. If the agent determines nothing needs attention, it responds with `HEARTBEAT_OK` and no action is taken.
|
||||
4. If the agent takes action, the result is logged to the daily memory log.
|
||||
|
||||
The default heartbeat checklist includes checking for failed scheduled tasks, reviewing pending reminders, and checking disk space. You can customize `HEARTBEAT.md` with any proactive checks you want.
|
||||
|
||||
---
|
||||
|
||||
## Voice Chat
|
||||
|
||||
CheddahBot supports a full voice conversation loop: speak, get a spoken response.
|
||||
|
||||
### Speech-to-Text (STT)
|
||||
|
||||
Audio input is transcribed using Whisper. The system tries local Whisper first (if the `whisper` package is installed), then falls back to the OpenAI Whisper API.
|
||||
|
||||
Audio can be provided in two ways:
|
||||
- **Microphone input** in the main chat -- audio files are automatically detected and transcribed, with the transcript appended to the message.
|
||||
- **Voice Chat accordion** -- a dedicated record-and-respond mode.
|
||||
|
||||
Supported audio formats: WAV, MP3, OGG, WebM, M4A.
|
||||
|
||||
### Text-to-Speech (TTS)
|
||||
|
||||
Responses are spoken using edge-tts, which is free and requires no API key. The default voice is `en-US-AriaNeural`. TTS output is saved to `data/generated/voice_response.mp3` and played back automatically in the Voice Chat panel.
|
||||
|
||||
Install edge-tts:
|
||||
```bash
|
||||
pip install edge-tts
|
||||
```
|
||||
|
||||
### Video Frame Extraction
|
||||
|
||||
The media module also supports extracting key frames from video files using ffmpeg (used internally for video analysis workflows). Requires `ffmpeg` and `ffprobe` in your PATH.
|
||||
|
||||
---
|
||||
|
||||
## Identity System
|
||||
|
||||
CheddahBot's identity is defined by three Markdown files in the `identity/` directory.
|
||||
|
||||
### SOUL.md
|
||||
|
||||
Defines the agent's personality, boundaries, and behavioral quirks. This is injected at the top of every system prompt.
|
||||
|
||||
Default personality traits:
|
||||
- Direct and no-nonsense but warm
|
||||
- Uses humor when appropriate
|
||||
- Proactive -- suggests things before being asked
|
||||
- Remembers and references past conversations naturally
|
||||
|
||||
Edit this file to customize the agent's personality to your liking.
|
||||
|
||||
### USER.md
|
||||
|
||||
Your user profile. Contains your name, how you want to be addressed, your technical level, primary language, current projects, communication preferences, and anything else you want the agent to know about you.
|
||||
|
||||
Fill this in after installation -- the more context you provide, the more personalized the agent's responses will be.
|
||||
|
||||
### HEARTBEAT.md
|
||||
|
||||
A checklist of proactive tasks for the heartbeat system. Each item is something the agent should check on periodically. See [Scheduler and Heartbeat](#scheduler-and-heartbeat).
|
||||
|
||||
---
|
||||
|
||||
## Known Issues and Limitations
|
||||
|
||||
### Claude Code CLI System Prompt
|
||||
|
||||
The Claude Code CLI (`claude -p`) is designed as a coding assistant and applies its own internal system prompt. Custom system prompts passed via `--system-prompt` are appended but do not override the built-in behavior. This means:
|
||||
|
||||
- The `SOUL.md` personality may not be followed reliably.
|
||||
- Tool-use instructions may be ignored or overridden.
|
||||
- The agent may behave more like a coding assistant than a personal assistant.
|
||||
|
||||
**Workaround:** Use Claude models through OpenRouter instead of the CLI. OpenRouter provides standard API access to Claude with full system prompt control.
|
||||
|
||||
### Claude Code CLI Does Not Support Streaming
|
||||
|
||||
The Claude Code CLI integration uses `subprocess.Popen` with `communicate()`, which means the entire response is collected before being displayed. There is no token-by-token streaming for Claude CLI responses. OpenRouter, Ollama, and LM Studio all support true streaming.
|
||||
|
||||
### Claude Code CLI Tool Calling
|
||||
|
||||
Tool calling is not supported through the Claude Code CLI path. The `--tools ""` flag is passed to disable Claude Code's built-in tools, and CheddahBot's own tools are described in the system prompt rather than via function-calling schema. This makes tool use unreliable with the CLI backend. Again, OpenRouter is the recommended provider for full tool support.
|
||||
|
||||
### Embedding Model Download
|
||||
|
||||
The first time the memory system initializes, it downloads the `all-MiniLM-L6-v2` sentence-transformer model (approximately 80 MB). This requires an internet connection and may take a moment. Subsequent starts use the cached model.
|
||||
|
||||
If `sentence-transformers` is not installed, the memory system falls back to keyword-based search. Semantic search will not be available but everything else works.
|
||||
|
||||
### Shell Command Safety
|
||||
|
||||
The shell tool blocks a set of known dangerous command patterns, but it is not a full sandbox. Commands run with the same permissions as the CheddahBot process. Exercise caution with the `run_command` tool, especially on production machines.
|
||||
|
||||
### Conversation Context Window
|
||||
|
||||
The system keeps the most recent 50 messages (configurable via `memory.max_context_messages`) in the LLM context window. Older messages are summarized and moved to the daily log when the count exceeds `flush_threshold` (default 40). Very long conversations may lose fine-grained detail from earlier messages.
|
||||
|
||||
### Single Conversation at a Time
|
||||
|
||||
The agent maintains one active conversation at a time in memory. You can start a new chat (which creates a new conversation in the database) and browse past conversations in the history panel, but there is no multi-user or multi-session support.
|
||||
|
||||
### Local Model Limitations
|
||||
|
||||
Ollama and LM Studio models vary widely in their ability to follow tool-calling schemas. Smaller models may not reliably use tools. For best results with local models, use models that are known to support function calling (e.g., Llama 3.1+ instruct variants).
|
||||
|
|
@ -0,0 +1,675 @@
|
|||
# CheddahBot Tools Reference
|
||||
|
||||
## Overview
|
||||
|
||||
CheddahBot uses an extensible tool system that allows the LLM to invoke Python
|
||||
functions during a conversation. Tools are registered via the `@tool` decorator
|
||||
and auto-discovered at startup. The LLM receives tool schemas in OpenAI
|
||||
function-calling format and can request tool invocations, which the agent
|
||||
executes and feeds back into the conversation.
|
||||
|
||||
---
|
||||
|
||||
## Registered Tools
|
||||
|
||||
### Category: files
|
||||
|
||||
#### `read_file`
|
||||
|
||||
Read the contents of a file.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|------------------|
|
||||
| `path` | string | Yes | Path to the file |
|
||||
|
||||
Returns the file contents as a string. Files larger than 50,000 characters are
|
||||
truncated. Returns an error message if the file is not found or is not a regular
|
||||
file.
|
||||
|
||||
---
|
||||
|
||||
#### `write_file`
|
||||
|
||||
Write content to a file (creates or overwrites).
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|--------------------------------|
|
||||
| `path` | string | Yes | Path to the file |
|
||||
| `content` | string | Yes | Content to write to the file |
|
||||
|
||||
Creates parent directories automatically if they do not exist.
|
||||
|
||||
---
|
||||
|
||||
#### `edit_file`
|
||||
|
||||
Replace text in a file (first occurrence).
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|------------|--------|----------|--------------------------|
|
||||
| `path` | string | Yes | Path to the file |
|
||||
| `old_text` | string | Yes | Text to find and replace |
|
||||
| `new_text` | string | Yes | Replacement text |
|
||||
|
||||
Replaces only the first occurrence of `old_text`. Returns an error if the file
|
||||
does not exist or the text is not found.
|
||||
|
||||
---
|
||||
|
||||
#### `list_directory`
|
||||
|
||||
List files and folders in a directory.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|--------------------------------------|
|
||||
| `path` | string | No | Directory path (defaults to `"."`) |
|
||||
|
||||
Returns up to 200 entries, sorted with directories first. Each entry shows the
|
||||
name and file size.
|
||||
|
||||
---
|
||||
|
||||
#### `search_files`
|
||||
|
||||
Search for files matching a glob pattern.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-------------|--------|----------|--------------------------------------|
|
||||
| `pattern` | string | Yes | Glob pattern (e.g. `"**/*.py"`) |
|
||||
| `directory` | string | No | Root directory (defaults to `"."`) |
|
||||
|
||||
Returns up to 100 matching file paths.
|
||||
|
||||
---
|
||||
|
||||
#### `search_in_files`
|
||||
|
||||
Search for text content across files.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-------------|--------|----------|--------------------------------------|
|
||||
| `query` | string | Yes | Text to search for (case-insensitive)|
|
||||
| `directory` | string | No | Root directory (defaults to `"."`) |
|
||||
| `extension` | string | No | File extension filter (e.g. `".py"`) |
|
||||
|
||||
Returns up to 50 matches in `file:line: content` format. Skips files larger
|
||||
than 1 MB.
|
||||
|
||||
---
|
||||
|
||||
### Category: shell
|
||||
|
||||
#### `run_command`
|
||||
|
||||
Execute a shell command and return output.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|---------|----------|------------------------------------------|
|
||||
| `command` | string | Yes | Shell command to execute |
|
||||
| `timeout` | integer | No | Timeout in seconds (default 30, max 120) |
|
||||
|
||||
Includes safety checks that block dangerous patterns:
|
||||
- `rm -rf /`
|
||||
- `format c:`
|
||||
- `:(){:|:&};:` (fork bomb)
|
||||
- `dd if=/dev/zero`
|
||||
- `mkfs.`
|
||||
- `> /dev/sda`
|
||||
|
||||
Output is truncated to 10,000 characters. Returns stdout, stderr, and exit code.
|
||||
|
||||
---
|
||||
|
||||
### Category: web
|
||||
|
||||
#### `web_search`
|
||||
|
||||
Search the web using DuckDuckGo.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|---------------|---------|----------|------------------------------------|
|
||||
| `query` | string | Yes | Search query |
|
||||
| `max_results` | integer | No | Number of results (default 5) |
|
||||
|
||||
Uses DuckDuckGo HTML search (no API key required). Returns formatted results
|
||||
with title, URL, and snippet.
|
||||
|
||||
---
|
||||
|
||||
#### `fetch_url`
|
||||
|
||||
Fetch and extract text content from a URL.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|----------------|
|
||||
| `url` | string | Yes | URL to fetch |
|
||||
|
||||
For HTML pages: strips script, style, nav, footer, and header elements, then
|
||||
extracts text (truncated to 15,000 characters). For JSON responses: returns raw
|
||||
JSON (truncated to 15,000 characters). For other content types: returns raw text
|
||||
(truncated to 5,000 characters).
|
||||
|
||||
---
|
||||
|
||||
### Category: code
|
||||
|
||||
#### `run_python`
|
||||
|
||||
Execute Python code and return the output.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|---------|----------|------------------------------------------|
|
||||
| `code` | string | Yes | Python code to execute |
|
||||
| `timeout` | integer | No | Timeout in seconds (default 30, max 60) |
|
||||
|
||||
Writes the code to a temporary file and runs it as a subprocess using the same
|
||||
Python interpreter that CheddahBot is running on. The temp file is deleted after
|
||||
execution. Output is truncated to 10,000 characters.
|
||||
|
||||
---
|
||||
|
||||
### Category: memory
|
||||
|
||||
#### `remember_this`
|
||||
|
||||
Save an important fact or instruction to long-term memory.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|---------------------------------|
|
||||
| `text` | string | Yes | The fact or instruction to save |
|
||||
|
||||
Appends a timestamped entry to `memory/MEMORY.md` and indexes it in the
|
||||
embedding database for future semantic search.
|
||||
|
||||
---
|
||||
|
||||
#### `search_memory`
|
||||
|
||||
Search through saved memories.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|-------------------|
|
||||
| `query` | string | Yes | Search query text |
|
||||
|
||||
Performs semantic search (or keyword fallback) over all indexed memory entries.
|
||||
Returns results with similarity scores.
|
||||
|
||||
---
|
||||
|
||||
#### `log_note`
|
||||
|
||||
Add a timestamped note to today's daily log.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|------------------------|
|
||||
| `text` | string | Yes | Note text to log |
|
||||
|
||||
Appends to `memory/YYYY-MM-DD.md` (today's date) and indexes the text for
|
||||
semantic search.
|
||||
|
||||
---
|
||||
|
||||
### Category: scheduling
|
||||
|
||||
#### `schedule_task`
|
||||
|
||||
Schedule a recurring or one-time task.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|------------|--------|----------|---------------------------------------------------|
|
||||
| `name` | string | Yes | Human-readable task name |
|
||||
| `prompt` | string | Yes | The prompt to send to the agent when the task runs|
|
||||
| `schedule` | string | Yes | Cron expression or `"once:YYYY-MM-DDTHH:MM"` |
|
||||
|
||||
Examples:
|
||||
- `schedule="0 9 * * *"` -- every day at 9:00 AM UTC
|
||||
- `schedule="once:2026-03-01T14:00"` -- one-time execution
|
||||
|
||||
---
|
||||
|
||||
#### `list_tasks`
|
||||
|
||||
List all scheduled tasks.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| (none) | | | |
|
||||
|
||||
Returns all tasks with their ID, name, schedule, enabled status, and next run
|
||||
time.
|
||||
|
||||
---
|
||||
|
||||
### Category: media
|
||||
|
||||
#### `analyze_image`
|
||||
|
||||
Describe or analyze an image file.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|------------|--------|----------|------------------------------------------------|
|
||||
| `path` | string | Yes | Path to the image file |
|
||||
| `question` | string | No | Question about the image (default: "Describe this image in detail.") |
|
||||
|
||||
Reads the image, base64-encodes it, and sends it to the current LLM as a
|
||||
multimodal message. Supports PNG, JPEG, GIF, WebP, and BMP formats. Requires a
|
||||
vision-capable model.
|
||||
|
||||
---
|
||||
|
||||
### Category: data
|
||||
|
||||
#### `read_csv`
|
||||
|
||||
Read a CSV file and return summary or specific rows.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|------------|---------|----------|-----------------------------------------|
|
||||
| `path` | string | Yes | Path to the CSV file |
|
||||
| `max_rows` | integer | No | Maximum rows to display (default 20) |
|
||||
|
||||
Returns the data formatted as a Markdown table, with a count of total rows if
|
||||
the file is larger than `max_rows`.
|
||||
|
||||
---
|
||||
|
||||
#### `read_json`
|
||||
|
||||
Read and pretty-print a JSON file.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|-----------------------|
|
||||
| `path` | string | Yes | Path to the JSON file |
|
||||
|
||||
Returns the JSON content pretty-printed with 2-space indentation. Truncated to
|
||||
15,000 characters.
|
||||
|
||||
---
|
||||
|
||||
#### `query_json`
|
||||
|
||||
Extract data from a JSON file using a dot-notation path.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-------------|--------|----------|--------------------------------------|
|
||||
| `path` | string | Yes | Path to the JSON file |
|
||||
| `json_path` | string | Yes | Dot-notation path (e.g. `"data.users.0.name"`) |
|
||||
|
||||
Supports `*` as a wildcard for arrays. For example, `"results.*.id"` returns
|
||||
the full array at `results`.
|
||||
|
||||
---
|
||||
|
||||
### Category: delivery
|
||||
|
||||
#### `email_file`
|
||||
|
||||
Email a file as an attachment via Gmail SMTP.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-------------|--------|----------|--------------------------------------------------|
|
||||
| `file_path` | string | Yes | Path to the file to send |
|
||||
| `to` | string | No | Recipient address (defaults to `EMAIL_DEFAULT_TO`)|
|
||||
| `subject` | string | No | Email subject (defaults to filename) |
|
||||
|
||||
If the file is `.txt`, it is automatically converted to `.docx` before sending.
|
||||
Requires `GMAIL_USERNAME` and `GMAIL_APP_PASSWORD` in `.env`.
|
||||
|
||||
---
|
||||
|
||||
### Category: content
|
||||
|
||||
#### `write_press_releases`
|
||||
|
||||
Full autonomous press-release pipeline.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-------------------|--------|----------|------------------------------------------------|
|
||||
| `topic` | string | Yes | Press release topic |
|
||||
| `company_name` | string | Yes | Company name |
|
||||
| `url` | string | No | Reference URL for context |
|
||||
| `lsi_terms` | string | No | LSI keywords to integrate |
|
||||
| `required_phrase` | string | No | Exact phrase to include once |
|
||||
|
||||
Generates 7 headlines, AI-picks the best 2, writes 2 full press releases
|
||||
(600-750 words each), generates JSON-LD schema for each, and saves all files.
|
||||
Output includes `.txt`, `.docx` (Google Docs-ready), and `.json` schema files
|
||||
in `data/generated/press_releases/{company}/`.
|
||||
|
||||
---
|
||||
|
||||
### Category: meta
|
||||
|
||||
#### `build_tool`
|
||||
|
||||
Create a new tool from a description. The agent writes Python code with the
|
||||
`@tool` decorator.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|---------------|--------|----------|----------------------------------------------|
|
||||
| `name` | string | Yes | Tool name in snake_case |
|
||||
| `description` | string | Yes | What the tool does |
|
||||
| `code` | string | Yes | Python code with `@tool` decorator |
|
||||
|
||||
See the dedicated section below for details.
|
||||
|
||||
---
|
||||
|
||||
#### `build_skill`
|
||||
|
||||
Create a new multi-step skill from a description.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|---------------|--------|----------|----------------------------------------------|
|
||||
| `name` | string | Yes | Skill name in snake_case |
|
||||
| `description` | string | Yes | What the skill does |
|
||||
| `steps` | string | Yes | Python code with `@skill` decorator |
|
||||
|
||||
See the dedicated section below for details.
|
||||
|
||||
---
|
||||
|
||||
## How to Create Custom Tools Using `@tool`
|
||||
|
||||
### Step 1: Create a Python file in `cheddahbot/tools/`
|
||||
|
||||
The file name does not matter (as long as it does not start with `_`), but by
|
||||
convention it should describe the category of tools it contains.
|
||||
|
||||
### Step 2: Import the decorator and define your function
|
||||
|
||||
```python
|
||||
"""My custom tools."""
|
||||
from __future__ import annotations
|
||||
from . import tool
|
||||
|
||||
|
||||
@tool("greet_user", "Greet a user by name", category="social")
|
||||
def greet_user(name: str, enthusiasm: int = 1) -> str:
|
||||
exclamation = "!" * enthusiasm
|
||||
return f"Hello, {name}{exclamation}"
|
||||
```
|
||||
|
||||
### Decorator Signature
|
||||
|
||||
```python
|
||||
@tool(name: str, description: str, category: str = "general")
|
||||
```
|
||||
|
||||
- `name` -- The tool name the LLM will use to invoke it. Must be unique across
|
||||
all registered tools.
|
||||
- `description` -- A short description shown to the LLM in the system prompt
|
||||
and in the tool schema.
|
||||
- `category` -- A grouping label for organizing tools in the system prompt.
|
||||
|
||||
### Function Requirements
|
||||
|
||||
- The return type should be `str`. If the function returns a non-string value,
|
||||
it is converted via `str()`. If it returns `None`, the result is `"Done."`.
|
||||
- Type annotations on parameters are used to generate the JSON Schema:
|
||||
- `str` -> `"string"`
|
||||
- `int` -> `"integer"`
|
||||
- `float` -> `"number"`
|
||||
- `bool` -> `"boolean"`
|
||||
- `list` -> `"array"` (of strings)
|
||||
- No annotation -> `"string"`
|
||||
- Parameters with default values are optional in the schema. Parameters without
|
||||
defaults are required.
|
||||
- To access the agent's runtime context (config, database, memory system, agent
|
||||
instance), add a `ctx: dict = None` parameter. The tool registry will
|
||||
automatically inject a dictionary with keys `"config"`, `"db"`, `"agent"`,
|
||||
and `"memory"`.
|
||||
|
||||
### Step 3: Restart CheddahBot
|
||||
|
||||
The tool module is auto-discovered on startup. No additional registration code
|
||||
is needed.
|
||||
|
||||
### Full Example with Context Access
|
||||
|
||||
```python
|
||||
"""Tools that interact with the database."""
|
||||
from __future__ import annotations
|
||||
from . import tool
|
||||
|
||||
|
||||
@tool("count_conversations", "Count total conversations in the database", category="stats")
|
||||
def count_conversations(ctx: dict = None) -> str:
|
||||
if not ctx or not ctx.get("db"):
|
||||
return "Database not available."
|
||||
row = ctx["db"]._conn.execute("SELECT COUNT(*) as cnt FROM conversations").fetchone()
|
||||
return f"Total conversations: {row['cnt']}"
|
||||
|
||||
|
||||
@tool("get_setting", "Retrieve a value from the key-value store", category="config")
|
||||
def get_setting(key: str, ctx: dict = None) -> str:
|
||||
if not ctx or not ctx.get("db"):
|
||||
return "Database not available."
|
||||
value = ctx["db"].kv_get(key)
|
||||
if value is None:
|
||||
return f"No value found for key: {key}"
|
||||
return f"{key} = {value}"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How `build_tool` (Meta-Tool) Works
|
||||
|
||||
The `build_tool` tool allows the LLM to create new tools at runtime without
|
||||
restarting the application. This is the mechanism by which you can ask the agent
|
||||
"create a tool that does X" and it will write, save, and hot-load the tool.
|
||||
|
||||
### Internal Process
|
||||
|
||||
1. **Validation** -- The tool name must be a valid Python identifier.
|
||||
2. **Code wrapping** -- The provided `code` parameter is wrapped in a module
|
||||
template that adds the necessary `from . import tool` import.
|
||||
3. **File creation** -- The module is written to
|
||||
`cheddahbot/tools/<name>.py`. If a file with that name already exists, the
|
||||
operation is rejected.
|
||||
4. **Hot-loading** -- `importlib.import_module()` imports the new module. This
|
||||
triggers the `@tool` decorator inside the code, which registers the tool in
|
||||
the global `_TOOLS` dictionary.
|
||||
5. **Cleanup on failure** -- If the import fails (syntax error, import error,
|
||||
etc.), the file is deleted to avoid leaving broken modules.
|
||||
|
||||
### What the LLM Generates
|
||||
|
||||
When the LLM calls `build_tool`, it provides:
|
||||
|
||||
- `name`: e.g. `"word_count"`
|
||||
- `description`: e.g. `"Count words in a text string"`
|
||||
- `code`: The body of the tool function, including the `@tool` decorator:
|
||||
|
||||
```python
|
||||
@tool("word_count", "Count words in a text string", category="text")
|
||||
def word_count(text: str) -> str:
|
||||
count = len(text.split())
|
||||
return f"Word count: {count}"
|
||||
```
|
||||
|
||||
The `build_tool` function wraps this in the necessary imports and writes it to
|
||||
disk.
|
||||
|
||||
### Persistence
|
||||
|
||||
Because the tool is saved as a `.py` file in the tools directory, it survives
|
||||
application restarts. On the next startup, auto-discovery will find and load it
|
||||
like any other built-in tool.
|
||||
|
||||
---
|
||||
|
||||
## How `build_skill` Works
|
||||
|
||||
The `build_skill` tool creates multi-step skills -- higher-level operations that
|
||||
can orchestrate multiple actions.
|
||||
|
||||
### Internal Process
|
||||
|
||||
1. **Validation** -- The skill name must be a valid Python identifier.
|
||||
2. **Code wrapping** -- The provided `steps` parameter is wrapped in a module
|
||||
template that adds `from cheddahbot.skills import skill`.
|
||||
3. **File creation** -- The module is written to
|
||||
`skills/<name>.py` (the project-level skills directory, not inside the
|
||||
package).
|
||||
4. **Dynamic loading** -- `skills.load_skill()` uses
|
||||
`importlib.util.spec_from_file_location` to load the module from the file
|
||||
path, triggering the `@skill` decorator.
|
||||
|
||||
### The `@skill` Decorator
|
||||
|
||||
```python
|
||||
from cheddahbot.skills import skill
|
||||
|
||||
@skill("my_skill", "Description of what this skill does")
|
||||
def my_skill(**kwargs) -> str:
|
||||
# Multi-step logic here
|
||||
return "Skill completed."
|
||||
```
|
||||
|
||||
Skills are registered in the global `_SKILLS` dictionary and can be listed
|
||||
with `skills.list_skills()` and executed with `skills.run_skill(name, **kwargs)`.
|
||||
|
||||
### Difference Between Tools and Skills
|
||||
|
||||
| Aspect | Tools | Skills |
|
||||
|------------|------------------------------------------|---------------------------------------|
|
||||
| Invoked by | The LLM (via function calling) | Code or agent internally |
|
||||
| Schema | OpenAI function-calling JSON schema | No schema; free-form kwargs |
|
||||
| Location | `cheddahbot/tools/` (inside the package) | `skills/` (project-level directory) |
|
||||
| Purpose | Single focused operations | Multi-step workflows |
|
||||
|
||||
---
|
||||
|
||||
## Example: Creating a Custom Tool Manually
|
||||
|
||||
Suppose you want a tool that converts temperatures between Fahrenheit and
|
||||
Celsius.
|
||||
|
||||
### 1. Create the file
|
||||
|
||||
Create `cheddahbot/tools/temperature.py`:
|
||||
|
||||
```python
|
||||
"""Temperature conversion tools."""
|
||||
from __future__ import annotations
|
||||
from . import tool
|
||||
|
||||
|
||||
@tool("convert_temperature", "Convert temperature between Fahrenheit and Celsius", category="utility")
|
||||
def convert_temperature(value: float, from_unit: str = "F") -> str:
|
||||
"""Convert a temperature value.
|
||||
|
||||
Args:
|
||||
value: The temperature value to convert
|
||||
from_unit: Source unit - 'F' for Fahrenheit, 'C' for Celsius
|
||||
"""
|
||||
from_unit = from_unit.upper()
|
||||
if from_unit == "F":
|
||||
celsius = (value - 32) * 5 / 9
|
||||
return f"{value} F = {celsius:.1f} C"
|
||||
elif from_unit == "C":
|
||||
fahrenheit = value * 9 / 5 + 32
|
||||
return f"{value} C = {fahrenheit:.1f} F"
|
||||
else:
|
||||
return f"Unknown unit: {from_unit}. Use 'F' or 'C'."
|
||||
```
|
||||
|
||||
### 2. Restart CheddahBot
|
||||
|
||||
The tool is auto-discovered. The LLM will see it in the system prompt:
|
||||
|
||||
```
|
||||
### Utility
|
||||
- **convert_temperature**(value, from_unit): Convert temperature between Fahrenheit and Celsius
|
||||
```
|
||||
|
||||
### 3. Use it in conversation
|
||||
|
||||
> User: "What is 72 degrees Fahrenheit in Celsius?"
|
||||
>
|
||||
> The LLM calls `convert_temperature(value=72.0, from_unit="F")` and receives
|
||||
> `"72.0 F = 22.2 C"`, which it incorporates into its response.
|
||||
|
||||
---
|
||||
|
||||
## Example: Asking the Agent to Create a Tool via `build_tool`
|
||||
|
||||
You can ask the agent to create a tool for you in natural language. The agent
|
||||
will use the `build_tool` meta-tool to generate and register it.
|
||||
|
||||
### Conversation
|
||||
|
||||
> **User:** Create a tool that counts the number of lines in a file.
|
||||
>
|
||||
> **Agent:** I'll create that tool for you.
|
||||
>
|
||||
> *[Uses tool: build_tool]*
|
||||
> ```
|
||||
> name: "count_lines"
|
||||
> description: "Count the number of lines in a file"
|
||||
> code:
|
||||
> @tool("count_lines", "Count the number of lines in a file", category="files")
|
||||
> def count_lines(path: str) -> str:
|
||||
> from pathlib import Path
|
||||
> p = Path(path).resolve()
|
||||
> if not p.exists():
|
||||
> return f"File not found: {path}"
|
||||
> if not p.is_file():
|
||||
> return f"Not a file: {path}"
|
||||
> try:
|
||||
> content = p.read_text(encoding="utf-8", errors="replace")
|
||||
> line_count = len(content.splitlines())
|
||||
> return f"{path}: {line_count} lines"
|
||||
> except Exception as e:
|
||||
> return f"Error: {e}"
|
||||
> ```
|
||||
> *Tool 'count_lines' created and loaded successfully.*
|
||||
>
|
||||
> Done. The `count_lines` tool is now available. You can ask me to count lines
|
||||
> in any file, or I'll use it automatically when relevant.
|
||||
|
||||
### What Happened Behind the Scenes
|
||||
|
||||
1. The agent decided `build_tool` was the right tool for the request.
|
||||
2. It generated valid Python code including the `@tool` decorator.
|
||||
3. `build_tool` wrote `cheddahbot/tools/count_lines.py` with the wrapped code.
|
||||
4. `importlib.import_module` loaded the module, triggering registration.
|
||||
5. The tool is now available for the LLM to use in the current session and all
|
||||
future sessions.
|
||||
|
||||
### Verifying the Tool Exists
|
||||
|
||||
After creation, the tool appears in:
|
||||
|
||||
- The system prompt's tools section (on the next turn).
|
||||
- The output of `ToolRegistry.get_tools_schema()`.
|
||||
- The file system at `cheddahbot/tools/count_lines.py`.
|
||||
|
||||
---
|
||||
|
||||
## Tool Registry API Summary
|
||||
|
||||
### `ToolRegistry(config, db, agent)`
|
||||
|
||||
Constructor. Auto-discovers and imports all tool modules.
|
||||
|
||||
### `ToolRegistry.get_tools_schema() -> list[dict]`
|
||||
|
||||
Returns all tools as OpenAI function-calling JSON schema objects.
|
||||
|
||||
### `ToolRegistry.get_tools_description() -> str`
|
||||
|
||||
Returns a human-readable Markdown string listing all tools organized by
|
||||
category. This is injected into the system prompt.
|
||||
|
||||
### `ToolRegistry.execute(name, args) -> str`
|
||||
|
||||
Executes a tool by name with the given arguments. Returns the result as a
|
||||
string. Automatically injects the `ctx` context dict if the tool function
|
||||
accepts one.
|
||||
|
||||
### `ToolRegistry.register_external(tool_def)`
|
||||
|
||||
Manually registers a `ToolDef` object. Used for programmatic tool registration
|
||||
outside the `@tool` decorator.
|
||||
Loading…
Reference in New Issue