mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-05-21 23:24:44 +02:00
Initial commit: MeshAI - LLM-powered Meshtastic assistant
Features: - Multi-backend LLM support (OpenAI, Anthropic, Google) - Rolling summary memory for token optimization (~70-80% reduction) - Per-user conversation history with SQLite persistence - Bang commands (!help, !ping, !reset, !status, !weather) - Meshtastic integration via serial or TCP - Message chunking for mesh network constraints (150 char limit) - Rate limiting to prevent network congestion - Rich TUI configurator - Docker support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
commit
fd3f995ebb
43 changed files with 7947 additions and 0 deletions
356
PLAN.md
Normal file
356
PLAN.md
Normal file
|
|
@ -0,0 +1,356 @@
|
|||
# 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue