AI 程式設計教程中文版
從原理到實戰

02 · 一條訊息的旅程

拆解一條訊息從渠道進入 OpenClaw 到 Agent 回覆的完整鏈路,包括路由、session、佇列、上下文、工具和回覆分塊。

你在 Telegram、Discord 或 Slack 裡發一句:“幫我查一下伺服器狀態。”

表面上看,這是一次普通聊天。實際上,這條訊息進入 OpenClaw 之後,會經過渠道標準化、路由、session 定位、佇列、上下文組裝、模型呼叫、工具迴圈、回覆投遞等多個階段。

理解這條路徑的價值很直接:以後 Agent 不回覆、回覆重複、答非所問、群聊亂觸發、長回覆切壞,你知道該查哪一層,而不是隻說“AI 壞了”。

這一篇的核心不是背流程,而是建立排錯順序:入口、許可權、路由、session、佇列、context,再到模型和工具。

1. 全鏈路先看一遍

OpenClaw 官方訊息文件把高層流程概括成:inbound message → routing/bindings → session key → queue → agent run → outbound replies。展開後可以這樣理解:

flowchart TD
    Inbound[外部訊息] --> Channel[Channel 標準化]
    Channel --> Policy[DM / group / mention 策略]
    Policy --> Routing[Binding 路由]
    Routing --> Session[Session key]
    Session --> Queue[Queue / steer / followup]
    Queue --> Context[Context 組裝]
    Context --> Model[模型呼叫]
    Model --> Tools{需要工具嗎}
    Tools -->|是| ToolRun[工具執行]
    ToolRun --> Context
    Tools -->|否| Reply[回覆生成]
    Reply --> Chunk[Streaming / chunking]
    Chunk --> Outbound[發回原渠道]

這條鏈路裡,真正需要模型參與的主要是模型呼叫和工具迴圈。前面的路由、session、佇列,大多是確定性系統邏輯。多數“訊息沒到”問題,不是模型能力問題,而是入口、路由或許可權問題。

2. 八個階段

階段系統在做什麼常見故障
Channel 標準化把 Telegram、Slack、Discord 等平臺訊息轉成內部格式channel auth、賬號狀態、平臺 webhook
訪問策略判斷 DM、群組、mention、allowlist、pairing 是否允許觸發群聊不回、私信被拒絕
Binding 路由按 channel、account、peer、team、guild 等規則選 Agent發給了錯誤 Agent
Session 定位用 session key 找到對話桶和 transcript上下文串臺、歷史丟失
Queue 處理當前 session 忙時決定 steer、排隊、合併或中斷回覆延遲、重複回覆
Context 組裝把系統提示、workspace、記憶、歷史、當前訊息拼成輸入答非所問、忘記規則
Agent run模型推理,必要時呼叫工具,多輪迴圈卡住、超時、工具失敗
Outbound 投遞分塊、流式、執行緒回覆、發回原渠道長訊息切壞、發錯執行緒

新手排錯時不要從第 7 階段開始。先確認前 4 個階段:訊息有沒有進來、是否允許觸發、路由到哪個 Agent、session key 是哪個。

多數“Agent 沒反應”的問題,不在模型,而在訊息是否進來、是否被允許觸發、是否路由到正確 Agent。

3. Channel 標準化:先把平臺差異抹平

不同渠道的訊息格式完全不同。Telegram 有 chat id、message id、topic;Slack 有 team、channel、thread;Discord 有 guild、channel、author、thread。Gateway 不能讓後面的 Agent 分別理解每個平臺的原始格式。

Channel 層的職責是把外部訊息統一成 OpenClaw 能處理的內部訊息,並保留必要的路由後設資料:

  • sender / user id 主要影響 DM allowlist、pairing、session key。
  • group / room / channel id 主要影響群組策略、binding、session key。
  • thread / topic id 主要影響執行緒或 topic 隔離。
  • accountId 主要影響多賬號隔離和預設賬號選擇。
  • message id 主要影響去重和 reply threading。
  • text / media / reply context 會進入當前 prompt、附件和引用上下文。

這一步一般不需要 AI 判斷。它越確定,後面的行為越穩定。

4. 訪問策略:不是每條訊息都應該觸發 Agent

訊息標準化之後,系統先判斷這條訊息有沒有資格觸發 Agent:

  • 私信不回:看 DM policy、pairing、allowFrom。
  • 群裡不回:看 group policy、group allowlist。
  • 群裡隨便一句就觸發:看 mention gating 是否配置。
  • 多賬號訊息混亂:看 accountId 和 defaultAccount。
  • 執行緒回覆不對:看 thread / topic 解析和 replyToMode。

這一層的目標不是“儘量多回復”,而是“只在該回復的時候回覆”。OpenClaw 面向的是能呼叫工具的 Agent,入口預設應該收緊。

5. Binding 路由:模型不決定訊息給誰

OpenClaw 路由是確定性的。官方 channel routing 文件列出的匹配順序包括 exact peer、parent peer、Discord guild / roles、Slack team、account、channel、default agent 等。

