行引擎深度拆解:為什么Runner決定Agent能否穩(wěn)定干活)
很多人第一次接觸 OpenClaw Agents 的時(shí)候注意力都放在模型對(duì)話、Skill 調(diào)用這些看得見(jiàn)的功能上很少有人去關(guān)注執(zhí)行引擎。但我把src/agents/pi-embedded-runner/這個(gè)目錄從頭到尾過(guò)了一遍之后我的結(jié)論很直接整個(gè) Agent 能不能穩(wěn)定干活靠的全是這一層。模型負(fù)責(zé)想Runner 負(fù)責(zé)轉(zhuǎn)工具負(fù)責(zé)做而 pi-embedded-runner 就是中間那個(gè)承上啟下的傳動(dòng)軸。如果你正在嘗試部署 OpenClaw、自己寫(xiě) Skill或者被各種AI 聊天正常但一執(zhí)行任務(wù)就翻車(chē)的問(wèn)題困擾這篇文章就是你排查問(wèn)題的路線圖。我會(huì)從設(shè)計(jì)邏輯拆到實(shí)際部署把我自己跑通和踩坑的過(guò)程一起放進(jìn)來(lái)。1. 為什么說(shuō)執(zhí)行引擎是 Agent 的大腦中樞1.1 從能聊天到能干活的關(guān)鍵一躍你可以把純粹的 LLM 聊天想象成一個(gè)特別能說(shuō)的朋友——你問(wèn)他任何問(wèn)題他都能給你一個(gè)語(yǔ)法正確、邏輯自洽的回答但他不會(huì)幫你關(guān)燈、不會(huì)幫你查數(shù)據(jù)庫(kù)、不會(huì)幫你把文件從 A 目錄挪到 B 目錄。Agent 要做的恰恰是這最后一步讓 AI 的想法變成真實(shí)的動(dòng)作。在這個(gè)轉(zhuǎn)變里執(zhí)行引擎Runner承擔(dān)的角色非常像操作系統(tǒng)里的進(jìn)程調(diào)度器。聊天場(chǎng)景下模型輸出一段文本就結(jié)束了但 Agent 場(chǎng)景下模型的輸出會(huì)被解析成調(diào)用哪個(gè)工具、傳什么參數(shù)、期望什么返回的結(jié)構(gòu)化指令。Runner 拿到這個(gè)指令之后要去查工具注冊(cè)表、做參數(shù)校驗(yàn)、執(zhí)行真實(shí)調(diào)用、把結(jié)果重新塞回上下文然后再交給模型進(jìn)行下一輪判斷。我見(jiàn)過(guò)不少剛上手的朋友有個(gè)誤區(qū)以為只要把模型 API Key 配好Agent 就能自動(dòng)聰明起來(lái)。實(shí)際完全不是這樣。你給模型再?gòu)?qiáng)如果執(zhí)行引擎不會(huì)解析工具調(diào)用、不會(huì)管理多輪上下文、不會(huì)處理工具異常那 Agent 就只是一個(gè)看起來(lái)很忙的聊天機(jī)器人任務(wù)稍微復(fù)雜一點(diǎn)就開(kāi)始空轉(zhuǎn)。1.2 一個(gè)典型的思考-行動(dòng)循環(huán)長(zhǎng)什么樣為了把執(zhí)行引擎的作用說(shuō)清楚我先拋出一個(gè)最小閉環(huán)。一個(gè)普通的多步任務(wù)在 Agent 內(nèi)部通常是這樣流轉(zhuǎn)的接收用戶目標(biāo)比如幫我查一下當(dāng)前目錄里所有文檔中提到了哪些 API 配置項(xiàng)整理成表格。模型規(guī)劃LLM 把這個(gè)大目標(biāo)拆成子步驟比如第一步列出當(dāng)前目錄文件第二步逐個(gè)讀取文檔內(nèi)容第三步篩選 API 配置項(xiàng)第四步生成表格。生成工具調(diào)用指令模型不直接執(zhí)行操作而是輸出一段結(jié)構(gòu)化的調(diào)用意圖例如list_files(directory.)。執(zhí)行引擎調(diào)度Runner 解析這個(gè)意圖找到注冊(cè)表中對(duì)應(yīng)的工具函數(shù)做參數(shù)校驗(yàn)然后真正調(diào)用。結(jié)果回填工具返回的文件列表被寫(xiě)入上下文模型看到真實(shí)結(jié)果后決定下一步動(dòng)作。循環(huán)直到目標(biāo)完成或達(dá)到上限整個(gè)過(guò)程可能經(jīng)歷很多輪每輪都是模型輸出調(diào)用意圖 → Runner 執(zhí)行 → 結(jié)果回填 → 模型再輸出。你有沒(méi)有發(fā)現(xiàn)這套流程里最容易出問(wèn)題的環(huán)節(jié)恰恰不是模型而是第 4 步和第 5 步。工具調(diào)用意圖解析錯(cuò)了、參數(shù)類(lèi)型不匹配、返回值太長(zhǎng)把上下文撐爆、工具拋異常沒(méi)有捕獲任何一個(gè)環(huán)節(jié)出問(wèn)題整個(gè)任務(wù)鏈條就斷了。pi-embedded-runner 存在的意義就是把這套流程做成一個(gè)穩(wěn)定、可控、可觀測(cè)的基礎(chǔ)設(shè)施。1.3 pi-embedded-runner 在 OpenClaw 里的定位從名字可以拆出三層信息。pi在 OpenClaw 的語(yǔ)境里通常指代 Personal Intelligence也就是面向個(gè)人助理場(chǎng)景的智能核心embedded表示它是進(jìn)程內(nèi)嵌入運(yùn)行的不是獨(dú)立部署的微服務(wù)runner則是執(zhí)行調(diào)度器的意思。合起來(lái)它的職責(zé)很清楚作為嵌入式執(zhí)行引擎直接跑在你的應(yīng)用進(jìn)程里負(fù)責(zé)把模型輸出翻譯成真實(shí)動(dòng)作。這個(gè)定位帶來(lái)兩個(gè)非常實(shí)際的好處。第一是低延遲工具調(diào)用不需要跨網(wǎng)絡(luò)請(qǐng)求Runner 和工具函數(shù)在同一個(gè)進(jìn)程內(nèi)省掉了大量序列化和網(wǎng)絡(luò)開(kāi)銷(xiāo)。第二是便于本地化控制Skill 的執(zhí)行權(quán)限、文件訪問(wèn)范圍、網(wǎng)絡(luò)請(qǐng)求白名單都可以直接在 Runner 層做約束不用依賴(lài)外部網(wǎng)關(guān)。對(duì)個(gè)人 Agent 這種既要靈活又要可控的場(chǎng)景嵌入式設(shè)計(jì)其實(shí)比微服務(wù)架構(gòu)更合適。2. 拆解 pi-embedded-runner嵌入式 Runner 的設(shè)計(jì)邏輯2.1 embedded不是一句口號(hào)而是架構(gòu)選擇很多 Agent 框架會(huì)把規(guī)劃器和執(zhí)行器拆成兩個(gè)獨(dú)立服務(wù)中間用消息隊(duì)列通信。OpenClaw 的 pi-embedded-runner 偏偏反著來(lái)它把自己做成一個(gè)庫(kù)直接嵌入到宿主進(jìn)程里。我第一次看到這個(gè)設(shè)計(jì)時(shí)也有些疑惑但真正跑起來(lái)才明白它的用意。嵌入式架構(gòu)的核心優(yōu)勢(shì)在于工具調(diào)用變成了純函數(shù)調(diào)用不再需要為每個(gè)工具定義一套網(wǎng)絡(luò)協(xié)議。以我接入的一個(gè)自定義 Skill 為例函數(shù)內(nèi)部需要讀取本地 Sqlite 數(shù)據(jù)庫(kù)、調(diào)用一個(gè)內(nèi)部 HTTP 接口、再寫(xiě)日志文件。如果是微服務(wù)架構(gòu)我得為這個(gè) Skill 單獨(dú)封裝一個(gè)服務(wù)接口然后處理認(rèn)證、超時(shí)、序列化但在 embedded runner 的設(shè)計(jì)下它就是一個(gè)普通的異步函數(shù)Runner 直接 await 它就可以了。代價(jià)也很明顯——宿主進(jìn)程的內(nèi)存和事件循環(huán)會(huì)決定 Agent 的性能上限。如果你在同一進(jìn)程里又跑模型推理又跑大量文件 IO就可能出現(xiàn)事件循環(huán)阻塞。所以 OpenClaw 的 Runner 在設(shè)計(jì)上把模型交互和工具執(zhí)行基本都做成了異步流程盡量不在關(guān)鍵路徑上放同步阻塞操作。你在實(shí)踐里如果發(fā)現(xiàn) Agent 卡頓可以先檢查自己寫(xiě)的 Skill 里有沒(méi)有同步的文件讀操作把它改成異步往往立竿見(jiàn)影。2.2 Runner 如何對(duì)接模型層、工具層和記憶層執(zhí)行引擎不可能孤立工作它必須同時(shí)面對(duì)三個(gè)方向向上對(duì)接模型、向下對(duì)接工具、側(cè)向?qū)佑洃?。pi-embedded-runner 的設(shè)計(jì)里這三條通道是明確分離的。模型層接口Runner 不關(guān)心你底層用的是 OpenAI 兼容接口、Ollama 本地模型還是 Anthropic 的 API它只面向統(tǒng)一的 Completion 接口。這里面有一個(gè)很重要的抽象模型輸出既可以是純文本也可以是結(jié)構(gòu)化的工具調(diào)用意圖。Runner 要做的是把這兩種輸出都統(tǒng)一成內(nèi)部消息格式再送給下一步處理。我實(shí)際接 Ollama 上的 qwen2.5-3b 時(shí)發(fā)現(xiàn)小模型對(duì)工具調(diào)用這種格式的支持不太穩(wěn)定經(jīng)常輸出一段 Markdown 而不是嚴(yán)格的 JSON優(yōu)化辦法是在系統(tǒng)提示詞里明確給例子而不是只描述規(guī)則。工具層接口工具在 OpenClaw 里通常以 Skill 的形式存在每個(gè) Skill 對(duì)外暴露一個(gè)描述清單包含名稱(chēng)、用途說(shuō)明、參數(shù) Schema。Runner 維護(hù)一張工具注冊(cè)表當(dāng)模型說(shuō)要調(diào)用某個(gè)工具時(shí)Runner 會(huì)先做語(yǔ)法層面的匹配和參數(shù)校驗(yàn)校驗(yàn)通過(guò)才真正執(zhí)行。有一次我寫(xiě) Skill 時(shí)把參數(shù)名從keyword改成了query結(jié)果模型連續(xù)三輪都在用舊參數(shù)名調(diào)用Runner 每輪都返回參數(shù)校驗(yàn)錯(cuò)誤。排查了半天才發(fā)現(xiàn)是描述信息沒(méi)有同步更新模型看到的注冊(cè)表還是舊的。這個(gè)教訓(xùn)說(shuō)明工具參數(shù) Schema 一旦變更必須同步更新工具描述否則模型不會(huì)自動(dòng)知道新參數(shù)。記憶層接口執(zhí)行引擎的第三個(gè)連接對(duì)象是記憶模塊。每一輪工具調(diào)用的產(chǎn)物比如讀取的文件內(nèi)容、查詢(xún)到的記錄不屬于長(zhǎng)期記憶只屬于當(dāng)前會(huì)話的短期上下文只有用戶明確要求記住或者到達(dá)某個(gè)重要節(jié)點(diǎn)時(shí)Runner 才會(huì)調(diào)用記憶接口做持久化。理解這一點(diǎn)你才能解釋為什么很多 Agent 在會(huì)話中表現(xiàn)很好但新開(kāi)一個(gè)會(huì)話就失憶——因?yàn)閳?zhí)行引擎默認(rèn)只做短期上下文管理長(zhǎng)期記憶需要顯式觸發(fā)。2.3 會(huì)話槽位與串并行調(diào)度pi-embedded-runner 里還有一個(gè)容易忽略但很重要的概念會(huì)話槽位slot。一個(gè) Runner 實(shí)例可以同時(shí)管理多個(gè) Agent 會(huì)話每個(gè)會(huì)話擁有獨(dú)立的上下文緩沖區(qū)和狀態(tài)棧。這有點(diǎn)像數(shù)據(jù)庫(kù)連接池——每個(gè)連接都是隔離的互不干擾。由于底層模型調(diào)用通常是串行的尤其本地模型Runner 需要一個(gè)調(diào)度策略來(lái)決定多個(gè)會(huì)話之間如何搶占模型資源。我跑下來(lái)的感受是OpenClaw 默認(rèn)更偏向一個(gè)主任務(wù)占住模型的調(diào)度方式并行能力有限。如果你在同一個(gè)進(jìn)程里同時(shí)跑多個(gè) Agent 任務(wù)可能會(huì)出現(xiàn)一個(gè)任務(wù)的長(zhǎng)工具調(diào)用堵住了另一個(gè)任務(wù)的模型請(qǐng)求。解決方案通常是把不同任務(wù)拆到不同的 Runner 實(shí)例不同的 Node 進(jìn)程而不是在同一個(gè)實(shí)例里硬塞高并發(fā)。下面是我整理的一份調(diào)度參數(shù)對(duì)照表幫助你在調(diào)優(yōu)時(shí)有一個(gè)直觀的參照參數(shù)作用建議值/做法maxIterations單個(gè)任務(wù)最多循環(huán)多少輪簡(jiǎn)單任務(wù) 8 輪復(fù)雜任務(wù) 15~20 輪maxTokens單輪模型輸出最大 token 數(shù)根據(jù)模型上下文留足工具結(jié)果的空間slotCountRunner 同時(shí)可承載的會(huì)話數(shù)本地模型建議 1~2云端 API 可到 5timeout單次工具調(diào)用超時(shí)網(wǎng)絡(luò)類(lèi) 30s本地文件類(lèi) 10stoolRetries工具失敗重試次數(shù)冪等工具 2 次非冪等工具 0 次這張表的每一條都來(lái)自真實(shí)調(diào)參經(jīng)歷。比如timeout這條我開(kāi)始沒(méi)設(shè)置結(jié)果某個(gè)網(wǎng)絡(luò)工具在目標(biāo)站點(diǎn)無(wú)響應(yīng)時(shí)直接掛起整個(gè)任務(wù)卡了快 10 分鐘。后來(lái)給工具加上 30 秒超時(shí)Runner 會(huì)在超時(shí)后把錯(cuò)誤信息返回給模型模型自己決定是重試還是換方案整個(gè)系統(tǒng)立刻靈活了很多。3. 核心循環(huán)深入從意圖到動(dòng)作的每一步3.1 意圖識(shí)別與任務(wù)分解LLM 如何輸出結(jié)構(gòu)化指令執(zhí)行引擎的第一道工序是把自然語(yǔ)言目標(biāo)變成可執(zhí)行的指令序列。這一步通常不在 Runner 里而是在模型層但 Runner 必須能正確解讀模型的輸出?,F(xiàn)在主流的做法有兩種一種是讓模型輸出function_call格式的原生工具調(diào)用另一種是讓模型輸出嚴(yán)格的 JSON再由 Runner 里的解析器讀取。我的經(jīng)驗(yàn)是不要過(guò)度相信模型會(huì)嚴(yán)格遵守 JSON 格式。哪怕是表現(xiàn)很好的模型在長(zhǎng)上下文和工具結(jié)果干擾下也可能輸出多余的解釋文字。我處理過(guò)一個(gè)很典型的情況模型明明應(yīng)該輸出{tool: read_file, params: {path: xxx}}結(jié)果在 JSON 前面加了一句好的我來(lái)幫你讀取這個(gè)文件如果 Runner 沒(méi)有做從文本中提取 JSON 片段的兜底這個(gè)調(diào)用就直接失敗了。所以一個(gè)健壯的執(zhí)行引擎必須內(nèi)置容錯(cuò)解析先嘗試嚴(yán)格解析失敗后再做提取再失敗才把錯(cuò)誤返回給模型。3.2 工具調(diào)度的決策規(guī)則選哪個(gè)工具、傳什么參數(shù)當(dāng)模型提出多個(gè)可能的工具調(diào)用時(shí)Runner 不能全盤(pán)照收它需要基于規(guī)則做決策。這里主要看三件事工具是否在當(dāng)前注冊(cè)表中、參數(shù)是否符合 Schema、是否有足夠的執(zhí)行權(quán)限。工具描述的質(zhì)量會(huì)直接決定模型的選擇準(zhǔn)確性。你在寫(xiě) Skill 時(shí)描述不能只寫(xiě)讀取文件要寫(xiě)清楚這個(gè)工具適合什么場(chǎng)景、有哪些邊界、參數(shù)的含義。讀取文件內(nèi)容支持普通 UTF-8 文本文件不適合二進(jìn)制文件返回文件前 200 行——這種描述能極大減少模型誤調(diào)用的概率。參數(shù)校驗(yàn)是另一道關(guān)鍵防線。模型可能從上下文里提取了一個(gè)并不存在的文件名或者把數(shù)字類(lèi)型的參數(shù)傳成了字符串。Runner 在調(diào)用真實(shí)工具之前做一次 Schema 校驗(yàn)?zāi)軘r截大部分低級(jí)錯(cuò)誤。有一次模型連續(xù)三次調(diào)我的analyze_logs工具參數(shù)里傳的次數(shù)都是負(fù)數(shù)如果 Runner 不攔截工具就會(huì)返回一堆沒(méi)意義的統(tǒng)計(jì)攔截之后把校驗(yàn)錯(cuò)誤給模型模型自己就意識(shí)到傳參數(shù)錯(cuò)了重新傳了正數(shù)。3.3 結(jié)果反饋與上下文更新失敗信號(hào)如何進(jìn)入下一輪工具執(zhí)行完之后返回結(jié)果不會(huì)直接丟給用戶它會(huì)先進(jìn)入 Runner 的結(jié)果處理器。處理器要做三件事把結(jié)果格式化成緊湊的消息、把結(jié)果注入上下文、判斷結(jié)果是否包含失敗信號(hào)。結(jié)果格式化這一點(diǎn)特別重要。工具可能返回一個(gè)巨大的 JSON全量塞進(jìn)上下文會(huì)浪費(fèi) token 占用。Runner 通常會(huì)做截?cái)嗷蛘槐A羟?N 個(gè)字符。我在做日志分析類(lèi) Skill 時(shí)工具動(dòng)輒返回上萬(wàn)行日志一開(kāi)始全量回填上下文很快就爆了后來(lái)在工具側(cè)先做了聚合統(tǒng)計(jì)只返回 Top 10 錯(cuò)誤和統(tǒng)計(jì)匯總模型處理起來(lái)又快又準(zhǔn)。失敗信號(hào)的處理同樣關(guān)鍵。工具調(diào)用失敗超時(shí)、權(quán)限不足、參數(shù)錯(cuò)誤不能簡(jiǎn)單結(jié)束任務(wù)Runner 要把失敗原因轉(zhuǎn)換成模型可理解的自然語(yǔ)言放進(jìn)上下文讓模型自己決定下一步。比如文件讀取失敗路徑不存在這個(gè)信號(hào)模型讀完會(huì)嘗試列出目錄看看有哪些文件存在而不是直接放棄。這種失敗即反饋的機(jī)制是 Agent 能自我糾錯(cuò)的核心。3.4 迭代剎車(chē)的安全邊界設(shè)置沒(méi)有剎車(chē)的 Agent 是危險(xiǎn)的。如果模型在一個(gè)錯(cuò)誤分支上打轉(zhuǎn)或者工具不停地返回同樣的結(jié)果任務(wù)可能會(huì)無(wú)限循環(huán)。pi-embedded-runner 提供了多層剎車(chē)機(jī)制。第一層是maxIterations也就是最多允許的思考-行動(dòng)輪數(shù)。我一般給普通任務(wù)設(shè) 10 輪復(fù)雜任務(wù) 20 輪。超過(guò)這個(gè)數(shù) Runner 會(huì)強(qiáng)制終止并返回已達(dá)到最大迭代次數(shù)的提示。第二層是相鄰輪次的相似度檢測(cè)如果連續(xù)兩輪模型的工具調(diào)用意圖完全相同Runner 會(huì)給模型一個(gè)警告你剛剛已經(jīng)做過(guò)同樣操作請(qǐng)確認(rèn)是否需要繼續(xù)。第三層是 token 預(yù)算上下文接近模型上限時(shí) Runner 會(huì)主動(dòng)觸發(fā)滑窗把最舊的消息摘要掉而不是盲目擴(kuò)充。這里我想多說(shuō)一句安全邊界本質(zhì)上是給模型一次認(rèn)錯(cuò)機(jī)會(huì)的設(shè)計(jì)。你不需要把每一條路徑都堵死只需要在失控時(shí)軟性打斷然后讓模型意識(shí)到自己在重復(fù)。實(shí)測(cè)下來(lái)相似度檢測(cè)這一條能解決八成左右的循環(huán)問(wèn)題比硬性中斷體驗(yàn)好很多。4. 實(shí)戰(zhàn)環(huán)境準(zhǔn)備與第一個(gè)多步任務(wù)跑通4.1 處理 WSL 環(huán)境異常與準(zhǔn)備 Node 運(yùn)行時(shí)我最初是在 Windows 上嘗試部署 OpenClaw 的結(jié)果啟動(dòng)階段就遇到了社區(qū)里出現(xiàn)頻率很高的那個(gè)提示openclaw 無(wú)法安全驗(yàn)證 wsl 環(huán)境。請(qǐng)?jiān)?powershell 中運(yùn)行 wsl -- status。上網(wǎng)一查遇到的人不少這個(gè)提示的意思是系統(tǒng)還沒(méi)有正確啟用 WSL 2或者默認(rèn)版本不對(duì)。排查思路不復(fù)雜。先打開(kāi) PowerShell執(zhí)行wsl --status看返回的信息里 WSL 版本是不是 2。如果系統(tǒng)提示適用于 Linux 的 Windows 子系統(tǒng)未安裝就需要先安裝wsl --install。安裝完成后重啟再執(zhí)行wsl --set-default-version 2確保用的是 WSL 2而不是舊版的 WSL 1。還有一個(gè)常見(jiàn)問(wèn)題是在 BIOS 里未開(kāi)啟虛擬化功能這個(gè)用systeminfo檢查虛擬化: 已啟用就能確認(rèn)。OpenClaw 本身的安裝路徑我建議直接走 Node.js 生態(tài)。先到官網(wǎng)安裝 LTS 版本的 Node.js我用的 20.x然后全局安裝 OpenClaw 就可以。這里有一個(gè)比較隱蔽的坑安裝完 Node 之后要重開(kāi)終端否則 npm 全局路徑不會(huì)加載。我第一次就是沒(méi)重開(kāi)終端直接執(zhí)行命令行提示找不到命令折騰了好一會(huì)兒。4.2 配置模型通道本地 Ollama 還是云端 API執(zhí)行引擎本身不帶模型它需要連接一個(gè)能輸出工具調(diào)用意圖的模型后端。兩種主流方式我都試過(guò)。本地模型社區(qū)里很多人嘗試用 Ollama 跑 qwen2.5-3b 關(guān)聯(lián)到 OpenClaw。好處是免費(fèi)、本地?cái)?shù)據(jù)不出機(jī)器壞處是小模型的工具調(diào)用能力比較弱經(jīng)常不按 JSON 格式輸出。我在跑通之前做了兩個(gè)優(yōu)化一是把溫度調(diào)到 0 或接近 0減少隨機(jī)性二是在配置里給模型填了詳細(xì)的工具調(diào)用示例。這里要說(shuō)句公道話3b 這種小模型做簡(jiǎn)單問(wèn)答可以做復(fù)雜的多步任務(wù)真的比較吃力建議至少 7b 起步有條件直接 14b。云端 API如果你配置了 OpenAI 兼容接口支持 OpenAI、或國(guó)內(nèi)可直連的兼容服務(wù)Runner 連接會(huì)順利很多。云端模型對(duì)工具調(diào)用的原生支持更好跑復(fù)雜任務(wù)的穩(wěn)定性明顯高于本地小模型。缺點(diǎn)是需要考慮 API 費(fèi)用和隱私問(wèn)題。我的建議是開(kāi)發(fā)調(diào)試階段用云端 API 把邏輯跑通之后換成本地模型做隱私敏感的任務(wù)兩者配合最舒服。配置模型通道時(shí)核心是把模型的輸入輸出格式對(duì)齊 Runner 的預(yù)期。我貼一段我使用的配置思路以 Ollama 為例{ llm: { provider: ollama, baseUrl: http://localhost:11434, model: qwen2.5:7b, temperature: 0, maxTokens: 4096 }, runner: { maxIterations: 15, toolTimeout: 30000, toolRetries: 1 } }你不需要照抄這段重點(diǎn)是注意temperature和maxTokens這兩個(gè)值。溫度太高模型就愛(ài)自由發(fā)揮格式容易亂maxTokens太低會(huì)導(dǎo)致模型輸出到一半就被截?cái)喙ぞ哒{(diào)用意圖不完整。4.3 編寫(xiě)一個(gè)自定義 Skill 并注冊(cè)到 Runner光會(huì)配置還不夠真正讓執(zhí)行引擎活起來(lái)的是自定義 Skill。我以讀取本地文檔并提取配置項(xiàng)為例演示一個(gè)最簡(jiǎn) Skill 的寫(xiě)法。在 OpenClaw 里一個(gè) Skill 通常包含一個(gè)描述文件和一個(gè)執(zhí)行函數(shù)執(zhí)行函數(shù)用 JavaScript 寫(xiě)Runner 通過(guò)描述文件知道這個(gè)工具的名稱(chēng)和參數(shù)要求。// skill: doc-config-extractor.js export default async function extractConfigItems({ filePath, keyword }) { const fs await import(node:fs); const content await fs.promises.readFile(filePath, utf-8); const lines content.split(\n); const results lines .filter(line line.includes(keyword)) .slice(0, 50); return { lineCount: results.length, matchedLines: results, }; }對(duì)應(yīng)的描述信息需要為這個(gè) Skill 寫(xiě)明名稱(chēng)、用途、參數(shù) Schema。描述越具體模型越不容易誤用{ name: extract_config_items, description: 讀取文本文件中包含指定關(guān)鍵字的行適合從配置文檔、日志、代碼文件中提取配置項(xiàng)。不適合二進(jìn)制文件。返回匹配行列表與前 50 行。, parameters: { type: object, properties: { filePath: { type: string, description: 文件絕對(duì)路徑或相對(duì)路徑 }, keyword: { type: string, description: 搜索關(guān)鍵字大小寫(xiě)敏感 } }, required: [filePath, keyword] } }把這兩個(gè)文件放到 OpenClaw 的 skills 目錄后重啟 Runner執(zhí)行引擎就會(huì)自動(dòng)把新 Skill 注冊(cè)進(jìn)工具表。驗(yàn)證注冊(cè)成功的方法是直接給 Agent 發(fā)一條使用該工具的任務(wù)然后看 Runner 的日志里是否有該 Skill 的加載記錄。4.4 跑通檢索整理輸出的三步任務(wù)環(huán)境就緒后我做的第一個(gè)完整測(cè)試是一個(gè)三步任務(wù)讀取項(xiàng)目文檔里所有包含API_KEY的行去重后按文件名分組生成一個(gè)匯總表格。Agent 的實(shí)際執(zhí)行路徑可能和我預(yù)想的不完全一樣但大體會(huì)是先列目錄 → 讀取文檔 → 調(diào)用extract_config_items→ 匯總 → 生成表格。整個(gè)過(guò)程經(jīng)歷了大概 6 輪思考-行動(dòng)循環(huán)每輪我都盯著 Runner 日志看確認(rèn)工具調(diào)用意圖被正確解析和執(zhí)行。第一次跑的時(shí)候并沒(méi)有一次成功。問(wèn)題出在模型調(diào)用了extract_config_items之后返回結(jié)果里有 200 多行匹配數(shù)據(jù)我把這些全塞進(jìn)了下一輪上下文模型很快就暈了最后的表格殘缺不全。后來(lái)我給工具返回結(jié)果加了截?cái)嘀槐A羟?30 行匹配并且在工具側(cè)先做了簡(jiǎn)單的去重統(tǒng)計(jì)問(wèn)題才消失。這個(gè)例子再次印證了我在第 3 章說(shuō)的工具返回結(jié)果必須在進(jìn)入上下文之前做瘦身否則模型再?gòu)?qiáng)也處理不了海量明細(xì)。5. 運(yùn)行中的坑與調(diào)優(yōu)工具返回格式、上下文與超時(shí)5.1 工具返回值不規(guī)范導(dǎo)致的LLM 發(fā)瘋這是執(zhí)行引擎實(shí)戰(zhàn)里我遇到最多的一類(lèi)問(wèn)題。工具函數(shù)為了自己方便返回的是無(wú)格式的純文本、或者嵌套很深的 JSON模型拿到這種結(jié)果后很容易產(chǎn)生幻覺(jué)開(kāi)始腦補(bǔ)不存在的信息。一個(gè)非常典型的場(chǎng)景我寫(xiě)了一個(gè)查詢(xún)服務(wù)器狀態(tài)的工具返回值里有一段純文本日志連接正常 200 OK。結(jié)果模型下一輪直接宣稱(chēng)服務(wù)器連接完全正常延遲 5ms丟包率 0%??晌业墓ぞ吒緵](méi)返回延遲和丟包率。這就是典型的模型腦補(bǔ)——它根據(jù)上下文風(fēng)格順嘴編了細(xì)節(jié)。解決方案分兩層。工具側(cè)返回值盡量結(jié)構(gòu)化用簡(jiǎn)單扁平的 JSONkey 命名清晰并明確注明以下數(shù)據(jù)為原始結(jié)果未加工Runner 側(cè)在工具描述里加一條約定工具返回的是原始數(shù)據(jù)只能基于返回字段做分析不得推斷字段之外的信息。加了這條約束后模型腦補(bǔ)的現(xiàn)象明顯減少了但說(shuō)實(shí)話無(wú)法百分百消除所以關(guān)鍵數(shù)據(jù)你一定要在工具側(cè)做好驗(yàn)證和統(tǒng)計(jì)不要指望模型忠實(shí)轉(zhuǎn)述。5.2 上下文窗口逼近限制時(shí)的處理策略執(zhí)行引擎是個(gè)上下文貪吃鬼每輪模型輸出、每輪工具結(jié)果、每輪狀態(tài)記錄都在累積 token。當(dāng)你的任務(wù)超過(guò) 10 輪、工具又返回大段數(shù)據(jù)時(shí)上下文很容易逼近模型窗口上限。我在 pi-embedded-runner 里驗(yàn)證過(guò)的策略有三個(gè)按優(yōu)先級(jí)排序截?cái)鄡?yōu)先、摘要其次、滑窗兜底。工具返回結(jié)果在進(jìn)入上下文前先截?cái)噙@是最廉價(jià)也最有效的方案如果截?cái)嗪笕越咏舷轗unner 會(huì)把最早幾輪思考-行動(dòng)記錄進(jìn)行摘要壓縮換成一行簡(jiǎn)短的第 1 輪完成了文件列表獲取最后一道是滑窗直接把最早的對(duì)話內(nèi)容丟棄。三種策略的取舍值得注意。截?cái)嘤衼G失信息的風(fēng)險(xiǎn)但工具結(jié)果的重復(fù)度通常不高丟失尾部明細(xì)影響較小摘要有信息失真風(fēng)險(xiǎn)適合處理歷史過(guò)程而不是關(guān)鍵數(shù)據(jù)滑窗則會(huì)讓模型失去記憶導(dǎo)致后半程任務(wù)可能遺漏早期的約束條件。我的建議是盡量讓每個(gè)工具在源頭就返回精簡(jiǎn)結(jié)果別依賴(lài) Runner 做售后處理。5.3 超時(shí)、重試與冪等設(shè)計(jì)執(zhí)行引擎在真實(shí)環(huán)境中工具調(diào)用失敗是常態(tài)不是異常。網(wǎng)絡(luò)抖動(dòng)、文件鎖定、外部服務(wù)無(wú)響應(yīng)任何一項(xiàng)都能讓工具調(diào)用失敗。Runner 如何處理失敗直接決定了 Agent 的穩(wěn)定性。超時(shí)設(shè)置是第一道防線。不給超時(shí)的工具調(diào)用是定時(shí)炸彈。我給網(wǎng)絡(luò)類(lèi)工具統(tǒng)一設(shè) 30 秒超時(shí)本地文件類(lèi)工具 10 秒超過(guò)就拋異常并把異常轉(zhuǎn)成自然語(yǔ)言錯(cuò)誤返回給模型。重試策略要區(qū)分工具是否冪等。冪等工具只讀操作可以重試非冪等工具寫(xiě)操作、發(fā)消息重試要格外小心。有一次我寫(xiě)的通知類(lèi) Skill 沒(méi)做冪等控制Runner 自動(dòng)重試了兩次結(jié)果用戶收到了三條重復(fù)通知。后來(lái)在代碼里加了請(qǐng)求去重 ID才解決問(wèn)題。5.4 觀察軌跡如何用日志審查 Agent 的思考過(guò)程執(zhí)行引擎比純聊天模型強(qiáng)的地方就是它可以復(fù)盤(pán)。Runner 只要開(kāi)啟軌跡日志每一輪的模型輸出、工具調(diào)用意圖、參數(shù)校驗(yàn)結(jié)果、工具返回摘要都會(huì)被記錄下來(lái)。遇到 Agent 行為異常時(shí)你完全可以像回放監(jiān)控錄像一樣看到它在哪一輪跑偏了。我強(qiáng)烈建議在調(diào)試階段開(kāi)啟詳細(xì)日志線上運(yùn)行階段至少保留錯(cuò)誤級(jí)日志。有一次我的 Agent 莫名其妙總是漏掉任務(wù)里的一項(xiàng)要求怎么調(diào) prompt 都沒(méi)用后來(lái)翻軌跡日志發(fā)現(xiàn)模型在第三輪就已經(jīng)忘記了原始目標(biāo)自顧自地推進(jìn)到了下一步。找到原因后我改了系統(tǒng)提示詞要求模型在每輪開(kāi)始前先復(fù)述一次原始目標(biāo)——問(wèn)題立即解決。這種問(wèn)題沒(méi)有軌跡日志基本不可能定位。6. 我的一點(diǎn)實(shí)操體會(huì)6.1 對(duì)執(zhí)行引擎選型與配置的建議如果你準(zhǔn)備在自己的項(xiàng)目里接 OpenClaw我的建議是先別急著堆功能花一天時(shí)間把pi-embedded-runner的配置和日志機(jī)制摸清楚。這個(gè)時(shí)間絕對(duì)值得因?yàn)楹竺婺阏{(diào)試任何 Skill、處理任何Agent 不聽(tīng)話的問(wèn)題都會(huì)回到執(zhí)行引擎這個(gè)層面。具體來(lái)說(shuō)一是把工具返回值瘦身變成你寫(xiě) Skill 的默認(rèn)習(xí)慣不要指望模型和 Runner 處理你隨手丟回去的海量數(shù)據(jù)二是重視工具描述的質(zhì)量你花在寫(xiě)描述上的時(shí)間會(huì)在模型調(diào)用準(zhǔn)確率上成倍賺回來(lái)三是建立軌跡日志的復(fù)盤(pán)習(xí)慣Agent 不是黑盒它每一步都有痕跡善用痕跡能省掉大量猜謎時(shí)間。6.2 這個(gè)執(zhí)行引擎未來(lái)還能怎么擴(kuò)展往大了說(shuō)pi-embedded-runner 這種嵌入式 Runner 的設(shè)計(jì)思路很適合往多 Agent 協(xié)作方向延伸。每個(gè) Runner 是一個(gè)迷你執(zhí)行單元多個(gè) Runner 之間可以通過(guò)消息傳遞協(xié)作——這正好呼應(yīng)了社區(qū)里討論的 agents anywhere 和多 AI 協(xié)作的方向。我個(gè)人的下一步計(jì)劃是嘗試在同一個(gè)進(jìn)程里跑多個(gè) Runner 實(shí)例分別負(fù)責(zé)規(guī)劃者和執(zhí)行者角色并探索更精細(xì)的權(quán)限隔離方案。如果你想更進(jìn)一步還可以研究怎么把 Runner 的事件流接入外部監(jiān)控系統(tǒng)做可視化回放。無(wú)論如何執(zhí)行引擎這個(gè)層面你理解得越深玩 Agent 的上限就越高。