上一篇 Agent Loop把執行過程壓成了一個循環:模型提出動作,程序執行工具,再把結果交回模型。其中的 runTool(call) 還只是一個函數名。這一篇把它打開:讀取文件時讀的是哪裏的文件,運行測試時誰在執行,修改後的代碼又保存在哪裏?
OpenAI 的 《From model to agent: Equipping the Responses API with a computer environment》從 Shell 入手,再介紹容器中的文件、數據和網絡。它讓我更關注 Agent 的另一半:模型決定下一步,計算環境承載這一步真正發生。
- 行動入口: Shell 怎樣把工具請求接到現成的計算機程序。
- 執行落點: 文件、進程和容器怎樣承載工作,數據庫與網絡怎樣連接其他資源。
- 觀察結果: 實際輸出怎樣包裝為 Tool Result,再回到 Agent Loop。
繼續沿用上一篇的 Java 排查場景:Agent 讀取 UserService.java,修改實現,再運行測試。這裏討論的是這幾步怎樣落到真實系統上,不是假設模型已經掌握了整台電腦。
一、Shell:把模型提出的動作接到現成程序h2
模型輸出一條命令,與命令已經運行,是兩件事。比如:
grep -R -n "UserService" src/如果它只出現在回答裏,就是一段文本。通過 Shell Tool 提交後,還需要運行時把命令交給執行環境,由 Shell 啓動 grep,讀取當前項目中的文件,再返回匹配結果。
所以,“模型本身沒有電腦”更準確的意思是:一次模型推理不會自動獲得項目目錄、系統進程或數據庫連接。 模型當然運行在計算硬件上,但那不等於它能夠直接操作用户的工作空間。可操作哪些資源,來自外部系統接入的執行能力。
Shell 的價值,是把大量已有程序變成可組合的行動方式:
| 想完成的工作 | 環境中可以使用的程序 |
|---|---|
| 查看目錄、讀取文件 | ls、cat |
| 搜索實現和引用 | grep、rg |
| 查看代碼差異 | git diff |
| 編譯、運行測試 | Maven、Gradle 或項目自己的腳本 |
| 處理數據、生成文件 | Python、Node.js 等運行時 |
| 發出網絡請求 | curl 等客户端 |
這裏以常見的 Unix 命令為例,具體能力取決於環境實際安裝了什麼。給 Agent 一個 Shell,不等於同時安裝了 Java、Maven 和所有依賴。
這也是 Shell 和專用工具的一個直觀區別:搜索 Java 類,不一定需要單獨做一個 search_java_class()。執行環境已經有搜索程序時,模型可以通過 Shell 請求使用它。通用程序提供了行動空間,運行時把模型的請求接到這些程序上。
原文還區分了兩種接法:自定義工具把執行交回客户端,由應用自己的 Harness 接手;託管 Shell 則由 Responses API 把命令送到容器運行時。兩者的執行位置不同,但都不是模型自己執行命令。
二、Filesystem:文件既是工作材料,也是環境狀態h2
Agent 讀取代碼,是從執行環境的文件系統拿到內容;Agent 修改代碼,改變的也是那裏的文件。工作目錄不是對話中的一個裝飾字段,它決定了相對路徑指向哪個項目。
假設這次工作使用下面的目錄:
/workspace/user-service/├── pom.xml├── src/main/java/.../UserService.java├── src/test/java/.../UserServiceTest.java└── target/這只是示例目錄,不是 Responses API 固定要求的路徑。它表達的是:源碼、測試和構建輸出,都有實際存放的位置。
假設工具先讀取 UserService.java,隨後應用了一次修改。在同一個仍然存在的工作空間中,再讀取這條路徑,拿到的應該是修改後的內容,而不是最初的版本。環境不會因為模型進行了下一次推理,就自動還原。
這裏有一個重要區別:
| 所在位置 | 保存的是什麼 |
|---|---|
| 模型 Context | 本次推理能夠使用的指令、消息、工具調用和結果 |
| 文件系統 | 項目文件、中間產物、日誌等實際內容 |
文件改了,不代表模型已經看到了最新文件;模型曾經看過文件,也不代表文件一直沒有變化。 兩者之間需要讀取結果、修改結果或差異等反饋來連接。
例如,工具執行一次修改後,返回“已應用補丁”,這能説明修改操作的狀態,但未必包含整個新文件。模型需要進一步核對時,可以請求讀取文件或查看 git diff。反過來,把舊代碼保留在 Context 中,也不會阻止外部程序繼續修改磁盤上的內容。
因此,文件系統不只是“給模型提供文本”的地方。它承載實際工作,也讓連續的工具調用能夠在已有成果上繼續推進。
三、Process:驗證來自程序運行,不是模型對運行結果的預測h2
代碼寫回文件後,Agent 可以提出運行測試:
mvn test真正發生的是:運行時在指定工作目錄啓動命令,Maven 讀取項目配置,並按構建生命週期調用相應插件,在 Java 運行環境中完成編譯和測試等步驟。這裏執行的是實際程序,使用的是實際項目文件。Maven 的構建生命週期文檔也將 test 定義為構建中的一個階段,而不是一段文字指令。
假設測試返回下面的摘要:
Tests run: 19, Failures: 1, Errors: 0, Skipped: 0BUILD FAILURE這段示例表示 19 個測試中有一個失敗。它的意義不在於數字,而在於來源:測試框架實際運行後的輸出,能夠與模型對修改效果的判斷相互核對。
但“運行一次命令”也不是一個只有成功或失敗的黑盒。需要區分幾個信息:
- stdout: 程序的標準輸出,可能包含結果、日誌或進度。
- stderr: 程序的標準錯誤輸出,可能包含診斷,也可能只是警告。
- 執行狀態: 進程是已經退出、仍在運行,還是超時。
- exit code: 進程退出時的狀態碼,需要結合對應程序的語義解讀。
例如,“找不到 mvn”説明測試程序根本沒有成功啓動,不能據此説代碼測試失敗。stderr 有內容也不必然表示失敗;退出碼為零,則仍需要確認運行了哪些檢查,而不是直接推導出整個項目沒有問題。
啓動服務也是一樣。命令被接受、進程已經啓動、端口開始監聽、接口能夠響應,是不同的狀態。模型需要依據執行端返回的狀態繼續判斷,而不是把“已經發出啓動命令”説成“服務已經可用”。
四、Container:為這些操作準備一份可用的工作空間h2
要讓前面的步驟連起來,執行端至少需要項目文件、Shell、構建工具、運行時和依賴。容器的一項直接價值,就是把這些工作條件組織到一個可以運行程序的環境中。
對一個 Java 項目,可以把準備好的環境理解為:
工作环境├── 项目目录与文件系统├── Shell 和系统工具├── JDK、Maven 等项目所需程序├── 已准备或可获取的依赖└── 本次工作产生的文件与进程這是所需條件的示意,不代表任意託管容器都預裝了這些程序。具體任務需要什麼,就要檢查實際環境有沒有。
把容器類比成 Agent 的“工作台”是有幫助的:文件放在這裏,程序在這裏運行,中間結果也可以繼續在這裏使用。但它不是一台完整虛擬機的同義詞。Docker 的基礎説明將容器解釋為帶有所需文件的隔離進程,不能把這個類比理解成模型擁有了一台獨立操作系統。
另外,模型在哪裏推理,與工具在哪裏執行,可以是兩個位置。 模型服務可以在雲端,工具可以接到本地工作目錄,也可以接到託管容器。原文展示的是託管容器這一種接法,不是説所有 Agent 都必須在容器裏行動。
同一工作空間中的文件能夠被後續調用繼續使用,也不等於它們會永久保存。環境被替換或銷燬後,不能假設原來的文件仍然存在。這是執行環境的生命週期問題,不是模型是否“記得”某段對話的問題。
五、Database 與 Network:行動也可以落到其他系統h2
文件和進程之外,Agent 還可以通過工具訪問數據庫與網絡服務。區別是執行落點不同,基本連接方式沒有改變。
| 動作 | 實際執行者 | 可以返回什麼 |
|---|---|---|
| 查詢本地 SQLite 文件 | 執行環境中的數據庫引擎 | 查詢結果或數據庫錯誤 |
| 通過 SQL Tool 查詢遠端數據庫 | 工具實現與所連接的數據庫服務 | 行、列及執行狀態 |
| 通過 HTTP Tool 發出請求 | HTTP 客户端與遠端 Web Service | 狀態碼、響應體或連接錯誤 |
例如,分析銷售數據時,可以先把結構化數據整理到 SQLite,再查詢需要的行,而不是把整張表塞進模型輸入。SQLite 的使用説明也將數據分析列為它的一種適用場景。這裏執行篩選的是數據庫引擎,模型獲得的是篩選結果。
網絡請求也是一樣:模型可以提出獲取某個接口的數據,但實際連接由客户端建立,遠端服務返回響應。環境中有沒有網絡、能訪問哪些地址、是否具備所需連接條件,都不是模型生成一個 URL 就能決定的。
需要注意,數據庫或服務可訪問,不等於它們都運行在當前容器內部。 SQLite 可以是本地文件,業務數據庫可以在另一台服務器上;環境提供的是操作入口與連接條件。
這裏也不必把每次行動都説成“改變世界”。讀取文件、執行 SELECT、請求一份數據,主要是在獲取已有狀態;修改文件、寫入記錄、提交請求,才可能改變相應狀態。兩類行動都可以產生供模型使用的觀察結果。
六、Observation:環境結果怎樣接回 Agent Loop?h2
執行環境不會把自己全部“裝進模型”。它通過工具返回一部分可觀察信息,運行時再把這些信息組織為 Tool Result。
以 Shell 為例,一條已經退出的命令可以返回這樣的結果。下面借用 Shell Tool 的字段説明,內容是示例,不是本次博客實際運行的測試記錄:
{ "type": "shell_call_output", "call_id": "call_run_tests", "output": [ { "stdout": "Tests run: 19, Failures: 1, Errors: 0, Skipped: 0", "stderr": "", "outcome": { "type": "exit", "exit_code": 1 } } ]}call_id 關聯的是模型提出的那次請求;輸出與執行狀態説明的是環境裏實際發生了什麼。OpenAI Docs 的 Shell 文檔也區分了本地執行、託管執行以及對應的輸出結果。
模型下一次拿到這些信息後,可以決定查看失敗測試、讀取報告,或者説明執行為什麼被阻塞。文件內容、查詢結果和 HTTP 響應也以類似方式成為 Observation:它們是模型對環境的觀察,不是環境本身。
這個區別很實用。日誌被截斷時,不能把沒返回的部分當作已經看過;文件發生變化卻沒有讀回時,不能假設 Context 中的舊內容自動更新。運行時提供什麼反饋,會直接影響下一次判斷能夠依據什麼。
於是,第三篇和第四篇就接在了一起:
| 視角 | 要理解的連接 |
|---|---|
| 03 · Agent Loop | 模型輸出怎樣接上工具結果,再進入下一次判斷 |
| 04 · Computer Environment | 工具請求怎樣落到真實資源,再產生可以回傳的結果 |
我把這兩層合起來理解為:Loop 組織決策與反饋,Environment 承載執行與狀態。 模型提出動作,運行時對接執行端,文件或程序產生結果,再由結果影響下一次行動。
原文還討論了 Compaction 和 Skills,分別涉及增長中的模型上下文,以及可複用的任務知識與流程。它們與計算環境配合,但不是執行環境本身:文件在哪裏、程序在哪裏運行,與模型這次帶上哪些信息,是不同的問題。
執行通路清楚以後,一個更具體的設計問題也就出現了:工具的名稱、參數和結果應該怎樣表達,才能讓模型穩定地選對、用對,並理解執行結果?
幾個關鍵問題,串起執行環境與反饋h2
模型在雲端運行,修改的文件就一定在雲端嗎?
不一定。推理服務的位置與工具執行端的位置可以不同。要看 Runtime 將文件操作交給了哪裏:本地工作目錄、託管容器,還是遠端服務。模型的位置不能直接推出文件的位置。
有 Shell,為什麼還會出現“命令不存在”?
Shell 是調用程序的入口,不是所有軟件的集合。運行 Maven 需要對應程序與 Java 環境,執行項目腳本還可能需要依賴。命令文本可以被模型正確生成,但執行環境仍然可能沒有滿足運行條件。
文件已經修改,為什麼還要把結果傳回模型?
修改改變了外部狀態,不會直接改寫模型當前輸入。只有通過修改結果、差異或重新讀取等反饋,後續判斷才能知道哪些內容發生了變化。磁盤上的新文件與 Context 中的舊內容可以同時存在。
測試命令失敗,就能判斷是代碼改錯了嗎?
不能只憑失敗狀態下結論。需要區分程序沒有啓動、依賴準備失敗、編譯失敗與測試斷言失敗。它們都可能阻止命令成功結束,但指向的原因不同;執行狀態和輸出需要一起看。
容器保留了文件,就等於模型保留了這些信息嗎?
不是。文件存在意味着它仍可被工具讀取,不代表全部內容都進入了這次模型輸入。反過來,模型輸入裏有某份文件的內容,也不保證原工作空間仍然存在。環境狀態與模型上下文各自有不同的邊界。