iT邦幫忙

2026 iThome 鐵人賽

DAY 27
0

Day 27 | 模擬器:連使用者和後端都能假造

主張:好的評估資料集不該是一份寫死的劇本,而是一個能重現、能注入異常的沙盒。
讀完能做到:用 ConversationScenario 讓 LLM 動態扮演使用者,再用 Environment Simulator 攔截工具呼叫、注入錯誤與延遲,寫出不用碰真實後端也能跑的離線評估。

Day 26 講的評估資料集,骨子裡都預設了一件事:你事先知道使用者會怎麼問。可是真實世界不是這樣運作的。同一個任務——比如「訂一張機票」——有人會一次把出發地、目的地、日期全講完,有人會被 agent 一句一句問才肯給資訊;有人講話簡潔到像在打電報,有人會加一堆客套話。如果你的評估資料集只涵蓋一種問法,你測到的其實不是 agent 的能力,是你自己想像力的邊界。

ADK 對這個問題的答案分成兩半:User Simulation 讓使用者本身變成一個可配置的模擬角色,Environment Simulation 則讓 agent 呼叫的外部工具也能被安全地攔截、替換成可控的假回應。兩者合起來,你可以在完全不碰真實後端、不需要真人測試員的情況下,跑出高度真實又能重現的評估。

https://ithelp.ithome.com.tw/upload/images/20260919/20183762WcRWFWnwuX.png

User Simulation:給 LLM 一份劇本大綱,不是逐字台詞

Supported in ADK。核心物件是 ConversationScenario,它不是寫死的對話稿,而是「使用者這次對話想達成什麼目標」的高階指引,由三個欄位組成:

  • starting_prompt:固定的開場白
  • conversation_plan:使用者要達成的目標,用自然語言描述
  • user_persona:使用者的性格特徵——多懂技術、講話風格怎麼樣

一個 hello_world 範例:

{
  "starting_prompt": "What can you do for me?",
  "conversation_plan": "Ask the agent to roll a 20-sided die. After you get the result, ask the agent to check if it is prime."
}

conversation_plan 只定義了「要做什麼」,至於怎麼問、用什麼順序問、要不要一次問完——由 LLM 依照對話歷史動態生成。這正是它比固定 prompt 集合更貼近真實的地方:同一個 conversation_plan 每次跑都可能生出不同的問法,但目標行為是穩定的。

三種內建人設,行為維度是量化的

user_persona 可以直接套一個預建人設:

{
  "starting_prompt": "What can you do for me?",
  "conversation_plan": "Ask the agent to roll a 20-sided die. After you get the result, ask the agent to check if it is prime.",
  "user_persona": "NOVICE"
}

conversation_plan 決定目標,user_persona 決定怎麼講、怎麼回應 agent。三個預建人設在五個行為維度上各自不同:

行為 EXPERT NOVICE EVALUATOR
Advance(主動提供細節) 主動,細節導向 被動,等被問才給 細節導向
Answer(回答問題) 只答相關問題 全部問題都答 只答相關問題
Correct Agent Inaccuracies(糾正 agent 的錯) 會 不會 不會
Troubleshoot Agent Errors(排解 agent 的錯誤) 一次 從不 從不
Tone(語氣) 專業 口語 口語

這張表值得多看一眼——它把「使用者類型」量化成五個可測、可比較的行為軸,而不是模糊地寫「這是個新手使用者」。想測 agent 面對不耐煩、講話破碎的使用者會不會崩潰,你也可以自己定義 UserPersona,每個行為(UserBehavior)包含名稱、描述、給模擬使用者的具體指令(behavior_instructions),以及判定違規的 rubric(violation_rubrics——只要任何一條被滿足,評估器就判定使用者沒有遵守這個行為)。官方範例是一個沒耐心的使用者:

{
  "starting_prompt": "I need help with my account.",
  "conversation_plan": "Ask the agent to reset your password.",
  "user_persona": {
    "id": "IMPATIENT_USER",
    "description": "A user who is in a rush and gets easily frustrated.",
    "behaviors": [{
      "name": "Short responses",
      "description": "The user should provide very short, sometimes incomplete responses.",
      "behavior_instructions": ["Keep your responses under 10 words.", "Omit polite phrases."],
      "violation_rubrics": ["The user response is over 10 words.", "The user response is overly polite."]
    }]
  }
}

跑起來:從場景到 eval set 到執行

把場景收進 EvalSet 分三步。先寫場景檔:

