

# 使用基于目标的路由进行 A/B 测试
<a name="ab-testing-target-based"></a>

当您正在测试的更改涉及代码更改、框架升级或完全不同的代理实现时，请使用基于目标的路由模式。 Target-based 路由在同一 AgentCore 运行时的多个版本（命名端点）之间或完全不同的 AgentCore 运行时之间路由流量。 AgentCore 网关将每个端点注册为单独的目标，并根据 A/B 测试的流量权重将每个会话路由到一个或另一个端点。

基于目标的 A/B 测试的关键配置：
+ 变体配置：`variantConfiguration.target`使用 AgentCore 网关目标名称
+ 评估配置：`perVariantOnlineEvaluationConfig`（每个变体都有一个在线评估配置，因为每个端点都有自己的日志组）
+ 网关过滤器： A/B 测试截获的 AgentCore 网关路径的`gatewayFilter.targetPaths`作用域

本演练部署了两个版本的客户支持代理——一个使用 Claude Sonnet（控制），另一个使用 Claude Opus（治疗）——为每个版本创建命名端点、创建 A/B 测试、发送流量、查看结果并部署获胜者。

**注意**  
本演练适用于 AgentCore 运行时上托管的代理。如果您的代理在 AgentCore 运行时**外部**运行（第三方代理或自托管代理，例如在 Lamb AWS da 上），[请参阅改为运行 A/B 时外部 AgentCore托管的代理运行测试](ab-testing-3p-agents.md)。

