
airi Satori 机器人提示词架构基于 Context-Injected Action Loop 的状态感知智能体实战指南【免费下载链接】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-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi本篇技术指南围绕 integrations/satori-bot/docs/PROMPTS.md 展开系统讲解 airi 项目中 Satori 机器人如何通过「状态感知智能体循环」State-Aware Agentic Loop而非简单的 Chat-QA 结构驱动对话行为。读者将掌握三层提示词架构静态层 / 历史层 / 动态状态层的设计思想、JSON 动作协议与 best-effort 解析容错机制并结合源码读懂每个提示词片段在实际循环中的生成与注入位置最终能够复现或改造一套不依赖原生 Function Calling 的纯提示词驱动 Agent 方案。一、总体架构为什么是「循环」而不是「一问一答」传统聊天机器人是「收到消息 → 生成回复」的单轮结构而 airi 的 satori-bot 实现的是文档中定义的State-Aware Agentic Loop状态感知智能体循环机器人每个循环 tickloop step都会动态重建一份完整提示词由 LLM 决定下一步动作——是回复消息、读取未读消息、还是挂起等待。整个循环由 src/core/loop/scheduler.ts 中的handleLoopStep驱动循环上限由MAX_LOOP_ITERATIONS默认 5 次约束防止死循环。这份提示词并非写死的静态文本而是由三个层次在运行时拼接而成层级来源作用注入位置静态层System Definitionsprompts 目录 下的*.velin.md定义「灵魂Soul」与「规则Rules」System Message历史层Short-term Memory会话上下文中的消息窗口提供对话连续性Context Body动态状态层Sensory Injectionllm-client.ts 运行时生成提供情境感知与事实锚定Grounding追加在上下文末尾的 User Message下面分别深入每一层。二、静态层系统定义Soul 与 Rules静态层是提示词的地基由 prompts/index.ts 中的personality()与systemPrompt()两个异步加载器提供底层通过 velin.ts 的velin()函数把.velin.md文件渲染为字符串——从实现看importVelin目前直接通过node:fs/promises的readFile读取文件原文路径解析依赖 path.ts 的relativeOf保留了未来接入真实 Velin 模板渲染引擎的扩展点。1. 协议定义system-action-gen-v1.velin.md文件 system-action-gen-v1.velin.md 硬编码了可用工具的 JSON 格式协议与逻辑流程是整套「动作循环」的规则手册包含四部分Available Actions工具清单——三个核心动作的完整 JSON Schema{ action: send_message, parameters: { channelId: ..., content: ... } }{ action: read_unread_messages, parameters: { channelId: ... } }{ action: continue }需要说明的是PROMPTS.md 文档中概括为send_message/read_unread_messages/sleep三个工具而当前仓库的实际实现中第三个动作为无参的continue停止当前动作循环、等待新消息。对照 src/core/types.ts 中的 valibotActionSchema实际注册的动作集合更完整还包括break、sleep可选duration参数、list_channels。Response Format响应格式——强制模型输出包含action、parameters、reasoning三字段的 JSON 对象。Guidelines行为准则——核心规则包括根据上下文选择最合适的动作为动作选择提供清晰 reasoning使用正确参数值channel ID 与内容必须准确生成消息内容时使用与用户相同的语言发送消息后若没有剩余未读消息必须使用continue等待用户回复严禁连续发送多条消息这条必须检查未读的逻辑流约束正是文档提到的关键点Persona 强制严格遵循用户上下文中定义的人设禁止退化成普通助手——If the persona is rude/lazy, be rude/lazy。Example Scenarios示例场景——给出四个典型决策路径用户提问 →send_message发现未读 →read_unread_messages发送完毕且无未读 →continue上一步已 send_message 且未读数为 0 →continue引导模型形成正确的循环退出条件。2. 人设personality-v1.velin.md文件 personality-v1.velin.md 定义了角色「吉川优子Yoshikawa Yuuko」——北宇治高中吹奏乐部高三小号手、现任部长。该文件用中文完整刻画了核心身份姓名、身份、外貌黄棕长发、米黄蝴蝶结性格特征直率情绪化、护短、傲娇、责任感关键关系对铠塚霙的保护欲、对伞木希美的竞争警惕、对中川夏纪的嫌弃吐槽、对香织前辈的花痴崇拜等九段关系设定说话风格口语化颜文字、波浪号、感叹号、情感鲜明、短促有力并附示例语句行为准则被戳一戳要大反应、不懂的事不强行解释、只在心情好时才帮忙。在 llm-client.ts 中systemPrompt()与personality()的渲染结果通过[await systemPrompt(), await personality()].join(\n\n)合并为同一个 System Message——这印证了文档「静态层定义 Soul 与 Rules」的定位。三、历史层短期记忆滑动窗口历史层为对话提供连续性。文档描述其机制为「最近约 20 条消息User/Assistant 轮次的滑动窗口紧跟在系统提示词之后注入」。对照实际实现以当前仓库为准scheduler.ts 的handleLoopStep在每个循环步通过getRecentMessages(chatCtx.channelId, 10)从数据库中取出最近 10 条消息按m.userId chatCtx.selfId判定角色映射为assistant/user得到LLMMessage[]后作为messages参数传入imagineAnAction。在llm-client.ts中这批消息通过message.messages(...)展开位于 System Message 之后——即「历史层注入到上下文主体」。值得注意的工程细节文档中「内存 messages 数组」的描述与当前实现略有差异实现上历史消息由 lib/db.ts 持久化并逐轮重取recordMessage在onMessageArrival中落库这意味着即使机器人进程重启短期记忆也能从数据库中恢复。窗口大小与裁剪策略集中在 constants.tsMAX_ACTIONS_IN_CONTEXT 50动作历史上限、ACTIONS_KEEP_ON_TRIM 20裁剪后保留条数、MAX_UNREAD_EVENTS 100单频道未读事件上限。四、动态状态层感官注入Sensory Injection这是整套架构最精巧的部分imagineAnAction会在上下文窗口的最末尾追加一条合成的 User Message强制 LLM 聚焦于此刻的现实。其构造代码位于 llm-client.ts各字段逐一对应文档的五个组成部分1. Incoming Stream实时流入来自调度器的globalStates.incomingEvents仅当有新事件时注入Incoming events: - [频道名/ID] 用户名/ID: 消息内容实现上按channel.name || channel.id、user.name || user.id、message.content逐条格式化无内容时显示[No content]。这个字段让 LLM 知道刚刚发生了什么是即时反应的触发源。2. Action History动作历史注入紧邻前序的工具执行结果帮助 LLM 形成观察-反思闭环History actions: - Action: {action:read_unread_messages,channelId:...}, Result: ...对应文档所说让 LLM 看到自己上一次尝试的结果——例如读取动作返回空它就应当停止。该历史来自chatCtx.actions由 dispatcher.ts 在每次动作执行成功后chatCtx.actions.push({ action, result })累积。3. Environment环境时间注入当前服务器时间Currently, its ${new Date()} on the server that hosts you.给 LLM 提供时区与时间锚点支撑晚上该道晚安这类时间敏感行为。4. Global State全局状态注入所有频道的未读消息计数汇总You have total N unread events. Unread events count are: Channel ID:xxx, Unread event count:5数据源是globalStates.unreadEventsRecordchannelId, StoredUnreadEvent[]由 context.ts 维护在BotContext上。这就是文档强调的Stateless Logic无状态逻辑每一轮都明确告诉 LLM你有 X 条未读消息让 LLM 成为读/回/睡的唯一决策者。5. Trigger最终指令整条 User Message 以两句话收尾Based on the context, what do you want to do? Choose a right action from the listing of the tools you want to take next. Respond with the action and parameters you choose in JSON only, without any explanation and markups.filter(Boolean)会剔除空段如无 incoming events 时保证注入紧凑干净。五、数据流总览文档给出了完整的端到端数据流原样保留配合源码可还原完整链路静态.velin.mdprompts 目录→ Velin 加载渲染 → System Message数据库历史消息 → 滑动窗口 → Context Body运行时状态未读/时间/动作结果→ 状态注入 → 末尾 User Message三者在imagineAnAction中汇成Final Prompt请求 LLM API返回的 JSON 经 dispatcher.ts 分发给 capabilities 中的动作处理器执行结果再回填到下一轮循环的 History actions 中。六、关键特征深度解析1. JSON EnforcementJSON 强制而非原生 Function Calling项目刻意不使用 OpenAI Tools 等原生函数调用 API而是完全依赖提示词工程强制模型输出裸 JSON。整条链路有三道防线提示词约束协议定义中反复强调 Your entire response should be parseable as JSON、Respond with ... in JSON only容错解析响应文本先剥离可能的think推理块与 json 围栏再由best-effort-json-parser的parse()兜底解析见 llm-client.tsSchema 校验解析结果交给 valibot 的v.parse(ActionSchema, ...)严格校验types.ts并做了参数展开兼容——若 LLM 把参数嵌套在parameters对象里会先扁平化再校验含channelId强制转字符串。dispatcher.ts中的v.safeParse二次校验失败时返回 Invalid action payload 并继续循环让 LLM 有机会自我纠正。这种提示词约束 宽松解析 严格校验的组合使其可以对接任意兼容 OpenAI 格式的 LLM 端点config.ts 中可配置LLM_API_KEY/LLM_API_BASE_URL/LLM_MODELollamaDisableThink可关闭 Ollama 的思考模式。2. Stateless Logic无状态逻辑机器人每一轮都显式注入你有 X 条未读消息流程控制权完全交给 LLM读未读还是直接回复、回复后继续还是continue都由模型基于注入状态自行决策。这带来的好处是无需在代码里硬编码对话状态机代价是依赖提示词的稳定性——这也是 constants.ts 中MAX_LOOP_ITERATIONS 5、LOOP_CONTINUE_DELAY_MS 2500、PERIODIC_LOOP_INTERVAL_MS 6000060 秒周期扫描仅处理有未读的频道避免无效 LLM 调用这些护栏存在的意义。3. Observation-Reflection观察-反思History actions 字段使 LLM 能在每个循环步看见自己上一动作的执行结果成功、失败、空结果从而调整策略。dispatcher.ts将执行失败的异常也包装为System Error: ...文本回填配合shouldContinue: true形成犯错 → 看到错误 → 修正动作的自我修正回路。七、动作执行与结果回填动作定义在 capabilities/definition.ts每个动作实现ActionHandler接口name/description/execute返回ActionResult含success/shouldContinue/result。实际能力包括send_message、read_unread_messages、system等见 capabilities/actions 目录。在handleLoopStep中dispatchAction的返回值shouldContinue决定循环是否继续继续则等待LOOP_CONTINUE_DELAY_MS后再进入下一轮且只有第一轮使用初始的 incoming eventcurrentIncoming undefined后续轮次仅依赖注入的未读状态与动作历史。调度器通过chatCtx.isProcessing加锁保证同一频道串行处理、不同频道并行机器人自身发送的消息sourceUserId chatCtx.selfId会被过滤不会回流成未读事件造成自我对话。八、运行配置要点基于 llm-client.ts 的错误处理分支可确认运行时依赖的环境配置LLM_API_KEYLLM 服务密钥配置缺失时报 LLM API Key Error提示检查.env.localLLM_API_BASE_URLOpenAI 兼容端点地址LLM_MODEL使用的模型标识ollamaDisableThink针对 Ollama 类端点关闭思考链输出并在生成后主动剥离think.../think内容。服务启动后周期性循环由startPeriodicLoop启动scheduler.ts事件入队、落库、触发即时反应等逻辑见onMessageArrival。相关说明文档还包括事件模型 EVENT.md、消息处理 HANDLER.md 与持久化 PERSISTENCE.md可与本文的提示词架构相互印证。综上airi satori-bot 的提示词架构是一条「静态规则打底、历史记忆续接、运行时状态锚定」的三层流水线以.velin.md静态文件定义灵魂与协议以滑动窗口维持对话连续性以合成的 User Message 完成感官注入最终通过JSON 强制 宽松解析 严格校验的容错管线把 LLM 稳定地约束在可执行的动作空间内。这套设计不依赖特定厂商的函数调用能力具有高度的模型与端点可移植性适合作为自托管、多平台机器人 Agent 循环的参考范式。【免费下载链接】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-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考