

# 使用者模擬
<a name="user-simulation"></a>

使用者模擬使用 LLM 支援的演員來扮演最終使用者與代理程式互動的角色。您可以定義演員的設定檔和目標，而演員會推動與您的客服人員的多轉對話，直到達到目標或達到轉彎限制為止。

**注意**  
使用者模擬會在 SDK 端叫用 Amazon Bedrock 模型，以產生演員的回應。這些呼叫需支付標準 Amazon Bedrock 模型調用費用。如需詳細資訊，請參閱 [AgentCore 定價頁面](https://aws.amazon.com/bedrock/agentcore/pricing/)。

當您想要：
+  **以逼真的變化進行測試：**演員會在每次執行時產生不同的措辭、後續問題和對話路徑，公開手寫案例遺漏的邊緣案例。
+  **評估開放式對話：**對於處理自由格式對話的客服人員 （客戶支援、教學、諮詢），模擬案例比固定輪換序列更能反映實際使用者行為。
+  **擴展案例涵蓋範圍：**使用不同的角色和目標來定義演員描述檔，並讓演員產生對話，而不是手動撰寫數十個多迴轉指令碼。
+  **具有多樣性的迴歸測試：**多次執行相同的演員描述檔，以檢查您的代理程式是否處理相同意圖的不同表達式。

使用者模擬適用於[隨需](dataset-evaluations-on-demand.md)和[批次](dataset-evaluations-batch.md)資料集執行器。

## 運作方式
<a name="user-simulation-how-it-works"></a>

執行器會透過對話迴圈處理每個模擬案例：

1.  **開始：**執行器會先將案例`input`的欄位傳送給您的代理程式。

1.  **客服人員回應：**您的客服人員會處理輸入並傳回回應。

1.  **演員評估：**LLM 支援的演員會收到客服人員的回應，並根據其設定檔和目標決定接下來要做什麼。演員會產生結構式回應，其中包含：
   +  **原因：**演員回應的內部原因 （例如，「客服人員提供航班選項，但未要求我偏好的時間。 我應該指定我偏好上午航班。」)。這對於偵錯演員以特定方式行為的原因很有用。
   +  **訊息：**要傳送給客服人員的下一個訊息。
   +  **停止訊號：**布林值，指出演員是否將其目標視為已實現。

1.  **繼續或停止：**如果演員發出目標完成訊號 (`stop: true`) 或輪換計數達到 `max_turns`，則對話會結束。否則，演員的下一個訊息會成為下一個回合的輸入。

1.  **評估：**對話完成後，執行器會使用設定的評估器評估工作階段，與預先定義的案例相同。

## 演員描述檔
<a name="user-simulation-actor-profile"></a>

每個模擬案例都需要一個 `ActorProfile`，定義演員是誰及其想要實現的目標：


| 欄位 | 必要 | 描述 | 
| --- | --- | --- | 
|  `context`  | 是 | 演員的背景資訊。描述情況和演員應該知道的任何相關詳細資訊。 | 
|  `goal`  | 是 | 演員想要在對話中達成的目標。當動作者判斷目標已達成時，會發出完成訊號。 | 
|  `traits`  | 否 | 描述演員特性的鍵值組 （例如，專業知識層級、溝通風格、耐心）。預設為空白。 | 

```
{
  "actor_profile": {
    "context": "A customer who purchased a laptop last week and it arrived with a cracked screen",
    "goal": "Get a replacement laptop shipped within 2 business days",
    "traits": {
      "expertise": "non-technical",
      "tone": "frustrated but polite",
      "patience": "low"
    }
  }
}
```

## 模擬組態
<a name="user-simulation-config"></a>

`SimulationConfig` 控制演員的行為，並在執行器的評估組態上設定：


| 欄位 | 預設 | 說明 | 
| --- | --- | --- | 
|  `model_id`  | 預設模型 | 用於演員 LLM 的 Amazon Bedrock 模型 ID。選擇可遵循複雜角色指示的模型。如果省略，則會使用預設模型。 | 

```
from bedrock_agentcore.evaluation import SimulationConfig

simulation_config = SimulationConfig(
    model_id="<model-id>",
)
```

## 資料集結構描述
<a name="user-simulation-dataset-schema"></a>

模擬案例使用 `actor_profile`和 `input`而非 `turns`：

```
{
  "scenarios": [
    {
      "scenario_id": "geography-student",
      "scenario_description": "A curious student asks geography questions",
      "actor_profile": {
        "traits": {"expertise": "novice", "tone": "curious"},
        "context": "A student studying world geography who wants to learn about capitals",
        "goal": "Find out the capital cities of at least two different countries"
      },
      "input": "Hi! I'm studying geography. Can you help me learn about world capitals?",
      "max_turns": 5,
      "assertions": [
        "Agent provides accurate capital city information",
        "Agent is helpful and encouraging to the student"
      ]
    }
  ]
}
```


