315行程式碼構建程式設計助手,Go大佬揭開智慧體的「神秘面紗」

選自ampcode.com
作者:Thorsten Ball
機器之心編譯
知名 Go 大佬 Thorsten Ball 最近用 315 行程式碼構建了一個程式設計智慧體,並表示「它執行得非常好」且「沒有護城河」(指它並非難以複製)。
Thorsten Ball 在程式設計領域以其對系統程式設計和程式語言的深入研究而聞名,尤其擅長直譯器、編譯器和虛擬機器等主題。他撰寫的《用 Go 語言自制編譯器》和《用 Go 語言自制直譯器》則被視為編譯原理領域的「入門平替」。
雖然這個程式設計智慧體無法和 Claude、Gemini 等推出的編碼功能相媲美,卻為初學者提供了一個探索智慧體的良好學習範例。這反映了他一貫的理念:透過實踐和開源專案揭開技術的「神秘面紗」。
Thorsten Ball 在部落格中分享了他的具體操作步驟。(注:本文中的程式碼截圖可能並不完整,詳細內容請參閱原部落格。)
部落格地址:https://ampcode.com/how-to-build-an-agent
乍看之下,智慧體編輯檔案、執行命令、自行解決錯誤似乎很複雜,但實際上只需一個大語言模型、一個迴圈和足夠的 tokens。構建一個小型的智慧體並不需要太多工作,少於 400 行程式碼即可實現,且大部分是樣板程式碼。
接下來將展示如何從零開始逐步構建一個「game changer」,讀者可以嘗試親自動手編寫程式碼。
準備工作
首先準備好我們的「文具」:
  • Go
  • ANTHROPIC_API_KEY
鉛筆出場!讓我們直接開始,用四個簡單的命令來設定一個新的 Go 專案:
現在,開啟 main.go,作為第一步,將需要的東西的框架放入其中:
是的,這還沒有編譯。但是我們這裡有一個 Agent,它可以訪問 anthropic.Client(預設情況下,它會查詢 ANTHROPIC_API_KEY),並且可以透過從終端上的 stdin 讀取來獲取使用者訊息。
現在讓我們新增缺少的 Run() 方法:
這並不多,對吧?90 行程式碼,而其中最重要的就是 Run() 中的這個迴圈,它讓我們能夠與 Claude 對話,但這已經是這個程式的核心了。
對於一個核心來說,這個過程相當簡單:我們首先列印一個提示,詢問使用者輸入內容,將其新增到對話中,傳送給 Claude,然後將 Claude 的回覆新增到對話中,打印出回覆,然後再迴圈進行。
你日常使用的 AI 聊天應用其實就是這樣的,只不過這是在終端中實現的。
執行它:
然後你可以和 Claude 對話了,就像這樣:
注意到我們在多個回合中保持了同一個對話嗎?它記住了我們在第一條訊息中的名字。每次回合對話都在增長,我們每次都發送整個對話。伺服器——準確來說是 Anthropic 的伺服器——是無狀態的。它只看到 conversation 片段中的內容,維護這一點由我們來負責。
現在繼續,因為輸出結果很糟糕,這還不是一個智慧體。什麼是智慧體?可以這樣定義:一個具有訪問工具能力的大語言模型(LLM),這些工具使其能夠修改上下文視窗之外的內容。
新增工具
一個具有工具訪問能力的大語言模型(LLM)是什麼呢?
工具的定義是這樣的:你向模型傳送一個 prompt,告知它在想要使用「工具」時應以特定方式回覆。然後,你接收訊息後「使用工具」執行該指令,並返回結果。其他一切都是在這一基礎上進行的抽象。
想象一下,你正在與朋友交談,你告訴他們:「在接下來的交流中,如果你想讓我舉起手臂,就眨眼。」這種表達方式雖然有些奇怪,但概念非常容易理解。
我們已經能夠在不改變任何程式碼的情況下嘗試這種方法。
我們告訴 Claude,當它想知道天氣時,就用 get_weather 來「眨眼」。接下來的步驟是舉起我們的手臂,並回復「工具的結果」。
第一次嘗試非常成功!
這些模型經過訓練和微調,能夠使用「工具」,並且非常注重利用這些工具。到 2025 年,它們在一定程度上「知道」自己不具備所有資訊,因此可以藉助工具獲取更多資訊。(雖然這不是完全準確的描述,但目前這個解釋足夠了。)
總結關於工具使用的關鍵點有:
  • 你告訴模型有哪些工具是可用的。
  • 當模型想要使用工具時,它會通知你,你執行工具並將響應傳送回模型。
