人工智能AI AgentMCP 服务工具调用GUI 自动化语音数字人交互助手【免费下载链接】airi 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器目标是接近 Neuro-sama 的高度支持实时语音聊天、Minecraft 和 Factorio 游玩支持 Web / macOS / Windows。项目地址https://gitcode.com/moeru-ai/airi点击查看免费下载本篇技术指南以 Satori Bot 待办事项文档 为骨架系统梳理 AIRI 项目中独立运行的 Satori Bot事件驱动 AI Agent在稳定性、并发架构、逻辑链完整性与类型安全四个层面的工程演进既回顾了已归档落地的 P0–P2 优化成果竞态条件修复、Channel 级锁、非阻塞调度、智能 Action 截断、硬性循环上限等也明确了仍待推进的 P1/P2 规划Action Summary 压缩、Trace 日志、as any收尾。读完你将掌握这套Mini-Core事件队列 → 调度器 → LLM Planner → Action Dispatcher → 数据库持久化在真实多平台场景下的关键设计决策与实现细节。背景Satori Bot 在 AIRI 中的定位AIRI 的 Satori Bot 位于 integrations/satori-bot是一个STANDALONE、事件驱动的 AI Agent通过 Satori Protocol 与 Koishi 桥接连接 QQ、Telegram、Discord、Lark 等多个聊天平台内置一个自主思考循环autonomous thought loop。按 README 的说明其src/core/部分是临时性的 Mini-Core用于模拟未来 AIRI 主框架 Core 的行为Dispatcher 与数据库部分会被保留并作为类工具模块暴露给 AIRI Core。因此 todolist.md 中记录的大部分优化本质上是在为一个未来会被替换的临时核心做严谨的工程收敛其并发与持久化经验对最终架构同样有迁移价值。模块的关键代码路径循环与逻辑临时src/core/loop、planner、session、dispatcherAdapter保留src/adapter/satori/Satori 协议处理Capabilities保留src/capabilities/Action 执行核心链路总览一条消息如何被处理在深入各项优化之前先建立整体认知。消息处理的调用链可从 queue.ts 与 scheduler.ts 中还原事件进入setupMessageEventHandler监听message-created事件先以${channel.id}-${message.id}生成messageId通过botContext.processedIds去重再将事件写入 Drizzle 的event_queue表pushToEventQueue随后推入内存eventQueue。队列消费onMessageArrival以isQueueConsumerRunning标志保证单消费者逐条取出队列头部消息立即执行协议侧持久化recordChannel/recordMessage并把消息推入对应频道的unreadEvents。循环调度对每个频道调用loopIterationForChannel→handleLoopStep后者在循环内调用 LLM Planner 生成 Action再通过dispatchAction执行。状态持久化频道、消息、未读事件、事件队列全部落库 PGlitesrc/lib/db.ts进程重启后可恢复。todolist.md 中 P0 的三项成果正是围绕这条链路中最容易出问题的三个点展开竞态清理、锁粒度、阻塞调度。P0 归档核心稳定性与并发架构1. 修复短时记忆清理的竞态条件基于 ID 的精准删除问题本质旧实现清理短时记忆未读事件时如果采用按频道整体覆盖/清空的方式在并发场景下会误删处理期间新到达的事件导致异步消息丢失。落地验证read_unread_messagesActionread-messages.ts如今采用基于 ID 的精准删除先快照将要读取的事件 ID 集合const readEventIds unreadEventsForThisChannel.map(item item.id)内存侧只移除readEventIds中命中的条目botContext.unreadEvents[channelId] ...filter(item !readEventIds.includes(item.id))因此处理期间新到达的事件被完整保留数据库侧调用deleteUnreadEventsByIds(channelId, ids)其实现db.ts使用 Drizzle 的inArray(unreadEvents.id, ids)做定点批量删除而非全表清空。这套快照 ID → 差值过滤 → IN 删除的模式是典型的口令式精准清理可有效杜绝异步覆盖丢消息。2. 消除全局锁死锁锁粒度下放到 Channel 级别问题本质若使用单一全局锁保护所有频道的处理多频道并发时容易出现互等死锁且单个频道处理慢会拖垮全局吞吐。落地验证锁被下沉到ChatContext 的isProcessing布尔字段types.ts每个频道各自持锁互不阻塞。释放锁统一放在finally中强制完成例如onMessageArrival中触发频道处理的自调用异步函数chatCtx.isProcessing true ;(async () { try { await loopIterationForChannel(botContext, satoriClient, chatCtx, currMsg.event) } catch (err) { botContext.logger.withError(err as Error).withField(channelId, chatCtx.channelId).log(Error in channel-specific loop) } finally { chatCtx.isProcessing false // try...finally 强制释放锁 } })()同样的isProcessing检查也出现在周期性循环中scheduler.ts若频道正在处理周期性任务直接跳过避免重复进入。try...finally保证了无论成功、异常还是中断锁最终都会被释放这是消除死锁残留的关键手法。3. 实现非阻塞调度消息到达与周期性任务并发问题本质早期实现对每条消息都串行await完整循环导致高流量下吞吐低下、后续消息被排队阻塞。落地验证调度被拆成两条并发路径即时反应路径onMessageArrival中处理频道时使用自调用 async 函数fire-and-forget不 await 完整循环让队列消费者可以立即继续处理其他频道scheduler.ts。周期性路径startPeriodicLoop启动的递归setTimeout定时器每隔PERIODIC_LOOP_INTERVAL_MS 60 * 1000constants.ts扫描所有有未读事件的频道对每个频道并行启动后台处理;(async () {...})()并跳过正在处理的频道。两条路径共享isProcessing这把频道级锁来避免同一频道的处理互相打架但不同频道之间、即时与周期任务之间都能并发执行。P1 归档逻辑链完整性与体验优化4. 智能 Action 截断trimActions自动回溯问题本质LLM 上下文中的 Action 历史不能无限累积粗暴截断会破坏成对逻辑链——典型场景是send_messagecontinue若截断后恰好以continue开头LLM 将失去该continue所承接的前置动作语义导致上下文失忆。落地验证trimActions(actions, max, keep)utils.ts实现了回溯式截断let startIndex actions.length - keep // Backtrack to avoid starting with a continue action which lacks its previous context while (startIndex 0) { const currentAction actions[startIndex].action if (currentAction.action continue) { startIndex-- } else { break } } return actions.slice(startIndex)即先保留最近keep条若保留段的起点恰好是continue则继续向前多保留一条直到起点不是continue为止确保 LLM 看到的动作序列语义完整。其在循环中的调用点为chatCtx.actions trimActions(chatCtx.actions, MAX_ACTIONS_IN_CONTEXT, ACTIONS_KEEP_ON_TRIM)scheduler.ts。相关常量MAX_ACTIONS_IN_CONTEXT 50、ACTIONS_KEEP_ON_TRIM 20constants.ts。5. 阻断 API 滥用MAX_LOOP_ITERATIONS 5硬性上限问题本质Action 的shouldContinue语义见continue/send_message等 Action 返回值几乎都返回shouldContinue: true允许 LLM 连续自主思考多步。若无上限LLM 可能陷入无限循环持续消耗 API 配额。落地验证handleLoopStep的 while 循环在每次迭代前检查if (iterationCount MAX_LOOP_ITERATIONS)达到 5 次即记日志并breakscheduler.tsMAX_LOOP_ITERATIONS 5定义于 constants.ts。此外每轮循环之间还有LOOP_CONTINUE_DELAY_MS 2500的间隔await new Promise(r setTimeout(r, LOOP_CONTINUE_DELAY_MS))既防止 LLM 连续空转也给新事件打断提供时间窗口。P2 归档类型安全与基础设施6. 修复 Satori API 类型不匹配待办中记录的使SatoriMessageCreateResponse的接口定义与运行时 Schema 保持一致从源码看已形成类型定义 运行时校验双份对齐的规范接口侧SatoriMessageCreateResponsetypes.ts定义了id、content?、channel?、guild?、member?、user?、created_at?、updated_at?等可选字段Schema 侧SatoriMessageCreateResponseSchema定义于 schema.ts消费侧SatoriAPI.sendMessage对/message.create的响应执行v.parse(v.array(SatoriMessageCreateResponseSchema), response)api.ts在运行时用 valibot 强制校验并丢弃多余字段使内存中的对象与 Schema 严格一致。7. 移除全局暴力退出问题本质早期在process.on(unhandledRejection)中直接process.exit(1)任何一次未捕获的 Promise 拒绝都会导致整个 Bot 进程粗暴退出、丢失内存状态。落地验证当前入口 index.ts 中unhandledRejection与uncaughtException处理器都只记录结构化错误日志含cause字段不再调用process.exit让进程有机会继续存活、等待重启或由上层守护进程接管。只有在main()自身致命失败时才exit(1)index.ts。8. 消除 Any 类型滥用待办提及修复了LLM 解析、数据库 Schema 等多处的类型退化。从源码看如今这些路径都有严格的类型边界LLM 解析imagineAnAction先用best-effort-json-parser的parse解析 LLM 原始输出再通过v.parse(ActionSchema, actionToValidate)做 valibot 校验llm-client.tsActionSchema是continue/break/sleep/list_channels/send_message/read_unread_messages六个判别式 uniontypes.ts。数据库 Schemasrc/lib/schema.ts用 Drizzle 的pgTable定义了channels、messages、eventQueue、unreadEvents四张表字段类型明确json事件体、bigint时间戳、text主键等并给messages建立了channel_timestamp_idx索引用于按频道检索最近消息。Dispatcher 层dispatchAction对 action payload 同样先v.safeParse(ActionSchema)再执行非法 payload 直接返回系统错误而非类型崩溃dispatcher.ts。9. 重写队列持久化 I/ODrizzle 增量更新模式问题本质旧版持久化对队列采用全量覆盖写先清空整表再整体写入在队列频繁变动时会放大磁盘 I/O且清空与写入之间窗口期的崩溃会导致数据丢失。落地验证现在的事件队列持久化db.ts全面改为增量更新入队pushToEventQueue生成nanoid()后单条insert出队removeFromEventQueue(id)按主键delete批量消费时逐条shift()removeFromEventQueue(currMsg.id)仅在极端兜底场景才使用saveEventQueue全量重写注释中明确注明best way is NOT to use saveEventQueue for single items。未读事件侧同样有对应的增量 APIpushToUnreadEvents插入、deleteUnreadEventsByIdsinArray定点删除见上文 P0-1、clearUnreadEventsForChannel按频道清理以及进程启动时的loadEventQueue/loadUnreadEvents恢复机制。整个 Bot 采用PGlite嵌入式 PostgreSQL Drizzle ORM数据库路径默认data/pglite-dbconfig.ts。待办规划P1 架构完善与 P2 增强P1重构 Action 截断逻辑防止上下文失忆策略层todolist.md 中当前唯一的 P1 待办是进阶策略将较旧的actions压缩为文本 Summary存入 LLM Context而非直接丢弃。这比现阶段的trimActions回溯截断更进一步——不满足于保住成对逻辑链而是让被裁掉的历史以摘要形态继续参与 LLM 推理。结合 llm-client.ts 可见目前 Action 历史是以- Action: {json}, Result: {json}的文本行注入用户消息的因此该规划的落点大概率是生成历史摘要文本替换尾部被截断的部分并与MAX_ACTIONS_IN_CONTEXT/ACTIONS_KEEP_ON_TRIM的阈值协同。P2监控增强与类型收紧Trace 日志为所有 Action 执行增加更详细的 Trace 日志。当前dispatchAction仅在执行时记录一条debug级日志log.withField(action, ...).debug(Executing action)dispatcher.ts异常时记录error级日志。规划方向是在 Action 的execute前后记录更细粒度的输入输出、耗时与结果快照便于多平台、多频道场景下的问题追溯。类型收紧持续检查并消除残留的as any类型断言。从源码看llm-client.ts中仍有(req as Recordstring, unknown).think falsellm-client.ts这类为兼容 Ollamathink参数而做的类型放宽可作为收尾工作的起点。运行环境与配置如果你要在本地把这份代码跑起来验证上述行为README前置依赖Node.js ≥ 18.0.0、pnpm ≥ 8.0.0、运行server-satori插件的 Koishi 实例以及任意 OpenAI 兼容的 LLM 服务Ollama、vLLM、DeepSeek 等。安装pnpm install配置cp .env .env.local关键变量如下对应 config.ts 的 valibot 校验逻辑配置缺失或非法会在启动时打印错误并退出# Satori 配置 SATORI_WS_URLws://localhost:5140/satori/v1/events SATORI_API_BASE_URLhttp://localhost:5140/satori/v1 SATORI_TOKEN # 可选Koishi 未开启鉴权时可留空 # LLMOpenAI 兼容 LLM_API_KEYyour_api_key_here LLM_API_BASE_URLhttps://api.openai.com/v1 LLM_MODELgpt-4 LLM_RESPONSE_LANGUAGEEnglish LLM_OLLAMA_DISABLE_THINKfalse运行开发模式pnpm --filter proj-airi/satori-bot dev热重载生产模式pnpm --filter proj-airi/satori-bot start。提示词与数据人物与系统提示词位于src/core/planner/prompts/*.velin.mdPGlite 数据库位于data/pglite-db。小结从 todolist.md 的归档项可以看出AIRI Satori Bot 的稳定性建设遵循了一条清晰的工程路径先用频道级锁 try...finally消除并发死锁与资源泄漏再用基于 ID 的精准删除与 Drizzle 增量持久化堵住数据丢失接着用trimActions回溯截断与MAX_LOOP_ITERATIONS硬上限约束 LLM 的自主循环最后以 valibot 运行时校验全面收敛类型边界。这套Mini-Core的经验——尤其是锁粒度设计、差分清理与上下文完整性保护——正是未来 AIRI 主 Core 接管认知循环时可以直接迁移的资产。赞分享人工智能AI AgentMCP 服务工具调用GUI 自动化语音数字人交互助手【免费下载链接】airi 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器目标是接近 Neuro-sama 的高度支持实时语音聊天、Minecraft 和 Factorio 游玩支持 Web / macOS / Windows。项目地址https://gitcode.com/moeru-ai/airi点击查看免费下载相关推荐AIRI Satori Bot 状态持久化架构解析内存优先Memory-First与 PGlite Drizzle 可靠恢复AIRI Satori Bot 状态持久化架构解析内存优先Memory First与 PGlite Drizzle 可靠恢复 本文以 AIRI 仓库中人工智能AI AgentMCP 服务工具调用GUI 自动化语音数字人交互助手后端AIRI Satori Bot 结构化动作系统提示词解析JSON Action 驱动的自主循环实现AIRI Satori Bot 结构化动作系统提示词解析JSON Action 驱动的自主循环实现 导读 本文以 AIRI 仓库 integrations/s人工智能AI AgentMCP 服务工具调用GUI 自动化语音数字人交互助手后端Vibe终极语音转文字指南从零开始掌握高效转录技巧Vibe终极语音转文字指南从零开始掌握高效转录技巧 在数字化信息时代音频和视频内容无处不在但将语音内容转换为可编辑的文字却常常成为效率瓶颈。Vibe语音转人工智能语音音频桌面应用本地部署模型推理服务上一篇CityEngine规则文件集合开启城市设计与建筑模拟的新纪元下一篇HackBrowserData 深度解析Safari 数据存储架构与多配置档案提取指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考