AI News HubLIVE
站内改写4 分钟阅读

在Amazon Bedrock上实现程序化工具调用

程序化工具调用(PTC)是一种新的范式,通过让模型生成代码来编排多个工具调用,从而减少延迟和令牌消耗。本文介绍了三种在Amazon Bedrock上实现PTC的方法:基于ECS的自托管Docker沙盒、使用Bedrock AgentCore Code Interpreter的托管方案,以及兼容Anthropic SDK的代理路径。

来源AWS Machine Learning Blog作者: Shreyas Subramanian

程序化工具调用(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,您可以显著降低多工具工作流的延迟和令牌消耗,同时提高准确性和处理大数据集的能力。