在Amazon Bedrock上實現程序化工具調用
程序化工具調用(PTC)是一種新的範式,通過讓模型生成代碼來編排多個工具調用,從而減少延遲和令牌消耗。本文介紹了三種在Amazon Bedrock上實現PTC的方法:基於ECS的自託管Docker沙盒、使用Bedrock AgentCore Code Interpreter的託管方案,以及兼容Anthropic SDK的代理路徑。
程序化工具調用(PTC)正在改變大語言模型(LLM)與外部工具交互的方式。在傳統的工具調用流程中,每次工具調用都需要一次完整的模型往返:模型調用工具、接收結果、推理、調用下一個工具,如此循環。對於需要多次工具調用的工作流,這會累積延遲和令牌消耗,因為每個中間結果都必須經過模型的上下文窗口。
PTC採取了不同的方法。模型編寫Python代碼,在沙盒執行環境中程序化地調用多個工具。代碼可以包含循環、條件判斷、過濾和聚合邏輯。模型只需推理一次生成代碼,執行環境處理工具調用,僅將最終處理結果返回模型上下文。這大大降低了多工具工作流的延遲和令牌使用。PTC特別適合大數據處理、精確數值計算、多步驟流程編排以及隱私敏感場景——這些場景中原始數據不應進入模型上下文。
PTC最初是特定於提供商的功能,但底層模式——模型生成代碼、沙盒執行、僅最終輸出返回上下文——是模型無關的。在本文中,我們展示了在Amazon Bedrock上實現PTC的三種方法:基於ECS的自託管Docker沙盒以實現最大控制、使用Amazon Bedrock AgentCore Code Interpreter的託管方案,以及通過代理實現Anthropic SDK兼容路徑供喜歡該開發體驗的團隊使用。
傳統工具調用的瓶頸
考慮一個例子:“哪些工程團隊成員超出了Q3的差旅預算?”在傳統工具調用中(假設沒有並行函數調用),模型必須:調用工具獲取團隊成員列表(20人);為每個人調用工具獲取報銷記錄(20次單獨調用,每次返回50-100行項目);調用額外工具獲取預算閾值;將超過2000條報銷記錄送入上下文窗口;用自然語言推理整個數據集以過濾、比較和總結。這些工具調用中的每一次都需要一次完整的模型往返,產生三個問題:令牌消耗(每個中間結果都經過上下文)、延遲(20次順序調用意味着20次推理往返)和準確性(讓語言模型用自然語言過濾和比較數千條記錄容易出錯,而幾行Python代碼就能精確處理)。
PTC如何解決
PTC翻轉了模式。模型編寫一個單一Python代碼塊來編排工具調用、處理結果並僅返回最終輸出。以相同的費用審計為例,啓用PTC時,模型生成以下代碼:
import asyncio import json
team_json = await get_team_members(department="engineering") team = json.loads(team_json)
expense_tasks = [get_expenses(employee_id=m["id"], quarter="Q3") for m in team] expenses_results = await asyncio.gather(*expense_tasks)
exceeded = [] for member, exp_json in zip(team, expenses_results): expenses = json.loads(exp_json) total_travel = sum(e["amount"] for e in expenses if e["category"] == "travel" and e["status"] == "approved") if total_travel > 5000: budget_json = await get_custom_budget(user_id=member["id"]) budget = json.loads(budget_json) limit = budget["budget_limit"] if total_travel > limit: exceeded.append({"name": member["name"], "spent": total_travel, "limit": limit, "exceeded_by": total_travel - limit})
print(f"{len(exceeded)} members exceeded budget:") print(json.dumps(exceeded, indent=2))
注意兩點:第一,asyncio.gather()並行發出所有20個費用查詢,工具調用幾乎同時發生;第二,過濾、聚合和預算比較在Python中完成,而非自然語言。只有最終的print()輸出返回模型上下文窗口,超過2000條原始費用記錄不會觸及它。模型僅採樣兩次:一次生成代碼,一次解讀最終輸出。中間的所有操作(工具調用、數據處理、過濾)都在容器內進行,無需額外模型推理。
第一部分:使用Amazon Bedrock和Amazon ECS的自託管PTC
自託管的理由
託管PTC實現依賴提供商管理的沙盒環境,但自託管也有充分理由:模型無關(支持Amazon Bedrock上的多種模型,如Claude、Qwen、MiniMax、Llama、Nova等)、完全控制(可定製沙盒環境、安裝特定領域的Python包、配置安全策略以滿足需求)以及私有部署(將代碼執行和中間數據保留在自己的AWS賬户內)。
架構
自託管解決方案有兩個組件:編排器(您的應用程序,如ECS任務、Lambda或計算資源,使用Boto3調用InvokeModel API,管理Docker沙盒生命週期並處理工具調用循環)和Docker沙盒(執行模型生成的Python代碼的隔離容器,通過標準輸入/輸出流與編排器通信)。核心思路很簡單:將通常放在tool_config中的工具定義注入系統提示中,並指示模型編寫編排這些工具的Python代碼。生成的代碼在Docker沙盒中運行,編排器作為控制平面,通過IPC攔截工具調用,在外部執行工具,並將結果注入回沙盒。
系統提示
系統提示是使模型表現得像原生支持PTC的關鍵部分。它描述執行環境、可用工具和生成代碼的規則。提示指導模型產生結構良好的Python代碼,遵循原生PTC實現的相同模式:單代碼塊、異步工具調用和print()輸出。
核心組件
SandboxExecutor是中心組件,管理隔離Docker容器的生命週期,安全執行模型生成的代碼,並處理工具調用的IPC協議。系統使用雙進程架構:編排器為每個代碼執行請求啓動一個Docker容器,通過標準I/O流通信,容器將工具調用請求寫入stderr,編排器通過stdin注入工具結果。
Runner腳本由編排器動態生成,在啓動時注入每個Docker容器,處理代碼執行、IPC協議和工具函數生成。它支持兩種執行模式:單次模式(執行代碼後退出,適用於無狀態的一次性任務)和循環模式(保持容器運行以接受多次代碼執行,支持會話重用和狀態保持)。
IPC協議
為了在文本流中可靠地分離不同消息類型,系統定義了邊界標記:PTC_TOOL_CALL / PTC_END_CALL包裝工具調用請求(工具名稱+參數為JSON),PTC_OUTPUT標記代碼執行的最終輸出。當Runner腳本在執行代碼中遇到工具調用時,它將調用序列化為JSON,在標記邊界之間寫入stderr,並在stdin上阻塞等待結果。編排器讀取stderr,解析工具調用,執行工具,並將結果寫回stdin。Runner腳本解除阻塞並繼續執行。
編排器循環
在Amazon Bedrock上啓用PTC需要三個元素:指示模型編寫Python代碼進行工具編排的系統提示、模型用於將代碼提交到沙盒的execute_code工具定義,以及嵌入系統提示中的業務工具描述(不作為單獨的Amazon Bedrock工具)。編排器將Amazon Bedrock和Docker沙盒聯繫起來,核心循環包括調用Bedrock、在沙盒中執行代碼、處理工具調用以及將結果返回給模型。
本文還介紹了使用Amazon Bedrock AgentCore Code Interpreter的託管方案和兼容Anthropic SDK的代理路徑,但自託管方案提供了最大的靈活性和控制力。通過實施PTC,您可以顯著降低多工具工作流的延遲和令牌消耗,同時提高準確性和處理大數據集的能力。