[AI] 一句「幫我編譯、執行、開 Edge、截圖」,Agent 到底跟 LLM 講了什麼?

  • 16
  • 0

先講結論,後面很長,其實不一定要看,我只是筆記

很多人在看 Agent 系統時,會不小心把所有事情都算在 LLM 身上。

最後文章就會寫成一種很神奇的敘事:彷彿 LLM 看完一句話,突然就自己把專案 build 好、跑起來、開瀏覽器、順便截圖,像一個住在 GPU 裡的 DevOps 工程師。

但實際上不是這樣。

LLM 比較像「負責判斷下一步要做什麼的人」,真正去動手做事的是 Agent。

所以這種系統不是這樣:

使用者輸入
-> LLM
-> 回答

而比較接近這樣:

使用者輸入
-> Agent 組 context + 注入 tools
-> LLM 判斷下一步
-> Agent 執行工具
-> Agent 把結果丟回 LLM
-> LLM 再判斷下一步
-> 重複幾輪
-> 最後才輸出人類看得懂的回答
    

一句話:LLM 負責想,Agent 負責做。

這件事如果一開始沒分清楚,後面你在看 code、debug、或寫文章時,很容易全部混在一起。然後最後每個元件都被誤會,連 bug 都不知道該怪誰。

整篇用同一個例子講,不然一旦開始抽象化,大家的大腦就會自動切去別的地方

幫我把專案編譯,執行起來,用 Edge 開啟,截圖給我看

這句話對人來說只有一句,但對系統來說,它其實是一個多步驟 workflow。

因為這裡面至少隱含了下面幾件事:

1. build 專案
2. run 專案
3. 拿到 URL
4. 用 Edge 開啟
5. 截圖
6. 回結果

關鍵點在於:使用者不會幫你拆步驟,系統要自己拆。

而拆步驟的過程,有些是 Agent 做的,有些是 LLM 做的。這就是整篇要講的核心。

先把責任分清楚,不然後面會一直混

在進入 transcript 前,先把邊界講清楚。

1. 收使用者輸入
2. 組 system prompt / project context / history
3. 把可用 tools 告訴 LLM
4. 收到 tool call 後真的執行工具
5. 做權限檢查與 guardrails
6. 壓縮過長的 tool output
7. 控制 loop,不讓系統鬼打牆

LLM 通常負責這些事情:

1. 讀懂使用者要什麼
2. 根據上下文判斷下一步
3. 選擇要呼叫哪個 tool
4. 產生 tool arguments
5. 根據 tool result 調整後續策略
6. 最後輸出自然語言答案

所以你可以先記一個非常實用的判斷方式:

凡是偏 orchestration、execution、guardrails 的,多半是 Agent;凡是偏 reasoning、planning、tool selection 的,多半是 LLM。

Round 0:Agent 送給 LLM 的原文,長什麼樣子?

這一段是最容易被忽略,但其實最值得打開來看的部分。

因為 LLM 並不是直接看到「使用者原封不動的一句話」,而是看到 Agent 幫它整理過的一整包 context。

也就是說,LLM 看到的世界,通常是 Agent 預先整理過的版本。

[
  {
    "role": "system",
    "content": "You are an AI agent that can operate on a software project. Use tools when necessary. Do not pretend actions succeeded unless tool results confirm success."
  },
  {
    "role": "system",
    "content": "[project]\\nPath: /workspace/MyApp\\nStack: ASP.NET Core"
  },
  {
    "role": "system",
    "content": "[capability]\\nCan build, run, open browser, take screenshot"
  },
  {
    "role": "system",
    "content": "[workflow]\\nUser intent likely includes build, run, browser open, and screenshot"
  },
  {
    "role": "user",
    "content": "幫我把專案編譯,執行起來,用 Edge 開啟,截圖給我看"
  }
]
    

除了 messages 之外,Agent 還會把可用 tools 一起交給 LLM。

[
  { "name": "build_project" },
  { "name": "run_project" },
  { "name": "open_browser" },
  { "name": "take_screenshot" }
]
    

這裡有兩個很重要的點。

第一,LLM 能看到哪些資訊,不是它自己決定的,是 Agent 組給它的。

第二,LLM 能用哪些工具,也不是它自己發明的,是 Agent 暴露給它的。

所以如果你哪天看到模型選錯工具,先別急著怪模型,先看是不是 tools 設計就很迷惑人。

Round 1:LLM 第一次回來,通常不是回答,而是 tool call

在這個案例裡,LLM 第一次大概率不會直接回答你「好的我幫你完成了」。

因為它根本還沒做任何事,它只會先決定第一步該做什麼。

{
  "role": "assistant",
  "content": "",
  "tool_calls": [
    {
      "id": "call_1",
      "function": {
        "name": "build_project",
        "arguments": "{}"
      }
    }
  ]
}
    

這個 response 的真正意思是:

「我判斷第一步應該先 build_project。」

注意,它沒有真的 build。

它只是產生了一個格式正確的 tool call,要求 Agent 去做事。

Round 1.5:Agent 才真的去 build

收到上面的 tool call 之後,真正開始工作的才是 Agent。

這一步通常包含:

1. 找到 build_project 對應的 tool
2. 檢查這輪是否允許執行
3. 檢查 command 是否安全
4. 真正執行 build
5. 取得輸出
6. 必要時壓縮 log

假設 build 成功,Agent 拿到的結果可能會被整理成這樣:

Build succeeded
    

這裡要再講一次:全部都不是 LLM 做的。

LLM 不會自己開 terminal,不會自己下 dotnet build,也不會自己檢查 shell command 安不安全。