{
  "scenarios": [
    {
      "starting_prompt": "What can you do for me?",
      "conversation_plan": "Ask the agent to roll a 20-sided die. After you get the result, ask the agent to check if it is prime.",
      "user_persona": "NOVICE"
    }
  ]
}

你還需要一份 session input 檔,內容是這個 agent 執行時的 app 名稱與 user id,例如存成 session_input.json:

{"app_name": "hello_world", "user_id": "user"}

contributing/samples/core/hello_world 是 adk-python 官方 repo 內建的範例 agent,跑下面這段前要先 git clone 這個 repo,或是把路徑換成你自己的 agent 目錄。再用 CLI 把場景加進 eval set:

adk eval_set create contributing/samples/core/hello_world eval_set_with_scenarios

adk eval_set add_eval_case \
  contributing/samples/core/hello_world \
  eval_set_with_scenarios \
  --scenarios_file contributing/samples/core/hello_world/conversation_scenarios.json \
  --session_input_file contributing/samples/core/hello_world/session_input.json

這裡有個容易踩的坑:動態場景沒有預先寫好的標準答案,所以 Day 26 那組需要參照答案的準則(tool_trajectory_avg_score、response_match_score、final_response_match_v2)在這裡用不了,必須換成不需要標準答案的準則,比如 hallucinations_v1 與 safety_v1:

{
  "criteria": {
    "hallucinations_v1": {"threshold": 0.5, "evaluate_intermediate_nl_responses": true},
    "safety_v1": {"threshold": 0.8}
  }
}

最後照常用 adk eval 跑。使用者模擬器本身也能調:

  • user_simulator_config:可以換掉背後的模型
  • max_allowed_invocations:對話被強制中止前允許的最大輪數。官方特別提醒設成 -1 拿掉上限是不建議的做法,因為模擬失控的對話會一直燒 token
  • include_function_calls:要不要把中間的 function call 也塞進給模擬器看的對話歷史,預設關閉
  • custom_instructions:想完全自訂模擬器的行為指令,接受 Jinja 樣板語法,可用的變數包含 {{ stop_signal }}、{{ conversation_plan }}、{{ conversation_history }}、{{ persona }}

嫌手寫場景太慢,還有一條路:adk eval_set generate_eval_cases 能用 Agent Platform Eval SDK 自動生成多樣化的對話場景,只要給定生成數量、一句生成指引、以及環境脈絡(比如你系統裡有哪些合法的裝置 ID),它就會產出一批貼近真實資料分布的測試場景——不過這需要 GCP 專案憑證。

Environment Simulation:讓後端也配合演出

User Simulation 解決的是「對話怎麼往前推」,Environment Simulation 解決的是另一半——agent 呼叫的工具背後那個世界要怎麼配合演。真實的 API、資料庫、第三方服務在測試時往往慢、貴,或乾脆不穩定;Environment Simulator 攔截這些工具呼叫,換成可控的合成回應,讓你能做到:離線測試(不接真實後端)、測試 agent 面對 API 錯誤或邊界回應的反應、用 LLM 自動生成逼真的假回應、以及用固定亂數種子讓機率性的錯誤注入變得可重現。

它掛進 ADK 的方式有兩種,都不用改 agent 程式碼本身——before_tool_callback 掛鉤,或整個包成 plugin。下面範例裡的 get_user_profile 假設是你原本 agent 就有的一個工具函式(例如查資料庫回傳使用者資料),Environment Simulator 要攔截的是它,不是重新定義它:

from google.adk.agents import LlmAgent
from google.adk.tools.environment_simulation import EnvironmentSimulationFactory
from google.adk.tools.environment_simulation.environment_simulation_config import (
    EnvironmentSimulationConfig, InjectedError, InjectionConfig, ToolSimulationConfig,
)

config = EnvironmentSimulationConfig(
    tool_simulation_configs=[
        ToolSimulationConfig(
            tool_name="get_user_profile",
            injection_configs=[InjectionConfig(
                injected_error=InjectedError(injected_http_error_code=503, error_message="Service temporarily unavailable.")
            )],
        )
    ]
)

agent = LlmAgent(
    name="my_agent", model="gemini-flash-latest", tools=[get_user_profile],
    before_tool_callback=EnvironmentSimulationFactory.create_callback(config),
)

三層決策順序:注入 → 模擬 → 放行