為簡化步驟(1),大型模型提供商已經內建了 API,用於傳送工具定義。
現在,讓我們開始構建我們的第一個工具:read_file。
read_file 工具
為了定義 read_file 工具,我們將使用 Anthropic SDK 建議的型別,但請記住:在底層,這一切最終都會變成傳送給模型的字串。這一切都是「如果你希望我使用 read_file,就眨眼」。
我們要新增的每個工具都需要以下內容:
• 名稱
• 描述,告訴模型這個工具的功能、何時使用、何時不使用、返回什麼等等。
• 輸入模式,描述為 JSON schema,說明該工具期望什麼輸入以及輸入的形式。
• 一個實際執行工具的函式,使用模型傳送給我們的輸入並返回結果。
那麼讓我們把這些新增到我們的程式碼中。
現在我們給出 Agent 工具定義:
並將它們傳送到 runInference 中的模型:
使用者傳送工具定義,Anthropic 在伺服器上將這些定義包裝在這個系統提示中(並不多),然後將其新增到對話中,如果模型想要使用該工具,它就會以特定的方式回覆。
好的,所以工具定義正在傳送,但我們還沒有定義任何工具。讓我們來定義 read_file 工具。
這並不多,是不是?這只是一個函式,ReadFile,以及模型將看到的兩個描述:一個是描述工具本身的 Description(Read the contents of a given relative file path. …),另一個是該工具擁有的單一輸入引數的描述(The relative path of a …)。
ReadFileInputSchema 和 GenerateSchema 之類的工作是做什麼的?我們需要這些來為工具定義生成一個 JSON 模式(schema),然後傳送給模型。為此,我們使用 jsonschema 包,需要進行匯入和下載:
然後執行以下命令:
go mod tidy
然後,在 main 函式中,我們需要確保我們使用定義:
是時候嘗試一下了!
哇哦,它想要使用這個工具!顯然,你的輸出可能會有些不同,但聽起來 Claude 確實知道它可以讀取檔案,對吧?
問題是我們沒能聆聽!當 Claude 給出提示時,我們沒有去注意這一點,我們需要解決這個問題。
透過一個簡單、快捷且異常敏捷的動作,我們可以透過替換智慧體的 Run 方法來實現:
可以說,這段過程 90% 是固定格式,只有 10% 是關鍵部分:當我們從 Claude 收到訊息時,我們會檢查 Claude 是否要求我們執行某個工具,透過檢視內容的型別是否為「tool_use」來判斷;如果是這樣,我們就交給 executeTool 處理,在本地登錄檔中透過名稱查詢該工具,解析(unmarshal)輸入,執行它,並返回結果。如果出現錯誤,我們會翻轉一個布林值。就是這樣。
(是的,的確有一個迴圈套在另一個迴圈裡,但這不重要。)
我們執行工具,將結果發回給 Claude,然後再次請求 Claude 的響應,就是這麼簡單。
echo'what animal is the most disagreeable because it always says neigh?' >> secret-file.txt
這會在我們的目錄中生成一個名為 secret-file.txt 的檔案,裡面包含一個神秘的謎題。
就在同一個目錄中,我們執行新的工具使用智慧體,要求它檢視該檔案:
你只需要給它一個工具,它就會在認為有助於解決任務時使用它。我們沒有說「當用戶詢問檔案時,閱讀檔案」,也沒有說「如果某個東西看起來像是檔名,找出如何讀取它」。我們說的是「幫我解決這個檔案裡的問題」,Claude 就意識到它可以讀取檔案來回答這個問題,然後就去做了。
當然,我們可以加以具體引導並鼓勵使用某個工具,但它基本上可以自主完成這些任務:
作者接下來還介紹了新增 list_files(列出檔案的工具)和 edit_file(讓 Claude 編輯檔案的工具)的方法,感興趣的讀者可以閱讀部落格原文。
© THE END 
轉載請聯絡本公眾號獲得授權
投稿或尋求報道:[email protected]


相關文章