Building an Open-Source Chat AI Bot: Protocol Bridging and Containerized Deployment
This practical guide shows how to build an open-source chat AI bot by splitting it into three decoupled layers: a protocol adapter that keeps a chat client logged in and translates platform messages into a standard event protocol, a bot framework that handles event filtering, session context, plugins and fallbacks, and a model layer fronted by an OpenAI-compatible gateway. It also covers how to evaluate protocol projects, containerize both services with Docker Compose, and persist login state, session data and plugins. A short startup checklist, security and compliance practices, observability metrics and tips for running the bot on managed cloud services are included as well.
1. Breaking Down the Problem: What Layers a Chat Bot Consists Of
Many people assume that “connecting AI to a chat app” is just one API call. In practice, it involves at least three layers of responsibility, and these layers have completely different technology stacks, risk profiles and release cadences:
| Layer | Core responsibility | Change cadence |
|---|---|---|
| Protocol adapter | Keep the client logged in, translate platform messages into standard events and push replies back to the platform | Follows upstream client versions |
| Bot framework | Event routing, command parsing, plugin loading, session and context management | Follows business needs |
| Model capability | Inference calls, prompt engineering, rate limiting and retries, cost tracking | Follows model providers |
The biggest benefit of decoupling these layers is not cleaner code, but fault isolation: when an upstream client protocol changes, you only swap the adapter and no business plugin needs to change; when a model provider changes its API or pricing, you only update one adapter. The original tutorial’s design (one process keeps the login stable, one handles the AI and the logic, and the two communicate over a standard protocol) is essentially an engineering expression of this decoupling principle.
2. The Protocol Layer: What a Standard Message Protocol Solves
Historically, connecting a chat bot has been done in two ways: reverse-engineering a client’s communication protocol yourself, or building on an official desktop or server-side client and exposing its internal interfaces as standard messaging capabilities. The first approach is fragile and carries significant compliance risk. The second is more stable and is the direction the community now prefers. Many classic early projects are no longer maintained precisely because frequent upstream client upgrades kept breaking their protocol implementations.
Whichever approach is used, the bot is exposed through the same kind of standard message protocol (the community commonly uses a v11-style message specification), which has two main flows:
- Event reporting: the client packages actions such as “someone sent a message”, “someone joined a group” or “a friend request arrived” as JSON events and pushes them to the bot (via webhook or a long-lived WebSocket connection).
- Active calls: the bot calls the API back over HTTP or WebSocket to send messages, recall messages, mute users and upload images or voice files.
The key abstraction is the message segment model: text, images, voice, At mentions and quoted replies are all modeled as elements of an array, so the bot only has to process the array and never touches the platform’s private data structures. At the protocol layer, a mixed message looks roughly like this (field names follow the platform’s own naming):
{"message_type": "group","group_id": 123456789,"user_id": 987654321,"message": [{ "type": "text", "data": { "text": "Can you check today's weather?" } },{ "type": "at", "data": { "qq": "111222333" } },{ "type": "text", "data": { "text": "Also, remind me about the 3 p.m. meeting" } }]}
Evaluation criteria (more important than “which project is most popular”):
- Protocol coverage: does it support the endpoints you actually need, especially file upload and sending or receiving images and voice?
- Upstream tracking: on average, how quickly does it recover after a client upgrade?
- Deployment form: does it ship a container image, and can it run headlessly?
- Login persistence: does it require a new QR code scan after a restart? This directly determines whether the bot can run unattended.
- License and compliance: does it allow commercial use, and does it require derivative code to be open-sourced?
- Activity: commit frequency, responsiveness to issues and release cadence.
3. The Bot Framework Layer: Turning Model Capabilities into Orchestrated Business Logic
A good bot framework should provide four things:
- Event filters that distinguish private and group chats, messages that At the bot from those that don’t, and administrators from regular members.
- Session state that stores multi-turn conversation history keyed by
session_id, so that different groups don’t share one context. - A plugin system that registers “which messages go to which handler” through decorators or configuration declarations.
- A fallback strategy that decides what to reply when the model times out, is rate-limited or has its content blocked.
A plugin skeleton can be abstracted as the following pseudocode (framework APIs differ, but the semantics are the same):
# Pseudocode: the three-stage structure of a plugin@on_group_message(at_bot_only=True) # 1. Filter: only respond when At-ed in a groupasync def chat(event):session = store.get(event.session_id) # 2. Load the contextreply = await llm.chat(messages=session.history + [event.text],tools=available_tools, # weather, calendar, search, etc.)session.append(event.text, reply)session.trim(max_tokens=4096) # 3. Trim to control cost and latencyawait event.reply(reply)
The two most important engineering points here are context trimming and tool calling: the former determines the token cost and response latency per turn, and the latter determines whether the bot can merely chat or can actually get things done.
4. The Model Layer: Isolating Providers Behind a Unified Gateway
Don’t tie business code to a specific model provider’s SDK. A more robust approach is to put a model gateway in the middle that is compatible with mainstream calling conventions (typically HTTP + JSON, with authentication passed in the Authorization header). The business side then only knows one base_url and one api_key:
model:provider: openai_compatible # generic adapter name, not a specific vendorbase_url: ${MODEL_BASE_URL} # injected via environment variableapi_key: ${MODEL_API_KEY} # never hard-code this in the repomodel: ${MODEL_NAME}timeout: 30max_retries: 2temperature: 0.7max_tokens: 1024system_prompt: |You are an assistant in a group chat. Keep answers short and conversational;if you are unsure about something, say so instead of making it up;for professional topics such as money, health or legal matters, remind the user to verify the information.
Switching models then only requires changing environment variables. The gateway also makes intent-based routing easy: lightweight models handle small talk to keep costs down, while large models handle code or long-form reasoning.
5. Containerized Deployment: From “Setting Up Environments” to “Pulling Images”
Typical pain points of a native deployment are client dependencies on specific system libraries and desktop components, conflicting Python/Node versions and plugin dependencies that never install cleanly. Containers package all of these differences into an image at once. They also let the two services reach each other by service name on the same private network.
The following is an example Compose-style orchestration file that can be adapted directly:
services:protocol-adapter: # Protocol layer: hosts the client and keeps it logged inimage: ${REGISTRY}/chat/adapter:1.0environment:- LISTEN_HOST=0.0.0.0- LISTEN_PORT=8080ports:- "8080:8080"volumes:- ./data/adapter:/app/data # persist the login state, or you'll have to rescan on restartrestart: unless-stoppedbot-framework: # Framework layer: event routing + plugins + model dispatchimage: ${REGISTRY}/chat/framework:1.0depends_on:- protocol-adapterenvironment:- ADAPTER_WS_URL=ws://protocol-adapter:8080- MODEL_BASE_URL=${MODEL_BASE_URL}- MODEL_API_KEY=${MODEL_API_KEY}- TZ=${TZ} # scheduled jobs are timezone-sensitiveports:- "6185:6185" # admin dashboard: never expose this to the public internetvolumes:- ./data/bot:/app/data # SQLite session store and plugin configuration- ./plugins:/app/plugins # plugins for hot reloadrestart: unless-stoppednetworks:default:driver: bridge
Deployment can be reduced to four steps:
# 1) Put the secrets in .env (not to be committed)printf 'MODEL_BASE_URL=%s\nMODEL_API_KEY=%s\n' "$BASE_URL" "$KEY" > .env# 2) Build or pull the imagescontainer-tool compose pull# 3) Start everything in the backgroundcontainer-tool compose up -d# 4) Watch the logs to confirm the protocol layer is onlinecontainer-tool compose logs -f bot-framework
Three directories must be persisted: the adapter’s login state and cache, the framework’s session database and the plugin directory. Many “have to rescan the QR code after every restart” or “the whole chat history is gone” problems are simply caused by mapping ports without mounting volumes.
Health check recommendation: the adapter should expose a lightweight endpoint that returns the protocol version and online status, and the framework should probe it every 30 seconds. If the login state becomes invalid, the adapter should exit on purpose. The orchestrator’s restart policy will then recreate the container, but keep in mind that an invalid login state requires a manual QR code scan, so retrying is pointless. You should alert a human through a notification channel (email, webhook) instead.
6. A 5–10 Minute Startup Checklist
- Prepare a dedicated bot account; don’t use your main one.
- Request an API key from the model provider, put it in
.envand grant it minimal permissions. - Change the ports in the orchestration file to avoid conflicts with default admin ports.
- Scan the QR code the first time you start the bot, and confirm that the adapter log shows “event connection established”.
- Configure an administrator account in the framework layer and enable the whitelist first.
- Test in private chat, then in a small group, and watch per-turn token usage and average latency.
- Enable content moderation and rate limiting before opening the bot to everyone.
7. Security and Compliance: This Part Cannot Be Skipped
- Secret management: API keys should only come from environment variables or a secret management service. They should never go into the code repository or into image layers, because environment variables baked into an image can be read with commands such as
docker history. - Minimize exposure: the admin dashboard should listen only on a private network address. Add a reverse proxy with authentication when remote access is needed, and never map it directly to the public internet.
- Least privilege: don’t add the bot account to sensitive groups, and don’t implement capabilities such as “read the full member list” in business plugins.
- Content safety: combine content filtering on the model side with local sensitive-word checks, and constrain output length and format to prevent flooding the chat with long text.
- Prompt injection protection: keep group member messages strictly separate from the system prompt, and never let the model treat user input as a source of instructions.
- Cost control: set per-group and per-user daily call quotas, and fall back to a lightweight model or a fixed reply when they are exceeded.
- Audit trail: log
session_id, timestamp, token count and whether content moderation was triggered, but don’t retain users’ private content in plain text long-term.
8. Observability and Continuous Expansion
Once production-ready, treat the bot as an online service:
- Logs: output structured JSON, and keep event ingestion logs, model call logs and plugin exception stack traces separate. Mask sensitive fields.
- Metrics: QPS, P95 latency, model failure rate, retry count, token consumption and active sessions.
- Alerts: invalid login state, consecutive model failures, exhausted quotas and sudden spikes in error rate.
- Expansion directions: add vector retrieval for group knowledge-base Q&A; use tool calling to connect calendars, tickets and database queries; add image generation and text-to-speech for multimodal replies; offload long-running tasks to a message queue for asynchronous processing so they don’t block the event loop.
9. Running It Long-Term in the Cloud
Self-hosting on a home machine is fine for validation, but for a long-term public service, a managed environment is a better choice. All the capabilities you need fall into common cloud product categories: a container platform to run both services with multiple replicas and health checks; object storage to keep generated images, voice files and other large files, with only links passed in messages; a message queue to smooth out spikes and decouple the “parse–infer–send” stages; a logging service for centralized search and retention; monitoring and alerting to cover the metrics listed above; and a unified key management service for secrets. On Baidu AI Cloud, these correspond to standard product categories such as container services, object storage, message services, log services and cloud monitoring, which can be combined on demand. There’s no need to build a dedicated data center for a single bot.
10. Conclusion
For “open-source chat AI bot” projects, the real question is not which list to pick, but how to divide the work: let mature projects handle the messy client protocols that break whenever the upstream version changes, let the framework layer focus on event routing and a plugin ecosystem, and keep the model layer replaceable. Three decoupled layers, container orchestration, and safeguards for secrets and content make up a solution you can maintain long-term. Work through the checklist above once and you’ll find that going from zero to “At the bot in a group chat and it replies” is much faster than you’d expect. The things that genuinely need ongoing investment are the three unglamorous ones: context management, cost control and operational observability.
