Example code is available on GitHub
Any application interface that defines logic based on events and supports
special commands can work easily with Honcho. Here’s how to use Honcho with
Telegram as an interface. If you’re not familiar with Telegram bot
development, the python-telegram-bot docs would be a good
place to start.
Message Handling
Most Telegram bots have async functions that handle incoming messages. We can use Honcho to store messages by user and session based on the chat context. Take the following function definition for example:
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""
Receive a message from Telegram and respond with a message from our LLM assistant.
"""
if not validate_message(update, context):
return
message_text = update.effective_message.text
input_text = sanitize_message(message_text, context.bot.username)
# If the message is empty after sanitizing, ignore it
if not input_text:
return
peer = honcho_client.peer(id=get_peer_id_from_telegram(update))
session = honcho_client.session(id=str(update.effective_chat.id))
# Send typing indicator
await context.bot.send_chat_action(
chat_id=update.effective_chat.id, action="typing"
)
response = llm(session, input_text)
await send_telegram_message(update, context, response)
# Save both the user's message and the bot's response to the session
session.add_messages(
[
peer.message(input_text),
assistant.message(response),
]
)
Let’s break down what this code is doing…
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
if not validate_message(update, context):
return
This is how you define a message handler in python-telegram-bot
that processes incoming messages. We use a helper function validate_message()
to check if the message should be processed.
Helper Functions
The code uses several helper functions to keep the main logic clean and readable. Let’s examine each one:
Message Validation
def validate_message(update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
"""
Determine if the message is valid for the bot to respond to.
Return True if it is, False otherwise. The bot will respond to:
- Direct messages (private chats)
- Group messages that mention the bot or reply to it
- Messages that are not from the bot itself
"""
message = update.effective_message
if not message or not message.text:
return False
# Don't respond to our own messages
if message.from_user.id == context.bot.id:
return False
# Always respond in private chats
if update.effective_chat.type == "private":
return True
# In groups, only respond if mentioned or replied to
if (
message.reply_to_message
and message.reply_to_message.from_user.id == context.bot.id
):
return True
# Check if bot is mentioned
if message.entities:
for entity in message.entities:
if entity.type == "mention":
username = message.text[entity.offset : entity.offset + entity.length]
if username == f"@{context.bot.username}":
return True
return False
This function centralizes all the logic for determining whether the bot should respond to a message. It handles different chat types:
- Private chats: Always respond
- Group chats: Only respond when mentioned or when replying to the bot’s messages
- Bot prevention: Never respond to the bot’s own messages
Message Sanitization
def sanitize_message(message_text: str, bot_username: str) -> str | None:
"""Remove the bot's mention from the message content if present"""
content = message_text.replace(f"@{bot_username}", "").strip()
if not content:
return None
return content
This helper removes the bot’s mention from the message content, leaving just the actual user input.
Peer ID Generation
def get_peer_id_from_telegram(update: Update) -> str:
"""Get a Honcho peer ID for the message author"""
return f"telegram_{update.effective_user.id}"
This creates a unique peer identifier for each Telegram user by prefixing their Telegram user ID.
LLM Integration
def llm(session, prompt) -> str:
"""
Call the LLM with the given prompt and chat history.
You should expand this function with custom logic, prompts, etc.
"""
messages: list[dict[str, object]] = session.get_context().to_openai(
assistant=assistant
)
messages.append({"role": "user", "content": prompt})
try:
completion = openai.chat.completions.create(
model=MODEL_NAME,
messages=messages,
)
return completion.choices[0].message.content
except Exception as e:
logger.error(f"LLM error: {e}")
return f"Error: {e}"
This function handles the LLM interaction. It uses Honcho’s built-in to_openai()
method to automatically convert the session context into the format expected by OpenAI’s chat completions API.
Message Sending
async def send_telegram_message(
update: Update, context: ContextTypes.DEFAULT_TYPE, response_content: str
):
"""Send a message to the Telegram chat, splitting if necessary"""
# Telegram has a 4096 character limit, but we'll use 4000 to be safe
max_length = 4000
if len(response_content) <= max_length:
await update.effective_message.reply_text(response_content)
else:
# Split response into chunks at newlines, keeping under max_length chars
chunks = []
current_chunk = ""
for line in response_content.splitlines(keepends=True):
if len(current_chunk) + len(line) > max_length:
if current_chunk:
chunks.append(current_chunk)
current_chunk = line
else:
current_chunk += line
if current_chunk:
chunks.append(current_chunk)
for chunk in chunks:
await update.effective_message.reply_text(chunk)
This function handles sending messages to Telegram, automatically splitting long responses into multiple messages to stay within Telegram’s 4096 character limit. It also includes a typing indicator to show the bot is processing.
Honcho Integration
The new Honcho peer/session API makes integration much simpler:
peer = honcho_client.peer(id=get_peer_id_from_telegram(update))
session = honcho_client.session(id=str(update.effective_chat.id))
Here we create a peer object for the user and a session object using the Telegram chat ID. This automatically handles user and session management across both private chats and group conversations.
# Save both the user's message and the bot's response to the session
session.add_messages(
[
peer.message(input_text),
assistant.message(response),
]
)
After generating the response, we save both the user’s input and the bot’s response to the session using the add_messages()
method. The peer.message()
creates a message from the user, while assistant.message()
creates a message from the assistant.
Commands
Telegram bots support slash commands natively. Here’s how to implement the /dialectic
command using Honcho’s dialectic feature:
async def dialectic_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""
Handle the /dialectic command to query the Honcho Dialectic endpoint.
"""
if not context.args:
await update.message.reply_text(
"Please provide a query. Usage: /dialectic <your query>"
)
return
query = " ".join(context.args)
try:
peer = honcho_client.peer(id=get_peer_id_from_telegram(update))
session = honcho_client.session(id=str(update.effective_chat.id))
response = peer.chat(
queries=query,
session_id=session.id,
)
if response:
await send_telegram_message(update, context, response)
else:
await update.message.reply_text(
f"I don't know anything about {update.effective_user.first_name} because we haven't talked yet!"
)
except Exception as e:
logger.error(f"Error calling Dialectic API: {e}")
await update.message.reply_text(
f"Sorry, there was an error processing your request: {str(e)}"
)
You can also add a /start
command for user onboarding:
async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
"""Handle the /start command"""
await update.message.reply_text(
"Hello! I'm your AI assistant. You can:\n"
"• Chat with me directly in private messages\n"
"• Mention me (@username) in groups to get my attention\n"
"• Use /dialectic <query> to search our conversation history\n\n"
"Let's start chatting!"
)
Setup and Configuration
The bot requires several environment variables and setup:
honcho_client = Honcho()
assistant = honcho_client.peer(id="assistant", config={"observe_me": False})
openai = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=MODEL_API_KEY)
honcho_client
: The main Honcho client
assistant
: A peer representing the bot/assistant
openai
: OpenAI client configured to use OpenRouter
Application Setup
Register your handlers with the Telegram application:
def main():
"""Start the bot"""
if not BOT_TOKEN:
logger.error("BOT_TOKEN not found in environment variables")
return
# Create the Application
application = Application.builder().token(BOT_TOKEN).build()
# Add handlers
application.add_handler(CommandHandler("start", start_command))
application.add_handler(CommandHandler("dialectic", dialectic_command))
application.add_handler(
MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message)
)
# Start the bot
logger.info("Starting Telegram bot...")
application.run_polling(allowed_updates=Update.ALL_TYPES)
Environment Variables
Your bot needs these environment variables:
# Your Telegram bot token from BotFather
BOT_TOKEN=<your-token>
# AI model to use (see OpenRouter for available models)
MODEL_NAME=<your-model>
# Your OpenRouter API key
MODEL_API_KEY=<your-openrouter-api-key>
Chat Types and Behavior
The bot handles different Telegram chat types intelligently:
Private Chats
- Behavior: Responds to all messages
- Session ID: Uses the private chat ID
- Memory: Maintains conversation history per user
Group Chats
- Behavior: Only responds when mentioned or replied to
- Session ID: Uses the group chat ID (shared across all members)
- Memory: Maintains group conversation context
Recap
The new Honcho peer/session API makes Telegram bot integration much simpler and more intuitive. Key patterns we learned:
- Peer/Session Model: Users are represented as peers, conversations as sessions
- Chat Type Handling: Different validation logic for private vs group chats
- Automatic Context Management:
session.get_context().to_openai()
automatically formats chat history
- Message Storage:
session.add_messages()
stores both user and assistant messages
- Dialectic Queries:
peer.chat()
enables querying conversation history
- Command System: Native Telegram command support with
/start
and /dialectic
- Message Splitting: Automatic handling of Telegram’s character limits
- Helper Functions: Clean code organization with focused helper functions
This approach provides a clean, maintainable structure for building Telegram bots with conversational memory and context management across both private conversations and group chats.