跪拜 Guibai
← All articles
AI Programming

A QQ AI Bot That Remembers, Listens, and Edits Feishu Docs: Full Deployment Walkthrough

By 偷偷摸鱼的少年 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Wiring chat, voice, and document tools into one agent means inheriting every layer's failure modes; this post names 17 of them with fixes, saving developers days of silent debugging.

Summary

On a single Ubuntu 24.04 server, the author stitches together four layers: NapCat handles the QQ protocol over OneBot v11, AstrBot orchestrates conversation and plugins, DeepSeek supplies the model and web search, and a custom DSH agent sub-process executes file and Feishu operations. Whisper locally transcribes voice, Edge TTS synthesizes a 'cute girl' reply, and a home-grown plugin gates who can wake the bot without @-mention. The result is a bot that remembers 700 group messages, recognizes voice even at -36 dB, and runs approval-gated agent tasks that can edit Feishu cloud docs via lark-cli.

The write-up is a troubleshooting log as much as a tutorial. The author documents 17 specific failures, from Docker Hub timeouts in China to a DeepSeek 422 caused by undeclared `modalities`, Whisper drifting to Swedish on low-volume audio, and the STT pipeline replacing voice segments with plain text so TTS never triggers. Each fix is concrete, with code snippets and configuration JSON. The final architecture binds all services to an internal IP, uses a dedicated QQ account to limit ban damage, and separates LLM keys for chat and agent work.

Takeaways
— Use `docker.m.daocloud.io/` prefix and retag to pull images without restarting the Docker daemon on a production server.
— AstrBot's provider `modalities` field must be explicitly set to `["text","image","tool_use"]`; otherwise it defaults to all modalities and DeepSeek returns 422 on audio.
— QQ voice clips average -36.9 dB, so Whisper often returns empty strings; ffmpeg `loudnorm` normalization to -16 LUFS fixes recognition.
— Whisper auto language detection goes wrong on quiet audio; forcing `language="zh"` and an `initial_prompt` prevents drift to Swedish or English.
— NapCat requires a fresh QR scan on every restart; keep the container running and only restart AstrBot.
— To let only the owner bypass @-mention, set `wake_prefix` to empty and write a plugin that calls `should_call_llm(False)` for non-whitelist senders without @ or name keywords.
— DSH (DeepSeek Harness) in `headless` mode runs one prompt to stdout and exits, which suits a sub-process call pattern.
— Install lark-cli (Feishu) and use its device code flow to authorize without a browser on the server.
— Bind all public ports to the internal IP (`192.168.0.70`), not `0.0.0.0`.
— Use a dedicated QQ account because automation violates Tencent ToS and risks bans (`KickedOffLine`, `serverErrorCode: 168`).
Conclusions

The hardest engineering problem in this build is not model choice but making the voice pipeline robust to the extreme variation in QQ audio (loudness, language drift); the same principle applies to any production voice agent.

The author treats the QQ account as disposable because Tencent's ToS risk is real, and this shapes operational decisions like never restarting NapCat to avoid a re-login.

Using a sub-process agent with a whitelist and approval flow is a conservative, controllable way to give an LLM real side-effecting tools; it's a pattern worth copying for non-critical automation.

Concepts & terms
OneBot v11
An open standard for QQ bot communication, typically via WebSocket, that decouples the protocol adapter (like NapCat) from the bot framework.
DSH headless
DeepSeek Harness's `--profile headless` mode runs a single task in a sub-process and prints the result to stdout before exiting.
lark-cli
The official Feishu command-line tool that wraps document, drive, and calendar APIs, with an OAuth device code flow for headless server authorization.
modalities
A provider config field that declares which input types (text, image, audio, tool_use) a model supports; leaving it unset in AstrBot defaults to all, causing 422 errors when audio reaches a text-only model.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