AIRI Satori Bot 集成指南:通过 Koishi 与 Satori 协议桥接 QQ、Telegram、Discord、Lark 多平台消息
【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi
本篇技术指南围绕 AIRI 仓库中的 Satori Bot 集成模块(integrations/satori-bot)展开,讲解如何通过 Koishi 的 server-satori 服务,将 AIRI 的 AI 智能体能力桥接到 QQ、Telegram、Discord、Lark 等聊天平台。读完本文,你将掌握 Satori Bot 的环境配置、启动方式、事件驱动的自治思考循环架构,以及消息从 WebSocket 进入到 LLM 决策、再到平台回复的完整调用链。
Satori Bot 是什么
Satori Bot 是 AIRI 仓库中的一个独立(STANDALONE)集成模块,定位为「基于 Satori 协议、事件驱动的 AI 智能体」。它本身不直接对接任何聊天平台的私有 API,而是通过Koishi 的 server-satori 插件作为统一桥接层,以一套协议同时连接多个平台。按照 integrations/satori-bot/docs/HANDLER.md 的说明,该模块运行在「事件驱动 + 自治循环」的混合模型之上:
- 事件层(Event Layer):处理原始 WebSocket 信号,负责去重与排队;
- 调度层(Scheduler Layer):消费事件队列,维护内部的「未读池(Unread Pool)」状态,并触发按频道隔离的处理循环;
- 规划层(Planner Layer):LLM 扮演 Agent,观察「未读池」与「历史动作(History Actions)」状态,自主决定下一步是
read_unread_messages(观察)还是send_message(行动)。
需要特别强调的是模块当前所处的过渡阶段:其src/core/下的 Event Loop、Scheduler、Planner 逻辑是临时占位实现,用于独立运行实验,模拟未来 AIRI Core 的行为,不应视为稳定的 AIRI Core 集成。而Dispatcher 与 Database 会被保留,未来将作为「工具型模块」暴露给 AIRI Core 执行动作与状态持久化;一旦主框架稳定,src/core/的循环/规划逻辑将被移除,本模块将重构为纯粹的Adapter(Satori 协议处理)与Capability Provider(动作提供)。当前的临时实现适合实验与维护用途。
前置条件
根据 integrations/satori-bot/README.md 与关联文档,运行 Satori Bot 需要满足以下条件:
| 前置条件 | 说明 |
|---|---|
| Node.js | >= 18.0.0 |
| pnpm | >= 8.0.0(且需从仓库根目录执行pnpm i安装依赖) |
| Koishi 实例 | 运行了server-satori插件的 Koishi 服务,Satori Bot 通过它连接各聊天平台 |
| LLM 服务 | 提供 OpenAI 兼容 API 的模型服务(如 Ollama、vLLM、DeepSeek 等) |
依赖统一通过 pnpm workspace 管理,包名为@proj-airi/satori-bot(见 integrations/satori-bot/package.json),其依赖包括@xsai/generate-text(LLM 文本生成)、@electric-sql/pglite与drizzle-orm(持久化)、valibot(配置与协议校验)、ws(WebSocket 客户端)等。
::: warning 凭据安全 Satori token、聊天平台凭据、模型 API Key 等敏感信息只能保存在本地的.env.local文件中,不要提交到版本库、截图或分享给他人。仓库的.gitignore已将该文件排除在版本控制之外。 :::
配置环境变量
配置的第一步是复制示例文件并编辑:
cp integrations/satori-bot/.env integrations/satori-bot/.env.local然后编辑integrations/satori-bot/.env.local,填写以下变量(示例文件):
# Satori Configuration SATORI_WS_URL=ws://localhost:5140/satori/v1/events SATORI_API_BASE_URL=http://localhost:5140/satori/v1 SATORI_TOKEN=your_satori_token_here # LLM Configuration LLM_API_KEY=your_api_key_here LLM_API_BASE_URL=https://api.openai.com/v1 LLM_MODEL=gpt-4 LLM_RESPONSE_LANGUAGE=English LLM_OLLAMA_DISABLE_THINK=false各变量含义与默认值
| 变量 | 必填 | 默认值 | 说明 |
|---|---|---|---|
SATORI_WS_URL | 是 | ws://localhost:5140/satori/v1/events | Koishi server-satori 暴露的 WebSocket 事件端点,用于接收实时事件 |
SATORI_API_BASE_URL | 否 | 由 WS URL 推导(/v1/events换成 HTTP 前缀) | Satori HTTP API 的基地址,用于发送消息等 REST 调用 |
SATORI_TOKEN | 否 | 空 | Satori 鉴权 token;若 Koishi 未开启鉴权可留空 |
LLM_API_KEY | 是 | 无 | LLM 服务的 API Key |
LLM_API_BASE_URL | 是 | https://api.openai.com/v1 | OpenAI 兼容 API 的基地址 |
LLM_MODEL | 是 | gpt-4 | 使用的模型名称 |
LLM_RESPONSE_LANGUAGE | 否 | English | 回复语言偏好 |
LLM_OLLAMA_DISABLE_THINK | 否 | false | 针对 Ollama 等推理模型的开关:为true时在请求中附加think: false禁用思考过程 |
DB_PATH | 否 | data/pglite-db | PGlite 数据目录路径 |
配置加载与校验机制
环境变量由 integrations/satori-bot/src/config.ts 统一读取,并用valibot定义ConfigSchema做运行时校验。核心要点:
SATORI_WS_URL缺省时回退到ws://localhost:5140/satori/v1/events,因此本地默认 Koishi 配置下甚至可以省略;parseBoolean会将字符串'true'/'1'解析为布尔值(大小写不敏感),用于LLM_OLLAMA_DISABLE_THINK;- 校验失败时,进程会打印每条
path: message形式的错误详情并以exit(1)退出,例如 LLM 相关变量缺失时能快速定位问题; - 在 integrations/satori-bot/src/planner/llm-client.ts 中,还会针对「API Key」关键字错误给出专门提示,引导检查
.env.local中的LLM_API_KEY、LLM_API_BASE_URL、LLM_MODEL。
启动 Satori Bot
配置完成后,在仓库根目录执行:
# 开发模式(热重载) pnpm -F @proj-airi/satori-bot dev # 生产模式 pnpm -F @proj-airi/satori-bot start从 package.json 的脚本定义可以看到,两种模式都是通过tsx加载环境变量后运行 src/index.ts:
"start": "tsx --env-file=.env --env-file-if-exists=.env.local src/index.ts", "dev": "tsx watch --env-file=.env --env-file-if-exists=.env.local src/index.ts"注意--env-file-if-exists=.env.local意味着.env.local中的值会覆盖示例.env的默认值——这正是「示例配置提交到仓库、真实凭据只留在本地」这一安全策略的实现基础。
启动流程(见 src/index.ts)依次为:
- 初始化 PGlite 数据库(
initDb,启动时自动执行 drizzle 迁移); - 创建
SatoriClient,注入wsUrl、token、apiBaseUrl; - 创建
BotContext,注册ready与message-created事件处理器; - 连接 Satori 服务器(
connect),向globalRegistry注册标准动作; - 启动周期性循环(
startPeriodicLoop)。
启动后可观察到 Debug 级别日志输出,方便验证「Connected to Satori server」「Periodic loop started」等关键状态。
架构与消息流:从 WebSocket 事件到 LLM 决策
入站阶段(Ingress):协议客户端与事件排队
位置:src/adapter/satori/client.ts → src/core/loop/queue.ts
SatoriClient是协议适配层(该部分将被永久保留)。它负责:
- WebSocket 连接管理:建立连接后发送
IDENTIFY信号(携带 token 与断线续传所需的sn序列号),并每 10 秒发送一次PING心跳; - 断线重连:连接断开后 5 秒自动重连,
shouldReconnect为false时才停止; - 协议校验:收到的每条原始 JSON 都用 valibot 的
SatoriSignalSchema、SatoriReadyBodySchema、SatoriEventSchema做严格校验,非法数据会被拒绝并记录错误; - 多平台 API 客户端:收到
READY信号后,根据ready.logins中每个{ platform, self_id }组合初始化独立的SatoriAPI客户端(key 为platform:self_id),发送消息时按此映射路由到对应平台; - 事件分发:
on()注册的事件处理器会被按类型触发,同时支持通配符'*'处理器,message-created事件由此进入消息处理链路。
随后在queue.ts的setupMessageEventHandler中:以channelId-messageId为 key 检查processedIds集合去重,将{ event, status: 'ready' }推入botContext.eventQueue内存队列,同时通过pushToEventQueue持久化到数据库,保证崩溃后可续处理。
消费与锚定(Consumption):调度器与未读池
位置:src/core/loop/scheduler.ts(onMessageArrival函数)→ src/core/session/context.ts
当系统处理锁空闲时,调度器从队列消费事件:
- 提取
channelId,调用ensureChatContext按需加载/创建该频道的ChatContext(以event.channel.id作为上下文主键); - 检查
selfId:如果发送者就是机器人自己,事件直接从队列移除并丢弃(不计入未读); - 将事件推入
botContext.unreadEvents[channelId]并持久化(pushToUnreadEvents),随后从持久化队列中删除(removeFromEventQueue); - 立即调用
loopIterationForChannel唤醒该频道的 Agent 循环。
推理阶段(Reasoning):LLM 决策
位置:src/core/loop/scheduler.ts → src/core/planner/llm-client.ts
LLM 的提示词设计核心是:不是让模型「回复这段文本」,而是让模型「基于状态决定下一步动作」。imagineAnAction构造的请求包含:
- System:注入
system-action-gen-v1(工具定义)与personality-v1(人格设定),来自 src/core/planner/prompts/ 下的.velin.md文件; - 短期记忆:注入
chatContext.messages最近对话轮次; - 全局状态(关键):提示词显式写出"You have X unread events."并逐频道列出未读数量;
- 入站注入:存在新流入事件时,以
Incoming events:块追加到提示词末尾; - 动作历史:注入
chatContext.actions,例如"Last action: read_messages, Result: Success",让 LLM 感知上次尝试的结果。
生成端使用@xsai/generate-text调用 OpenAI 兼容接口;LLM_OLLAMA_DISABLE_THINK=true时会在请求中附加think: false,随后清理<think>...</think>标签。返回文本会剥离 ```json 代码块围栏,用best-effort-json-parser容错解析,再经ActionSchema(valibot)校验,最终得到类似{"action": "read_unread_messages", "channelId": "..."}的严格 JSON 动作。
分发与执行(Dispatch):动作注册表
位置:src/core/dispatcher.ts → src/capabilities/registry.ts
系统根据 JSON 动作在globalRegistry中查找对应 Handler:
read_unread_messages(src/capabilities/actions/read-messages.ts):取回指定频道的全部积压未读事件,格式化为单一文本块(如[User]: Content)存入 Action Result,并清空该频道unreadEvents。下一个 Tick 中,LLM 会在 History Actions 里看到这段文本并生成回复;send_message(src/capabilities/actions/send-message.ts):执行前会再次检查未读池——如果生成期间有新消息到达,会中止发送并返回[INTERRUPT]结果,引导 LLM 优先read_unread_messages获取新上下文;通过后调用satoriClient.sendMessage按platform:selfId路由到对应平台,并将回复通过recordMessage持久化到数据库。
循环延续(Loop Continuation)
dispatchAction返回带shouldContinue标志的ActionResult:若为true(通常在读完消息、期待回复时为真),调度器等待LOOP_CONTINUE_DELAY_MS(默认 2.5 秒)后递归调用handleLoopStep。为防止 LLM 幻觉或 API 滥用导致死循环,每轮循环设有硬上限MAX_LOOP_ITERATIONS = 5;达到上限、模型选择终止动作或shouldContinue为false时循环结束。
持久化与状态恢复:PGlite + Drizzle
根据 integrations/satori-bot/docs/PERSISTENCE.md,模块已从 lowdb(JSON 文件)迁移到PGlite(PostgreSQL 的 WASM/Node 实现)+Drizzle ORM,采用「内存优先 + 关键数据落盘」策略:
- 内存侧:活跃聊天上下文存放在
BotContext中的原生Map<string, ChatContext>,通过ensureChatContext懒加载,进程结束前常驻;在handleLoopStep中对单频道做裁剪(MAX_ACTIONS_IN_CONTEXT = 50、ACTIONS_KEEP_ON_TRIM = 20),消息历史动态从数据库取最近 10 条,保持 LLM 上下文精简; - 磁盘侧(src/lib/schema.ts):
channels(频道元数据)、messages(持久消息日志,按channel_id、timestamp建索引)、event_queue(待处理事件持久队列)、unread_events(各频道未读事件存储)四张表; - 增量 I/O:队列项按 ID 单独
pushToEventQueue/removeFromEventQueue,未读按频道pushToUnreadEvents/clearUnreadEventsForChannel,避免旧版「整体重写」的开销; - 恢复能力:
eventQueue与unreadEvents完全持久化,崩溃后重启可从未处理处继续;对话历史可从messages表重建,保证跨重启的连续性; - 迁移管理:drizzle 迁移文件位于 integrations/satori-bot/drizzle/,由
drizzle-kit管理(pnpm -F @proj-airi/satori-bot db:generate/db:push),启动时在 src/lib/db.ts 自动应用。
运维注意事项
- 凭据隔离:
SATORI_TOKEN、各平台 token、LLM_API_KEY等敏感值只写入integrations/satori-bot/.env.local。该文件已列入.gitignore,不要提交,也不要将内容截图或发送给任何人。 - 过渡期定位:
src/core/中的循环与规划逻辑是临时模拟实现,仅供实验与维护;真正的长线资产是src/adapter/satori/(协议适配)与src/capabilities/(动作提供),未来将作为 Adapter 与 Capability Provider 挂接到 AIRI Core。 - 故障排查:使用
pnpm -F @proj-airi/satori-bot dev启动后观察日志:若出现「Configuration validation failed」请检查.env.local必填变量;若 LLM 调用报 API Key 错误,优先核对LLM_API_KEY与LLM_API_BASE_URL;若收不到事件,确认 Koishi 的server-satori已开启且SATORI_WS_URL端口一致。 - 循环保护:默认单轮最多 5 次迭代、发送前再次校验未读,这两个机制共同防止了「模型自说自话」与「忽略新消息强行回复」两类典型故障。
如需深入了解,可继续阅读模块内文档:HANDLER.md(事件到动作的完整流转)、PERSISTENCE.md(记忆与持久化策略)、EVENT.md 与 PROMPTS.md,以及人格与系统提示词源文件 integrations/satori-bot/src/core/planner/prompts/。
【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考