利用QQ平台远程操控freecode_agent
(更新于 2026-06-02)教程:搭建 QQ × free-code 机器人
项目和本文的绝大部分使用deepseek完成。
本教程中安装的free-code可以脱离QQ使用
##目录结构
/home/user/ # 你的用户主目录
├── .claude/ # free-code 配置和会话(一键安装脚本自动生成)
│ ├── settings.json # DeepSeek API 配置
│ └── sessions/ # 对话上下文文件
│ └── *.jsonl
├────────── qq-bot/ # 机器人项目目录
│ ├── docker-compose.yml
│ ├── Dockerfile
│ ├── requirements.txt
│ ├── bridge.py # 粘合剂代码
│ └── config.yaml # 你的配置文件(可选)
│
└── free-code/ # (一键安装脚本产生的源码目录,可忽略)
整体架构
整个系统由三部分组成:
QQ 用户 ↔ [Napcat] ↔ [Python 粘合剂 (FastAPI)] ↔ [free-code 子进程]
- Napcat:把 QQ 协议转成 HTTP,负责接收 QQ 消息并回调给你的服务器,也负责接收你的指令给 QQ 发消息
- Python 粘合剂:核心逻辑。接收 Napcat 转发的消息,决定是回复指令(如”切对话”)还是启动 free-code 子进程处理
- free-code:AI 引擎。以子进程方式启动,每次消息启动一个独立进程,用
--resume session_id保持上下文连续
消息流向:
- 你在 QQ 上给机器人发消息
- Napcat 收到后,通过 HTTP POST 转发给 Python 粘合剂
- Python 粘合剂判断是普通消息 → 启动 free-code 子进程(
cli-dev --bare --print --output-format=stream-json) - free-code 流式输出每一行 JSON 事件
- Python 边解析边调用 Napcat 的 HTTP API 把结果推送到你的 QQ
- 你实时看到 AI 的思考、工具调用、执行结果
前置准备
- 一台 Linux 服务器(安装了 Docker)
- 一个 QQ 小号(用于机器人登录,不要用主号,有封号风险)
- 一个 API Key(DeepSeek 的 Anthropic 兼容接口,
api.deepseek.com/anthropic)
##第一步:安装 free-code【修改】
先跳转到/home/yourname目录cd /home/yourname
在 /home/yourname/ 目录下使用一键安装脚本执行了安装命令。curl -fsSL https://raw.githubusercontent.com/paoloanzn/free-code/main/install.sh | bash
安装完成后,验证是否成功:free-code --version
你应该能看到类似 v2.1.87-dev 的版本号。如果提示 command not found,请重新登录 SSH 或执行 source ~/.bashrc。
第二步:配置 API
free-code 默认连接 Anthropic 官方 API(api.anthropic.com),国内服务器无法访问。但它兼容 Anthropic 格式的第三方 API,所以只需改环境变量指向 DeepSeek 即可:
####方式一:临时环境变量(每次会话有效)
1 | export ANTHROPIC_BASE_URL="https://api.deepseek.com/anthropic" |
然后直接运行 free-code 即可。
####方式二:永久写入配置文件(推荐)
编辑 ~/.claude/settings.json(如果目录不存在,先创建):
1 | mkdir -p ~/.claude |
填入以下内容(注意 env 对象后的逗号不能少,否则会报 JSON 解析错误):
1 | { |
第三步:写 Python 粘合剂(核心)
这是项目核心,下面讲设计思路,完整代码下载放文末。
这个 Python 程序需要完成以下功能:
3.1 会话管理
因为 free-code 每次启动是独立进程,需要通过 --resume <session_id> 来延续上下文。Python 层需要维护一个映射表:每个 QQ 用户 → 他的多个会话 → 每个会话的 session_id 和编号。
数据用 JSON 文件存,结构大致:
1 | { |
关键设计点:
- 占位符机制:用户说”新对话”时,先创建一个带
__new__{编号}占位 session id 的记录。等他发了第一条真实消息,调用 free-code 拿到真实 session_id 后再替换。这避免了在创建阶段就调用 free-code 做无意义的初始化。 - 编号复用:如果删除了中间的某个会话(比如删了 #2),新的会话应该复用 #2 而不是跳到 #4。用
while number in used找最小可用编号即可。 - 并发锁:同一个用户同时发两条消息,如果不加锁会导致 session_id 错乱。用
asyncio.Lock让每个用户串行处理。
3.2 调用 free-code
这是技术难点。free-code 的 --print 模式支持 --output-format=stream-json,输出是 JSONL(每行一个 JSON 对象):
1 | {"type":"assistant","message":{"content":[{"type":"text","text":"让我看看..."},{"type":"tool_use","id":"toolu_xxx","name":"Bash","input":{"command":"ls"}}]}} |
处理流程:
- 用
asyncio.create_subprocess_exec启动 free-code 子进程,启动参数包含--permission-mode acceptEdits,自动批准所有操作(安全性由底层的 Docker volume:ro/:rw保证) - 将要问的消息通过 stdin 写入,然后关闭 stdin
- 循环
readline()逐行读取 stdout - 解析 JSON,
type == "assistant"时提取 text 内容和 tool_use 调用 type == "user"时提取 tool_result(命令执行结果)type == "result"时拿到最终回答和 session_id- 每解析出一个 step,立即通过 Napcat API 推送给 QQ,实现流式推送
注意 stderr 也要异步读取,否则 pipe 堵满会导致进程卡死。
3.3 指令系统
需要拦截的 QQ 消息:
| 用户发 | 行为 |
|---|---|
| 新对话 | 创建新会话,回复编号 |
| 切对话 | 列出当前用户所有会话的编号和第一句话 |
| 删对话 | 清空所有会话 |
| 回复数字 | 切换到对应编号的会话 |
| 其他消息 | 发给 free-code 处理 |
3.4 长消息分块
free-code 的回答可能会很长(比如执行结果几百行)。QQ 消息有长度限制,不能一次性发送。解决方案:按换行符分块,每块 1500 字符以内,优先在换行处断开,拆成多条消息逐条发送。
1 | def split_long_text(text, limit=1500): |
第四步:配置 Napcat
Napcat 用 Docker 启动,通过环境变量配置:
1 | services: |
关键点:
HTTP_POST_URLS是 Napcat 收到消息时回调的地址。bridge 容器也在同一个 docker compose 网络里,所以用http://bridge:5000/...(容器名作为 hostname)- 首次启动需要扫码登录,看日志
docker compose logs -f napcat
第五步:整体编排
完整的 docker-compose.yml:
1 | services: |
为什么要做只读/可写拆分? 通过 Docker 卷的 :ro 和 :rw 挂载权限来控制 AI 能修改的目录——所有目录只读,只有指定工作区可写。这比 free-code 内置的权限配置(settings.json 的 permissions 字段)稳定得多,因为 free-code 的权限系统只支持简单通配符(如 Read*、Edit(path/*)),不支持复杂正则,写错了会导致整个配置文件被跳过。
为什么 free-code 的 session 要用 volume 持久化? free-code 把 session 存在 ~/.claude/sessions/ 下。如果不挂载 volume,每次容器重建后这些文件就丢了,旧会话无法继续,会报 No conversation found。
第六步:启动这DS写的啥啊,怎么少这么多
Dockerfile
1 | FROM python:3.12-slim |
requirements.txt
1 | fastapi>=0.115.0 |
config.yaml
1 | allowed_qq: |
###启动前请注意,这些文件是需要修改的,请确保你进行了修改。
第七步:启动
1 | docker compose up -d |
看到二维码后扫码登录。之后你的 QQ 小号就会保持在线。
验证:
1 | curl http://localhost:5000/health |
然后用授权 QQ 号给机器人发消息测试。
使用效果
发一条消息,你会看到类似这样的流式推送:
1 | ● 我来看看服务器上有哪些项目... |
踩坑记录
Pydantic 422 错误 — Napcat 发来的 message 字段有时是字符串有时是数组
[{"type":"text","data":{"text":"你好"}}]。需要写@field_validator做兼容转换,并且设置model_config = {"extra": "ignore"}忽略 Napcat 的额外字段。默认 60 秒超时 — free-code 处理复杂问题可能超过 60 秒(比如分析整个项目)。Python 的
asyncio.wait_for默认超时需要改成 300 秒。Session 丢失 — 容器重建后 free-code 的 session 文件没了,所有旧会话报
No conversation found。解决方案是在 docker compose 里加一个独立 volume 挂载到/root/.claude。权限弹窗卡死 — free-code 遇到危险操作时会弹出交互式确认
? 允许执行此命令?(Y/n),但纯 QQ 对话环境无法回应,导致进程挂起。解决方案:启动参数加--permission-mode acceptEdits自动批准(安全性由 Docker volume:ro/:rw保证)。并发消息错乱 — 用户连续发两条消息,第二个子进程可能读到第一个的 session_id 导致覆盖。每个用户的
asyncio.Lock解决。会话编号重复 — 用了
max(s.number) + 1取编号,但中间删除会话后会产生重复。改成while number in used: number += 1。流式输出 — 用
--output-format=stream-json加readline()逐行读取,边解析边推送。长消息 — 按换行符分块发送,避免超出 QQ 消息长度限制。
free-code 权限配置 regex 不支持 — free-code 的 settings.json 里的 permissions 字段只支持简单的通配符(
Read*、Edit(path/*)),不支持负向前瞻这类复杂正则。用正则会导致整个配置文件被跳过,API 配置也不生效。正确的做法是通过 Docker volume 的:ro/:rw来控制文件系统访问,而不是依赖 free-code 的权限系统。
设计取舍说明
为什么用 HTTP 回调而不是 WebSocket? Napcat 同时支持 HTTP 和 WebSocket。HTTP 更简单——Napcat 把消息 POST 过来,处理完就返回,不需要维护长连接。Python 要主动发消息时,调用 Napcat 的 HTTP API 即可。
为什么每次消息启动独立进程? free-code 的 --print 模式就是为这种一次性调用设计的。每次启动新进程,用完就退,不用担心常驻进程的内存泄漏、进程挂掉、重启恢复等问题。上下文通过 --resume session_id 延续。
为什么用 DeepSeek 的 API? DeepSeek 兼容 Anthropic 的 API 格式(api.deepseek.com/anthropic),国内服务器可以直接访问,成本也比 Anthropic 官方低很多。只需改 ANTHROPIC_BASE_URL 就能切换,不需要改代码。
为什么用 Docker volume 控制权限而不是 free-code 的 settings.json? free-code 的权限配置语法受限(仅支持简单通配符),且写错了会导致整个配置文件被跳过。Docker 的 :ro/:rw 是操作系统级别的文件系统权限,稳定可靠,且与 AI 引擎无关——换任何 AI 引擎都能复用同样的权限策略。