深入理解 Strands Agents SDK:从 @tool 装饰器到多 Agent 协作的工程实践

引子

我之前花了不少时间折腾 AI Agent。遇到的核心难题就一个:工具调度

用户说一句话,你怎么判断该调哪个工具?调完之后结果怎么回传给模型?多个工具之间有依赖关系怎么处理?

这些问题看似简单,写起来都是坑。

最近在用 Strands Agents SDK,发现它的设计思路很干脆——不做意图路由,把调度权完全交给模型。

这篇从源码层面分析一下它的工具系统设计,然后用实际代码走一遍:自定义工具、MCP 集成、多 Agent 协作。

架构概览

Strands Agents 的架构其实很简单:

用户输入
  ↓
Agent Loop(核心循环)
  ↓
模型推理 → 是否需要调用工具?
  │          ↓ 是                ↓ 否
  │     执行工具函数          直接回复
  │          ↓
  │     工具返回结果
  │          ↓
  └──── 继续推理(带上工具结果)

整个 Agent 就是一个循环。模型在每一步决定:是调工具,还是直接回复。

默认模型后端是 Amazon Bedrock(Claude 4 Sonnet,us-west-2 区域)。需要提前配好亚马逊云科技凭证。

GitHub 仓库:https://github.com/strands-agents/sdk-python

环境搭建

python -m venv .venv
source .venv/bin/activate
pip install strands-agents strands-agents-tools

配置凭证:

aws configure
# 或环境变量
export AWS_ACCESS_KEY_ID=你的AK
export AWS_SECRET_ACCESS_KEY=你的SK
export AWS_DEFAULT_REGION=us-west-2

去 Amazon Bedrock 控制台开通 Claude 4 Sonnet 模型权限。

踩坑记录:ap-northeast-1 区域报 AccessDeniedException。原因是 Claude 4 Sonnet 在该区域未开通。直接用 us-west-2。

@tool 装饰器的工作原理

这是 Strands Agents 的核心 API。看个例子:

from strands import Agent, tool
import urllib.request
import json

@tool
def get_weather(city: str) -> str:
    """查询指定城市的当前天气信息。

    Args:
        city: 城市名称,如 Beijing、Shanghai、Tokyo
    """
    url = f"https://wttr.in/{city}?format=j1"
    req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
    with urllib.request.urlopen(req, timeout=10) as resp:
        data = json.loads(resp.read().decode())
    current = data["current_condition"][0]
    temp = current["temp_C"]
    desc = current["weatherDesc"][0]["value"]
    humidity = current["humidity"]
    return f"{city}: {temp}°C, {desc}, 湿度 {humidity}%"

@tool 装饰器在背后做了三件事:

1. 从类型注解生成 JSON Schema

def get_weather(city: str) -> str:

SDK 会读这个签名,生成:

{
  "name": "get_weather",
  "parameters": {
    "type": "object",
    "properties": {
      "city": {"type": "string"}
    },
    "required": ["city"]
  }
}

这个 Schema 会随着 system message 一起发给模型。模型根据 Schema 决定怎么填参数。

2. 从 docstring 提取工具描述

模型需要知道这个工具能干嘛。SDK 直接用 docstring 作为工具的 description 字段。

所以 docstring 写得好不好,直接影响模型的调用准确率。

我做了个对比测试:

  • """查天气""" → 复杂提问时命中率大约 60%
  • """查询指定城市的当前天气信息。\n\nArgs:\n city: 城市名称""" → 命中率 95%+

差距非常大。

3. 包装成标准工具接口

装饰后的函数会被包装成一个符合 Strands 工具规范的对象,可以直接传给 Agent(tools=[...])

实战:三合一 Agent

把天气、计算、文档搜索三个工具组合:

from strands import Agent, tool
from strands_tools import calculator
import urllib.request, json

@tool
def get_weather(city: str) -> str:
    """查询指定城市的当前天气信息。

    Args:
        city: 城市名称,如 Beijing、Shanghai
    """
    url = f"https://wttr.in/{city}?format=j1"
    req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
    with urllib.request.urlopen(req, timeout=10) as resp:
        data = json.loads(resp.read().decode())
    current = data["current_condition"][0]
    return f"{city}: {current['temp_C']}°C, {current['weatherDesc'][0]['value']}, 湿度 {current['humidity']}%"

@tool
def search_docs(keyword: str) -> str:
    """在技术文档中搜索关键词,返回匹配内容。

    Args:
        keyword: 要搜索的关键词
    """
    docs = {
        "bedrock": "Amazon Bedrock 是亚马逊云科技的全托管生成式 AI 服务,支持多种基础模型。",
        "lambda": "AWS Lambda 是事件驱动的无服务器计算服务。",
        "s3": "Amazon S3 是对象存储服务,支持无限存储容量。"
    }
    for key, value in docs.items():
        if keyword.lower() in key:
            return value
    return f"未找到关于 '{keyword}' 的文档。"

