This release is a pre-release and may not be stable for production use.
MarkanM Python SDK
The official Python SDK for building bots, AI agents, and integrations on MarkanM Chat.
Table of Contents
- What is MarkanM?
- Installation
- Getting Your Bot Token
- Quick Start — Your First Bot in 60 Seconds
- Bot Commands
- Event Listeners
- Receiving User Input
- Sending Messages
- CommandContext Reference
- Event Reference
- AI Integration
- Conversation Memory
- Multi-Turn Chat
- Bot Personas & Custom Training
- RAG — Custom Knowledge Base
- Persistent Memory (Database)
- Interactive Forms & Multi-Step Flows
- Per-User State Management
- Webhooks (Production)
- Polling (Development)
- Error Handling
- Complete Bot Examples
- Deploying to Production
- API Reference
- Changelog
What is MarkanM?
MarkanM Chat is a real-time chat platform — like WhatsApp or Telegram — where developers can build and publish bots that users can interact with directly in chat rooms.
With the markanm Python SDK you can:
- 🤖 Build bots that respond to commands and messages
- 🧠 Connect any AI (Sarvam, Groq, Gemini, OpenAI, Ollama...) to your bot
- 💬 Receive and process any text input from users
- 🌐 Deploy to production with webhooks or run locally with polling
- 🔐 Authenticate users with OAuth 2.0 ("Continue with MarkanM")
Installation
pip install markanm
Optional: Install httpx for async HTTP (recommended)
pip install markanm httpx
The SDK automatically falls back to
urllibifhttpxis not installed.
Requirements: Python 3.8+
Getting Your Bot Token
- Go to chat.markanm.com/developers
- Click "Developer Portal"
- Navigate to the "Bots" tab
- Click "Create Bot"
- Fill in:
- Name — display name (e.g. "My Helper Bot")
- Username — @handle (e.g.
helperbot→ users find it as@helperbot) - Description — what your bot does
- Category — AI Assistant, Utility, Fun, etc.
- Click Create → copy your Bot Token (
mkbot_xxxxxxxxxxxxxxxxxx)
⚠️ Keep your bot token secret. Never commit it to Git. Use environment variables.
# Set as environment variable (recommended)
export MARKANM_BOT_TOKEN="mkbot_xxxxxxxxxxxxxxxxxxxxxxxx"
Quick Start
Your First Bot in 60 Seconds
# hello_bot.py
import os
from markanm import Bot
# Initialize bot with your token
bot = Bot(os.environ["MARKANM_BOT_TOKEN"])
# Respond to /hello command
@bot.command("hello")
async def hello(ctx):
await ctx.reply("Hello! 👋 I'm alive and running!")
# Respond to /help command
@bot.command("help")
async def help_cmd(ctx):
await ctx.reply("""🤖 Available Commands:
/hello — Say hello
/help — Show this help message
/ping — Check if I'm online
/echo <text> — Repeat your text back""")
# Respond to /ping command
@bot.command("ping")
async def ping(ctx):
await ctx.reply("Pong! 🏓 Bot is online.")
# Respond to /echo command
@bot.command("echo")
async def echo(ctx):
text = " ".join(ctx.args)
if not text:
await ctx.reply("Usage: /echo <your message>")
return
await ctx.reply(f"🔊 {text}")
# Start the bot
bot.run_polling()
Run it:
python hello_bot.py
Output:
🤖 MarkanM Bot running in development polling mode (interval: 2.0s)...
⏳ Polling... (20s elapsed, no events yet)
📨 Received 1 event(s)
▶ Event: type='command.received' payload={...}
Now go to MarkanM, find your bot (@yourbotusername), click Start Bot, and send /hello. Your bot will reply instantly!
Bot Commands
Commands are triggered when users send /commandname in chat.
Basic Command
@bot.command("greet")
async def greet(ctx):
await ctx.reply("Hello there! 👋")
User sends: /greet → Bot replies: Hello there! 👋
Command with Arguments
Arguments are the words after the command name. They are split by spaces and available as ctx.args (a list).
@bot.command("say")
async def say(ctx):
# ctx.args = ["hello", "world"] for input "/say hello world"
message = " ".join(ctx.args)
if not message:
await ctx.reply("Usage: /say <message>")
return
await ctx.reply(f"📢 {message}")
# Accessing individual arguments
@bot.command("add")
async def add(ctx):
if len(ctx.args) < 2:
await ctx.reply("Usage: /add <number1> <number2>")
return
try:
a = float(ctx.args[0])
b = float(ctx.args[1])
await ctx.reply(f"✅ {a} + {b} = {a + b}")
except ValueError:
await ctx.reply("❌ Please provide valid numbers.")
Command with Named Arguments
Parse structured arguments like /order --item pizza --qty 2:
@bot.command("order")
async def order(ctx):
# Parse --key value pairs from ctx.args
args = ctx.args
params = {}
i = 0
while i < len(args):
if args[i].startswith("--") and i + 1 < len(args):
params[args[i][2:]] = args[i + 1]
i += 2
else:
i += 1
item = params.get("item", "unknown")
qty = params.get("qty", "1")
await ctx.reply(f"🛒 Order placed: {qty}x {item}")
Registering Multiple Commands
from markanm import Bot
bot = Bot("mkbot_YOUR_TOKEN")
COMMANDS = {
"hello": "hello_handler",
"help": "help_handler",
"ping": "ping_handler",
}
@bot.command("hello")
async def hello_handler(ctx):
await ctx.reply("Hello! 👋")
@bot.command("help")
async def help_handler(ctx):
lines = ["📋 **Commands:**"]
for cmd in COMMANDS:
lines.append(f" /{cmd}")
await ctx.reply("\n".join(lines))
@bot.command("ping")
async def ping_handler(ctx):
await ctx.reply("Pong! 🏓")
bot.run_polling()
Command with User Info
@bot.command("profile")
async def profile(ctx):
user = ctx.user
await ctx.reply(
f"👤 Your Profile:\n"
f"Username: @{user.username}\n"
f"Display Name: {user.display_name}\n"
f"User ID: {user.id}"
)
Event Listeners
Event listeners respond to any event including plain text messages (not just commands).
Listen to Any Message
@bot.on("command.received")
async def on_any_message(evt):
text = evt.payload.get("text", "").strip()
user = evt.payload.get("user", {})
user_name = user.get("display_name", "there")
print(f"[{user_name}]: {text}")
Note:
command.receivedfires for EVERY message sent to your bot — whether it starts with/or not.
Separate Slash Commands from Plain Text
@bot.on("command.received")
async def on_message(evt):
text = evt.payload.get("text", "").strip()
if text.startswith("/"):
# This is a slash command — @bot.command() will handle it
return
# This is a plain-text message
await evt.reply(f"You said: {text}")
Respond to Keywords
@bot.on("command.received")
async def keyword_handler(evt):
text = evt.payload.get("text", "").lower().strip()
user_name = evt.payload.get("user", {}).get("display_name", "friend")
if text.startswith("/"):
return # Skip commands
if "hello" in text or "hi" in text or "hey" in text:
await evt.reply(f"Hey {user_name}! 👋 How can I help you?")
elif "bye" in text or "goodbye" in text:
await evt.reply(f"Goodbye {user_name}! 👋 Come back soon.")
elif "thank" in text:
await evt.reply("You're welcome! 😊")
elif "?" in text:
await evt.reply("Good question! Let me think... 🤔")
else:
await evt.reply(f"Got your message! Type /help to see what I can do.")
Multiple Listeners for Same Event
# Both of these will fire for every "command.received" event
@bot.on("command.received")
async def logger(evt):
print(f"[LOG] {evt.payload.get('user', {}).get('username')}: {evt.payload.get('text')}")
@bot.on("command.received")
async def responder(evt):
text = evt.payload.get("text", "")
if "help" in text.lower():
await evt.reply("Type /help for commands!")
Receiving User Input
How User Data Arrives
Every event's payload contains:
| Field | Type | Description |
|---|---|---|
text |
str |
The full message text (e.g. /ask who is PM) |
command |
str |
The command name without / (e.g. ask) |
user.id |
int |
Sender's user ID |
user.username |
str |
Sender's @username |
user.display_name |
str |
Sender's display name |
user.avatar_url |
str |
Sender's avatar URL |
room_id |
str |
The conversation/room ID |
conversation_id |
str |
Same as room_id |
message_id |
str |
Unique message ID |
message.text |
str |
Message content |
args |
list |
List of words after command (if slash command) |
Extracting Everything from a Message
@bot.on("command.received")
async def full_example(evt):
payload = evt.payload
# Text
text = payload.get("text", "").strip()
# User info
user = payload.get("user", {})
user_id = user.get("id")
username = user.get("username", "")
display_name = user.get("display_name", "")
avatar_url = user.get("avatar_url", "")
# Room info
room_id = payload.get("room_id") or payload.get("conversation_id")
# Message info
message = payload.get("message", {})
message_id = payload.get("message_id")
# Command-specific (if slash command)
command = payload.get("command", "")
print(f"Message from @{username} (ID:{user_id}) in room {room_id}: '{text}'")
await evt.reply(f"Hi {display_name}! Got your message.")
Accessing User Info in Commands
@bot.command("info")
async def info(ctx):
# ctx.user — UserContext object
print(ctx.user.id)
print(ctx.user.username)
print(ctx.user.display_name)
# ctx.room — RoomContext object
print(ctx.room_id)
# ctx.args — list of arguments
print(ctx.args)
# ctx.text — full raw text after command
print(ctx.text)
# ctx.command — command name (without /)
print(ctx.command)
Sending Messages
Reply to the Current Room
# In a command handler:
await ctx.reply("Hello! 👋")
# In an event listener:
await evt.reply("Got your message!")
Send to a Specific Room
await bot.send_message(room_id="42", text="Hello room 42!")
Send a Message with Formatting
MarkanM supports plain text. Use newlines for structure:
@bot.command("menu")
async def menu(ctx):
await ctx.reply("""🍔 Today's Menu:
1. Burger — ₹120
2. Pizza Slice — ₹80
3. Cold Drink — ₹40
4. French Fries — ₹60
Type /order <item number> to place your order!""")
Send Long Responses in Parts
@bot.command("report")
async def report(ctx):
await ctx.reply("📊 Generating report...")
# Simulate processing
import asyncio
await asyncio.sleep(1)
await ctx.reply("📈 Part 1: Sales Data\n- Jan: ₹50,000\n- Feb: ₹62,000\n- Mar: ₹78,000")
await ctx.reply("📉 Part 2: Expenses\n- Rent: ₹20,000\n- Salaries: ₹30,000")
await ctx.reply("✅ Report complete!")
CommandContext Reference
When you register a @bot.command() handler, it receives a CommandContext (called ctx):
@bot.command("example")
async def handler(ctx):
# ── Properties ────────────────────────────────────────
ctx.command # str — command name ("example")
ctx.text # str — full text after command name
ctx.args # list — words split from text
ctx.raw_data # dict — full raw event payload
# ── Nested Objects ────────────────────────────────────
ctx.user # UserContext
ctx.user.id # int
ctx.user.username # str
ctx.user.display_name # str
ctx.user.avatar_url # str
ctx.room # RoomContext
ctx.room_id # str — shortcut for ctx.room.id
ctx.room.id # str
ctx.room.name # str
ctx.message # MessageContext
ctx.message.id # str
ctx.message.text # str
ctx.message.type # str ("text", "card", etc.)
# ── Methods ───────────────────────────────────────────
await ctx.reply("text") # Send message to room
await bot.send_message(room_id, "text") # Send to any room
Event Reference
When you register a @bot.on() handler, it receives an Event:
@bot.on("command.received")
async def handler(evt):
# ── Properties ────────────────────────────────────────
evt.id # str — unique event ID
evt.type # str — event type ("command.received")
evt.timestamp # str — when event was created
evt.payload # dict — full payload (see table above)
evt.raw_data # dict — raw event data from server
evt.room_id # str — extracted from payload
# ── Aliases ───────────────────────────────────────────
evt.event_id # same as evt.id
evt.event_type # same as evt.type
evt.created_at # same as evt.timestamp
# ── Methods ───────────────────────────────────────────
await evt.reply("text") # Send message to this event's room
AI Integration
The markanm.ai module lets you connect any AI provider to your bot with a single import. No lock-in — bring your own API key.
Basic Pattern
from markanm import Bot
from markanm.ai import AI
bot = Bot("mkbot_YOUR_TOKEN")
ai = AI(provider="PROVIDER_NAME", api_key="YOUR_API_KEY")
@bot.command("ask")
async def ask(ctx):
question = " ".join(ctx.args)
if not question:
await ctx.reply("Usage: /ask <your question>")
return
answer = await ai.generate(question)
await ctx.reply(answer)
bot.run_polling()
Just change provider= and api_key= to switch between services.
Supported AI Providers
| Provider | provider= value |
Default Model | Best For | Cost |
|---|---|---|---|---|
| Sarvam AI | "sarvam" |
sarvam-105b |
Indian languages (Hindi, Tamil…) | Paid |
| Groq | "groq" |
llama3-8b-8192 |
Fastest responses | Free tier |
| Google Gemini | "gemini" |
gemini-1.5-flash |
Multimodal, web context | Free tier |
| OpenAI | "openai" |
gpt-3.5-turbo |
Most capable | Paid |
| Anthropic Claude | "anthropic" |
claude-3-haiku-20240307 |
Precise, safe | Paid |
| Mistral AI | "mistral" |
mistral-small-latest |
EU-hosted, private | Paid |
| DeepSeek | "deepseek" |
deepseek-chat |
Affordable, smart | Paid |
| Together AI | "together" |
mistralai/Mistral-7B-Instruct-v0.1 |
100s of open models | Free tier |
| Perplexity | "perplexity" |
llama-3.1-sonar-small-128k-online |
Live web search | Paid |
| Cohere | "cohere" |
command-r |
Business NLP | Free tier |
| HuggingFace | "huggingface" |
mistralai/Mistral-7B-Instruct-v0.1 |
Open source models | Free |
| Ollama | "ollama" |
llama3 |
Local/private, no cost | FREE |
| OpenRouter | "openrouter" |
meta-llama/llama-3-8b-instruct:free |
100+ models, free tier | Free tier |
| Custom API | "custom" |
your choice | Your own server | — |
Sarvam AI
Sarvam AI is an Indian-built AI that understands Hindi, Tamil, Telugu, Bengali, Marathi, Kannada, Malayalam, Odia, Punjabi, and Gujarati natively.
Get API Key: api.sarvam.ai → Sign up → Create API Key
Available Models:
sarvam-105b— General-purpose (recommended for Q&A)sarvam-105b-conversations— Optimized for multi-turn chat
from markanm import Bot
from markanm.ai import AI
bot = Bot("mkbot_YOUR_TOKEN")
ai = AI(
provider="sarvam",
api_key="sk_YOUR_SARVAM_KEY",
model="sarvam-105b",
system_prompt=(
"You are a helpful assistant. "
"If the user writes in Hindi or any Indian language, always respond in the same language."
)
)
@bot.command("ask")
async def ask(ctx):
question = " ".join(ctx.args)
if not question:
await ctx.reply("कुछ पूछिए! 🙏\nUsage: /ask <your question>")
return
answer = await ai.generate(question)
await ctx.reply(answer)
@bot.on("command.received")
async def on_message(evt):
text = evt.payload.get("text", "").strip()
if not text or text.startswith("/"):
return
reply = await ai.generate(text)
await evt.reply(reply)
bot.run_polling()
Example usage:
| User sends | Bot replies |
|---|---|
/ask भारत का PM कौन है? |
भारत के प्रधानमंत्री नरेंद्र मोदी हैं। |
/ask who is the president of India? |
The President of India is Droupadi Murmu. |
நமஸ்காரம் |
நமஸ்காரம்! நான் உங்களுக்கு எப்படி உதவ முடியும்? |
Groq
Groq is the fastest AI inference platform — responses arrive in under 1 second. It runs open-source models like LLaMA 3.
Get Free API Key: console.groq.com → Sign up → API Keys → Create
Available Models:
llama3-8b-8192— Fast, free, good qualityllama3-70b-8192— Smarter but slowermixtral-8x7b-32768— Long context (32k tokens)gemma2-9b-it— Google's Gemma 2llama-3.1-8b-instant— Even faster
from markanm import Bot
from markanm.ai import AI
bot = Bot("mkbot_YOUR_TOKEN")
ai = AI(
provider="groq",
api_key="gsk_YOUR_GROQ_API_KEY",
model="llama3-8b-8192", # Free, fast model
system_prompt="You are a helpful, friendly assistant. Keep answers concise.",
timeout=30.0
)
@bot.command("ask")
async def ask(ctx):
question = " ".join(ctx.args)
if not question:
await ctx.reply("Usage: /ask <question>")
return
answer = await ai.generate(question)
await ctx.reply(answer)
@bot.command("explain")
async def explain(ctx):
topic = " ".join(ctx.args)
if not topic:
await ctx.reply("Usage: /explain <topic>")
return
answer = await ai.generate(
f"Explain {topic} in simple terms, like I'm 12 years old."
)
await ctx.reply(answer)
@bot.command("code")
async def code(ctx):
task = " ".join(ctx.args)
if not task:
await ctx.reply("Usage: /code <what you want to build>")
return
answer = await ai.generate(
f"Write Python code for: {task}\nProvide clean, working code with brief comments."
)
await ctx.reply(answer)
bot.run_polling()
Google Gemini
Google Gemini — powerful, multimodal AI with a generous free tier.
Get Free API Key: aistudio.google.com → Sign in → Get API key
Available Models:
gemini-1.5-flash— Fast, free (recommended)gemini-1.5-pro— More capable, higher limitsgemini-1.0-pro— Stable older versiongemini-2.0-flash-exp— Experimental, cutting-edge
from markanm import Bot
from markanm.ai import AI
bot = Bot("mkbot_YOUR_TOKEN")
ai = AI(
provider="gemini",
api_key="AIzaSy_YOUR_GOOGLE_API_KEY",
model="gemini-1.5-flash",
system_prompt="You are a smart assistant. Provide accurate, helpful answers."
)
@bot.command("ask")
async def ask(ctx):
question = " ".join(ctx.args)
if not question:
await ctx.reply("Usage: /ask <question>")
return
answer = await ai.generate(question)
await ctx.reply(answer)
@bot.command("summarize")
async def summarize(ctx):
text = " ".join(ctx.args)
if not text:
await ctx.reply("Usage: /summarize <long text>")
return
summary = await ai.generate(f"Summarize this in 3 bullet points:\n\n{text}")
await ctx.reply(summary)
@bot.command("translate")
async def translate(ctx):
if len(ctx.args) < 2:
await ctx.reply("Usage: /translate <language> <text>\nExample: /translate Hindi Hello world")
return
lang = ctx.args[0]
text = " ".join(ctx.args[1:])
result = await ai.generate(f"Translate this to {lang}: {text}")
await ctx.reply(result)
bot.run_polling()
OpenAI
OpenAI — the creators of GPT. Most capable but requires a paid account.
Get API Key: platform.openai.com → API Keys → Create new secret key
Available Models:
gpt-3.5-turbo— Fast, cheap (recommended for bots)gpt-4o— Most capablegpt-4o-mini— Affordable, smarter than 3.5gpt-4-turbo— Large context window
from markanm import Bot
from markanm.ai import AI
bot = Bot("mkbot_YOUR_TOKEN")
ai = AI(
provider="openai",
api_key="sk-YOUR_OPENAI_KEY",
model="gpt-3.5-turbo", # or "gpt-4o-mini" for better quality
system_prompt="You are a helpful assistant."
)
@bot.command("ask")
async def ask(ctx):
question = " ".join(ctx.args)
if not question:
await ctx.reply("Usage: /ask <question>")
return
answer = await ai.generate(question)
await ctx.reply(answer)
bot.run_polling()
Anthropic Claude
Anthropic Claude — known for being safe, precise, and great at reasoning.
Get API Key: console.anthropic.com → API Keys
Available Models:
claude-3-haiku-20240307— Fastest, cheapestclaude-3-sonnet-20240229— Balancedclaude-3-opus-20240229— Most capableclaude-3-5-sonnet-20241022— Latest, best quality
from markanm import Bot
from markanm.ai import AI
bot = Bot("mkbot_YOUR_TOKEN")
ai = AI(
provider="anthropic",
api_key="sk-ant-YOUR_ANTHROPIC_KEY",
model="claude-3-haiku-20240307",
system_prompt="You are a helpful, harmless, and honest assistant."
)
@bot.command("ask")
async def ask(ctx):
question = " ".join(ctx.args)
if not question:
await ctx.reply("Usage: /ask <question>")
return
answer = await ai.generate(question)
await ctx.reply(answer)
bot.run_polling()
Mistral AI
Mistral AI — European AI company, GDPR-compliant, strong models.
Get API Key: console.mistral.ai → API Keys
Available Models:
mistral-small-latest— Fast, affordablemistral-medium-latest— Balancedmistral-large-latest— Most capableopen-mistral-7b— Open source, cheaperopen-mixtral-8x7b— Mixture of experts
from markanm import Bot
from markanm.ai import AI
bot = Bot("mkbot_YOUR_TOKEN")
ai = AI(
provider="mistral",
api_key="YOUR_MISTRAL_API_KEY",
model="mistral-small-latest"
)
@bot.command("ask")
async def ask(ctx):
question = " ".join(ctx.args)
answer = await ai.generate(question)
await ctx.reply(answer)
bot.run_polling()
DeepSeek
DeepSeek — powerful Chinese AI models, very affordable pricing.
Get API Key: platform.deepseek.com → API Keys
Available Models:
deepseek-chat— General purposedeepseek-coder— Specialized for code
from markanm.ai import AI
ai = AI(
provider="deepseek",
api_key="sk-YOUR_DEEPSEEK_KEY",
model="deepseek-chat"
)
# For coding bots:
ai_coder = AI(
provider="deepseek",
api_key="sk-YOUR_DEEPSEEK_KEY",
model="deepseek-coder",
system_prompt="You are an expert programmer. Provide clean, working code with explanations."
)
Together AI
Together AI — run hundreds of open-source models with one API.
Get API Key: api.together.xyz → Sign up → API Keys
Popular Models:
mistralai/Mistral-7B-Instruct-v0.1meta-llama/Llama-2-70b-chat-hfNousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO
from markanm.ai import AI
ai = AI(
provider="together",
api_key="YOUR_TOGETHER_API_KEY",
model="mistralai/Mistral-7B-Instruct-v0.1"
)
Perplexity AI
Perplexity — AI with live internet access. Perfect for real-time news/facts bots.
Get API Key: www.perplexity.ai → Settings → API
Available Models:
llama-3.1-sonar-small-128k-online— Fast + web searchllama-3.1-sonar-large-128k-online— Smarter + web searchllama-3.1-sonar-huge-128k-online— Most capable + web
from markanm import Bot
from markanm.ai import AI
bot = Bot("mkbot_YOUR_TOKEN")
# Real-time facts bot with live internet access!
ai = AI(
provider="perplexity",
api_key="pplx-YOUR_PERPLEXITY_KEY",
model="llama-3.1-sonar-small-128k-online",
system_prompt="You are a news bot. Answer based on current, real-time information."
)
@bot.command("news")
async def news(ctx):
topic = " ".join(ctx.args)
if not topic:
await ctx.reply("Usage: /news <topic>\nExample: /news cricket score India")
return
answer = await ai.generate(f"What is the latest news about: {topic}")
await ctx.reply(answer)
@bot.command("price")
async def price(ctx):
item = " ".join(ctx.args)
if not item:
await ctx.reply("Usage: /price <item>\nExample: /price Bitcoin")
return
answer = await ai.generate(f"What is the current price of {item}?")
await ctx.reply(answer)
bot.run_polling()
Cohere
Cohere — enterprise-grade NLP models, good free tier.
Get API Key: dashboard.cohere.com → API Keys
from markanm.ai import AI
ai = AI(
provider="cohere",
api_key="YOUR_COHERE_API_KEY",
model="command-r"
)
HuggingFace
HuggingFace Inference API — access thousands of open-source models for free.
Get Free API Key: huggingface.co → Settings → Access Tokens
from markanm.ai import AI
ai = AI(
provider="huggingface",
api_key="hf_YOUR_HUGGINGFACE_TOKEN",
model="mistralai/Mistral-7B-Instruct-v0.1"
)
# Other popular HuggingFace models:
# "google/flan-t5-xxl"
# "facebook/blenderbot-400M-distill"
# "tiiuae/falcon-7b-instruct"
# "HuggingFaceH4/zephyr-7b-beta"
Ollama
Ollama — run AI models 100% locally on your own computer. No API key, no cost, fully private.
Setup:
# 1. Install Ollama (Linux/macOS)
curl -fsSL https://ollama.ai/install.sh | sh
# 2. Pull a model (choose one)
ollama pull llama3 # Meta LLaMA 3 (recommended)
ollama pull mistral # Mistral 7B
ollama pull gemma2 # Google Gemma 2
ollama pull phi3 # Microsoft Phi-3 (small but smart)
ollama pull qwen2 # Alibaba Qwen 2
ollama pull codellama # Code-specialized
# 3. Verify it's running
ollama list
from markanm import Bot
from markanm.ai import AI
bot = Bot("mkbot_YOUR_TOKEN")
# No API key needed!
ai = AI(
provider="ollama",
model="llama3", # must match a model you've pulled
system_prompt="You are a helpful assistant.",
# base_url="http://localhost:11434/api" # default
)
@bot.command("ask")
async def ask(ctx):
question = " ".join(ctx.args)
if not question:
await ctx.reply("Usage: /ask <question>")
return
await ctx.reply("🤔 Thinking (running locally)...")
answer = await ai.generate(question)
await ctx.reply(answer)
bot.run_polling()
✅ Works completely offline. Your data never leaves your computer.
OpenRouter
OpenRouter — one API key, 100+ models, many completely free.
Get Free API Key: openrouter.ai → Sign up → Keys
Free Models (no cost):
meta-llama/llama-3-8b-instruct:freemistralai/mistral-7b-instruct:freegoogle/gemma-2-9b-it:freeqwen/qwen-2-7b-instruct:free
from markanm import Bot
from markanm.ai import AI
bot = Bot("mkbot_YOUR_TOKEN")
ai = AI(
provider="openrouter",
api_key="sk-or-v1-YOUR_OPENROUTER_KEY",
model="meta-llama/llama-3-8b-instruct:free", # 100% FREE
system_prompt="You are a helpful assistant."
)
@bot.command("ask")
async def ask(ctx):
question = " ".join(ctx.args)
if not question:
await ctx.reply("Usage: /ask <question>")
return
answer = await ai.generate(question)
await ctx.reply(answer)
bot.run_polling()
Custom HTTP Endpoint
You can connect to any AI service — including your own server — by using provider="custom".
Your server must:
- Accept
POSTrequests with JSON body:{"prompt": "...", "system": "...", "history": [...], "model": "..."} - Return JSON with one of:
text,content,response,output,result, oranswer
from markanm.ai import AI
# Connect to your own AI server
ai = AI(
provider="custom",
base_url="https://my-ai-server.com/api/generate",
api_key="my-secret-key", # optional — sent as Bearer token
model="my-model-v1",
)
# Example server that accepts this format (Python/FastAPI):
# @app.post("/api/generate")
# async def generate(req: Request):
# body = await req.json()
# prompt = body["prompt"]
# response = my_model.generate(prompt)
# return {"text": response} ← must return "text" key
Connect to a local AI service:
# If you're running a local AI service on port 5000:
ai = AI(
provider="custom",
base_url="http://localhost:5000/predict",
model="my-local-model"
)
Conversation Memory
By default, ai.generate() is stateless — it doesn't remember previous messages. Use ai.chat() for memory.
generate() — Stateless (no memory)
# Each call is independent — AI doesn't remember past messages
answer1 = await ai.generate("What is Python?")
answer2 = await ai.generate("What is it used for?") # AI doesn't know "it" refers to Python
chat() — Stateful (remembers conversation)
# Each call remembers the entire previous conversation
answer1 = await ai.chat("What is Python?")
answer2 = await ai.chat("What is it used for?") # AI knows "it" = Python ✅
answer3 = await ai.chat("Give me a simple example") # Full context retained ✅
Reset Memory
ai.reset() # Clears all conversation history
Multi-Turn Chat
The most common pattern: each user gets their own AI instance with independent memory.
import os
from markanm import Bot
from markanm.ai import AI
bot = Bot(os.environ["MARKANM_BOT_TOKEN"])
# Dictionary to store one AI instance per user
user_sessions = {} # user_id → AI instance
def get_session(user_id: int) -> AI:
"""Get or create an AI session for a user."""
if user_id not in user_sessions:
user_sessions[user_id] = AI(
provider="groq",
api_key=os.environ["GROQ_API_KEY"],
model="llama3-8b-8192",
system_prompt=(
"You are a friendly and helpful assistant named Aria. "
"Remember what the user tells you throughout the conversation."
)
)
return user_sessions[user_id]
@bot.on("command.received")
async def chat_handler(evt):
text = evt.payload.get("text", "").strip()
user_id = evt.payload.get("user", {}).get("id")
if not text or text.startswith("/") or not user_id:
return
ai = get_session(user_id)
reply = await ai.chat(text) # ← remembers all previous messages
await evt.reply(reply)
@bot.command("reset")
async def reset_cmd(ctx):
"""User can clear their chat history."""
user_id = ctx.user.id
if user_id in user_sessions:
user_sessions[user_id].reset()
await ctx.reply("🧹 Chat history cleared! Starting fresh.")
@bot.command("history")
async def history_cmd(ctx):
"""Show how many messages are in memory."""
user_id = ctx.user.id
if user_id in user_sessions:
count = len(user_sessions[user_id]._history)
await ctx.reply(f"🧠 I remember {count} messages in our conversation.")
else:
await ctx.reply("No conversation history yet. Start chatting!")
bot.run_polling()
Bot Personas & Custom Training
You don't need fine-tuning to make your bot behave a specific way. The system prompt is your training tool.
Customer Support Bot
ai = AI(
provider="groq",
api_key="gsk_...",
system_prompt="""You are Riya, the official support agent for ShopIndia.
RULES (follow strictly):
1. Only answer questions related to ShopIndia orders, products, and policies.
2. If asked about anything else, say: "I can only help with ShopIndia-related questions."
3. Always be polite, professional, and empathetic.
4. Never make up order details — ask the user to provide their Order ID.
5. If you cannot resolve an issue, say: "I'll escalate this to our team."
POLICIES:
- Shipping: Free above ₹500, ₹49 for orders below.
- Returns: 7-day no-questions-asked return policy.
- Refunds: Processed within 5–7 business days.
- Support hours: Mon–Sat, 9am–6pm IST.
FORMAT: Keep replies under 3 sentences unless listing policies."""
)
Educational Tutor Bot
ai = AI(
provider="gemini",
api_key="AIza...",
system_prompt="""You are a patient, encouraging tutor for Class 10 students.
TEACHING STYLE:
1. Always explain in simple, clear language.
2. Use real-world examples and analogies.
3. Break complex topics into small steps.
4. After explaining, ask: "Does this make sense? Shall I give an example?"
5. When a student is wrong, gently correct them.
6. Use emojis occasionally to keep it friendly 😊
SUBJECTS: Math, Science, English, Social Studies, Hindi."""
)
Fitness Coach Bot
ai = AI(
provider="openai",
api_key="sk-...",
model="gpt-4o-mini",
system_prompt="""You are FitBot, a certified fitness coach.
CAPABILITIES:
- Create personalized workout plans based on user's goals
- Give nutrition advice
- Track progress when users share updates
- Motivate users with encouragement
IMPORTANT:
- Always recommend consulting a doctor before starting new exercise routines
- Do not recommend specific medications or supplements
- Keep workout advice safe and beginner-friendly unless user specifies advanced level
FORMAT: Use bullet points for exercises. Always specify: sets, reps, and rest time."""
)
Hindi/Regional Language Bot
ai = AI(
provider="sarvam",
api_key="sk_...",
model="sarvam-105b",
system_prompt="""आप एक सहायक बॉट हैं।
नियम:
1. अगर उपयोगकर्ता हिंदी में लिखे, तो हिंदी में उत्तर दें।
2. अगर English में लिखे, तो English में उत्तर दें।
3. सरल और स्पष्ट भाषा का उपयोग करें।
4. हमेशा विनम्र और सहायक रहें।"""
)
RAG — Custom Knowledge Base
RAG (Retrieval Augmented Generation) = inject your own knowledge into AI prompts.
Simple Keyword-Based RAG
from markanm import Bot
from markanm.ai import AI
bot = Bot("mkbot_YOUR_TOKEN")
ai = AI(provider="groq", api_key="gsk_...",
system_prompt="You are a helpful support agent. Only answer using the provided context.")
# Your custom knowledge base
KNOWLEDGE = {
"price": "Our plans: Free (5GB storage), Pro ₹299/month (50GB), Business ₹999/month (unlimited).",
"refund": "We offer a 30-day money-back guarantee. Refunds process in 5–7 business days.",
"support": "Email: help@myapp.com | WhatsApp: +91-9999999999 | Hours: Mon–Fri, 10am–6pm IST.",
"features": "MyApp features: cloud storage, file sharing, team collaboration, version history.",
"install": "Download the app from myapp.com/download. Supports Windows, Mac, Linux, Android, iOS.",
"login": "Visit myapp.com and click 'Login'. Use your email and password, or 'Continue with Google'.",
"password": "To reset your password, click 'Forgot Password' on the login page. Check your email.",
"upgrade": "To upgrade, go to Settings → Billing → Choose Plan → Pay securely via Razorpay/Stripe.",
}
def find_relevant_facts(question: str) -> str:
"""Find knowledge base entries relevant to the question."""
q = question.lower()
relevant = []
keyword_map = {
"price": ["price", "cost", "plan", "subscription", "how much", "fee", "pay"],
"refund": ["refund", "money back", "cancel", "return"],
"support": ["support", "contact", "help", "email", "call", "reach"],
"features": ["feature", "what can", "what does", "capability", "function"],
"install": ["install", "download", "setup", "how to get"],
"login": ["login", "log in", "sign in", "access"],
"password": ["password", "forgot", "reset", "can't login"],
"upgrade": ["upgrade", "change plan", "switch plan", "premium"],
}
for key, keywords in keyword_map.items():
if any(kw in q for kw in keywords):
relevant.append(KNOWLEDGE[key])
return "\n".join(relevant)
@bot.command("ask")
async def ask(ctx):
question = " ".join(ctx.args).strip()
if not question:
await ctx.reply("Usage: /ask <question>\nExample: /ask what is the price?")
return
# Find relevant knowledge
context = find_relevant_facts(question)
if context:
prompt = f"Context:\n{context}\n\nUser question: {question}\n\nAnswer based only on the context above."
else:
prompt = f"{question}\n\nNote: Answer helpfully but mention this may be outside your knowledge base."
answer = await ai.generate(prompt)
await ctx.reply(answer)
bot.run_polling()
Persistent Memory
Save conversation history to a file or database so the bot remembers users across restarts.
Save to JSON File
import json
import os
from markanm import Bot
from markanm.ai import AI
bot = Bot("mkbot_YOUR_TOKEN")
MEMORY_FILE = "bot_memory.json"
MAX_HISTORY = 20 # Keep last 20 messages per user
def load_all_memories() -> dict:
if os.path.exists(MEMORY_FILE):
with open(MEMORY_FILE, "r") as f:
return json.load(f)
return {}
def save_all_memories(data: dict):
with open(MEMORY_FILE, "w") as f:
json.dump(data, f, indent=2)
def get_user_history(user_id: str) -> list:
return load_all_memories().get(user_id, [])
def save_user_history(user_id: str, history: list):
data = load_all_memories()
data[user_id] = history[-MAX_HISTORY:] # Trim to limit
save_all_memories(data)
@bot.on("command.received")
async def persistent_chat(evt):
text = evt.payload.get("text", "").strip()
user_id = str(evt.payload.get("user", {}).get("id", ""))
if not text or not user_id or text.startswith("/"):
return
# Load this user's history from file
ai = AI(provider="groq", api_key=os.environ["GROQ_API_KEY"])
ai._history = get_user_history(user_id)
# Chat (uses history)
reply = await ai.chat(text)
# Save updated history back to file
save_user_history(user_id, ai._history)
await evt.reply(reply)
@bot.command("reset")
async def reset(ctx):
user_id = str(ctx.user.id)
data = load_all_memories()
if user_id in data:
del data[user_id]
save_all_memories(data)
await ctx.reply("🧹 Memory cleared!")
bot.run_polling()
Interactive Forms & Multi-Step Flows
Collect structured data from users step-by-step, like a form or wizard.
import os
from markanm import Bot
from markanm.ai import AI
bot = Bot("mkbot_YOUR_TOKEN")
# State storage: user_id → {"step": "...", "data": {...}}
user_states = {}
def set_state(user_id, step, data=None):
user_states[user_id] = {"step": step, "data": data or {}}
def get_state(user_id):
return user_states.get(user_id)
def clear_state(user_id):
user_states.pop(user_id, None)
# ── Step 1: Start the form ─────────────────────────────────────
@bot.command("register")
async def register_start(ctx):
set_state(ctx.user.id, "ask_name")
await ctx.reply("📝 Registration Form (Step 1/4)\n\nWhat is your full name?")
# ── Steps 2–4: Handle form input ──────────────────────────────
@bot.on("command.received")
async def handle_form(evt):
text = evt.payload.get("text", "").strip()
user_id = evt.payload.get("user", {}).get("id")
if not user_id or not text or text.startswith("/"):
return
state = get_state(user_id)
if not state:
return # User isn't in a form flow
step = state["step"]
data = state["data"]
if step == "ask_name":
if len(text) < 2:
await evt.reply("❌ Please enter a valid name.")
return
data["name"] = text
set_state(user_id, "ask_email", data)
await evt.reply(f"✅ Name: {text}\n\n📝 Step 2/4 — What is your email address?")
elif step == "ask_email":
if "@" not in text or "." not in text:
await evt.reply("❌ Please enter a valid email address (e.g. name@example.com).")
return
data["email"] = text
set_state(user_id, "ask_phone", data)
await evt.reply(f"✅ Email: {text}\n\n📝 Step 3/4 — What is your phone number?")
elif step == "ask_phone":
if not text.isdigit() or len(text) < 10:
await evt.reply("❌ Please enter a valid 10-digit phone number.")
return
data["phone"] = text
set_state(user_id, "confirm", data)
await evt.reply(
f"📋 Step 4/4 — Please confirm your details:\n\n"
f"👤 Name: {data['name']}\n"
f"📧 Email: {data['email']}\n"
f"📞 Phone: {data['phone']}\n\n"
f"Type YES to confirm or NO to cancel."
)
elif step == "confirm":
if text.upper() == "YES":
# Process the registration
clear_state(user_id)
await evt.reply(
f"🎉 Registration successful!\n\n"
f"Welcome, {data['name']}!\n"
f"We'll send details to {data['email']}."
)
# Here you would save to a database
elif text.upper() == "NO":
clear_state(user_id)
await evt.reply("❌ Registration cancelled. Type /register to start again.")
else:
await evt.reply("Please type YES to confirm or NO to cancel.")
@bot.command("cancel")
async def cancel_form(ctx):
clear_state(ctx.user.id)
await ctx.reply("❌ Form cancelled.")
bot.run_polling()
Per-User State Management
A reusable pattern for tracking different states per user:
from enum import Enum, auto
from dataclasses import dataclass, field
from typing import Dict, Any, Optional
class UserStep(Enum):
IDLE = auto()
WAITING_NAME = auto()
WAITING_CHOICE = auto()
WAITING_CONFIRM = auto()
@dataclass
class UserState:
step: UserStep = UserStep.IDLE
data: Dict[str, Any] = field(default_factory=dict)
class StateManager:
def __init__(self):
self._states: Dict[int, UserState] = {}
def get(self, user_id: int) -> UserState:
if user_id not in self._states:
self._states[user_id] = UserState()
return self._states[user_id]
def set_step(self, user_id: int, step: UserStep, **data):
state = self.get(user_id)
state.step = step
state.data.update(data)
def reset(self, user_id: int):
self._states[user_id] = UserState()
def is_idle(self, user_id: int) -> bool:
return self.get(user_id).step == UserStep.IDLE
states = StateManager()
@bot.command("start")
async def start_flow(ctx):
states.set_step(ctx.user.id, UserStep.WAITING_NAME)
await ctx.reply("What's your name?")
@bot.on("command.received")
async def flow_handler(evt):
text = evt.payload.get("text", "").strip()
user_id = evt.payload.get("user", {}).get("id")
if not user_id or not text or text.startswith("/"):
return
state = states.get(user_id)
if state.step == UserStep.IDLE:
return
if state.step == UserStep.WAITING_NAME:
states.set_step(user_id, UserStep.WAITING_CHOICE, name=text)
await evt.reply(f"Hi {text}! Choose: 1) Option A or 2) Option B")
elif state.step == UserStep.WAITING_CHOICE:
if text not in ["1", "2", "Option A", "Option B"]:
await evt.reply("Please choose 1 or 2.")
return
states.set_step(user_id, UserStep.IDLE)
name = state.data.get("name", "friend")
await evt.reply(f"✅ {name} chose {text}. Done!")
states.reset(user_id)
Webhooks (Production)
Webhooks are for deploying your bot to a real server. Instead of polling, MarkanM pushes events to your server instantly.
Requirements
- A public HTTPS URL (e.g.
https://mybot.railway.app/webhook) - A server framework (FastAPI, Flask, Django, etc.)
FastAPI Webhook Server
# webhook_bot.py
import os
import uvicorn
from fastapi import FastAPI, Request, HTTPException
from markanm import Bot
from markanm.ai import AI
app = FastAPI(title="My MarkanM Bot")
# Initialize bot with webhook_secret for signature verification
bot = Bot(
token=os.environ["MARKANM_BOT_TOKEN"],
webhook_secret=os.environ["MARKANM_WEBHOOK_SECRET"] # from Developer Portal
)
ai = AI(
provider="groq",
api_key=os.environ["GROQ_API_KEY"],
model="llama3-8b-8192"
)
# ── Register your handlers ─────────────────────────────────────
@bot.command("ask")
async def ask(ctx):
question = " ".join(ctx.args)
if not question:
await ctx.reply("Usage: /ask <question>")
return
answer = await ai.generate(question)
await ctx.reply(answer)
@bot.on("command.received")
async def on_message(evt):
text = evt.payload.get("text", "").strip()
if not text or text.startswith("/"):
return
reply = await ai.generate(text)
await evt.reply(reply)
# ── Webhook endpoint ───────────────────────────────────────────
@app.post("/webhook")
async def webhook(request: Request):
body = await request.body()
signature = request.headers.get("X-MarkanM-Signature", "")
try:
# Verify signature & dispatch to handlers
event = await bot.handle_webhook(body.decode("utf-8"), signature)
return {"ok": True, "event_id": event.id}
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
@app.get("/health")
async def health():
return {"status": "ok", "bot": "running"}
# ── Run ────────────────────────────────────────────────────────
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", 8000)))
Deploy:
# Install deps
pip install markanm httpx fastapi uvicorn
# Set environment variables
export MARKANM_BOT_TOKEN="mkbot_..."
export MARKANM_WEBHOOK_SECRET="whsec_..."
export GROQ_API_KEY="gsk_..."
# Run
python webhook_bot.py
Flask Webhook Server
import os
from flask import Flask, request, jsonify
from markanm import Bot
from markanm.ai import AI
import asyncio
app = Flask(__name__)
bot = Bot(
token=os.environ["MARKANM_BOT_TOKEN"],
webhook_secret=os.environ["MARKANM_WEBHOOK_SECRET"]
)
ai = AI(provider="groq", api_key=os.environ["GROQ_API_KEY"])
@bot.command("ask")
async def ask(ctx):
answer = await ai.generate(" ".join(ctx.args))
await ctx.reply(answer)
@app.route("/webhook", methods=["POST"])
def webhook():
body = request.get_data(as_text=True)
signature = request.headers.get("X-MarkanM-Signature", "")
async def process():
await bot.handle_webhook(body, signature)
asyncio.run(process())
return jsonify({"ok": True})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Polling (Development)
Polling is for local development — no server needed.
bot.run_polling() # Default: polls every 2 seconds
bot.run_polling(interval=1.0) # Poll every 1 second (faster)
bot.run_polling(interval=5.0) # Poll every 5 seconds (lighter)
bot.run(mode="polling", interval=2) # Alternative syntax
How it works:
- Every
intervalseconds, your bot callsPOST /api/bot/v1/polling - The server returns any pending events
- Your handlers are called for each event
- The cycle repeats
✅ Use polling for local development. 🚀 Switch to webhooks for production.
Error Handling
Handle AI Errors Gracefully
from markanm.exceptions import MarkanMError, MarkanMAPIError, MarkanMAuthError
@bot.command("ask")
async def ask(ctx):
question = " ".join(ctx.args)
if not question:
await ctx.reply("Usage: /ask <question>")
return
try:
answer = await ai.generate(question)
await ctx.reply(answer)
except MarkanMAuthError:
await ctx.reply("❌ AI authentication failed. Check your API key.")
except MarkanMAPIError as e:
await ctx.reply(f"❌ AI service error: {e}")
except MarkanMError as e:
await ctx.reply(f"❌ Error: {e.message}")
except Exception as e:
await ctx.reply("❌ An unexpected error occurred. Please try again.")
print(f"Unexpected error: {e}")
Retry on Failure
import asyncio
async def generate_with_retry(ai, prompt, max_retries=3):
for attempt in range(max_retries):
try:
return await ai.generate(prompt)
except Exception as e:
if attempt < max_retries - 1:
wait = 2 ** attempt # Exponential backoff: 1s, 2s, 4s
await asyncio.sleep(wait)
else:
raise e
@bot.command("ask")
async def ask(ctx):
question = " ".join(ctx.args)
try:
answer = await generate_with_retry(ai, question)
await ctx.reply(answer)
except Exception:
await ctx.reply("❌ Failed after 3 attempts. Please try again later.")
Validate User Input
@bot.command("calculate")
async def calculate(ctx):
if not ctx.args:
await ctx.reply("❌ Please provide an expression.\nUsage: /calculate 2 + 3")
return
expression = " ".join(ctx.args)
# Only allow safe characters
allowed = set("0123456789+-*/(). ")
if not all(c in allowed for c in expression):
await ctx.reply("❌ Invalid characters. Only use numbers and + - * / ( )")
return
try:
result = eval(expression) # Safe since we validated input
await ctx.reply(f"🧮 {expression} = **{result}**")
except ZeroDivisionError:
await ctx.reply("❌ Cannot divide by zero!")
except Exception:
await ctx.reply("❌ Invalid expression.")
Complete Bot Examples
Example 1: Full AI Assistant Bot
# ai_assistant.py — A complete, production-ready AI bot
import os
from markanm import Bot
from markanm.ai import AI
bot = Bot(os.environ["MARKANM_BOT_TOKEN"])
user_sessions = {} # user_id → AI instance
def get_ai(user_id: int) -> AI:
if user_id not in user_sessions:
user_sessions[user_id] = AI(
provider="groq",
api_key=os.environ["GROQ_API_KEY"],
model="llama3-8b-8192",
system_prompt="You are Aria, a friendly and intelligent assistant."
)
return user_sessions[user_id]
@bot.command("start")
async def start(ctx):
await ctx.reply(
f"👋 Hello {ctx.user.display_name}!\n\n"
"I'm Aria, your AI assistant powered by LLaMA 3.\n\n"
"Commands:\n"
"/ask <question> — Ask me anything\n"
"/chat — Start a conversation (I'll remember context!)\n"
"/reset — Clear conversation history\n"
"/help — Show this message"
)
@bot.command("ask")
async def ask(ctx):
question = " ".join(ctx.args).strip()
if not question:
await ctx.reply("Usage: /ask <question>\nExample: /ask what is machine learning?")
return
await ctx.reply("🤔 Thinking...")
ai = AI(provider="groq", api_key=os.environ["GROQ_API_KEY"], model="llama3-8b-8192")
answer = await ai.generate(question)
await ctx.reply(f"💡 {answer}")
@bot.command("reset")
async def reset(ctx):
user_id = ctx.user.id
if user_id in user_sessions:
user_sessions[user_id].reset()
await ctx.reply("🧹 Conversation history cleared!")
@bot.command("help")
async def help_cmd(ctx):
await ctx.reply(
"🤖 **Aria AI Bot — Commands:**\n\n"
"/start — Welcome message\n"
"/ask <q> — One-shot question (no memory)\n"
"/reset — Clear my memory of you\n"
"/help — Show this message\n\n"
"💬 Or just send any message to chat with me!"
)
@bot.on("command.received")
async def chat(evt):
text = evt.payload.get("text", "").strip()
user_id = evt.payload.get("user", {}).get("id")
if not text or not user_id or text.startswith("/"):
return
ai = get_ai(user_id)
reply = await ai.chat(text)
await evt.reply(reply)
bot.run_polling()
Example 2: Customer Support Bot
# support_bot.py
import os
from markanm import Bot
from markanm.ai import AI
bot = Bot(os.environ["MARKANM_BOT_TOKEN"])
ai = AI(
provider="sarvam",
api_key=os.environ["SARVAM_API_KEY"],
model="sarvam-105b",
system_prompt="""You are a customer support agent for MyShop India.
Answer only about: orders, products, shipping, returns, and payments.
If asked anything else: "I can only help with MyShop queries. Please contact us at help@myshop.in"
Language: Respond in the same language the customer uses (Hindi/English/etc.)
Tone: Professional, empathetic, helpful.
Policies:
- Shipping: Free above ₹499, ₹49 below. 2-5 business days.
- Returns: 7-day return. Initiate via /return command.
- Refunds: 5-7 business days after return received.
- COD available on orders below ₹10,000."""
)
@bot.command("start")
async def start(ctx):
await ctx.reply(
f"🛍️ Welcome to MyShop Support!\n\n"
f"Hi {ctx.user.display_name}! I'm here to help.\n\n"
"How can I assist you today?\n\n"
"Commands:\n"
"/order <ID> — Track your order\n"
"/return — Start a return\n"
"/help — See all options"
)
@bot.command("order")
async def track_order(ctx):
if not ctx.args:
await ctx.reply("Please provide your Order ID.\nUsage: /order ORD123456")
return
order_id = ctx.args[0].upper()
# In a real bot, query your database here
await ctx.reply(
f"📦 Order Status: {order_id}\n\n"
"Status: In Transit 🚚\n"
"Expected: 2–3 business days\n\n"
"For more details, email orders@myshop.in"
)
@bot.command("return")
async def start_return(ctx):
await ctx.reply(
"📦 Return Policy:\n\n"
"✅ 7-day return window\n"
"✅ Free pick-up for defective items\n"
"✅ Refund in 5–7 business days\n\n"
"To initiate: Email returns@myshop.in with:\n"
"- Order ID\n- Reason for return\n- Photos (if damaged)"
)
@bot.on("command.received")
async def support_chat(evt):
text = evt.payload.get("text", "").strip()
if not text or text.startswith("/"):
return
reply = await ai.generate(text)
await evt.reply(reply)
bot.run_polling()
Example 3: Quiz Bot
# quiz_bot.py
import os, random
from markanm import Bot
bot = Bot(os.environ["MARKANM_BOT_TOKEN"])
QUESTIONS = [
{"q": "What is the capital of India?", "a": "new delhi", "hint": "It's in the north."},
{"q": "Who invented the telephone?", "a": "alexander graham bell", "hint": "Scottish-American inventor."},
{"q": "What is 15 × 15?", "a": "225", "hint": "It's between 200 and 250."},
{"q": "What is the largest planet in our solar system?", "a": "jupiter", "hint": "It's a gas giant."},
{"q": "Who wrote the Mahabharata?", "a": "ved vyasa", "hint": "Ancient Indian sage."},
{"q": "What is the chemical symbol for water?", "a": "h2o", "hint": "Hydrogen and Oxygen."},
{"q": "In what year did India gain independence?", "a": "1947", "hint": "After World War II."},
{"q": "Who is known as the Father of the Nation in India?", "a": "mahatma gandhi", "hint": "Led the freedom movement."},
]
quiz_state = {} # user_id → {"question": ..., "attempts": 0, "score": 0}
@bot.command("quiz")
async def start_quiz(ctx):
user_id = ctx.user.id
q = random.choice(QUESTIONS)
quiz_state[user_id] = {"question": q, "attempts": 0, "score": quiz_state.get(user_id, {}).get("score", 0)}
await ctx.reply(f"🎯 Question:\n\n{q['q']}\n\nType your answer! (or /hint for a clue, /skip to skip)")
@bot.command("hint")
async def hint(ctx):
user_id = ctx.user.id
state = quiz_state.get(user_id)
if not state:
await ctx.reply("Start a quiz first with /quiz")
return
await ctx.reply(f"💡 Hint: {state['question']['hint']}")
@bot.command("skip")
async def skip(ctx):
user_id = ctx.user.id
state = quiz_state.get(user_id)
if not state:
await ctx.reply("Start a quiz first with /quiz")
return
ans = state['question']['a'].title()
await ctx.reply(f"⏭️ Skipped!\nThe answer was: **{ans}**\n\nType /quiz for next question!")
del quiz_state[user_id]
@bot.command("score")
async def score(ctx):
state = quiz_state.get(ctx.user.id, {})
score = state.get("score", 0)
await ctx.reply(f"🏆 Your score: {score} points!")
@bot.on("command.received")
async def check_answer(evt):
text = evt.payload.get("text", "").strip().lower()
user_id = evt.payload.get("user", {}).get("id")
if not user_id or not text or text.startswith("/"):
return
state = quiz_state.get(user_id)
if not state:
return
state["attempts"] += 1
correct = state["question"]["a"].lower()
if text == correct or correct in text:
pts = max(10 - state["attempts"] * 2, 1)
state["score"] = state.get("score", 0) + pts
await evt.reply(f"🎉 Correct! +{pts} points\n\n🏆 Total score: {state['score']}\n\nType /quiz for next question!")
del quiz_state[user_id]
elif state["attempts"] >= 3:
ans = state["question"]["a"].title()
await evt.reply(f"❌ Out of attempts!\nThe answer was: **{ans}**\n\nType /quiz to try another!")
del quiz_state[user_id]
else:
remaining = 3 - state["attempts"]
await evt.reply(f"❌ Wrong! {remaining} attempt(s) remaining.\nTry again or type /hint for a clue.")
bot.run_polling()
Deploying to Production
Option 1: Railway.app (Recommended — Free Tier)
# 1. Create a Railway account at railway.app
# 2. New Project → Deploy from GitHub
# Create requirements.txt
cat > requirements.txt << EOF
markanm>=0.1.0
httpx>=0.24.0
fastapi>=0.104.0
uvicorn>=0.24.0
EOF
# Create Procfile
echo "web: uvicorn webhook_bot:app --host 0.0.0.0 --port \$PORT" > Procfile
# Set environment variables in Railway dashboard:
# MARKANM_BOT_TOKEN = mkbot_...
# MARKANM_WEBHOOK_SECRET = whsec_...
# GROQ_API_KEY = gsk_...
Option 2: Render.com (Free Tier)
# render.yaml
services:
- type: web
name: my-markanm-bot
env: python
startCommand: uvicorn webhook_bot:app --host 0.0.0.0 --port $PORT
envVars:
- key: MARKANM_BOT_TOKEN
sync: false
- key: GROQ_API_KEY
sync: false
Option 3: VPS / DigitalOcean
# On your server:
git clone https://github.com/your-username/my-bot.git
cd my-bot
pip install -r requirements.txt
# Run with systemd (auto-restart on crash)
cat > /etc/systemd/system/markanm-bot.service << EOF
[Unit]
Description=MarkanM Bot
After=network.target
[Service]
User=www-data
WorkingDirectory=/var/www/my-bot
Environment=MARKANM_BOT_TOKEN=mkbot_...
Environment=GROQ_API_KEY=gsk_...
ExecStart=/usr/bin/python3 webhook_bot.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl enable markanm-bot
systemctl start markanm-bot
systemctl status markanm-bot
Option 4: Keep Polling Running 24/7 (no server needed)
# Install screen or tmux to keep it running after you disconnect
sudo apt install screen
screen -S mybot
python main.py
# Press Ctrl+A, then D to detach
# Bot keeps running in background!
# To re-attach later:
screen -r mybot
API Reference
Bot class
Bot(
token: str, # Required. Bot token from Developer Portal.
base_url: str = "https://chat.markanm.com/api/bot/v1",
timeout: float = 30.0, # HTTP request timeout in seconds.
webhook_secret: Optional[str] = None # Required for webhook verification.
)
| Method | Description |
|---|---|
@bot.command(name) |
Register a slash command handler |
@bot.on(event_type) |
Register an event listener |
bot.run_polling(interval=2.0) |
Start development polling loop |
bot.run(mode="polling") |
Alias for run_polling |
await bot.send_message(room_id, text) |
Send message to a room |
await bot.get_room(room_id) |
Fetch room details |
await bot.get_room_members(room_id) |
Fetch room members |
await bot.react(message_id, emoji) |
React to a message |
await bot.delete_message(message_id) |
Delete a message |
await bot.handle_webhook(body, signature) |
Verify + process webhook |
AI class
AI(
provider: str = "openai", # AI provider name
api_key: str = "", # API key (or list with one key)
model: Optional[str] = None, # Model name (uses provider default if not set)
base_url: Optional[str] = None, # Override API base URL
system_prompt: str = "...", # Bot's personality/instructions
timeout: float = 60.0, # Request timeout in seconds
)
| Method | Description |
|---|---|
await ai.generate(prompt, system_prompt=None) |
Stateless generation (no memory) |
await ai.chat(user_message) |
Multi-turn chat with memory |
ai.reset() |
Clear conversation history |
ai._history |
Access/set raw conversation history |
Exception Types
| Exception | When it's raised |
|---|---|
MarkanMError |
Base error class |
MarkanMAPIError |
Non-2xx HTTP response from API |
MarkanMAuthError |
401 — Invalid bot token or API key |
MarkanMPermissionError |
403 — Bot lacks permission |
MarkanMNotFoundError |
404 — Resource not found |
MarkanMRateLimitError |
429 — Too many requests |
MarkanMWebhookError |
Invalid webhook signature |
from markanm.exceptions import (
MarkanMError,
MarkanMAPIError,
MarkanMAuthError,
MarkanMPermissionError,
MarkanMNotFoundError,
MarkanMRateLimitError,
)
Changelog
v0.1.0a1 (current)
- ✅ Core
Botclass with command and event routing - ✅ Long-polling development mode
- ✅ Webhook support with HMAC-SHA256 signature verification
- ✅
AIclass with 13 provider integrations - ✅ Multi-turn
chat()with automatic conversation memory - ✅
CommandContextandEventwith.reply()method - ✅ Async/sync handler support
- ✅ Automatic
httpx/urllibfallback
Roadmap
- 📋 v0.2.0 — Message reactions, room management, file uploads
- 📋 v0.3.0 — Interactive card messages (buttons, menus)
- 📋 v1.0.0 — Stable API with full test coverage
License
MIT License — see LICENSE for details.
Built with ❤️ by the MarkanM Team
Metadata
Release files for markanm 0.1.0a2
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 |
|---|---|---|---|---|
| markanm-0.1.0a2.tar.gz | 74.4 kB | With provenance | Details for markanm-0.1.0a2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| markanm-0.1.0a2-py3-none-any.whl | Python 3 | none | any | Details for markanm-0.1.0a2-py3-none-any.whl |
Total release size: 110.2 kB
Release files / markanm-0.1.0a2.tar.gz
| Download URL | markanm-0.1.0a2.tar.gz |
|---|---|
| Size | 74.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
09b061ebd0846387b67603a649a6de2e82a127a48b58da8e3a1567d2c04548dc
|
|
BLAKE2b-256 checksum How to use checksums |
3ad7ceeff66bdf20761c03b763ba2d19fd87a71ce069c0ab39d07d855ef6e2d1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.
Transparency logRelease files / markanm-0.1.0a2-py3-none-any.whl
| Download URL | markanm-0.1.0a2-py3-none-any.whl |
|---|---|
| Size | 35.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
332816ea81827aba2748d7fad47b56153d65725a2c41f84505fdd797ef2024e2
|
|
BLAKE2b-256 checksum How to use checksums |
e6229eb6e2b5b3ae9bf128e50ccd0d2e1aef8c6d3f8b23422c1205f7c4d1ab9f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.
Transparency logSecurity for markanm 0.1.0a2
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.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for markanm-0.1.0a2.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.
Transparency logA 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.