manolo-bot is an AI-powered Telegram Chat Bot and Library built with Python, leveraging modern LLM frameworks and the
Model Context Protocol (MCP).
It is designed to be both a standalone application and a reusable library for building your own AI-powered bots.
Full documentation is available at https://manolo-bot.readthedocs.io/
You can install manolo-bot directly from PyPI:
pip install manolo-botIf you want to run the bot from the source code, you need to install the required packages using uv:
uv sync --no-devYou can copy and rename the provided env.example to .env and edit the file according to your data
You can create a bot on Telegram and get its API token by following the official instructions.
To use the bot in a group, you have to use the @BotFather bot to set the Group Privacy off. This allows the bot to access all group messages.
You can use the GOOGLE_API_KEY, OPENAI_API_KEY, OPENAI_API_BASE_URL or OLLAMA_MODEL for selecting the required
LLM provider.
The OPENAI_API_BASE_URL will look for an OpenAI API like, as the LM Studio API
- Note: When
GOOGLE_API_KEYoption is selected, the default model will be Gemini 2.0 Flash.
TELEGRAM_BOT_NAME: Your Telegram bot name
TELEGRAM_BOT_USERNAME: Your Telegram bot username
TELEGRAM_BOT_TOKEN: Your Telegram bot token
BOT_UUID: A unique identifier for this bot instance (default: default-bot-uuid). Used to isolate conversation
history in storage.
USER_ID: Your Telegram User ID (default: 0). Used for internal tracking and metadata.
OPENAI_API_MODEL: LLM to use for OpenAI or OpenAI-like API; if not provided, the default model will be used.
GOOGLE_API_MODEL: LLM to use for Google API; if not provided, the default model will be used.
AI_MODE: Selects the bot's AI mode (llm, agent, deep_agent). Default is agent.
| Mode | Class | Description |
|---|---|---|
agent |
LLMAgent |
LangGraph-based agent with an automatic tool loop. Uses the LLM as a reasoning engine, iterating through multiple steps (like searching the internet and analyzing results) to complete complex tasks. |
deep_agent |
LLMDeepAgent |
Full Deep Agents harness on top of LLMAgent. Adds to-do list planning, a virtual filesystem, sub-agent support, skills, and long-term memory for complex, multi-step tasks. |
llm |
LLMBot |
Simple LLM with a manual tool loop. Best for models that don't support tool calling or simpler use cases. |
AGENT_MODE: (Deprecated) Enable agent mode (True, False). Use AI_MODE instead. When AI_MODE is not set,
AGENT_MODE=True selects agent.
AGENT_INSTRUCTIONS: (Optional) Custom instructions to guide the agent's behavior and reasoning when in agent mode.
DEEP_AGENT_WORKSPACE_PATH: Directory used as the virtual filesystem root for the deep_agent mode
(only used by the local_fs backend). Defaults to a system temporary directory
(e.g. /tmp/manolo_bot/workspace). This is separate from DOCUMENT_STORAGE_PATH, which handles
temporarily uploaded documents.
Security note: the default workspace lives under the system temp directory, which on Linux is world-readable. Files the deep-agent writes there (chat working notes, file extracts, data echoed back from tool results) can be read by any local user on a shared host. On multi-tenant or shared machines set this to a path only the bot process can read, e.g.
$HOME/.local/share/manolo_bot/workspacewith mode0700.
DEEP_AGENT_BACKEND: Filesystem backend type for the deep_agent mode (in_memory or local_fs).
Default is local_fs. The local_fs backend persists scratch files per chat under
DEEP_AGENT_WORKSPACE_PATH/bot_uuid/chat_id and refuses to delete anything outside that workspace.
The in_memory backend keeps the virtual filesystem in process memory, scoped per
(bot_uuid, chat_id); state is cleared on clean_context() or process restart.
DEEP_AGENT_SKILLS_PATHS: Comma-separated list of skill source paths for the deep_agent mode; skills are loaded into the agent's system prompt via progressive disclosure.
DEEP_AGENT_MEMORY_PATH: Root directory for per-chat long-term memory in the deep_agent mode; each chat gets its own independent, seeded AGENTS.md file at DEEP_AGENT_MEMORY_PATH/bot_uuid/chat_id/AGENTS.md (no cross-chat leakage). Memory files are fully loaded into the agent's system prompt on every turn (keep them concise — they cost tokens on every message, unlike skills). The bot constructs a per-chat MemoryFilesystemDeepAgentBackend with a default process-local InMemoryStore; /flushcontext wipes the chat's memory along with its workspace.
DEEP_AGENT_MEMORY_ADD_CACHE_CONTROL: Add an Anthropic prompt-cache breakpoint on the memory block (default False; no-op on non-Anthropic models). Fed to LLMDeepAgent(memory_add_cache_control=...) at agent construction.
WEBUI_SD_API_URL: you can define a Stable Diffusion Web UI API URL for image generation. If this option is enabled the
bot will answer image generation requests using Stable Diffusion generated images.
WEBUI_SD_API_PARAMS: A JSON string containing Stable Diffusion Web UI API params. If not provided, default parameters
for the SDXL Turbo model will be used.
TELEGRAM_BOT_INSTRUCTIONS_CHARACTER: You can define a custom character for the bot instructions.
This will override the default bot character. For example:
You are a software engineer, geek and nerd, user of linux and free software technologies.
TELEGRAM_BOT_INSTRUCTIONS_EXTRA: You can include extra LLM system instructions using this variable.
TELEGRAM_BOT_INSTRUCTIONS: You can define custom LLM system instructions using this variable.
This will override the default instructions, and the custom bot character instructions.
TELEGRAM_ALLOWED_CHATS: You can use a comma-separated list of allowed chat IDs to limit bot interaction to those
chats.
ALLOW_PRIVATE_CHATS: Enable or disable direct bot interaction in private chats (True, False). Default is True.
ADD_NO_ANSWER: If True, the bot will reply with "NO_ANSWER" if it doesn't understand a message or isn't sure if it
should respond (True, False). Default is False.
IMAGE_MULTIMODAL: Enable multimodal capabilities for images (True, False). The selected model must support multimodal
capabilities.
AUDIO_MULTIMODAL: (Experimental) Enable multimodal capabilities for audio/voice messages (True, False). Currently,
this feature only works with the Gemini API.
DOCUMENT_MULTIMODAL: Enable document analysis for PDF, DOCX, and TXT files (True, False). Default is False.
When enabled, the bot can extract text from uploaded documents and use it as context.
For large documents, it is recommended to use a model with a large context window (like Gemini) and increase
CONTEXT_MAX_TOKENS.
The document text is stored separately from chat history to keep memory clean.
ENABLE_GROUP_ASSISTANT: Enable group assistant for group chats (True, False). The bot will respond to group chats with
a question mark. The default value is False.
MAX_DOCUMENT_SIZE_BYTES: Maximum size of documents the bot will process (default: 2MB).
MAX_VOICE_SIZE_BYTES: Maximum size of voice messages the bot will process (default: 2MB).
DOCUMENT_STORAGE_PATH: Directory where extracted document text is stored. Defaults to a system temporary directory.
RATE_LIMITER_REQUESTS_PER_SECOND: The number of requests per second allowed by the bot.
RATE_LIMITER_CHECK_EVERY_N_SECONDS: The number of seconds between rate limit checks.
RATE_LIMITER_MAX_BUCKET_SIZE: The maximum bucket size for rate limiting.
PREFERRED_LANGUAGE: The preferred language for the bot. (English, Spanish, etc.)
CONTEXT_MAX_TOKENS: The maximum number of tokens allowed for the bot's context.
When the chat context exceeds CONTEXT_MAX_TOKENS, the bot can summarize the oldest
messages instead of silently dropping them, and include that summary in subsequent prompts.
ENABLE_CONTEXT_SUMMARIZATION: Enable incremental LLM summarization of the oldest messages
when the context limit is reached (True, False). Default is True. When disabled (or when
summarization fails), the bot falls back to dropping the oldest messages.
SUMMARY_MAX_TOKENS: Maximum number of tokens allowed for the generated summary (default: 512).
SUMMARY_KEEP_MESSAGES: How many of the most recent messages to keep intact when summarizing
(minimum 2, default: 6).
WEB_CONTENT_REQUEST_TIMEOUT_SECONDS: Timeout in seconds for HTTP requests when retrieving web content. Default is 10
seconds.
SIMULATE_TYPING: Enable simulating human typing behavior. The default is False. This typing simulation will influence
the bot's response time in all chats.
SIMULATE_TYPING_WPM: The words per minute for simulating human typing behavior. Default is 100.
SIMULATE_TYPING_MAX_TIME: The maximum time in seconds for simulating human typing behavior. Default is 10 seconds (we
usually don't want to wait too long).
USE_TOOLS: Enable tool usage (True, False). Default is False. When tool usage is enabled, the bot will use the LLM's
tools capabilities. When tool usage is disabled, the bot will use the prompt-based pseudo-tools implementation.
The bot uses DuckDuckGo by default for web searches. You can optionally enable Tavily Search for more advanced search capabilities.
USE_TAVILY_SEARCH: Enable Tavily Search (True, False). Default is False.
TAVILY_SEARCH_KEY: Your Tavily API key. Required if USE_TAVILY_SEARCH is set to True.
STORAGE_TYPE: Sets the storage type for conversation context (memory, redis). Default is memory.
REDIS_URL: The Redis URL for storage when STORAGE_TYPE is set to redis. Default is redis://localhost:6379/0.
manolo_bot supports the Model Context Protocol for connecting to external tool servers.
Set the following environment variables:
ENABLE_MCP: Enable MCP support (True, False). Default is False.
MCP_SERVERS_CONFIG: MCP server configuration in JSON format.
MCP servers are configured via the MCP_SERVERS_CONFIG environment variable, which accepts a JSON object mapping server
names to their configurations.
stdio transport example:
{
"math": {
"command": "python",
"args": [
"/path/to/math_server.py"
],
"transport": "stdio"
}
}streamable_http transport example:
{
"weather": {
"url": "http://localhost:8000/mcp/",
"transport": "streamable_http"
}
}Multiple servers:
{
"math": {
"command": "python",
"args": [
"/path/to/math_server.py"
],
"transport": "stdio"
},
"weather": {
"url": "http://localhost:8000/mcp/",
"transport": "streamable_http"
}
}Notes:
- MCP tools are loaded alongside custom tools defined in
ai/tools.py - If tool name conflicts occur, MCP tools will override custom tools (a warning is logged)
- The bot will start successfully even if MCP initialization fails (graceful degradation)
- MCP is only loaded when both
ENABLE_MCP=Trueand validMCP_SERVERS_CONFIGare provided
LOGGING_LEVEL: Sets the logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL), defaulting to INFO.
The bot supports the following commands:
/flushcontext- Clears the conversation context for the current chat. In group chats, only admins can use this command. The bot will respond with a confirmation message in the configured language.
You can run the bot using the following command:
uv run manolo-botUse uv sync --dev to install the development dependencies.
After installing the development dependencies, to install pre-commit scripts, including ruff checks, you can run the following command:
pre-commit installYou can run the tests using the following command:
uv run python -m unittest discover testsmanolo-bot can also be used as a library to build your own AI assistants. It provides a clean abstraction for LLM
providers, message storage, and agentic capabilities.
The LLMAgent is the most powerful way to use the library. It allows the bot to use tools and iterate through multiple
steps to solve complex queries.
import asyncio
from manolo_bot.ai.llmagent import LLMAgent
from manolo_bot.ai.llmbot import LLMBuilder
from manolo_bot.ai.config import BotConfig, LLMConfig
from manolo_bot.storage.messages.memory_storage import MemoryMessagesStorage
from manolo_bot.ai.tools import get_tools
async def main():
# 1. Configure LLM (Google, OpenAI, or Ollama)
llm_config = LLMConfig(google_api_key="your_api_key")
llm = LLMBuilder(llm_config).get_llm()
# 2. Define Bot identity
bot_config = BotConfig(bot_uuid="my-bot", bot_name="Assistant")
# 3. Setup Storage for a specific conversation
storage = MemoryMessagesStorage(bot_uuid="my-bot", chat_id=123)
await storage.refresh_messages()
# 4. Initialize Agent with tools
tools = get_tools()
agent = LLMAgent(
llm=llm,
bot_config=bot_config,
system_instructions="You are a helpful assistant.",
messages_storage=storage,
tools=tools
)
# 5. Interact
# The agent can search the web, analyze content, etc.
response = await agent.answer_message(chat_id=123, message="What is the current price of Bitcoin?")
print(f"Agent: {response.content}")
# 6. Persistent changes (if using Redis)
await storage.commit()
if __name__ == "__main__":
asyncio.run(main())For the most advanced use case, LLMDeepAgent extends LLMAgent with the full Deep Agents harness: to-do list
planning, a virtual filesystem, and sub-agent support. It is ideal for complex, multi-step tasks that benefit from
planning and persistent scratch space.
import asyncio
from manolo_bot.ai.llmdeepagent import LLMDeepAgent
from manolo_bot.ai.llmbot import LLMBuilder
from manolo_bot.ai.config import BotConfig, LLMConfig
from manolo_bot.storage.messages.memory_storage import MemoryMessagesStorage
from manolo_bot.storage.deep_agent_backends.memory_backend import MemoryDeepAgentBackend
async def main():
# 1. Configure LLM (Google, OpenAI, or Ollama)
llm_config = LLMConfig(google_api_key="your_api_key")
llm = LLMBuilder(llm_config).get_llm()
# 2. Define Bot identity
bot_config = BotConfig(bot_uuid="my-bot", bot_name="Assistant")
# 3. Setup Storage for a specific conversation
storage = MemoryMessagesStorage(bot_uuid="my-bot", chat_id=123)
await storage.refresh_messages()
# 4. Setup the deep agent filesystem backend (in-memory or filesystem)
backend = MemoryDeepAgentBackend(bot_uuid="my-bot", chat_id=123)
# For a persistent filesystem workspace instead:
# backend = FilesystemDeepAgentBackend("my-bot", 123, "/path/to/workspace")
# 5. Initialize the Deep Agent
agent = LLMDeepAgent(
llm=llm,
bot_config=bot_config,
system_instructions="You are a helpful assistant.",
messages_storage=storage,
backend=backend,
)
await agent.initialize_async_resources()
# 6. Interact
response = await agent.answer_message(chat_id=123, message="Research and summarize the current state of LLM agents.")
print(f"Agent: {response.content}")
if __name__ == "__main__":
asyncio.run(main())The deep agent virtual filesystem uses a pluggable backend, similar to how message and document storage work:
MemoryDeepAgentBackend: In-memory virtual filesystem. State is scoped per(bot_uuid, chat_id)and shared acrossLLMDeepAgentinstances in the same process; cleared onclean_context()or process restart.FilesystemDeepAgentBackend: Persistent virtual filesystem stored underworkspace_path/bot_uuid/chat_id.
If no backend is provided, LLMDeepAgent falls back to an in-memory StateBackend.
You can provide your own tools when initializing LLMAgent, LLMDeepAgent, or LLMBot:
from langchain_core.tools import tool
from manolo_bot.ai.llmagent import LLMAgent
@tool
def my_tool(query: str) -> str:
"""Description of my tool."""
return "Result"
agent = LLMAgent(..., tools=[my_tool])For simpler use cases or when using models that don't support tool calling, you can use the basic LLMBot. It provides
a direct chat interface without iterative reasoning.
from manolo_bot.ai.llmbot import LLMBot
# ... (same setup as above)
bot = LLMBot(
llm=llm,
bot_config=bot_config,
system_instructions="You are a simple assistant.",
messages_storage=storage
)
response = await bot.answer_message(chat_id=123, message="Hello!")For more advanced usage and full API details, please refer to the Full Documentation.
If you'd like to contribute to this project, feel free to submit a pull request. We're always open to new ideas or improvements to the code.
This project is licensed under the MIT License - see the LICENSE file for details.