meshai/PLAN.md

356 lines
13 KiB
Markdown
Raw Normal View History

# MeshAI - Meshtastic LLM Bridge
## Project Overview
A Python application that connects to a Meshtastic node and provides LLM-powered responses to mesh network users. Responds to direct mentions (@nodename) or direct messages. Includes bang commands (`!command`) for utility functions.
## Design Decisions
### 1. Trigger Mechanism
- **@mentions**: Respond when message contains `@<nodename>` (configurable node name)
- **Direct Messages**: Respond to all DMs automatically
- **Bang commands**: `!command` syntax for utility functions (handled before LLM)
- Ignore general channel chatter that doesn't mention the bot
### 2. Conversation History
- Maintain per-user conversation history
- Storage: SQLite database for persistence across restarts
- Context window: Last N messages per user (configurable, default ~20 exchanges)
- With 300 char limit per exchange, context stays small - can maintain long conversations
- Include timestamp tracking for potential "conversation timeout" (e.g., reset after 24h inactivity)
### 3. Rate Limiting & Response Behavior
- **Response delay**: Configurable 2.2-3.0 second random delay before sending
- **Message chunking**: Split responses at 150 characters max per message
- **Max chunks**: 2 messages maximum per response (300 chars total)
- **Brevity prompt**: System prompt instructs LLM to keep responses concise
- **Cooldown**: Optional per-user cooldown to prevent spam
### 4. Identity & Configuration
- Node name/ID determined by the physical node configuration
- Application config includes:
- `bot_name`: The @mention trigger name (e.g., "meshbot", "ai")
- `owner`: Owner identification for logging/admin purposes
- Connection settings (serial port or TCP host:port)
### 5. Channel Filtering
- Configurable list of channels to respond on
- Option to respond on all channels or specific ones only
- DMs always processed regardless of channel settings
## Technical Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ MeshAI │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ Meshtastic │ │ Message │ │ LLM Backend │ │
│ │ Connector │───▶│ Router │───▶│ (pluggable) │ │
│ │ Serial/TCP │ │ │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────────┘ │
│ │ │ │ │
│ │ ┌─────▼─────┐ │ │
│ │ │ Conversation│ │ │
│ │ │ History │◀────────────┘ │
│ │ │ (SQLite) │ │
│ │ └───────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Response │ - 2.2-3s delay │
│ │ Handler │ - Chunk to 150 chars │
│ │ │ - Max 2 messages │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
## LLM Backend Support
### Pluggable Backend Interface
```python
class LLMBackend(ABC):
@abstractmethod
async def generate(self, messages: list[dict], system_prompt: str) -> str:
pass
```
### Supported Backends (Priority Order)
1. **OpenAI-compatible** (covers most bases)
- OpenAI (GPT-4, GPT-4o, etc.)
- Local LiteLLM/Open WebUI (ai.echo6.co)
- Any OpenAI-compatible API
2. **Anthropic** (Claude)
- Direct Anthropic API
3. **Google** (Gemini)
- Google AI Studio / Vertex AI
### Configuration Example
```yaml
llm:
backend: "openai" # openai, anthropic, google
api_key: "${OPENAI_API_KEY}"
base_url: "https://api.openai.com/v1" # or http://ai.echo6.co/api for local
model: "gpt-4o-mini"
# For local LiteLLM:
# backend: "openai"
# base_url: "http://192.168.1.239:4000/v1"
# model: "llama3"
```
## Configuration File Structure
```yaml
# config.yaml
bot:
name: "ai" # @mention trigger
owner: "K7ZVX" # Owner callsign/name
respond_to_mentions: true
respond_to_dms: true
connection:
type: "serial" # serial or tcp
serial_port: "/dev/ttyUSB0" # if serial
tcp_host: "192.168.1.100" # if tcp
tcp_port: 4403 # if tcp
channels:
mode: "all" # "all" or "whitelist"
whitelist: [0, 1] # Only if mode is "whitelist"
response:
delay_min: 2.2 # seconds
delay_max: 3.0 # seconds
max_length: 150 # chars per message
max_messages: 2 # messages per response
history:
database: "conversations.db"
max_messages_per_user: 20
conversation_timeout: 86400 # seconds (24h)
llm:
backend: "openai"
api_key: "${LLM_API_KEY}"
base_url: "https://api.openai.com/v1"
model: "gpt-4o-mini"
system_prompt: |
You are a helpful assistant on a Meshtastic mesh network.
Keep responses VERY brief - under 250 characters total.
Be concise but friendly. No markdown formatting.
weather:
primary: "openmeteo" # openmeteo, wttr, or llm
fallback: "llm" # openmeteo, wttr, llm, or none
default_location: "" # Fallback if node has no GPS (e.g., "Seattle, WA")
openmeteo:
url: "https://api.open-meteo.com/v1" # or self-hosted URL
wttr:
url: "https://wttr.in" # or self-hosted
```
## Bang Commands
Commands use `!` prefix (like fq51bbs). Processed before LLM routing.
| Command | Description | Example |
|---------|-------------|---------|
| `!help` | List available commands | `!help` |
| `!ping` | Connectivity test, responds "pong" | `!ping` |
| `!reset` | Clear your conversation history | `!reset` |
| `!status` | Bot uptime, message count, version | `!status` |
| `!weather` | Weather for your node's GPS location (or default) | `!weather` |
| `!weather <loc>` | Weather for specified location | `!weather Seattle` |
### Weather Command Details
Location resolution order:
1. If `!weather <location>` - geocode the provided location
2. If `!weather` (no args) - use sender's node GPS position if available
3. Fall back to `weather.default_location` from config
4. If no location found: "No location available. Use !weather <city> or enable GPS on your node."
**Providers:**
- `openmeteo` - Open-Meteo API (free, no key, self-hostable)
- `wttr` - wttr.in (free, simple, self-hostable)
- `llm` - Pass to LLM with websearch (flexible, slower)
Primary/fallback configurable. If primary fails, tries fallback.
### Command Processing Flow
```
Message received
┌─────────────┐
│ Starts with │──No──▶ Check @mention / DM ──▶ LLM
│ "!"? │
└─────────────┘
│Yes
┌─────────────┐
│ Parse cmd │
│ & args │
└─────────────┘
┌─────────────┐
│ Lookup in │──Not found──▶ "Unknown command. Try !help"
│ registry │
└─────────────┘
│Found
┌─────────────┐
│ Execute │
│ handler │
└─────────────┘
```
### Command Handler Interface
```python
class CommandHandler(ABC):
@abstractmethod
async def execute(self, sender_id: str, args: str, context: MessageContext) -> str:
"""Execute command and return response string."""
pass
```
## CLI Configurator
Interactive TUI configurator using Rich library (same style as fq51bbs).
**Features:**
- Hierarchical menu system with numeric selection
- `0` always = back/save & exit
- Tables showing current values
- Status icons (✓/✗) with color coding
- Setup wizard for first-time configuration
- Unsaved changes tracking
- Inline help for complex options
**Menu Structure:**
```
Main Menu
├── 1. Bot Settings (name, owner, triggers)
├── 2. Connection (serial/TCP config)
├── 3. LLM Backend (provider, API keys, model)
├── 4. Commands & Weather (providers, fallbacks)
├── 5. Response Settings (delays, chunking)
├── 6. Channel Filtering
├── 7. History Settings
├── 8. Run Setup Wizard
└── 0. Save & Exit
```
**Invocation:**
```bash
meshai --config # Launch configurator
meshai # Run bot (uses config.yaml)
meshai --config-file /path/to/config.yaml # Use alternate config
```
**Config Reload/Restart:**
- On save, prompt: "Restart bot with new config? [Y/n]"
- If bot is running as systemd service: `systemctl restart meshai`
- If running in foreground: signal reload (SIGHUP) or full restart
- Store PID file at runtime for service management
## File Structure
```
meshai/
├── meshai/
│ ├── __init__.py
│ ├── main.py # Entry point
│ ├── config.py # Configuration loading/saving
│ ├── connector.py # Meshtastic serial/TCP connection
│ ├── router.py # Message routing logic
│ ├── history.py # Conversation history (SQLite)
│ ├── responder.py # Response handling (delay, chunking)
│ ├── cli/
│ │ ├── __init__.py
│ │ └── configurator.py # Rich-based TUI configurator
│ ├── commands/
│ │ ├── __init__.py
│ │ ├── base.py # Command handler interface
│ │ ├── dispatcher.py # Command registry & routing
│ │ ├── help.py # !help
│ │ ├── ping.py # !ping
│ │ ├── reset.py # !reset
│ │ ├── status.py # !status
│ │ └── weather.py # !weather
│ └── backends/
│ ├── __init__.py
│ ├── base.py # Abstract backend interface
│ ├── openai.py # OpenAI-compatible backend
│ ├── anthropic.py # Anthropic backend
│ └── google.py # Google Gemini backend
├── config.yaml # User configuration
├── requirements.txt
├── pyproject.toml
└── README.md
```
## Dependencies
```
meshtastic>=2.3.0
pyyaml>=6.0
aiosqlite>=0.19.0
openai>=1.0.0
anthropic>=0.18.0
google-generativeai>=0.4.0
```
## Implementation Phases
### Phase 1: Core Foundation
- [ ] Project structure setup
- [ ] Configuration loading
- [ ] Meshtastic connector (serial first, then TCP)
- [ ] Basic message receiving and logging
### Phase 2: Message Processing
- [ ] Message router (detect @mentions and DMs)
- [ ] Conversation history database
- [ ] User context management
### Phase 3: LLM Integration
- [ ] Backend interface definition
- [ ] OpenAI-compatible backend (covers local + OpenAI)
- [ ] Response generation with history
### Phase 4: Response Handling
- [ ] Delay implementation (2.2-3s random)
- [ ] Message chunking (150 char limit)
- [ ] Send responses back to mesh
### Phase 5: Additional Backends
- [ ] Anthropic backend
- [ ] Google Gemini backend
### Phase 6: Polish
- [ ] Error handling and resilience
- [ ] Logging and monitoring
- [ ] Documentation
- [ ] Packaging for easy installation
## Future Considerations
- **Multi-node support**: One instance managing multiple nodes (different presets/locations)
- **Store-and-forward**: Queue messages for offline users
- **Games**: Simple text games (trivia, 8-ball, etc.)
- **Scheduled broadcasts**: Periodic announcements
## Notes
- Meshtastic Python API: https://meshtastic.org/docs/software/python/cli/
- Message size limit is 237 bytes, but we're targeting 150 chars for safety and readability
- The meshtastic library handles serial/TCP abstraction well