對每個被設定的工具,模擬器依序檢查三種可能:先看有沒有符合條件的 injection config(按順序檢查參數是否匹配、機率是否命中,命中就直接回傳預先定義的錯誤或回應);沒有命中的話,退到 mock strategy,用 LLM 依照工具 schema 與目前的狀態脈絡生成一個合理回應;工具完全沒被設定過,就是 no-op,回傳 None,讓真實工具照常執行。這個順序本身就是設計重點——精確的手動情境(比如「這個 SKU 一定要回缺貨」)優先於泛用的自動生成,自動生成又優先於什麼都不做。

注入錯誤很直覺,指定 HTTP 狀態碼與訊息:

ToolSimulationConfig(
    tool_name="charge_payment",
    injection_configs=[InjectionConfig(
        injected_error=InjectedError(injected_http_error_code=402, error_message="Payment declined.")
    )],
)

條件式注入用 match_args 只在特定參數出現時才觸發,其餘呼叫繼續走下一個 injection config 或 mock strategy:

InjectionConfig(
    match_args={"item_id": "ITEM-404"},
    injected_error=InjectedError(injected_http_error_code=404, error_message="Item not found."),
)

機率性注入拿 injection_probability 模擬時好時壞的不穩定服務,搭配 random_seed 讓這個「不穩定」變成可重現的測試,而不是每次跑出不同結果、讓你沒法比對:

InjectionConfig(
    injection_probability=0.3, random_seed=42,
    injected_error=InjectedError(injected_http_error_code=500, error_message="Internal server error."),
)

injected_latency_seconds(上限 120 秒)則是專門測 timeout 處理與使用者體驗降級的手法。

Mock strategy:讓 LLM 自己記住「誰創造了誰」

MOCK_STRATEGY_TOOL_SPEC 是這個功能裡最聰明的一塊。它不是每次呼叫都獨立生成隨機回應,而是先分析 agent 手上所有工具的 schema,辨識出工具之間的狀態依賴關係(例如 create_order 產生的 order_id,是 get_order 會消費的輸入),接著維護一個貫穿整個 session 的狀態存放區,讓後續生成的回應跟這個狀態一致——如果 get_order 被要求查一個從未被 create_order 建立過的 order_id,它會正確地回傳類似 404 的錯誤,而不是憑空生一筆假訂單。這意味著你可以放心地把一整組互相依賴的工具都交給 mock strategy:

from google.adk.tools.environment_simulation.environment_simulation_config import MockStrategy

config = EnvironmentSimulationConfig(
    tool_simulation_configs=[
        ToolSimulationConfig(tool_name="create_order", mock_strategy_type=MockStrategy.MOCK_STRATEGY_TOOL_SPEC),
        ToolSimulationConfig(tool_name="get_order", mock_strategy_type=MockStrategy.MOCK_STRATEGY_TOOL_SPEC),
        ToolSimulationConfig(tool_name="cancel_order", mock_strategy_type=MockStrategy.MOCK_STRATEGY_TOOL_SPEC),
    ]
)

想讓生成的回應更貼近你的實際業務資料,environment_data 可以塞一份 JSON 資料庫快照進去(商品名稱、價格、庫存這種),tracing 則可以餵一份先前真實跑過的 agent trace 當歷史脈絡——兩者都是讓 LLM「照著你的資料長相」生成,而不是生出通用的佔位文字。注入與 mock strategy 也能混用在同一個工具上:先檢查是不是已知的壞案例(比如某個帳號一律回失敗),其餘情況才交給 mock strategy 生成合理的成功回應。

這本質上就是混沌工程(chaos testing)搬進 agent 評估的做法——傳統系統測試怎麼故意注入延遲與錯誤來驗證容錯能力,ADK 現在讓你對 agent 的工具呼叫做同一件事,而且全程離線、可重現、不需要一個真的會故障的後端。

明天:把「安不安全」也變成可測項目

今天處理的是「agent 面對不確定的使用者與不確定的後端會不會照樣正確運作」。明天要處理另一個更嚴肅的問題——如果使用者是惡意的呢?如果工具被拿去做了 agent 開發者從沒授權過的事呢?ADK 的縱深防禦怎麼在身分、guardrail、沙盒執行三個層次上把這件事鎖住。


Google ADK 官方網站
GitHub - Agent Development Kit (ADK) 2.0

GitHub 開源實作:https://github.com/SeanLinH/adk_tutor


上一篇
Day 26 - 評估與最佳化:讓 Agent 的品質可以被測量
下一篇
Day 28 - 企業級安全網:誰能替 Agent 的行為負責
系列文
Google ADK Agent 教戰:30 天從原型到可上線的 AI Agent 系統 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言