agent = Agent(tools=[calculator, get_weather, search_docs])
agent("北京天气如何?2 的 10 次方是多少?什么是 Bedrock?")

模型会自动拆解这三个子任务,依次(或并行)调用对应工具。

MCP 集成:让 Agent 读真实文档

上面的 search_docs 用的是硬编码数据。实际项目中,可以用 MCP 接入真实的文档源。

MCP(Model Context Protocol)是一个开放协议。Strands Agents 原生支持。

from strands import Agent
from strands.tools.mcp import MCPClient
from mcp import stdio_client, StdioServerParameters

aws_docs_client = MCPClient(
    lambda: stdio_client(
        StdioServerParameters(
            command="uvx",
            args=["awslabs.aws-documentation-mcp-server@latest"]
        )
    )
)

with aws_docs_client:
    agent = Agent(tools=aws_docs_client.list_tools_sync())
    response = agent("Amazon Bedrock 用 Python SDK 怎么调?")
    print(response)

前提是装了 uvpip install uv),首次运行 MCP 服务器会自动下载依赖。

混合使用

自定义 tool 和 MCP tool 可以混在一起:

with aws_docs_client:
    all_tools = [calculator, get_weather] + aws_docs_client.list_tools_sync()
    agent = Agent(tools=all_tools)
    agent("上海天气如何?查查 S3 的 Python 用法。再算 99 * 99。")

Agent 的 tool 列表就是一个 Python list。想加就加,模型自己挑。

多 Agent 协作:Agent-as-Tool

复杂系统需要拆分职责。Strands 的方案是把 Agent 包装成 tool。

from strands import Agent, tool
from strands_tools import calculator

@tool
def get_weather(city: str) -> str:
    """查询城市天气。"""
    import urllib.request, json
    url = f"https://wttr.in/{city}?format=j1"
    req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
    with urllib.request.urlopen(req, timeout=10) as resp:
        data = json.loads(resp.read().decode())
    current = data["current_condition"][0]
    return f"{city}: {current['temp_C']}°C, {current['weatherDesc'][0]['value']}"

# 专家 Agent
weather_agent = Agent(
    system_prompt="你是天气查询助手。用 get_weather 工具回答天气问题。",
    tools=[get_weather]
)

math_agent = Agent(
    system_prompt="你是数学计算助手。用 calculator 工具解决数学问题。",
    tools=[calculator]
)

# 包装成工具
@tool
def ask_weather_expert(question: str) -> str:
    """向天气专家提问。查询天气信息时使用。

    Args:
        question: 关于天气的问题
    """
    return str(weather_agent(question))

@tool
def ask_math_expert(question: str) -> str:
    """向数学专家提问。需要数学计算时使用。

    Args:
        question: 数学问题
    """
    return str(math_agent(question))

# 协调者
coordinator = Agent(
    system_prompt="你是智能助手协调者。天气问题交给天气专家,数学问题交给数学专家。",
    tools=[ask_weather_expert, ask_math_expert]
)

coordinator("北京今天多少度?123 × 456 等于多少?")

这个模式的工程意义:

  • 职责隔离:每个 Agent 有自己的 system prompt 和 tools
  • 可独立测试:子 Agent 可以单独跑
  • 灵活组合:新需求来了加个子 Agent,注册给 coordinator 就行

自定义模型

from strands import Agent
from strands.models import BedrockModel

model = BedrockModel(
    model_id="us.amazon.nova-pro-v1:0",
    temperature=0.3,
    streaming=True
)

agent = Agent(model=model, tools=[...])

Amazon Bedrock 支持的模型都能用。

踩坑记录

坑 1:工具参数类型不匹配

工具函数参数必须有类型注解。不写的话,SDK 没法生成 Schema,模型也就不知道怎么调。

# 错误:没有类型注解
@tool
def bad_tool(city):
    ...

# 正确
@tool
def good_tool(city: str) -> str:
    ...

坑 2:docstring 格式不规范

推荐用 Google 风格:

@tool
def my_tool(param: str) -> str:
    """一句话描述工具功能。

    Args:
        param: 参数说明
    """

坑 3:MCP 服务器首次启动慢

aws-documentation-mcp-server 首次运行要下载依赖。别以为卡死了,等两分钟就好。

坑 4:region 和模型权限

Amazon Bedrock 的模型需要在控制台手动开通。默认区域 us-west-2。用之前确认一下你的区域有没有目标模型的权限。

总结

Strands Agents SDK 的设计理念可以用一句话概括:工具是函数,调度交给模型

从工程角度看:

  • @tool 装饰器 = 零胶水代码的工具注册
  • MCP 支持 = 标准化的外部工具接入
  • Agent-as-Tool = 可组合的多 Agent 架构

代码全部可运行,Python 3.10+,Amazon Bedrock 做模型后端。


参考资料:

posted @ 2026-03-23 11:07  亚马逊云开发者  阅读(0)  评论(0)    收藏  举报