flowchart TD
    Msg[標準化訊息] --> Exact[exact peer]
    Exact --> Parent[parent peer / thread]
    Parent --> Guild[Discord guild / roles]
    Guild --> Team[Slack team]
    Team --> Account[accountId]
    Account --> Channel[channel match]
    Channel --> Default[default agent]
路由條件適合場景
exact peer某個固定群、頻道、私信入口進指定 Agent
guild / rolesDiscord 伺服器按角色分流
teamIdSlack workspace 級分流
accountId同一渠道多個賬號分流
channel match所有 Telegram 或 Slack 入口統一進某 Agent
default agent沒匹配到規則時兜底

重要的是:路由不讓模型自由發揮。模型不應該決定“這條訊息交給誰”。如果這一步讓 AI 猜,系統就不可預測。

路由越確定,系統越可審計。AI 負責生成回覆,配置負責決定訊息歸屬。

6. Session key:找到正確的對話桶

路由選出 Agent 之後,還要決定這條訊息屬於哪個 session。Session 是對話上下文和併發控制的基本單位。

常見 session key 形狀:

  • Agent main session:agent:{agentId}:main
  • 群組:agent:{agentId}:{channel}:group:{id}
  • 頻道 / 房間:agent:{agentId}:{channel}:channel:{id}
  • Slack / Discord thread:在基礎 key 後追加 :thread:{threadId}
  • Telegram forum topic:在 group key 中包含 :topic:{topicId}

session key 解決兩個問題:

  • 上下文隔離:不同人、不同群、不同執行緒不要混在一起。
  • 併發控制:同一個 session 同一時間只允許一個 active run。

很多“Agent 怎麼把 A 群的上下文帶到 B 群了”這類問題,本質都要回到 session key 設計。

7. Inbound dedupe 和 debounce

現實裡的訊息入口不乾淨。網路重連、平臺重試、使用者分多條傳送,都會讓系統收到比你想象更多的訊息。

7.1 去重解決重複投遞

Channel 可能重複投遞同一條訊息。OpenClaw 會用 channel、account、peer、session、message id 等資訊做短期快取,避免同一訊息觸發多次 agent run。

如果使用者說“怎麼回了兩次”,先看訊息 id 是否重複進入、channel 是否重連、Gateway 日誌是否顯示重複 run。

7.2 防抖解決分條傳送

使用者經常這樣發:

帮我查一下
服务器状态
主要看 CPU 和内存

如果每一條都立刻觸發模型,系統會浪費三次呼叫,還可能因為第一條意圖不完整而答偏。messages.inbound.debounceMs 會把同一 sender 在短時間內的連續文本訊息合成一次 agent turn。

相關配置先記四個點:

  • messages.inbound.debounceMs:全域性防抖視窗,預設常見寫法是 2000ms。
  • messages.inbound.byChannel.whatsapp:WhatsApp 這類分條習慣明顯的平臺可以更長。
  • media / attachments:通常立即 flush,不和純文本一樣等待。
  • control commands:通常獨立處理,不應被普通訊息合併。

防抖不是越長越好。太短會重複處理,太長會讓使用者覺得系統慢。

8. Queue:Agent 正忙時怎麼辦

同一個 session 同時跑兩個 Agent turn 會破壞上下文一致性,所以 OpenClaw 用佇列保證同一 session 的執行被序列管理。

官方佇列文件裡,當前預設模式是 steer。常見模式可以這樣理解:

模式行為適合場景
steer把新訊息注入當前 run 的下一個模型邊界;不行就 fallback 到 followup使用者經常中途糾正方向
followup當前 run 結束後逐條處理每條訊息都應獨立響應
collect當前 run 後把等待訊息合併為一次 followup使用者會連續補充上下文
steer-backlog既嘗試 steer,也保留 followup需要即時性但要避免丟後續
interrupt中斷當前 run,處理最新訊息舊任務可拋棄的場景
queuelegacy one-at-a-time steering只為相容舊行為

如果你看到 Agent “等很久才回”,不一定是模型慢,也可能是 session 正在排隊。看 Gateway 日誌、openclaw status 和任務狀態,比盲目調 prompt 更有用。

9. Context 組裝:AI 看到的遠不止你那句話

當訊息真正進入 Agent run,系統會把多種上下文拼起來:

  • system prompt 定義執行規則、工具邊界、行為約束。
  • workspace bootstrap 包括 AGENTS.mdSOUL.mdTOOLS.mdUSER.md 等。
  • memory 提供長期事實、偏好、近期記憶。
  • session transcript 提供當前 session 歷史。
  • group pending history 提供群聊中未觸發 run 但可作為背景的訊息。
  • current message 是使用者這次真正要回復的內容。
  • reply / forward metadata 提供被引用訊息、轉發上下文。

你發的一句話,通常只是最終輸入包的一小部分。Agent 答非所問時,不要只看使用者訊息,要看 system prompt、workspace 檔案、歷史 transcript 和 memory 有沒有汙染或缺失。

