mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-05-21 15:14:45 +02:00
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>
13 KiB
13 KiB
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:
!commandsyntax 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
class LLMBackend(ABC):
@abstractmethod
async def generate(self, messages: list[dict], system_prompt: str) -> str:
pass
Supported Backends (Priority Order)
-
OpenAI-compatible (covers most bases)
- OpenAI (GPT-4, GPT-4o, etc.)
- Local LiteLLM/Open WebUI (ai.echo6.co)
- Any OpenAI-compatible API
-
Anthropic (Claude)
- Direct Anthropic API
-
Google (Gemini)
- Google AI Studio / Vertex AI
Configuration Example
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
# 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:
- If
!weather <location>- geocode the provided location - If
!weather(no args) - use sender's node GPS position if available - Fall back to
weather.default_locationfrom config - If no location found: "No location available. Use !weather 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
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
0always = 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:
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