Round 2:Agent 把 build 結果丟回 LLM

Agent 執行完 tool 之後,不是直接把結果拿去回使用者。

它會先把結果包成一則 role=tool 的訊息,再送回 LLM,讓 LLM 繼續判斷下一步。

{
  "role": "tool",
  "tool_call_id": "call_1",
  "name": "build_project",
  "content": "Build succeeded."
}
    

這段內容不是寫給使用者看的,而是寫給 LLM 看的。

它的意思是:剛剛你要求的 build_project,我真的幫你做了,結果成功。

Round 3:LLM 根據 build 結果,決定下一步是 run

現在 LLM 的上下文裡,已經有「build succeeded」這個訊號了。

所以它很自然會往下一步走:

{
  "role": "assistant",
  "content": "",
  "tool_calls": [
    {
      "id": "call_2",
      "function": {
        "name": "run_project",
        "arguments": "{}"
      }
    }
  ]
}
    

這裡它做的事情不是執行 run,而是:

根據目前上下文,判斷下一步應該叫 Agent 去 run_project。

然後 Agent 真的去執行之後,可能拿到原始輸出:

Application started. Listening on http://localhost:5000
    

但很多 runtime 不會把完整 output 原封不動塞回去,因為 log 很容易長到失控。

比較常見的是 Agent 先做一次壓縮,只留下對下一輪真正有用的資訊:

{
  "role": "tool",
  "tool_call_id": "call_2",
  "name": "run_project",
  "content": "Run succeeded. URL: http://localhost:5000"
}
    

這一步也很重要,因為它再次說明了一件事:

工具輸出的整理與壓縮,也是 Agent 在做,不是 LLM 在做。

Round 4:LLM 讀到 URL 之後,才知道該開哪裡

現在 LLM 看到了這條訊息

Run succeeded. URL: http://localhost:5000
    

所以它下一步就能很合理地要求打開瀏覽器,而且把參數補齊:

{
  "role": "assistant",
  "content": "",
  "tool_calls": [
    {
      "id": "call_3",
      "function": {
        "name": "open_browser",
        "arguments": "{\"browser\":\"edge\",\"url\":\"http://localhost:5000\"}"
      }
    }
  ]
}
    

這裡就能很清楚看出 LLM 的價值:它不是單純照表操課,而是會根據前一輪 tool result 把參數接起來。

但再次強調,它還是沒有真的打開 Edge。那件事依然是 Agent 去做。

Round 5:Agent 開完 Edge,再把結果送回去

假設瀏覽器順利打開,Agent 會把結果包成下一條 tool message:

{
  "role": "tool",
  "tool_call_id": "call_3",
  "name": "open_browser",
  "content": "Edge opened successfully."
}
    

這一輪之後,LLM 就會知道:瀏覽器已經開好了,接下來可以截圖。

Round 6:LLM 要求截圖

{
  "role": "assistant",
  "content": "",
  "tool_calls": [
    {
      "id": "call_4",
      "function": {
        "name": "take_screenshot",
        "arguments": "{}"
      }
    }
  ]
}
    

然後 Agent 真正去截圖,成功後再回一條 tool result

{
  "role": "tool",
  "tool_call_id": "call_4",
  "name": "take_screenshot",
  "content": "Screenshot saved: /artifacts/screen.png"
}
    

到了這裡,整個任務才算真的完成。

不是因為 LLM 說完成了,而是因為工具真的執行過,而且結果回來了。

最後一輪:LLM 才把整件事整理成給人看的回答

前面幾輪其實都不是在跟使用者講話,而是在做系統內部協作。

直到所有必要步驟都完成後,LLM 才會輸出最後一段人類可讀的答案

{
  "role": "assistant",
  "content": "專案已成功編譯並執行,已使用 Edge 開啟並完成截圖。"
}
    

所以你在產品 UI 上最後看到的,通常只是一個收尾。

真正精彩、也真正麻煩的地方,其實都藏在前面的 tool-calling loop 裡。

把整件事濃縮成一張圖

使用者: 幫我把專案編譯,執行起來,用 Edge 開啟,截圖給我看

Agent:
  - 組 context
  - 提供 tools

LLM:
  - 先選 build_project

Agent:
  - 真的去 build
  - 回傳 Build succeeded

LLM:
  - 再選 run_project

Agent:
  - 真的去 run
  - 回傳 URL

LLM:
  - 再選 open_browser

Agent:
  - 真的開 Edge

LLM:
  - 再選 take_screenshot

Agent:
  - 真的截圖

LLM:
  - 最後整理成人話回答
    

這個順序不能倒過來看。

你如果只看最後一句話,就會以為整件事是 LLM 一次完成的;但你如果把 transcript 攤開來看,就會發現 LLM 比較像是在指揮流程,Agent 才是在工地裡真的搬磚的人。

最重要的理解

LLM 不會真的做事
LLM 只是在決定下一步
真正做事的是 Agent + Tools
真正把系統串起來的是 Orchestration
    

所以你之後在看任何 Agent codebase 時,最值得先問的不是「它用哪個 model」,而是下面這三題:

1. Agent 到底送了什麼給 LLM?
2. LLM 回來的是文字,還是 tool call?
3. tool 是誰真的執行的?結果又是誰整理後再送回去的?
    

這三題一旦分清楚,你就比較不會把 Agent orchestration 跟 LLM reasoning 寫成一鍋粥。

也比較不會在模型連續 build 三次時,第一反應就是覺得模型智商有問題。很多時候,那其實是 runtime 在提醒你:哥,您的 loop control 還沒寫完。

 

 

---

The bug existed in all possible states.
Until I ran the code.