| 欄位 | 必要 | 預設 | 說明 | 
| --- | --- | --- | --- | 
|  `scenario_id`  | 是 | — | 案例的唯一識別符。 | 
|  `scenario_description`  | 否 |  `""`  | 描述案例的選用中繼資料。用於整理和識別結果中的案例。 | 
|  `actor_profile`  | 是 | — | 演員的身分和目標。請參閱 [演員描述檔](#user-simulation-actor-profile)。 | 
|  `input`  | 是 | — | 傳送給客服人員以開始對話的第一個訊息。 | 
|  `max_turns`  | 否 | 10 | 對話停止前的轉彎次數上限。必須至少為 1。 | 
|  `assertions`  | 否 | — | 有關預期行為的自然語言聲明。供工作階段層級評估者使用，例如 `Builtin.GoalSuccessRate`。 | 

**注意**  
模擬案例不支援`expected_trajectory`或迴轉，`expected_response`因為對話流程無法事先得知。將 `assertions`用於模擬案例的 Ground Truth。

 `FileDatasetProvider` 從 JSON 結構自動偵測案例類型：具有 `actor_profile` 欄位 （且無`turns`欄位） 的案例會載入為 `SimulatedScenario`。

## 搭配批次資料集執行器使用
<a name="user-simulation-batch-example"></a>

下列範例使用[批次資料集執行器](dataset-evaluations-batch.md)執行模擬案例評估。在 `simulation_config`上設定 ，`BatchEvaluationRunConfig`並在 資料集中包含`SimulatedScenario`執行個體：

```
import boto3
import json
from bedrock_agentcore.evaluation import (
    BatchEvaluationRunner,
    BatchEvaluationRunConfig,
    BatchEvaluatorConfig,
    CloudWatchDataSourceConfig,
    SimulationConfig,
    AgentInvokerInput,
    AgentInvokerOutput,
    Dataset,
    SimulatedScenario,
    ActorProfile,
)

AGENT_ARN = "arn:aws:bedrock-agentcore:us-west-2:123456789012:runtime/MyAgent-abc123"  # Replace with your agent runtime ARN
REGION = "us-west-2"  # Replace with your region
RUNTIME_ID = AGENT_ARN.split("/")[-1]
AGENT_NAME = RUNTIME_ID.rsplit("-", 1)[0]
ENDPOINT_NAME = "DEFAULT"
LOG_GROUP = f"/aws/bedrock-agentcore/runtimes/{RUNTIME_ID}-{ENDPOINT_NAME}"
SERVICE_NAME = f"{AGENT_NAME}.{ENDPOINT_NAME}"
ACTOR_MODEL_ID = "global.anthropic.claude-haiku-4-5-20251001-v1:0"  # Replace with your preferred model

# Define the dataset with simulated scenarios
dataset = Dataset(
    scenarios=[
        SimulatedScenario(
            scenario_id="support-frustrated-customer",
            scenario_description="A frustrated customer with a defective product",
            actor_profile=ActorProfile(
                traits={"expertise": "non-technical", "tone": "frustrated but polite"},
                context="Purchased a laptop last week that arrived with a cracked screen",
                goal="Get a replacement laptop shipped within 2 business days",
            ),
            input="I received my laptop and the screen is cracked. I need help.",
            max_turns=8,
            assertions=[
                "Agent acknowledges the issue and apologizes",
                "Agent offers a replacement or refund",
                "Agent provides a timeline for resolution",
            ],
        ),
        SimulatedScenario(
            scenario_id="support-billing-question",
            scenario_description="A customer with a billing discrepancy",
            actor_profile=ActorProfile(
                traits={"expertise": "moderate", "tone": "calm"},
                context="Noticed a double charge on the last credit card statement",
                goal="Get the duplicate charge reversed and confirmation of the refund",
            ),
            input="I see two charges for the same order on my statement. Can you look into this?",
            max_turns=6,
            assertions=[
                "Agent investigates the billing issue",
                "Agent confirms whether a duplicate charge exists",
            ],
        ),
    ]
)

# Configure the evaluation
config = BatchEvaluationRunConfig(
    batch_evaluation_name="simulated-support-eval",
    evaluator_config=BatchEvaluatorConfig(
        evaluator_ids=[
            "Builtin.GoalSuccessRate",
            "Builtin.Helpfulness",
        ],
    ),
    data_source=CloudWatchDataSourceConfig(
        service_names=[SERVICE_NAME],
        log_group_names=[LOG_GROUP],
        ingestion_delay_seconds=180,
    ),
    simulation_config=SimulationConfig(
        model_id=ACTOR_MODEL_ID,
    ),
    polling_timeout_seconds=1800,
    polling_interval_seconds=30,
)

# Define the agent invoker
agentcore_client = boto3.client("bedrock-agentcore", region_name=REGION)

def agent_invoker(inp: AgentInvokerInput) -> AgentInvokerOutput:
    payload = inp.payload
    if isinstance(payload, str):
        raw_bytes = json.dumps({"prompt": payload}).encode()
    elif isinstance(payload, dict):
        raw_bytes = json.dumps(payload).encode()
    else:
        raw_bytes = json.dumps({"prompt": str(payload)}).encode()

    print(f"[{inp.session_id}] > sending payload: {raw_bytes.decode()}")
    response = agentcore_client.invoke_agent_runtime(
        agentRuntimeArn=AGENT_ARN,
        runtimeSessionId=inp.session_id,
        payload=raw_bytes,
    )
    response_body = response["response"].read()
    print(f"[{inp.session_id}] < received response: {response_body.decode()}")
    return AgentInvokerOutput(agent_output=json.loads(response_body))

# Run the evaluation
runner = BatchEvaluationRunner(region=REGION)
result = runner.run_dataset_evaluation(
    config=config,
    dataset=dataset,
    agent_invoker=agent_invoker,
)

# Display results
print(f"Status: {result.status}")
if result.evaluation_results:
    er = result.evaluation_results
    print(f"Sessions completed: {er.number_of_sessions_completed}")
    print(f"Sessions failed:    {er.number_of_sessions_failed}")
    for summary in er.evaluator_summaries or []:
        avg = summary.statistics.average_score if summary.statistics else None
        print(f"  {summary.evaluator_id}: avg={avg}")
```

## 搭配隨需資料集執行器使用
<a name="user-simulation-on-demand-example"></a>

[隨需資料集執行器](dataset-evaluations-on-demand.md)遵循相同的模式。在 `simulation_config`上設定 ，`EvaluationRunConfig`並在 資料集中包含`SimulatedScenario`執行個體：

**注意**  
隨需評估會根據使用量收費。如需詳細資訊，請參閱 [AgentCore 定價頁面](https://aws.amazon.com/bedrock/agentcore/pricing/)。

```
from bedrock_agentcore.evaluation import (
    OnDemandEvaluationDatasetRunner,
    EvaluationRunConfig,
    EvaluatorConfig,
    CloudWatchAgentSpanCollector,
    SimulationConfig,
    FileDatasetProvider,
)

AGENT_ARN = "arn:aws:bedrock-agentcore:us-west-2:123456789012:runtime/MyAgent-abc123"  # Replace with your agent runtime ARN
REGION = "us-west-2"  # Replace with your region
RUNTIME_ID = AGENT_ARN.split("/")[-1]
ENDPOINT_NAME = "DEFAULT"
LOG_GROUP = f"/aws/bedrock-agentcore/runtimes/{RUNTIME_ID}-{ENDPOINT_NAME}"
ACTOR_MODEL_ID = "global.anthropic.claude-haiku-4-5-20251001-v1:0"  # Replace with your preferred model

# Load dataset (auto-detects simulated scenarios from actor_profile field)
dataset = FileDatasetProvider("simulated_dataset.json").get_dataset()

# Create span collector
span_collector = CloudWatchAgentSpanCollector(
    log_group_name=LOG_GROUP,
    region=REGION,
)

# Configure with simulation support
config = EvaluationRunConfig(
    evaluator_config=EvaluatorConfig(
        evaluator_ids=["Builtin.GoalSuccessRate", "Builtin.Helpfulness"],
    ),
    evaluation_delay_seconds=180,
    max_concurrent_scenarios=5,
    simulation_config=SimulationConfig(
        model_id=ACTOR_MODEL_ID,
    ),
)

# Run
runner = OnDemandEvaluationDatasetRunner(region=REGION)
result = runner.run(
    agent_invoker=agent_invoker,
    dataset=dataset,
    span_collector=span_collector,
    config=config,
)

for scenario in result.scenario_results:
    print(f"Scenario: {scenario.scenario_id} ({scenario.status})")
    for evaluator in scenario.evaluator_results:
        for r in evaluator.results:
            print(f"  {evaluator.evaluator_id}: {r.get('value')} ({r.get('label')})")
```

## 停止條件
<a name="user-simulation-stop-conditions"></a>

模擬對話會在符合下列任一條件時結束：

1.  **目標完成：**演員判斷其目標已達成，並發出 訊號`stop: true`。這是預期的結果。

1.  **達到轉彎上限：**對話達到`max_turns`限制。這可做為安全停止。如果您的案例經常達到轉彎限制，請考慮增加`max_turns`或簡化演員的目標。

1.  **未產生訊息：**演員不會產生下一個訊息，但不會明確發出停止訊號。這被視為隱含目標完成。

## 有效模擬案例的提示
<a name="user-simulation-tips"></a>
+  **在目標中具體化：**「對話」等模糊目標會導致不專注的互動。像是「取得訂單 \#12345 的退款」的特定目標為演員提供了明確的端點。
+  **使用特徵來控制困難：具有 **的演員`"expertise": "expert"`詢問比具有 的更硬的問題`"expertise": "novice"`。使用特徵來測試不同使用者區段的代理程式。
+  **設定逼真的輪換限制：**大多數客戶支援對話會在 5 到 10 輪內解決。設定`max_turns`過高的浪費運算；設定過低可能會在達到目標之前中斷對話。
+  **對基本事實使用聲明：**由於對話流程是動態的，`expected_response`因此無法按次使用。撰寫描述您預期結果的聲明，無論採取的特定路徑為何。
+  **選擇適當的演員模型：**演員模型應足以跨回合維持一致的角色。較小的模型適用於簡單的角色；具有細微目標的複雜角色受益於更強大的模型。