anyrobo
Build a voice AI assistant in 10 lines — local-first STT, LLM, TTS, and tool-calling included.
anyrobo is a batteries-included framework for building voice AI assistants that run entirely on your own hardware. It ties together speech-to-text (Whisper, Vosk), an LLM brain (Ollama by default via anyllm), and text-to-speech (pyttsx3, ElevenLabs) behind a single class. It supports multi-step tool-calling, built-in personalities (Jarvis, GLaDOS, assistant), a plugin system for custom skills, an event bus, a RAG knowledge base, and MCP client support for calling external tool servers.
Built by Viet-Anh Nguyen at NRL.ai.
Why anyrobo?
- One-liner API —
anyrobo.Robo().listen()is a complete voice assistant - Plugin architecture — Add custom skills, tools, personalities, and backends
- Local-first — Whisper + Ollama + pyttsx3 run 100% offline
- Minimal core deps — Base install is light; STT/TTS/LLM backends are extras
- Production-ready — Event system, memory persistence, MCP client, RAG
Installation
pip install anyrobo
For backends:
pip install anyrobo[whisper] # openai-whisper for local STT
pip install anyrobo[vosk] # Vosk for offline STT
pip install anyrobo[tts] # pyttsx3 for offline TTS
pip install anyrobo[elevenlabs] # ElevenLabs cloud TTS
pip install anyrobo[llm] # anyllm for LLM routing
pip install anyrobo[rag] # knowledge base with embeddings
pip install anyrobo[mcp] # MCP client for external tool servers
pip install anyrobo[all] # everything
Python 3.8+ supported (tested on 3.8, 3.9, 3.10, 3.11, 3.12, 3.13)
Quick Start
import anyrobo
# 1. Simplest possible voice assistant (Whisper + Ollama + pyttsx3)
bot = anyrobo.Robo(personality="jarvis")
bot.listen() # starts mic, transcribes, replies with voice, loops
# 2. Add a tool the assistant can call (schema auto-extracted from type hints)
def set_timer(minutes: int, label: str = "timer") -> str:
"""Set a countdown timer for the given number of minutes."""
return f"Timer '{label}' set for {minutes} minutes."
bot.add_tool(set_timer)
bot.listen() # now: "Hey Jarvis, set a 5 minute pasta timer" -> tool call
# 3. Text-mode (no mic/speaker, useful for testing)
reply = bot.ask("What's the weather like in Tokyo?")
Models & Methods
Backends (all local-first)
| Component | Backend | Model | Install |
|---|---|---|---|
| STT | Whisper | openai-whisper tiny/base/small/medium/large |
anyrobo[whisper] |
| STT | Vosk | Vosk offline models | anyrobo[vosk] |
| LLM | Ollama (default) | Any Ollama model (llama3.1:8b, qwen2.5, ...) |
anyrobo[llm] |
| LLM | OpenAI / Anthropic | via anyllm | anyrobo[llm] |
| TTS | pyttsx3 | OS-native voices (SAPI / NSSpeechSynthesizer / espeak) | anyrobo[tts] |
| TTS | ElevenLabs | Cloud API | anyrobo[elevenlabs] |
Tool / function calling
Pass plain Python functions; anyrobo (via anyllm) auto-extracts parameter schemas from type hints and docstrings, then runs a multi-step agentic loop:
- LLM receives the user query + tool list
- LLM decides whether to call a tool (structured output)
anyrobodispatches the tool and feeds the result back- Loop until the LLM emits a final natural-language response
Built-in personalities
| Name | Style |
|---|---|
jarvis |
Polite British butler, concise and proactive |
glados |
Dry, sarcastic, vaguely threatening (Portal-inspired) |
assistant |
Neutral, helpful default |
custom |
Pass your own system_prompt |
Conversation memory
SlidingWindowMemory keeps the last N turns in context, with optional disk persistence (JSON). Robo.save_memory(path) / load_memory(path) for persistence across sessions.
Event system
Subscribe to any lifecycle event:
bot.on("user_message", lambda text: print("heard:", text))
bot.on("tool_call", lambda name, args: log_tool(name, args))
bot.on("response", lambda text: print("bot:", text))
RAG Knowledge Base
anyrobo.KnowledgeBase() ingests text/PDF/markdown, chunks via anynlp, embeds via anyllm.embed, and performs similarity search to augment the LLM prompt.
MCP client
Robo.add_mcp_server(command, args) connects to any Model Context Protocol server (filesystem, GitHub, web search, a model exposed via anydeploy.mcp, ...) and exposes its tools to the assistant automatically.
Plugin system
Subclass anyrobo.Skill to package reusable behavior:
class WeatherSkill(anyrobo.Skill):
name = "weather"
def tools(self):
return [self.get_weather]
def get_weather(self, city: str) -> dict: ...
bot.add_skill(WeatherSkill())
API Reference
| Function / class | Purpose |
|---|---|
anyrobo.Robo(personality, stt, tts, llm) |
Main assistant class |
Robo.listen(hotword=None) |
Voice loop: STT -> LLM -> TTS |
Robo.ask(text) |
Text-mode interaction |
Robo.add_tool(fn) |
Register a Python function as a tool |
Robo.add_skill(skill) |
Register a plugin skill |
Robo.add_mcp_server(cmd, args) |
Connect to an MCP server |
Robo.on(event, handler) |
Subscribe to lifecycle events |
anyrobo.KnowledgeBase() |
RAG knowledge base |
anyrobo.Skill |
Base class for plugins |
CLI Usage
anyrobo listen --personality jarvis --model llama3.1:8b
anyrobo ask "What's on my calendar today?"
anyrobo list-personalities
anyrobo list-voices
Examples
Voice assistant with custom tools
import anyrobo
def turn_on_lights(room: str) -> str:
"""Turn on the smart lights in a specific room."""
return f"Lights in {room} are now on."
def play_music(genre: str, volume: int = 50) -> str:
"""Play music of a given genre at the specified volume (0-100)."""
return f"Playing {genre} music at volume {volume}."
bot = anyrobo.Robo(personality="jarvis", model="llama3.1:8b")
bot.add_tool(turn_on_lights)
bot.add_tool(play_music)
bot.listen()
RAG-powered Q&A over your docs
import anyrobo
kb = anyrobo.KnowledgeBase()
kb.ingest("docs/") # chunks + embeds every markdown/pdf
bot = anyrobo.Robo(knowledge_base=kb, personality="assistant")
print(bot.ask("What's our refund policy?"))
Connect to external MCP servers
import anyrobo
bot = anyrobo.Robo(personality="jarvis")
bot.add_mcp_server("npx", ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"])
bot.listen() # now the LLM can read/write files via MCP
License
MIT (c) Viet-Anh Nguyen
Metadata
Release files for anyrobo 0.2.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| anyrobo-0.2.4.tar.gz | 27.3 kB | Without provenance | Details for anyrobo-0.2.4.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| anyrobo-0.2.4-py3-none-any.whl | Python 3 | none | any | Details for anyrobo-0.2.4-py3-none-any.whl |
Total release size: 56.3 kB
Release files / anyrobo-0.2.4.tar.gz
| Download URL | anyrobo-0.2.4.tar.gz |
|---|---|
| Size | 27.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6e3020f84b6d37f19842b1d61dad386746bccd3e2950de6d5b6da26c53020973
|
|
BLAKE2b-256 checksum How to use checksums |
5191415d69e0fbaecb870f955befa259b402b130fc2977d884c11fe213bf412e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.9
|
Release files / anyrobo-0.2.4-py3-none-any.whl
| Download URL | anyrobo-0.2.4-py3-none-any.whl |
|---|---|
| Size | 29.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3b6aa844e7ebd98c2c5645b2025fa39ee339af611d7b466913cc70c64ed3c7eb
|
|
BLAKE2b-256 checksum How to use checksums |
f3fa4b80fb81cd0c37097a2d2985c322cb201b1f3e66255538e3a065d787335c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.9
|
Security for anyrobo 0.2.4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.