详解如何在 SageMaker 上组合 OpenAI 兼容端点与 Bedrock AgentCore 运行时,实现专业化 Agent 分工调用不同模型,并解决 Strands Agents 默认不提供的 token 级可观测性问题。
在构建 Agent 工作流时,一个常见挑战是如何将托管的基础模型(FM)与自建的成本优化或领域特定模型混合使用,而无需为了集成而重写整个 Agent 框架。本文将展示如何将 Amazon SageMaker AI 上的 OpenAI 兼容端点与 Amazon Bedrock AgentCore 运行时(Amazon Bedrock AgentCore 的一项能力)及其托管部署相结合。专业化的 Agent 可以协作完成复杂任务,同时每个 Agent 都使用最适合其工作的模型。这一组合在单一生产就绪的架构中实现了成本优化、数据驻留和模型灵活性。
我们将演示如何在 Amazon SageMaker AI 上部署 Qwen 3.5 9B,将其整合到 Strands Agents 多 Agent 系统中(与 Amazon Bedrock 上的模型并肩工作),并将整个工作流部署到 Amazon Bedrock AgentCore 运行时。重点在于集成机制,包括如何从 SageMaker 端点获取令牌级别的可观测性——这是 Strands 默认不提供的能力。
该架构通过一个 Amazon Bedrock AgentCore 容器连接三条模型托管路径:
Orchestrator Agent(Bedrock 上的 Claude Haiku 4.5)—— 分类用户意图并通过全局跨区域推理路由任务。
Budget Agent(Bedrock 上的 Claude Sonnet 4.6)—— 处理 50/30/20 预算分配方案,输出结构化的 Pydantic 数据。
Financial Analysis Agent(Amazon SageMaker AI 上的 Qwen 3.5 9B)—— 使用工具调用进行股票分析和投资组合构建。
Amazon Bedrock 模型可用性因 AWS 区域而异。参见 Amazon Bedrock 中的按 AWS 区域划分的支持模型。
用户请求进入运行在 Amazon Bedrock AgentCore 运行时内的 Orchestrator Agent。Orchestrator 使用 Strands Agents 的"以 Agent 为工具"模式,将请求路由到 Budget Agent 或 Financial Analysis Agent。两个专业 Agent 各自调用其对应的模型。Budget Agent 通过 Amazon Bedrock 调用 Claude Sonnet 4.6,Financial Analysis Agent 通过使用 OpenAI 兼容 API 的 SageMaker AI 实时端点调用 Qwen 3.5 9B。结果通过 Orchestrator 流回用户。完整源代码参见配套的 GitHub 仓库。下图说明了这个架构。
图 1:跨 Amazon Bedrock 和 Amazon SageMaker AI 的多 Agent 工作流架构
要跟随本文操作,必须满足以下前置条件。
拥有 Amazon SageMaker AI、Amazon Bedrock 和 AgentCore 权限的 AWS 账户。
安装依赖:pip install sagemaker-core openai httpx strands-agents[otel] yfinance pydantic bedrock-agentcore。
具有 sagemaker:InvokeEndpoint 和 sagemaker:CallWithBearerToken 权限的 AWS Identity and Access Management(IAM)角色。
Claude Haiku 4.5 和 Claude Sonnet 4.6 的 Bedrock 模型访问权限。
使用 vLLM Deep Learning Container(DLC),镜像为 vllm:0.22.1-gpu-py312-cu130,在 ml.g6e.2xlarge 上部署 Qwen 3.5 9B。
region = "us-west-2"
model_id = "Qwen/Qwen3.5-9B"
instance_type = "ml.g6e.2xlarge" # 1x L40S (48GB VRAM)
num_gpu = 1
# vLLM 0.22.1, Python 3.12, CUDA 13.0, Ubuntu 22.04
inference_image = f"763104351884.dkr.ecr.{region}.amazonaws.com/vllm:0.22.1-gpu-py312-cu130-ubuntu22.04-sagemaker"
env = {
"SM_VLLM_MODEL": model_id,
"SM_VLLM_TENSOR_PARALLEL_SIZE": "1",
"SM_VLLM_MAX_MODEL_LEN": "32768",
}
# Create Model
sm.create_model(
ModelName=model_name,
ExecutionRoleArn=role,
PrimaryContainer={"Image": inference_image, "Environment": env},
)
# Create Endpoint Config + Endpoint
sm.create_endpoint_config(
EndpointConfigName=endpoint_config_name,
ProductionVariants=[{
"VariantName": "v1",
"ModelName": model_name,
"InstanceType": instance_type,
"InitialInstanceCount": 1,
"ContainerStartupHealthCheckTimeoutInSeconds": 1200,
"InferenceAmiVersion": inference_ami_version,
}],
)
sm.create_endpoint(EndpointName=endpoint_name, EndpointConfigName=endpoint_config_name)
SageMaker AI 的 OpenAI 兼容 API 需要 Bearer Token。Token 会过期,因此对于任何长时间运行的 Agent 会话,都需要一种在每次请求时刷新 Token 的方式。使用 httpx.Auth 子类设置自动刷新 Bearer Token:
import httpx
from openai import AsyncOpenAI
from sagemaker.core.token_generator import generate_token
class SageMakerAuth(httpx.Auth):
def __init__(self, region): self.region = region
def auth_flow(self, request):
request.headers["Authorization"] = f"Bearer {generate_token(region=self.region)}"
yield request
strands_client = AsyncOpenAI(
base_url=f"https://runtime.sagemaker.{REGION}.amazonaws.com/endpoints/{ENDPOINT_NAME}/openai/v1",
api_key="sagemaker",
http_client=httpx.AsyncClient(auth=SageMakerAuth(region=REGION)),
)
使用 Strands Agents 的"以 Agent 为工具"模式构建,每次调用时创建新的 Agent 实例。
from strands import Agent, tool
from strands.models.openai import OpenAIModel
qwen_model = OpenAIModel(
client=strands_client, model_id="",
params={"temperature": 0.7, "max_tokens": 4096, "stream_options": {"include_usage": True}},
)
@tool
def financial_analysis_agent_tool(query: str) -> str:
fresh = Agent(model=qwen_model, tools=[...], callback_handler=None)
return str(fresh(query))
orchestrator = Agent(
model=BedrockModel(model_id="global.anthropic.claude-haiku-4-5-20251001-v1:0"),
tools=[budget_agent_tool, financial_analysis_agent_tool],
)
使用 bedrock-agentcore-starter-toolkit 进行部署。完整部署 notebook 参见 deploy_agentcore.ipynb。
from bedrock_agentcore_starter_toolkit import Runtime
agentcore_runtime = Runtime()
agentcore_runtime.configure(
entrypoint="main.py", auto_create_execution_role=True,
auto_create_ecr=True, requirements_file="requirements.txt",
region="ap-south-1", agent_name="personal_finance_agent",
)
launch_result = agentcore_runtime.launch(
env_vars={
"SAGEMAKER_ENDPOINT_NAME": "qwen35-9b-260612-082732",
"SAGEMAKER_REGION": "ap-south-1",
"AGENT_OBSERVABILITY_ENABLED": "true",
}
)
Amazon Bedrock AgentCore 运行时自动使用 OpenTelemetry 检测 Agent,但这种检测并不会均等地延伸到每个模型提供商。在监控 Amazon SageMaker AI 上 Qwen 模型的成本和延迟之前,必须了解默认检测在哪些地方存在不足,以及如何弥补这一差距。
Amazon Bedrock AgentCore 运行时自动使用 OpenTelemetry 检测 Agent。然而存在一个关键缺口:
Amazon Bedrock 模型调用获得完整的生成式 AI span,包含自动统计的 Token 计数。无需额外工作。
Amazon SageMaker OpenAI 兼容端点(通过 Strands OpenAIModel)不会获得自动的 Token 遥测。检测机制不会将它们识别为生成式 AI 调用。
这意味着 Financial Analysis Agent 调用 Amazon SageMaker AI 上的 Qwen 3.5 9B 所消耗的 Token 在追踪中完全不可见。无法监控成本、无法检测回归,也无法调试延迟。
根本原因:Strands 的 OTEL 集成会为工具调用和 Agent 生命周期事件发出 span,但不会为 OpenAIModel 提供商发出带有 Token 属性的 gen_ai.chat span。AgentCore 的自动插桩仅将 Amazon Bedrock 模型推理调用(通过 boto3 发起)识别为生成式 AI 操作。
手动发出一个 gen_ai.chat span,包装 Amazon SageMaker Agent 调用并从 Strands 内部 AgentResult.metrics.accumulated_usage 提取 Token 使用量:
from opentelemetry import trace
tracer = trace.get_tracer("financial_analysis_agent")
@tool
def financial_analysis_agent_tool(query: str) -> str:
"""Route investment queries to Qwen on SageMaker with observability."""
with tracer.start_as_current_span("gen_ai.chat", attributes={
"gen_ai.system": "openai",
"gen_ai.request.model": f"qwen3.5-9b ({SAGEMAKER_ENDPOINT_NAME})",
"gen_ai.operation.name": "chat",
}) as span:
fa_agent = Agent(
model=OpenAIModel(
client=strands_client, model_id="",
params={"temperature": 0.7, "max_tokens": 4096,
"stream_options": {"include_usage": True}},
),
system_prompt=FINANCIAL_ANALYSIS_PROMPT,
tools=[get_stock_analysis, create_diversified_portfolio, compare_stock_performance],
callback_handler=None,
)
result = fa_agent(query)
# Extract token usage from Strands agent metrics
usage = result.metrics.accumulated_usage
span.set_attribute("gen_ai.usage.input_tokens", usage.get("inputTokens", 0))
span.set_attribute("gen_ai.usage.output_tokens", usage.get("outputTokens", 0))
span.set_attribute("gen_ai.usage.total_tokens", usage.get("totalTokens", 0))
return str(result)
关键细节:Strands 使用键名 inputTokens、outputTokens 和 totalTokens 在内部追踪 Token 使用量。只有在模型提供商返回 usage 数据时,这个字典才会被填充。
stream_options 对 vLLM 是必需的默认情况下,vLLM 在流式响应中不包含 usage 块。Strands 接收文本块,但永远不会收到最终的 usage 对象。因此 accumulated_usage 保持为零。添加 stream_options: {"include_usage": True} 告知 vLLM 发送一个额外的最终块,其中包含 Token 计数:
qwen_model = OpenAIModel(
client=strands_client,
model_id="",
params={
"temperature": 0.7,
"max_tokens": 4096,
"stream_options": {"include_usage": True}, # Critical for token tracking
},
)
没有这个参数,gen_ai.chat span 报告的 Token 数为 0。这使得自定义 span 形同虚设。
开启 Amazon CloudWatch Transaction Search(每个账户或区域一次性操作):
aws xray update-trace-segment-destination --region ap-south-1 --destination CloudWatchLogs
aws xray update-indexing-rule --region ap-south-1 --name "Default" \
--rule '{"Probabilistic": {"DesiredSamplingPercentage": 100}}'
安装带 OTEL 扩展的 Strands:strands-agents[otel]>=1.0.0。
在代码或环境变量中设置 AGENT_OBSERVABILITY_ENABLED=true。
使用 opentelemetry-instrument 作为容器 CMD。
在 OpenAIModel 参数中添加 stream_options: {"include_usage": True}。
创建包装 SageMaker Agent 调用的自定义 gen_ai.chat span。
{
"name": "gen_ai.chat",
"attributes": {
"gen_ai.system": "openai",
"gen_ai.request.model": "qwen3.5-9b (qwen35-9b-260612-082732)",
"gen_ai.operation.name": "chat",
"gen_ai.usage.input_tokens": 1391,
"gen_ai.usage.output_tokens": 1432,
"gen_ai.usage.total_tokens": 2823
},
"durationNano": 37237386894
}
图 2 展示了 Agent 轨迹在 Bedrock AgentCore Observability 仪表板上的样子。这张追踪视图展示了 Amazon SageMaker AI 托管的 Qwen 模型的 gen_ai.chat span,以及自动插桩的 Amazon Bedrock AgentCore span,两个模型的 Token 计数现在都可见了。从零开始构建这种端到端可观测性过程中发现了几个值得说明的实现细节。
图 2:带有 SageMaker 托管模型 Token 计数的 AgentCore 可观测性追踪
gen_ai.chat spanstream_options —— vLLM 默认不在流式中发送 usageresult.metrics.accumulated_usage —— 键名:inputTokens、outputTokens、totalTokens该架构具有可组合性。可以探索的几个方向:
接入微调模型:将 SM_VLLM_MODEL 指向 Amazon Simple Storage Service(Amazon S3)上的微调检查点。认证层、OTEL span 和 AgentCore 部署保持不变。
使用推理组件进行 A/B 测试:在同一个 Amazon SageMaker 端点上部署基础版和微调版变体。在 OTEL span 中添加 variant 属性以便在追踪中比较质量。
成本感知路由:在调度前检查查询复杂度。将简单查询路由到 Amazon Bedrock 上的 Haiku。将 Amazon SageMaker GPU 端点留给多步骤推理任务。
为避免将来产生费用,删除相关资源:
agentcore_control = boto3.client("bedrock-agentcore-control", region_name=region)
agentcore_control.delete_agent_runtime(agentRuntimeId=launch_result.agent_id)
sagemaker_client.delete_endpoint(EndpointName=ENDPOINT_NAME)
sagemaker_client.delete_endpoint_config(EndpointConfigName=f"qwen35-9b-epc-{TIMESTAMP}")
sagemaker_client.delete_model(ModelName=f"qwen35-9b-{TIMESTAMP}")
在本文中,我们展示了如何将 Amazon SageMaker AI 上的自托管模型连接到 Amazon Bedrock AgentCore 运行时,以及关键——如何从 Strands Agents 默认不插桩的 Amazon SageMaker 端点获取完整的令牌级别可观测性。
核心要点:
httpx.Auth + generate_token() + AsyncOpenAI —— AgentCore 内的生产就绪 SageMaker 认证
自定义 gen_ai.chat OTEL span + stream_options: {"include_usage": True} —— Amazon SageMaker 端点的完整 Token 可视性
result.metrics.accumulated_usage —— 提取 Token 计数的 Strands API
要开始使用,请克隆配套仓库并参阅 OBSERVABILITY.md 获取完整参考。
OpenAI-compatible API for SageMaker AI
Strands Agents — agents as tools
Amazon Bedrock AgentCore Observability
OpenTelemetry generative AI semantic conventions