flowchart TD
    System[System prompt] --> Prompt[模型輸入]
    Workspace[Workspace files] --> Prompt
    Memory[Memory / notes] --> Prompt
    History[Session history] --> Prompt
    Current[Current message] --> Prompt
    Prompt --> Model[Model]

理解 context 組裝,是理解“為什麼同一句話有時快、有時慢、有時答偏”的關鍵。

Agent 答非所問時,不要只改 prompt。先看本輪 context 裡混進了什麼、缺了什麼、歷史有沒有汙染。

10. Agent run:模型、工具和多輪迴圈

模型第一次收到 context 後,不一定馬上給最終回覆。它可能決定呼叫工具:

  1. 查狀態。
  2. 讀取日誌。
  3. 根據日誌再查程序。
  4. 再生成結論。

每次工具結果都會回到 context,模型再決定下一步。這就是 Agent loop。

常見現象可以先這樣判斷:

  • 一直轉圈:可能是工具執行慢、provider 慢、session 佇列未釋放。
  • 反覆呼叫同一個工具:可能是指令不清、工具結果不足、loop detection 觸發前仍在嘗試。
  • 工具失敗但 Agent 繼續:錯誤被當作工具結果回到模型,由模型決定替代路徑。
  • 上下文突然變大:可能是工具輸出太長、歷史太長、workspace 檔案太重。

工具失敗不是系統崩潰。失敗資訊也是上下文的一部分。問題在於 Agent 是否能從錯誤裡找到下一步。

11. Streaming、chunking 和最終投遞

模型生成結果後,OpenClaw 還要把回覆發回原渠道。這裡有兩個容易混淆的概念:

  • Block streaming:把已完成的文本塊作為正常 channel message 提前發出。
  • Preview streaming:在 Telegram、Discord、Slack 等支援的渠道上更新臨時預覽訊息。

官方 streaming 文件強調:今天的 channel message 不是直接 token delta 流。它更像“文本塊或預覽訊息的漸進更新”。

長回覆還要做 chunking。Chunking 會尊重平臺文本長度限制,並儘量不要切壞 Markdown,尤其是 fenced code。切分順序通常從最自然到最後兜底:

  1. paragraph。
  2. newline。
  3. sentence。
  4. whitespace。
  5. hard break。

如果長回覆在 Discord 或 Telegram 裡看起來很碎,先看 block streaming、chunk limits、coalescing 和 channel text limit,而不是先怪模型。

12. Silent reply:做了事但不打擾

OpenClaw 支援 silent reply。精確的 NO_REPLY / no_reply 表示不要發使用者可見文本。它適合內部 orchestration、群聊靜默、某些心跳或後臺事件。

但 silent reply 有邊界:

  • direct conversation 預設不應悄悄吞掉所有回覆。
  • 有媒體附件時,靜默文本可被去掉,但附件仍可能投遞。
  • group / channel 和 internal orchestration 的預設策略不同。

使用者沒看到回覆,不一定代表 Agent 沒跑。要結合 logs、tasks、session transcript 判斷。

13. 常見故障怎麼定位

  • 完全沒反應:優先查 Channel auth、Gateway 是否線上、DM/group policy、mention gating。
  • 發給錯 Agent:優先查 bindings、accountId、peer、default agent。
  • 上下文串臺:優先查 session key、thread/topic、dmScope、WebChat main session。
  • 回覆重複:優先查 inbound dedupe、平臺重投遞、steer-backlog、block/preview 雙路徑。
  • 使用者分三條導致三次回覆:優先查 inbound debounce 配置。
  • Agent 很慢:優先查 context 大小、provider、工具耗時、session queue。
  • 長回覆切得難看:優先查 chunk limit、block streaming、code fence、channel 限制。
  • 群裡不該回復卻回覆:優先查 group allowlist、mentionPatterns、activation gating。

按階段定位,比調 prompt 更可靠。

14. 給 Agent 的實踐任務

如果你已經有 OpenClaw 環境,可以讓本地 Agent 幫你檢查訊息路徑:

请只读检查 OpenClaw 消息管道:
1. 查看 openclaw status 和 gateway status,确认 Gateway 是否在线。
2. 读取 openclaw.json,列出 messages.inbound、messages.queue、bindings、channels 的关键设置。
3. 不打印任何 token、botToken、secret、auth profile。
4. 解释一条 Telegram 或 Slack 消息会路由到哪个 Agent,以及 session key 大概如何形成。
5. 如果当前配置有群聊入口,检查 allowlist、mention gating 和 pairing 边界。

沒有本地環境時,就讓 Agent 讀官方 messages、channel routing、queue、streaming 文件,按你的目標渠道生成一份排錯表。

15. 本章自檢

讀完這一章,你應該能回答:

  1. 一條訊息進入 OpenClaw 後,哪幾步不需要模型參與?
  2. Binding 路由為什麼必須是確定性的?
  3. session key 解決了哪兩個問題?
  4. dedupe、debounce、queue 分別解決什麼問題?
  5. Agent 答非所問時,為什麼要檢查 context 而不是隻改使用者 prompt?
  6. block streaming 和 preview streaming 有什麼區別?

16. 接下來去哪

17. 官方資料

本頁目錄