~/blog/hermes-config-gotchas

從 0 開始的 AI Agent 生活 · part 18

[Agent 進階 #18] 給你的 Hermes 做一次設定健檢:五個沒人細講、但你一定會撞的雷

cat --toc

TL;DR

第 13 篇說:助理發瘋八成是「車子」(工具、設定、記憶)壞了,不是「引擎」(模型)笨。這篇是那張健檢表——五個我親手踩過的設定雷。寫這篇時我回去翻 Hermes code,發現兩個「不對了」:context_length 設 nested 那個,新版已經幫你修了;MCP 環境變數那個,是我當初解錯方向——env: 才對,我卻去包 wrapper。剩三個(Qwen thinking 沒關、label 沒跟端點對齊、plist/dashboard 維運)照舊會咬。每個都附一條丟給 agent 自己跑的檢查——「連我的雷都會過時」正是重點:以現在跑的為準,別信舊記憶。

從 0 開始的 AI Agent 生活系列 #18 封面:少年側臉看著一個白藍發光的小機器人,身邊漂浮著儀表與檢查清單面板,象徵幫 Hermes 助理做一次設定健檢。

13 篇我說過一句話:助理開始鬼打牆的時候,先別怪模型笨——八成是它外面那一圈(工具、設定、記憶)出了狀況。模型是引擎,外面那圈是車子;車子開不動,常常不是引擎壞,是輪胎沒氣、油路堵了。

那篇是概念。這篇是那張健檢表:車子最常壞的五個地方,每個我都親手撞過、也都給你一條可以直接丟給你的 agent 去跑的檢查——因為你的 Hermes 本來就有手,叫它「讀這篇、幫自己做一次設定健檢」是最快的。

前提照舊:你已經照入門 1-8 裝好、有一個在用的 Hermes 助理。這五個雷有個共通點——就算有噴錯,錯誤訊息也看不出真正的原因(頂多是 ECONNREFUSED、401 這種),只會讓助理「怪怪的」;知道往哪看,五分鐘;不知道,耗你一整晚。

先老實說一件事:寫這篇時我回去對了現在的 Hermes code,發現五個裡有兩個要更新——一個是 Hermes 後來修了(context_length),一個是我當初根本解錯方向(MCP 環境變數)。我照樣留著、也標清楚——因為這正是重點:設定的雷會過時、你對它的理解也會,兩邊都要以現在跑的東西為準(這其實就是雷 4 那件事)。所以每個雷我都附上「現在還算不算數」跟一條不管哪一版都成立的檢查。

Hermes 設定健檢總覽:五個不噴錯誤、只讓助理「怪怪的」的設定雷,左邊你看到的症狀對右邊真正的原因——① context_length 設在 nested 層讀不到、② Qwen thinking 沒關、③ MCP 環境變數被安全白名單濾掉、④ label 沒跟端點對齊、⑤ plist 缺 PATH。

雷 1:context_length 設錯一層會「一直健忘」(這個新版 Hermes 幫你修了)

症狀:對話才走到一半,助理就好像忘了前面講什麼,一直在壓縮、一直在「摘要前情」。你以為是記憶太小。

當時的雷:Hermes 要知道「這顆腦能吃多少字」,才知道在多少 token 該壓縮。我踩到的那版,它只讀最上層model.context_length;而我(跟很多人一樣)把它塞在 provider 底下:

# 我當時的寫法:塞在 nested
providers:
  ds4:
    models:
      deepseek-chat:
        context_length: 262144

舊版讀不到這層,就一路 fallback 去查公版資料庫,拿 alias deepseek-chat 查到一個不是我 256K 的公版數字。結果:模型明明開 256K,Hermes 內部卻用一個小很多的值——壓縮觸發點是抓「那個小值的一半」,不是真正 256K 的一半,所以遠比你想的更早就開始壓縮、丟掉前面,看起來就是「一直健忘」。

以前的雷:舊版 Hermes 只讀 top-level 的 context_length,設在 nested 就讀不到、一路 fallback 到一個公版數字,在一半就提早壓縮=健忘。現行 Hermes 已修:provider-nested 那一層也會讀了。

現行狀態(寫這篇時我回去翻了 code):新版 Hermes 已經修了——它現在連 provider-nested 的 context_length 也會解析(get_custom_provider_context_length),你設哪一層都吃得到。所以你跑的是近期版本的話,這個雷本身已經不會咬你了。

各版本都適用的檢查:不管哪一版,Hermes 一律用它自己算出來的 context_length,你要確認那個值就是後端真的支援的——而且別只比對設定檔,要看 Hermes 實際解析到多少。三邊對一下:後端 /propsn_ctx、你設定的 context_length、跟 Hermes 啟動時印的 Context limit:(互動/非 quiet 模式才會印,或用 /info 查):

curl -s http://<你的-後端-ip>:<port>/props   # 後端 n_ctx
# 再對照 Hermes 啟動 log 的 Context limit: 跟你設定的 context_length —— 三個要一致

進階(想調到極致再看):真正決定「壓縮前能留多少」的不只是主模型的 context window,還有摘要模型(summarizer)的 context window——auxiliary.compression.context_length,要對齊它自己後端的 n_ctx。另外壓縮觸發點預設是主模型 context window 的 50%,某些模型/路徑會自動往上調(GPT-5.4/5.5 走 Codex OAuth 路徑時 85%、GPT-5.3 Codex Spark 70%、Arcee Trinity Large Thinking 固定 75%,而且不會把你自己設更高的值調低);主模型開 256K、但 summarizer 只有 96K 的話,實際能留的還是會卡在 summarizer 的 96K。

雷 2:Qwen 沒關 thinking,同一件事慢 10 倍

症狀:助理反應很慢、每句話前面都要「想很久」;叫它做機械式的事(例如一連串生圖 / 工具呼叫)慢到懷疑人生,還常常失敗。

真相:Qwen 3 / 3.6 這類模型預設開 <think>。對需要推理的問題很好,但對聊天、對一步一步照做的 pipeline,那段思考是純浪費。我實測過同一個 prompt:關掉 thinking,1058ms → 98ms(快 10.8 倍);生圖類任務從 6-10 分鐘掉到 30-90 秒,成功率也大幅提高。

關掉 Qwen 的 thinking,同一件事快 10.8 倍:thinking 開著每句先想一大段(1058ms/短句、生圖類 6-10 分鐘常失敗),用 enable_thinking:false 關掉後變 98ms/短句、生圖類 30-90 秒成功率大升。

修法:如果你的後端是 vLLM / SGLang 這類本地 OpenAI 相容端點,直接在該 provider 的 extra_body 用 nested chat_template_kwargs 關掉,不用改任何程式碼:

providers:
  forge-gemma:
    extra_body:
      chat_template_kwargs:
        enable_thinking: false

補充:網路上有些教學是去改 run_agent.py、靠環境變數關 thinking——那是舊的私改 patch,換版之後常常沒重貼、就失效了。寫在 config 裡才是換版也不會掉、你能直接複製的做法。(注意:關 thinking 的寫法跟後端有關——vLLM / SGLang 這類走上面 nested 的 chat_template_kwargs;阿里雲 Model Studio 則是直接在頂層放 enable_thinking: false。)

順帶一提,Hermes 沒有專門的 temperature: 欄位——所有取樣參數(temperature / top_p / top_k)也都走同一個 extra_body。所以「關 thinking」跟「調 temperature」是同一個地方的事,一次弄懂。

丟給 agent 的檢查:叫它「量一下你回一句短話要多久;如果每次都先卡個一兩秒才吐字,去 provider 的 extra_body 確認 enable_thinking 是不是 false」(延遲多少算慢跟機器/後端有關,別當死標準,對照自己平常的手感就好)。

雷 3:MCP 工具全部連不上——你 shell export 的變數傳不進去

症狀:你照第 12 篇幫助理接了一個 MCP 工具,設定看起來都對,但一呼叫就失敗——ECONNREFUSED、連到 localhost、或直接沒反應。你檢查半天找不到哪裡錯。

真相:我踩到時是這樣——我在 shell export 了 API key、服務位址,理所當然以為 MCP 工具的子行程會像一般子行程那樣繼承。但 Hermes 開 stdio 子行程時有一份環境變數白名單(_SAFE_ENV_KEYS),只放行 PATH / HOME / USER / LANG / SHELL 這類基本的;你在 shell export 的東西不在白名單就被濾掉。工具拿不到位址,只好 fallback 到 localhost → 全部連線失敗。而且它不噴「環境變數不見了」,只噴一個看起來像網路問題的錯,超難猜。

雷 3:你在 shell export 的 API key/服務位址,被 _SAFE_ENV_KEYS 白名單濾掉→工具沒位址 fallback 到 localhost→ECONNREFUSED(看起來像網路問題)。正解是把變數寫進設定檔 mcp_servers 底下該 server 的 env: 區塊,那一區塊 Hermes 一直都會 merge 進子行程。

正解:別靠 shell export,把變數寫進設定檔的 mcp_servers.<name>.env:——這一區塊 Hermes 一直都會 merge 進子行程,是這些變數該待的地方:

mcp_servers:
  my-tool:
    command: my-real-mcp-server
    env:
      MY_API_KEY: "…"
      MY_SERVICE_URL: "http://<你的-服務-ip>:9000"

(誠實承認:我當初反射性去包了一層 shell wrapper,把變數 export 好再 exec 真工具——其實不必要,env: 一直都是對的管道,是我當時沒搞清楚,而且把 API key 硬寫進 wrapper script 反而更不安全。這正是這篇的另一半:有時候不是 code 過時,是你對它的理解過時。)

各版本都適用的檢查:MCP 工具連不上時,先確認它要的變數有沒有真的進到子行程(有沒有寫進 config env:),再去懷疑網路。叫你的 agent「列出所有 MCP 工具、各發一次最基本的呼叫測試;失敗那個先確認 env 有沒有真的傳進子行程」。

雷 4:光看 label 會誤判——要確認跑哪個模型,查 base_url 端點比較準

症狀:你的某隻分身(sib)行為跟你預期的模型不太一樣。你去翻設定檔,provider 名字寫著 gemma、default model 也寫 gemma,看起來沒問題。

真相:設定檔裡的 label 只是你當初打的標記——它會參與一些路由(例如挑 provider 專屬的 extra_body),但不代表後端現在真的載入哪顆。我有一隻分身,provider 標籤一直寫 gemma,但那個 base_url 指的端點,某天換腦之後實際在跑 Qwen3.6-27B,label 忘了改。於是我被這個 label 誤導了好幾天——以為在跑 A,其實在跑 B。

設定檔的 label(provider: gemma)是你當初打算跑的標記;curl base_url 的 /v1/models 回報的(Qwen3.6-27B)才是端點現在宣稱在服務的模型。換腦之後 label 忘了改,兩者就漂開,你會被誤導好幾天。

修法 / 檢查:要知道某隻 sib 真的在跑什麼,去 curl 它 base_url 指的端點,別讀設定檔的名字。

curl -s http://<你的-後端-ip>:<port>/v1/models   # 端點回報它宣稱在服務的模型;單一模型後端就等於實際載入的,要更硬的證據就看後端啟動 log 或 /props

這條不是改一次設定就結束——是要養成一個習慣:設定檔記的是「你當初打算跑什麼」,端點回報的才是「現在後端宣稱在跑什麼」;時間久了,兩邊會對不上。 這也是這整篇的核心:設定檔不是故意騙你,是環境改了、卻沒人回頭同步它。要確認現在到底跑什麼,還是看端點實際回的。

雷 5:維運面兩個雷——plist 少了 PATH、還有別亂重啟 dashboard

前面四個是設定檔裡面的;最後兩個是設定檔外面、但一樣會雷到你的。

(a) launchd plist 少了 PATH,agent 的手就「拿不到工具」。Hermes gateway 是用 launchd 起的,plist 的 EnvironmentVariables 如果只給了半套 PATH,gateway 行程就找不到你裝在 ~/.local/bin / ~/bin 的命令列工具(像 qmdmusubi)——助理「有手但抓不到工具」。修法:在 plist 的 EnvironmentVariables 補上完整 PATH(要用絕對路徑,像 /Users/你/.local/bin——launchd 不會展開 ~),子工具需要的話再加 VIRTUAL_ENV,plutil -lint 驗過再重啟。

重啟的正確做法bootout → 動作 → bootstrap,而且別用固定的 sleep 3 去判斷它停了——實測 bootout 指令跑完、服務不一定已經停,通常還要再等幾秒,死等會誤判。改成先抓住舊的 PID、再輪詢 kill -0 <pid> 直到它真的沒了(設個上限別無限等),bootstrap 之後用 launchctl print 確認新服務真的起來,才穩(別直接 pgrep -f gateway 判斷:那顆行程的命令列是 hermes_cli.main gateway run、不是你想的 label,而且別的 profile 也可能一起被抓到,兩邊都會誤判)。

(b) 維護 gateway 的時候,別手動重啟 dashboard。dashboard 跟 bridge 靠一個共用的 session token 互信;你手動重啟 dashboard,會讓兩邊的 token 對不上,結果現有的手機連線就噴 401「bad response」。要重啟的話,先把當前活著的 token 抓下來、再釘回去,才不會斷線。

丟給 agent 的檢查:叫它「確認你的 gateway plist 的 PATH 有含 ~/.local/bin~/bin;然後別碰 dashboard/bridge」。

把這篇丟給你的 agent 跑一遍

這五個雷的共通點,前面說過了:就算噴錯也不指向根因,只讓助理「怪怪的」。 所以最好的用法不是你自己逐條檢查,而是直接叫你的 Hermes 讀這篇、幫自己做一次設定健檢——它有手,跑得動這些檢查。

一份可以照抄的健檢清單(成功條件寫在後面,agent 做完自己知道過了沒):

  1. curl 你後端/propsn_ctx == 你設定的 context_length?(雷 1)
  2. 回一句短話很快、沒有「想很久」?(延遲多少算快看你自己機器的手感)(雷 2)
  3. 每個 MCP 工具都能發一次最基本的呼叫測試、成功?(雷 3)
  4. 每隻 sib 的 base_url 端點 curl /v1/models 回報的模型 == 你以為的?(雷 4)
  5. gateway plist 的 PATH 含 ~/.local/bin~/bin?(雷 5a)

五條全過,你的車子就順了。過不了的那條,回上面對應的段落——修法都在。

這一篇是設定層的健檢;想更往下挖「記憶那一塊怎麼運作」,可以回頭配著第 13 篇看——設定先調順、再回頭理解記憶,順序別反。


同系列文章:Part 17 — 教 Hermes 自己寫技能 · Part 13 — 助理鬼打牆,先別怪模型 · Part 12 — 幫助理接上你的工具

常見問題

Hermes 助理莫名其妙一直「忘記」前面講過的話,是記憶壞了嗎?
常常不是記憶壞,是 Hermes 讀到的 context_length 跟你以為的不一樣。我踩到的舊版 Hermes 只讀 top-level 的 model.context_length,你設在 providers 底下(nested)它讀不到、會掉到一個公版數字,結果在一半就開始壓縮、丟掉前面=健忘。新版 Hermes 已經修了——現在 nested 那層也會讀。不管哪一版,穩的做法是把三個值對一下:後端 /props 的 n_ctx、你設定的 context_length、跟 Hermes 啟動印的 Context limit:,別假設它讀到你以為的值。
為什麼我幫 Hermes 接了一個 MCP 工具,它卻一直說連不上 / 全部失敗?
多半是你在 shell export 的變數沒傳進去。Hermes 開 stdio MCP 子行程時有個安全白名單(_SAFE_ENV_KEYS),只放行 PATH、HOME 這類基本的;你在 shell export 的 API key / 服務位址不在名單內就被濾掉,工具只好 fallback 到 localhost、全部連線失敗,而且只噴一個像網路問題的錯。正解不是包 wrapper,是把變數寫進設定檔的 mcp_servers.<name>.env: 區塊——那一塊 Hermes 一直都會 merge 進子行程。工具連不上時先確認變數有沒有真的進到子行程,再懷疑網路。
怎麼確認我的 sib 到底真的在跑哪個模型?
去 curl 它 base_url 指的那個端點,不要看設定檔裡的 provider 名字。設定裡的標籤(label)只是你當初打的標記、不會自己更新;你換了後端腦、標籤忘了改,它就會一直誤導你。要確認,直接打 base_url 的 /v1/models 或 /props 看它回報什麼模型——那才是端點現在宣稱在服務的(單一模型後端就等於實際載入的那顆)。
這篇適合誰?我還沒裝好 Hermes 可以看嗎?
這是進階篇,預設你已經照入門 1-8 裝好、能用一個 Hermes 助理了。如果你只是想先把助理裝起來、接上手機,先回頭看入門那幾篇。這篇是給「已經在用、想把它調順、或已經開始踩雷」的人。

接著讀

不想錯過新文章?

訂閱我確保不漏接!

隨時一鍵退訂。