有关 A/B 测试模式的详细比较，请参阅[选择模式](ab-testing.md#ab-testing-pattern-comparison)。

## 步骤 1：创建项目
<a name="target-based-create-project"></a>

使用 AgentCore CLI 创建项目：

```
agentcore create --name ABTestTargetBased --no-agent
cd ABTestTargetBased
```

## 第 2 步：添加运行时
<a name="target-based-add-runtime"></a>

添加代理运行时。您将部署此运行时的两个版本（一个用于控制，一个用于治疗），然后创建命名的端点来为每个版本设置别名。

```
agentcore add agent \
  --name csAgent \
  --language Python \
  --framework Strands \
  --model-provider Bedrock \
  --memory none \
  --build CodeZip
```

项目结构：

```
ABTestTargetBased/
├── agentcore/
│   ├── agentcore.json
│   ├── aws-targets.json
│   └── cdk/
└── app/
    └── csAgent/
        ├── main.py
        └── pyproject.toml
```

## 步骤 3：部署控制和治疗版本
<a name="target-based-agent-code"></a>

`app/csAgent/main.py`替换为控制版本（使用 Claude Sonnet）：

```
"""Customer support agent — control variant."""
from strands import Agent, tool
from strands.models.bedrock import BedrockModel
from bedrock_agentcore.runtime import BedrockAgentCoreApp

app = BedrockAgentCoreApp()
MODEL_ID = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
SYSTEM_PROMPT = "You are a helpful customer support assistant for Acme Store."


@tool
def lookup_order(order_id: str) -> str:
    """Look up an order by ID."""
    orders = {
        "ORD-1001": {"status": "delivered", "item": "Blue T-Shirt", "total": "$29.99"},
        "ORD-1002": {"status": "in_transit", "item": "Running Shoes", "est_delivery": "2026-04-05"},
        "ORD-1003": {"status": "delayed", "item": "Wireless Headphones", "days_late": 5},
    }
    return str(orders.get(order_id, {"error": f"Order {order_id} not found"}))


@tool
def initiate_return(order_id: str, reason: str) -> str:
    """Initiate a return for an order."""
    return f"Return initiated for {order_id}. Reason: {reason}. Return label sent to customer email."


@tool
def apply_discount(order_id: str, discount_percent: int, reason: str) -> str:
    """Apply a discount to an order."""
    return f"Applied {discount_percent}% discount to {order_id}. Reason: {reason}."


agent = Agent(
    model=BedrockModel(model_id=MODEL_ID),
    tools=[lookup_order, initiate_return, apply_discount],
    system_prompt=SYSTEM_PROMPT,
)


@app.entrypoint
def invoke(payload, context):
    result = agent(payload.get("prompt", "Hello"))
    return {"response": result.message["content"][0]["text"]}


if __name__ == "__main__":
    app.run()
```

更新`app/csAgent/pyproject.toml`依赖关系：

```
dependencies = [
    "aws-opentelemetry-distro",
    "bedrock-agentcore >= 1.8.0",
    "boto3",
    "botocore[crt] >= 1.35.0",
    "strands-agents[otel] >= 1.13.0",
    "opentelemetry-distro",
    "opentelemetry-instrumentation",
]
```

部署控件版本（这将创建版本 1）：

```
agentcore deploy
```

现在更新`main.py`为对治疗变体使用不同的模型并进行部署（这将创建版本 2）：

```
MODEL_ID = "global.anthropic.claude-opus-4-6-v1"
```

```
agentcore deploy
```

为每个版本创建命名端点并部署：

```
agentcore add runtime-endpoint \
  --runtime csAgent \
  --endpoint control \
  --version 1 \
  --description "Control variant — Claude Sonnet"

agentcore add runtime-endpoint \
  --runtime csAgent \
  --endpoint treatment \
  --version 2 \
  --description "Treatment variant — Claude Opus"

agentcore deploy
```

你现在有：
+ 运行时端点 `control` — 与 Claude Sonnet 一起提供版本 1。
+ 运行时端点 `treatment` — 使用 Claude Opus 提供版本 2。

验证运行时是否正常运行：

```
agentcore invoke --runtime csAgent --prompt "What is the status of order ORD-1003?"
```

你现在有：
+ 运行时端点 `control` — 与 Claude Sonnet 一起提供版本 1。
+ 运行时端点 `treatment` — 使用 Claude Opus 提供版本 2。

## 步骤 4：创建在线评估配置
<a name="target-based-online-eval"></a>

每个端点都有自己的日志组（日志组名称以端点名称结尾），因此每个变体都需要一个在线评估配置：

```
agentcore add online-eval \
  --name controlEvalTb \
  --runtime csAgent \
  --endpoint control \
  --evaluator "Builtin.Helpfulness" \
  --sampling-rate 100.0 \
  --enable-on-create

agentcore add online-eval \
  --name treatmentEvalTb \
  --runtime csAgent \
  --endpoint treatment \
  --evaluator "Builtin.Helpfulness" \
  --sampling-rate 100.0 \
  --enable-on-create

agentcore deploy
```

每次部署后，请记下在线评估配置 ARN，创建测试时需要两者。 A/B 

有关评估器选项和配置的更多详细信息，请参阅[创建在线评估](create-online-evaluations.md)。

## 步骤 5：创建网关和目标
<a name="target-based-create-gateway"></a>

基于目标的 A/B 测试通过 AgentCore 网关路由流量，因此在开始测试之前，必须已经部署了网关及其两个目标。添加网关并将每个运行时端点注册为`http-runtime`目标，然后部署：

```
agentcore add gateway --name csGateway

agentcore add gateway-target \
  --name customer-support-control \
  --gateway csGateway \
  --type http-runtime \
  --runtime csAgent \
  --runtime-endpoint control

agentcore add gateway-target \
  --name customer-support-treatment \
  --gateway csGateway \
  --type http-runtime \
  --runtime csAgent \
  --runtime-endpoint treatment

agentcore deploy
```

## 步骤 6：创建 A/B 测试
<a name="target-based-create-test"></a>

使用开始 A/B 测试`agentcore run ab-test`。每个变体都引用您创建的网关目标之一，并且有自己的在线评估配置。该命令直接在服务上针对已部署的网关启动测试。

**Example**  

```
agentcore run ab-test \
  --mode target-based \
  --name customerSupportTargetTest \
  --gateway csGateway \
  --runtime csAgent \
  --control-target customer-support-control \
  --treatment-target customer-support-treatment \
  --control-online-eval controlEvalTb \
  --treatment-online-eval treatmentEvalTb \
  --control-weight 80 \
  --treatment-weight 20
```
命令返回后，测试即在运行。传球`--disable-on-create`来创建它已停止。该`--gateway`标志为必填项，并且必须引用您在步骤 5 中部署的网关。每个网关一次只能运行一个测试。该命令会打印测试的作业 ID，也可以从`id`字段中`--json`获得。以下生命周期命令需要此 ID。

```
import boto3
import uuid

REGION = "us-west-2"
ACCOUNT_ID = "123456789012"

# Runtime ARNs from Step 2 deployment output
CONTROL_RUNTIME_ARN = f"arn:aws:bedrock-agentcore:{REGION}:{ACCOUNT_ID}:runtime/ABTestTargetBased_CustomerSupportControl-abc123"
TREATMENT_RUNTIME_ARN = f"arn:aws:bedrock-agentcore:{REGION}:{ACCOUNT_ID}:runtime/ABTestTargetBased_CustomerSupportTreatment-def456"

# Online evaluation config ARNs from Step 3
CONTROL_EVAL_ARN = f"arn:aws:bedrock-agentcore:{REGION}:{ACCOUNT_ID}:online-evaluation-config/controlEvalTb-abc123"
TREATMENT_EVAL_ARN = f"arn:aws:bedrock-agentcore:{REGION}:{ACCOUNT_ID}:online-evaluation-config/treatmentEvalTb-def456"

# IAM roles
GATEWAY_ROLE_ARN = f"arn:aws:iam::{ACCOUNT_ID}:role/AgentCoreGatewayRole"
AB_TEST_ROLE_ARN = f"arn:aws:iam::{ACCOUNT_ID}:role/ABTestRole"

cp_client = boto3.client("bedrock-agentcore-control", region_name=REGION)
dp_client = boto3.client("bedrock-agentcore", region_name=REGION)

# 1. Create an AgentCore Gateway
gateway_response = cp_client.create_gateway(
    name="customerSupportTargetTest-gw",
    roleArn=GATEWAY_ROLE_ARN,
    authorizerType="AWS_IAM",
    clientToken=str(uuid.uuid4()),
)
gateway_id = gateway_response["gatewayId"]
gateway_arn = gateway_response["gatewayArn"]
print(f"Created AgentCore Gateway: {gateway_id}")

# 2. Add control runtime as an AgentCore Gateway target
cp_client.create_gateway_target(
    gatewayIdentifier=gateway_id,
    name="customer-support-control",
    targetConfiguration={
        "http": {
            "agentcoreRuntime": {
                "arn": CONTROL_RUNTIME_ARN,
                "qualifier": "DEFAULT"
            }
        }
    },
    clientToken=str(uuid.uuid4()),
)
print("Added target: customer-support-control")

# 3. Add treatment runtime as an AgentCore Gateway target
cp_client.create_gateway_target(
    gatewayIdentifier=gateway_id,
    name="customer-support-treatment",
    targetConfiguration={
        "http": {
            "agentcoreRuntime": {
                "arn": TREATMENT_RUNTIME_ARN,
                "qualifier": "DEFAULT"
            }
        }
    },
    clientToken=str(uuid.uuid4()),
)
print("Added target: customer-support-treatment")

# 4. Create the A/B test
response = dp_client.create_ab_test(
    name="customerSupportTargetTest",
    gatewayArn=gateway_arn,
    roleArn=AB_TEST_ROLE_ARN,
    evaluationConfig={
        "perVariantOnlineEvaluationConfig": [
            {"name": "C", "onlineEvaluationConfigArn": CONTROL_EVAL_ARN},
            {"name": "T1", "onlineEvaluationConfigArn": TREATMENT_EVAL_ARN}
        ]
    },
    gatewayFilter={
        "targetPaths": ["/customer-support-control/*"]
    },
    variants=[
        {
            "name": "C",
            "weight": 80,
            "variantConfiguration": {
                "target": {"name": "customer-support-control"}
            }
        },
        {
            "name": "T1",
            "weight": 20,
            "variantConfiguration": {
                "target": {"name": "customer-support-treatment"}
            }
        }
    ],
    enableOnCreate=True,
    clientToken=str(uuid.uuid4()),
)

ab_test_id = response["abTestId"]
print(f"Created A/B test: {ab_test_id}")
print(f"Status: {response['status']}")
print(f"Execution status: {response['executionStatus']}")
```

## 步骤 7：通过 AgentCore 网关发送流量
<a name="target-based-send-traffic"></a>

 A/B 测试运行后，通过 AgentCore 网关 HTTP 端点发送流量。 AgentCore Gateway 根据运行时会话 ID 将每个请求分配给变体（对照或治疗）。

### 变体赋值的工作原理
<a name="_how_variant_assignment_works"></a>

 AgentCore 网关使用标`X-Amzn-Bedrock-AgentCore-Runtime-Session-Id`头来确定要将流量路由到哪个目标。此标头是**可选**的，如果您不提供该标头，则运行时会自动生成会话 ID。然后， AgentCore 网关使用会话 ID（无论是您提供的，还是运行时生成的），根据您配置的流量权重将请求分配给变体。

会话分配是**粘性**的：将会话 ID 分配给变体后，所有具有相同会话 ID 的后续请求都会路由到同一个目标。这样可以确保在会话中获得一致的体验，同时仍能根据您的流量分配跨变体分配新会话。

### 生成用于测试的流量
<a name="_generate_traffic_for_testing"></a>

将以下脚本另存为 `loadgen.sh``<gateway-id>`，`<target-name>`用部署输出中的值替换和。您也可以从`agentcore view ab-test <ab-test-id>`以下地址复制完整的调用 URL：

```
#!/bin/bash
export AWS_ACCESS_KEY_ID=$(aws configure get aws_access_key_id)
export AWS_SECRET_ACCESS_KEY=$(aws configure get aws_secret_access_key)
export AWS_SESSION_TOKEN=$(aws configure get aws_session_token)

GATEWAY_URL="https://<gateway-id>.gateway.bedrock-agentcore.us-west-2.amazonaws.com/<target-name>/invocations"

PROMPTS=(
  "What is the status of order ORD-1003?"
  "I want to return order ORD-1001, it doesn't fit."
  "My order ORD-1003 is late. Can I get a discount?"
  "Where is my order ORD-1002?"
  "I need help with a return for order ORD-1001. The color is wrong."
  "Can you check on order ORD-1003? I've been waiting forever."
  "I'd like to cancel order ORD-1002 if it hasn't shipped yet."
  "Order ORD-1003 is delayed again. This is unacceptable."
  "What's your return policy for order ORD-1001?"
  "My headphones order ORD-1003 still hasn't arrived. What can you do?"
)

for i in $(seq 1 30); do
  PROMPT="${PROMPTS[$(( (i - 1) % ${#PROMPTS[@]} ))]}"
  echo "=== Request $i: $PROMPT ==="
  curl -s --aws-sigv4 "aws:amz:us-west-2:bedrock-agentcore" \
    --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
    -H "x-amz-security-token: $AWS_SESSION_TOKEN" \
    -H "Content-Type: application/json" \
    -H "X-Amzn-Bedrock-AgentCore-Runtime-Session-Id: $(uuidgen)" \
    -d "{\"prompt\": \"$PROMPT\"}" \
    -X POST \
    "$GATEWAY_URL"
  echo ""
  sleep 2
done
```

运行脚本：

```
bash loadgen.sh
```

## 第 8 步：获取结果
<a name="target-based-get-results"></a>

随着样本量的增长，对 A/B 测试进行轮询以监控结果。轮询不影响统计的有效性。

**Example**  
获取当前结果（`<ab-test-id>`替换为步骤 6 中的任务 ID）：  

```
agentcore view ab-test <ab-test-id>
```
以 JSON 格式获取结果：  

```
agentcore view ab-test <ab-test-id> --json
```
在结果达到统计意义之前进行民意调查：  

```
import boto3
import time

client = boto3.client("bedrock-agentcore", region_name="us-west-2")

ab_test_id = "customerSupportTargetTest-Ab1Cd2Ef3G"

while True:
    response = client.get_ab_test(abTestId=ab_test_id)

    status = response["status"]
    exec_status = response["executionStatus"]
    print(f"Status: {status}, Execution: {exec_status}")

    results = response.get("results")
    if results:
        print(f"Analysis timestamp: {results.get('analysisTimestamp')}")
        for metric in results["evaluatorMetrics"]:
            evaluator = metric["evaluatorArn"]
            control = metric["controlStats"]
            print(f"\nEvaluator: {evaluator}")
            print(f"  Control: mean={control['mean']:.3f}, n={control['sampleSize']}")

            for variant in metric["variantResults"]:
                print(f"  {variant['variantName']}: mean={variant['mean']:.3f}, "
                      f"n={variant['sampleSize']}, "
                      f"pValue={variant.get('pValue', 'N/A')}, "
                      f"significant={variant['isSignificant']}")

                if variant["isSignificant"]:
                    print(f"  >>> Statistically significant! "
                          f"Change: {variant.get('percentChange', 0):.1f}%")

        # Check if any evaluator has reached significance
        all_significant = all(
            variant["isSignificant"]
            for metric in results["evaluatorMetrics"]
            for variant in metric["variantResults"]
        )
        if all_significant:
            print("\nAll evaluators have reached statistical significance.")
            break

    time.sleep(300)  # Poll every 5 minutes
```

**注意**  
显示结果所需的时间主要取决于在线评估配置中配置的会话超时。如果在超时窗口内没有新的请求到达，则认为会话已完成。会话结束后，结果通常会在 15 分钟内出现。结果会随着更多会话的完成而累积——统计显著性随着样本量的增加而提高。
+  **p 值 < 0.05 且呈阳性`percentChange`：**治疗明显优于对照组。考虑部署治疗方案。
+  **p 值 < 0.05 且为阴性`percentChange`：**治疗明显恶化。保持控制权。
+  **p 值 >= 0.05：**没有足够的证据来得出差异结论。继续收集样本或增加前往治疗的流量。
+  **检查所有评估者：**一种治疗可以改善一个指标，而另一个指标却倒退。在做出决定之前，请先查看所有评估者的结果。

## 第 9 步：确认结果并停止 A/B 测试
<a name="target-based-confirm-stop"></a>

一旦 A/B 检验达到统计显著性，请查看结果并停止实验。

1.  **确认重要性。**验证目标评估者对`isSignificant: true`治疗变异的检测结果是否为阳性`percentChange`（或者如果治疗恶化，则确认对照组是赢家）。

1.  **停止 A/B 测试。**运行 `agentcore stop ab-test -i <ab-test-id>`。流量路由立即结束，所有请求都恢复到默认目标。请参阅[查看、暂停、恢复和停止](ab-testing-manage.md#manage-ab-test-start-stop)。

## 第 10 步：部署获胜者
<a name="target-based-deploy-winner"></a>

停止 A/B 测试后，将所有流量路由到获胜的变体。

```
agentcore promote ab-test -i <ab-test-id>
agentcore deploy
```

 `promote`停止 A/B 测试（如果仍在运行），更新控制端点以指向治疗版本（例如，`control`从版本 1 更新到版本 2），并移除治疗端点。运行`agentcore deploy`以应用更改。

或者，您可以通过执行以下任一操作来手动部署获胜者：
+  **选项 A：**使用[AgentCore 网关路由规则](gateway-rules.md)将流量从两个目标引导到获胜目标。
+  **选项 B：**从 AgentCore 网关中移除失败的目标，并将所有流量路由到获胜者。
+  **选项 C：**更新失败的目标以指向获胜端点。

部署获胜者后：
+  **删除 A/B 测试以**清理资源。请参见[删除 A/B 测试](ab-testing-manage.md#manage-ab-test-remove)。
+  **监控新基线。**在线评估继续对获胜配置进行评分。注意回归。
+  **开始下一次迭代。**获胜配置的新痕迹为下一个推荐周期奠定了基础。查看[其工作原理](optimization-how-it-works.md)。

## 了解结果
<a name="target-based-results-shape"></a>

当您调用时`GetABTest`，一旦聚合管道处理了足够的会话，响应就会包含一个`results`对象。结果包含按变体细分的每个评估者的指标。

### 结果结构
<a name="_results_structure"></a>

```
{
  "results": {
    "analysisTimestamp": "2026-04-30T18:45:00Z",
    "evaluatorMetrics": [
      {
        "evaluatorArn": "arn:aws:bedrock-agentcore:us-west-2:123456789012:evaluator/Builtin.Helpfulness",
        "controlStats": {
          "variantName": "C",
          "sampleSize": 24,
          "mean": 0.72
        },
        "variantResults": [
          {
            "variantName": "T1",
            "sampleSize": 6,
            "mean": 0.85,
            "absoluteChange": 0.13,
            "percentChange": 18.1,
            "pValue": 0.032,
            "confidenceInterval": {
              "lower": 0.02,
              "upper": 0.24
            },
            "isSignificant": true
          }
        ]
      }
    ]
  }
}
```

### 字段引用
<a name="_field_reference"></a>


| 字段 | 说明 | 
| --- | --- | 
|  `analysisTimestamp`  | 服务上次计算统计数据的时间。 | 
|  `evaluatorMetrics`  | 在线评估配置中每位评估者只能输入一个条目。 | 
|  `controlStats.mean`  | 所有对照会话中评估者的平均分数。 | 
|  `controlStats.sampleSize`  | 对照变体的得分会话数。 | 
|  `variantResults[].mean`  | 所有治疗疗程中评估者的平均分数。 | 
|  `variantResults[].sampleSize`  | 该治疗变体的评分疗程数。 | 
|  `variantResults[].absoluteChange`  | 治疗均值和对照均值之间的差异。 | 
|  `variantResults[].percentChange`  | 相对于对照组的改善百分比（正）或回归（负）。 | 
|  `variantResults[].pValue`  | 概率观测到的差异是偶然造成的。低于 0.05 表示统计学显著性。 | 
|  `variantResults[].confidenceInterval`  | 绝对变化（`lower`和`upper`界限）的 95% 置信区间。 | 
|  `variantResults[].isSignificant`  |  `true`当 p 值 < 0.05 且样本数量足够时。 | 

## 问题排查
<a name="target-based-troubleshooting"></a>

### A/B 发送流量后测试未显示任何结果
<a name="target-based-no-results"></a>

结果不会立即出现。所需时间取决于在线评估配置中配置的会话超时——只有在超时窗口内没有新的请求到达后，才会认为会话已完成。会话结束后，预计将在大约 15 分钟内获得结果。

如果在此窗口之后仍未显示结果：
+  **验证在线评估日志组。**在线评估配置必须指向运行时代理的输出日志组。如果在线评估配置引用了不同的日志组（或未从运行时接收跨度的日志组），则不会对会话进行评分， A/B 测试也永远不会产生结果。
+  **检查日志组名称。**对于基于目标的路由，每个端点都有自己的日志组（日志组名称以终端节点名称结尾）。确保每个在线评估配置都引用了正确的端点日志组。
+  **确认运行时正在发出跨度。**检查 CloudWatch 日志以了解预期的日志组。你要在每个跨度上寻找的关键属性：
  +  `aws.agentcore.gateway.routing_experiment_arn` 
  +  `aws.agentcore.gateway.routing_experiment_variant_name`（值：`C`或`T1`）
  +  `session.id` 
+  **验证配置 CLI-created 与手动配置。**如果您使用了`agentcore add online-eval --runtime <name>`，CLI 会自动配置正确的日志组。如果您通过 API 手动创建了在线评估配置，请确保 Onl AgentCore ine Eval Config `dataSourceConfig.cloudWatchLogs.logGroupNames` 与运行时的跨度日志组相匹配。