הערכת סוכנים באמצעות ADK

1. פער האמון

רגע של השראה

יצרתם נציג שירות לקוחות. היא פועלת במחשב שלכם. אבל אתמול, הוא אמר ללקוח ששעון חכם שאין במלאי זמין, או גרוע מכך, המציא מדיניות החזר כספי. איך ישנים בלילה כשנציג נמצא אונליין?

כדי לגשר על הפער בין הוכחת היתכנות לבין סוכן AI שמוכן לייצור, נדרשת מסגרת הערכה חזקה ואוטומטית.

שירות לקוחות

מה אנחנו בודקים בפועל?

הערכת סוכנים מורכבת יותר מהערכה רגילה של מודל שפה גדול. אתם לא נותנים ציון רק לחיבור (התשובה הסופית), אלא גם למתמטיקה (הלוגיקה או הכלים ששימשו להגעה לתשובה).

תרשים הערכה

  1. המסלול (התהליך): האם הסוכן השתמש בכלי הנכון בזמן הנכון? האם התקשרת אל check_inventory לפני place_order?
  2. תשובה סופית (הפלט): האם התשובה נכונה, מנומסת ומבוססת על הנתונים?

מחזור החיים של הפיתוח

בשיעור הזה נסביר על מחזור החיים המקצועי של בדיקת סוכנים:

  1. בדיקה ויזואלית מקומית (ממשק משתמש באינטרנט של ADK): צ'אט ידני ואימות של הלוגיקה (שלב 1).
  2. בדיקות יחידה/רגרסיה (ADK CLI): הפעלת תרחישי בדיקה ספציפיים באופן מקומי כדי לזהות שגיאות במהירות (שלבים 3 ו-4).
  3. ניפוי באגים (פתרון בעיות): ניתוח של כשלים ותיקון הלוגיקה של ההנחיה (שלב 5).
  4. שילוב CI/CD‏ (Pytest): אוטומציה של בדיקות בצינור העיבוד של ה-build (שלב 6).

2. הגדרה

כדי להפעיל את סוכני ה-AI שלנו, אנחנו צריכים שני דברים: פרויקט ב-Google Cloud שיספק את הבסיס.

שלב 1: הפעלת חשבון לחיוב

  • כדי לממש את הזיכוי של 5 דולר בחשבון לחיוב, תצטרכו אותו לפריסה. חשוב לוודא שאתם מחוברים לחשבון Gmail.

שלב 2: פותחים את הסביבה

‫👈 לוחצים על 'הפעלת Cloud Shell' בחלק העליון של מסוף Google Cloud (זהו סמל בצורת טרמינל בחלק העליון של חלונית Cloud Shell).

טקסט חלופי

‫👈 לוחצים על הלחצן Open Editor (פתיחת העורך) (הוא נראה כמו תיקייה פתוחה עם עיפרון). חלון Cloud Shell Code Editor ייפתח. בצד ימין יופיע סייר הקבצים. טקסט חלופי

‫👈פותחים את הטרמינל בסביבת הפיתוח המשולבת (IDE) בענן, טקסט חלופי

‫👈💻 בטרמינל, מוודאים שכבר עברתם אימות ושהפרויקט מוגדר למזהה הפרויקט שלכם באמצעות הפקודה הבאה:

gcloud auth list

‫👈💻 משכפלים את פרויקט ה-bootstrap מ-GitHub:

git clone https://github.com/cuppibla/adk_eval_starter

‫👈💻 מריצים את סקריפט ההגדרה מהספרייה של הפרויקט.

⚠️ הערה לגבי מזהה הפרויקט: התסריט יציע מזהה פרויקט שנוצר באופן אקראי כברירת מחדל. אפשר להקיש על Enter כדי לאשר את ברירת המחדל.

עם זאת, אם אתם מעדיפים ליצור פרויקט חדש ספציפי, אתם יכולים להקליד את מזהה הפרויקט הרצוי כשתתבקשו לעשות זאת על ידי הסקריפט.

cd ~/adk_eval_starter
./init.sh

התסריט יטפל בשאר תהליך ההגדרה באופן אוטומטי.

👉 שלב חשוב אחרי השלמת התהליך: אחרי שהסקריפט מסיים לפעול, צריך לוודא שבמסוף Google Cloud מוצג הפרויקט הנכון:

  1. נכנסים לכתובת console.cloud.google.com.
  2. לוחצים על התפריט הנפתח לבחירת פרויקט בחלק העליון של הדף.
  3. לוחצים על הכרטיסייה All (הכול) (כי יכול להיות שהפרויקט החדש עדיין לא יופיע ב-Recent (אחרונים)).
  4. בוחרים את מזהה הפרויקט שהגדרתם בשלב init.sh.

03-05-project-all.png

‫👈💻 מגדירים את מזהה הפרויקט הנדרש:

gcloud config set project $(cat ~/project_id.txt) --quiet

הגדרת הרשאות

‫👉💻 מפעילים את ממשקי ה-API הנדרשים באמצעות הפקודה הבאה. היא עשויה להימשך כמה דקות.

gcloud services enable \
    cloudresourcemanager.googleapis.com \
    servicenetworking.googleapis.com \
    run.googleapis.com \
    cloudbuild.googleapis.com \
    artifactregistry.googleapis.com \
    aiplatform.googleapis.com \
    compute.googleapis.com

‫👉💻 מעניקים את ההרשאות הנדרשות על ידי הרצת הפקודות הבאות בטרמינל:

. ~/adk_eval_starter/set_env.sh

שימו לב שנוצר לכם קובץ .env. יוצג מידע על הפרויקט.

3. יצירת מערך הזהב (adk web)

Golden

כדי שנוכל לתת לנציג ציון, אנחנו צריכים מפתח תשובות. ב-ADK, אנחנו קוראים לזה מערך הנתונים הזהוב. מערך הנתונים הזה מכיל אינטראקציות 'מושלמות' שמשמשות כנתוני אמת להערכה.

מהו מערך נתונים רשמי?

מערך נתונים מוזהב הוא תמונת מצב של סוכן שמבצע פעולות בצורה נכונה. זו לא רק רשימה של זוגות של שאלות ותשובות. היא מתעדת:

  • שאילתת המשתמש ("אני רוצה לקבל החזר כספי")
  • המסלול (הרצף המדויק של קריאות הכלים: check_order -> verify_eligibility -> refund_transaction).
  • התשובה הסופית (התשובה הטקסטואלית ה "מושלמת").

אנחנו משתמשים בנתונים האלה כדי לזהות רגרסיות. אם תעדכנו את ההנחיה והנציג יפסיק פתאום לבדוק את הזכאות לפני מתן ההחזר הכספי, הבדיקה של מערך הזהב תיכשל כי המסלול כבר לא תואם.

פתיחת ממשק המשתמש האינטרנטי

ממשק המשתמש האינטרנטי של ADK מאפשר ליצור את מערכי הנתונים המוזהבים האלה בצורה אינטראקטיבית על ידי תיעוד אינטראקציות אמיתיות עם הסוכן.

‫👈 בטרמינל, מריצים את הפקודה:

cd ~/adk_eval_starter
uv run adk web

‫👈 פתיחת התצוגה המקדימה של ממשק המשתמש האינטרנטי (בדרך כלל בכתובת http://127.0.0.1:8000).

‫👈 בממשק המשתמש של הצ'אט, מקלידים

Hi, I'm customer CUST001. Can you check my orders? I need a refund for order ORD-102. It arrived damaged.

adk eval result

תראו תשובה כמו:

I've processed your refund for order ORD-102 due to the items arriving damaged. A full refund of $35.0 has been processed, and the status of order ORD-102 is now updated to "refunded".

Is there anything else I can assist you with today, CUST001? 🛍️

תיעוד אינטראקציות מוצלחות

עוברים לכרטיסייה Sessions (סשנים). כאן אפשר לראות את היסטוריית השיחות של הנציג על ידי לחיצה על הסשן.

  1. אתם יכולים לנהל שיחה עם הסוכן כדי ליצור תהליך שיחה אידיאלי, למשל לבדוק את היסטוריית הרכישות או לבקש החזר כספי.
  2. בודקים את השיחה כדי לוודא שהיא מייצגת את ההתנהגות הצפויה.

מעקב אחר הערכה

4. ייצוא של מערך הנתונים הרשמי

אימות באמצעות הכלי 'צפייה בעקבות'

לפני שמייצאים, צריך לוודא שהנציג לא קיבל את התשובה הנכונה במקרה. צריך לבדוק את הלוגיקה הפנימית.

  1. לוחצים על הכרטיסייה Trace בממשק המשתמש האינטרנטי.
  2. המעקב מתבצע אוטומטית לפי הודעת משתמש. מעבירים את העכבר מעל שורה של נתוני מעקב כדי להדגיש את ההודעה התואמת בצ'אט.
  3. בדיקת השורות הכחולות: השורות האלה מציינות אירועים שנוצרו מהאינטראקציה. לוחצים על שורה כחולה כדי לפתוח את חלונית הבדיקה.
  4. כדי לאמת את הלוגיקה, בודקים את הכרטיסיות הבאות:
    • תרשים: ייצוג חזותי של הקריאות לכלי ושל זרימת הלוגיקה. האם היא בחרה בנתיב הנכון?
    • בקשה/תגובה: בדיקה מדויקת של מה שנשלח למודל ומה שהתקבל ממנו.
    • אימות: אם הסוכן ניחש את סכום ההחזר הכספי בלי להתקשר לכלי מסד הנתונים, זהו "חיזיון ממוזל". eval verify

Add Session to EvalSet

אחרי שמרוצים מהשיחה ומהמעקב: 👉 לוחצים על הכרטיסייה Eval, ואז על הלחצן Create Evaluation Set ומזינים את שם ההערכה:

evalset1

קבוצת נתונים להערכה

‫👈 במערך ההערכה הזה, לוחצים על Add current session to evalset1. בחלון הקופץ, מזינים את שם הסשן:

eval1

eval create

הפעלת Eval ב-ADK Web

‫👈 בממשק האינטרנט של ADK, לוחצים על Run Evaluation, בחלון הקופץ משנים את המדדים ולוחצים על Start:

הפעלת הערכה

אימות מערך הנתונים במאגר

יופיע אישור שקובץ של מערך נתונים (למשל, ‫evalset1.evalset.json) נשמר במאגר. הקובץ הזה מכיל את התיעוד הגולמי של השיחה שנוצר באופן אוטומטי.

שמירת קבוצת הערכה

5. קבצי ההערכה

קובץ eval

ממשק המשתמש באינטרנט יוצר קובץ .evalset.json מורכב, אבל הרבה פעמים אנחנו רוצים ליצור קובץ בדיקה נקי ומובנה יותר לבדיקות אוטומטיות.

הכלי ADK Eval משתמש בשני רכיבים עיקריים:

  1. קבצי בדיקה: יכולים להיות קבוצת הנתונים המוזהבת שנוצרה אוטומטית (לדוגמה, customer_service_agent/evalset1.evalset.json) או קבוצה שנבחרה ידנית (למשל, customer_service_agent/eval.test.json).
  2. קובצי תצורה (למשל, ‫customer_service_agent/test_config.json): מגדירים את המדדים ואת ערכי הסף להצלחה.

הגדרת קובץ התצורה של הבדיקה

‫👈 פותחים את customer_service_agent/test_config.json בעורך.

מזינים את הקוד הבא:

{
  "criteria": {
    "tool_trajectory_avg_score": 0.8,
    "response_match_score": 0.5
  }
}

הסבר על המדדים

  1. tool_trajectory_avg_score (התהליך) המדד הזה בודק אם הנציג השתמש בכלים בצורה נכונה.
  • 0.8: אנחנו דורשים התאמה של 80%.
  1. response_match_score (הפלט) נעשה כאן שימוש ב-ROUGE-1 (חפיפה בין מילים) כדי להשוות את התשובה להפניה המוזהבת.
  • יתרונות: מהיר, דטרמיניסטי, חינמי.
  • חסרונות: נכשל אם הסוכן מנסח את אותו רעיון בצורה שונה (למשל, ההבדל בין 'הוחזר כסף' לבין 'הכסף הוחזר').

מדדים מתקדמים (למקרים שבהם נדרשת עוצמה רבה יותר)

6. הפעלת הערכה של מערך נתונים מוזהב (adk eval)

לולאה פנימית

השלב הזה מייצג את 'הלולאה הפנימית' של הפיתוח. אתם מפתחים שמבצעים שינויים ורוצים לאמת את התוצאות במהירות.

הפעלת מערך הנתונים הרשמי

נריץ את מערך הנתונים שיצרתם בשלב 1. כך תוכלו לוודא שנקודת הבסיס שלכם אמינה.

‫👈 בטרמינל, מריצים את הפקודה:

cd ~/adk_eval_starter
uv run adk eval customer_service_agent customer_service_agent/evalset1.evalset.json --config_file_path=customer_service_agent/test_config.json --print_detailed_results

מה קורה?

ה-ADK עכשיו:

  1. טעינת הנציג מ-customer_service_agent.
  2. מריצים את שאילתות הקלט מ-evalset1.evalset.json.
  3. השוואה בין המסלול והתשובות בפועל של הסוכן לבין אלה שצפויים.
  4. התוצאות מקבלות ניקוד על סמך הקריטריונים שמופיעים במאמר test_config.json.

ניתוח התוצאות

צופים בפלט הטרמינל. יוצג סיכום של הבדיקות שעברו והבדיקות שנכשלו.

Eval Run Summary
evalset1:
  Tests passed: 1
  Tests failed: 0
********************************************************************
Eval Set Id: evalset1
Eval Id: eval1
Overall Eval Status: PASSED
---------------------------------------------------------------------
Metric: tool_trajectory_avg_score, Status: PASSED, Score: 1.0, Threshold: 0.8
---------------------------------------------------------------------
Metric: response_match_score, Status: PASSED, Score: 0.5581395348837208, Threshold: 0.5
---------------------------------------------------------------------
Invocation Details:
+----+---------------------------+---------------------------+--------------------------+---------------------------+---------------------------+-----------------------------+------------------------+
|    | prompt                    | expected_response         | actual_response          | expected_tool_calls       | actual_tool_calls         | tool_trajectory_avg_score   | response_match_score   |
+====+===========================+===========================+==========================+===========================+===========================+=============================+========================+
|  0 | Hi, I'm customer CUST001. | Great news! Your refund   | Great news, CUST001! 🎉   | id='adk-051409fe-c230-43f | id='adk-4e9aa570-1cc6-4c3 | Status: PASSED, Score:      | Status: PASSED, Score: |
|    | Can you check my orders?  | for order **ORD-102** has | I've successfully        | 4-a7f1- 5747280fd878'     | c-aa3e- 91dbe113dd4b'     | 1.0                         | 0.5581395348837208     |
|    | I need a refund for order | been successfully         | processed a full refund  | args={'customer_id':      | args={'customer_id':      |                             |                        |
|    | ORD-102. It arrived       | processed due to the item | of $35.0 for your order  | 'CUST001'} name='get_purc | 'CUST001'} name='get_purc |                             |                        |
|    | damaged.                  | arriving damaged. You     | ORD-102 because it       | hase_history'             | hase_history'             |                             |                        |
|    |                           | should see a full refund  | arrived damaged. The     | partial_args=None         | partial_args=None         |                             |                        |
|    |                           | of $35.0 back to your     | status of that order has | will_continue=None id= 'a | will_continue=None        |                             |                        |
|    |                           | original payment method   | been updated to          | dk-8a194cb8-5a82-47ce-a3a | id='adk- dad1b376-9bcc-48 |                             |                        |
|    |                           | shortly. The status of    | "refunded."  Is there    | 7- 3d24551f8c90'          | bb-996f-a30f6ef5b70b'     |                             |                        |
|    |                           | this order has been       | anything else I can      | args={'reason':           | args={'reason':           |                             |                        |
|    |                           | updated to "refunded".    | assist you with today?   | 'damaged', 'order_id':    | 'damaged', 'order_id':    |                             |                        |
|    |                           | Here's your updated       |                          | 'ORD-102'}                | 'ORD-102'}                |                             |                        |
|    |                           | purchase history for      |                          | name='issue_refund'       | name='issue_refund'       |                             |                        |
|    |                           | CUST001: *   **ORD-101**: |                          | partial_args=None         | partial_args=None         |                             |                        |
|    |                           | Wireless Headphones,      |                          | will_continue=None        | will_continue=None        |                             |                        |
|    |                           | delivered on 2023-10-15   |                          |                           |                           |                             |                        |
|    |                           | (Total: $120) *           |                          |                           |                           |                             |                        |
|    |                           | **ORD-102**: USB-C Cable, |                          |                           |                           |                             |                        |
|    |                           | Phone Case, refunded on   |                          |                           |                           |                             |                        |
|    |                           | 2023-11-01 (Total: $35)   |                          |                           |                           |                             |                        |
|    |                           | Is there anything else I  |                          |                           |                           |                             |                        |
|    |                           | can help you with today?  |                          |                           |                           |                             |                        |
|    |                           | 😊                         |                          |                           |                           |                             |                        |
+----+---------------------------+---------------------------+--------------------------+---------------------------+---------------------------+-----------------------------+------------------------+

הערה: מכיוון שהקוד נוצר ישירות מהסוכן, הוא אמור לעבור את הבדיקה ב-100%. אם הוא נכשל, הסוכן לא דטרמיניסטי (אקראי).

7. יצירת מבחן בהתאמה אישית

מערכי נתונים שנוצרים אוטומטית הם מצוינים, אבל לפעמים צריך ליצור ידנית מקרים קיצוניים (למשל, התקפות יריבות או טיפול בשגיאות ספציפיות). בואו נראה איך eval.test.json מאפשר לכם להגדיר את המושג 'נכונות'.

בואו ניצור חבילת בדיקות מקיפה.

מסגרת הבדיקות

כשכותבים תרחיש בדיקה ב-ADK, צריך לפעול לפי הנוסחה הבאה בת 3 החלקים:

  • ההגדרה (session_input): מי המשתמש? (למשל: user_id, state). כך הבדיקה מבודדת.
  • ההנחיה (user_content): מהו הטריגר?

עם הטענות (הציפיות):

  • מסלול (tool_uses): האם החישוב נכון? (לוגיקה)
  • תשובה (final_response): האם התשובה הייתה נכונה? (איכות)
  • בינוני (intermediate_responses): האם הסוכנים המשניים דיברו בצורה נכונה? (תזמור)

כתיבת חבילת הבדיקות

‫👈 פותחים את customer_service_agent/eval.test.json בעורך.

מזינים את הקוד הבא:

{
  "eval_set_id": "customer_service_eval",
  "name": "Customer Service Agent Evaluation",
  "description": "Evaluation suite for the customer service agent covering product info, purchase history, and refunds.",
  "eval_cases": [
    {
      "eval_id": "product_info_check",
      "session_input": {
        "app_name": "customer_service_agent",
        "user_id": "eval_user_1",
        "state": {}
      },
      "conversation": [
        {
          "invocation_id": "turn_1_product_info",
          "user_content": {
            "role": "user",
            "parts": [
              {
                "text": "Do you have wireless headphones in stock?"
              }
            ]
          },
          "final_response": {
            "role": "model",
            "parts": [
              {
                "text": "Yes, we have wireless headphones in stock! They are priced at $120.00 and feature noise-canceling with a 20-hour battery life. 🎧"
              }
            ]
          },
          "intermediate_data": {
            "tool_uses": [
              {
                "name": "lookup_product_info",
                "args": {
                  "product_name": "wireless headphones"
                }
              }
            ],
            "intermediate_responses": []
          }
        }
      ]
    },
    {
      "eval_id": "purchase_history_check",
      "session_input": {
        "app_name": "customer_service_agent",
        "user_id": "eval_user_2",
        "state": {}
      },
      "conversation": [
        {
          "invocation_id": "turn_1_purchase_history",
          "user_content": {
            "role": "user",
            "parts": [
              {
                "text": "What did I buy recently? My customer ID is CUST001."
              }
            ]
          },
          "final_response": {
            "role": "model",
            "parts": [
              {
                "text": "Here's your recent purchase history for Customer ID CUST001:\n\n*   **Order ORD-101** (October 15, 2023): Wireless Headphones, Status: delivered, Total: $120\n*   **Order ORD-102** (November 1, 2023): USB-C Cable, Phone Case, Status: refunded, Total: $35\n\nLet me know if you have any other questions or need further assistance! 🛍️"
              }
            ]
          },
          "intermediate_data": {
            "tool_uses": [
              {
                "name": "get_purchase_history",
                "args": {
                  "customer_id": "CUST001"
                }
              }
            ],
            "intermediate_responses": []
          }
        }
      ]
    },
    {
      "eval_id": "refund_request",
      "session_input": {
        "app_name": "customer_service_agent",
        "user_id": "eval_user_3",
        "state": {}
      },
      "conversation": [
        {
          "invocation_id": "turn_1_refund",
          "user_content": {
            "role": "user",
            "parts": [
              {
                "text": "I want a refund for order ORD-102 because it was damaged."
              }
            ]
          },
          "final_response": {
            "role": "model",
            "parts": [
              {
                "text": "Your refund for order **ORD-102** due to \"damaged\" has been successfully processed!  Refund amount: **$35.0**. Your order status has been updated to **refunded**. \nIs there anything else I can help you with today? 🛍️"
              }
            ]
          },
          "intermediate_data": {
            "tool_uses": [
              {
                "name": "issue_refund",
                "args": {
                  "order_id": "ORD-102",
                  "reason": "damaged"
                }
              }
            ],
            "intermediate_responses": []
          }
        }
      ]
    }
  ]
}

פירוק סוגי הבדיקות

יצרנו כאן שלושה סוגים שונים של בדיקות. בואו נפרט מה כל אחד מהם בודק ולמה.

  1. מבחן הכלי היחיד (product_info_check)
  • המטרה: אימות של שליפת מידע בסיסי.
  • Key Assertion: אנחנו מסמנים את התיבה intermediate_data.tool_uses. אנחנו טוענים שהפונקציה lookup_product_info נקראת. הטענה שלנו היא שהארגומנט product_name הוא בדיוק 'אוזניות אלחוטיות'.
  • למה: אם המודל ממציא מחיר בלי להפעיל את הכלי, הבדיקה הזו נכשלת. כך מוודאים שיש הארקה.
  1. הבדיקה של חילוץ ההקשר (purchase_history_check)
  • מטרה: לוודא שהסוכן יכול לחלץ ישויות (CUST001) מההנחיה של המשתמש ולהעביר אותן לכלי.
  • Key Assertion: אנחנו בודקים שהפונקציה get_purchase_history נקראת עם customer_id: "CUST001".
  • למה: מצב נפוץ של כשל הוא שהסוכן קורא לכלי הנכון אבל עם מזהה null. כך אפשר לוודא שהפרמטרים מדויקים.
  1. הבדיקה של פעולה או מסלול (refund_request)
  • יעד: אימות פעולת כתיבה קריטית.
  • טענה מרכזית: המסלול. בתרחיש מורכב יותר, הרשימה הזו תכלול כמה שלבים: [verify_order, calculate_refund, issue_refund]. ה-ADK בודק את הרשימה הזו לפי הסדר.
  • הסיבה: כשמדובר בפעולות שקשורות להעברת כספים או לשינוי נתונים, הרצף חשוב לא פחות מהתוצאה. אתם לא רוצים להנפיק החזר כספי לפני האימות.

8. הרצת הערכה לבדיקות בהתאמה אישית (adk eval)

לולאה פנימית

‫👈 בטרמינל, מריצים את הפקודה:

cd ~/adk_eval_starter
uv run adk eval customer_service_agent customer_service_agent/eval.test.json --config_file_path=customer_service_agent/test_config.json --print_detailed_results

הסבר על הפלט

אמורה להתקבל תוצאה של PASS (עבר) שנראית כך:

Eval Run Summary
customer_service_eval:
  Tests passed: 3
  Tests failed: 0
********************************************************************
Eval Set Id: customer_service_eval
Eval Id: purchase_history_check
Overall Eval Status: PASSED
---------------------------------------------------------------------
Metric: tool_trajectory_avg_score, Status: PASSED, Score: 1.0, Threshold: 0.8
---------------------------------------------------------------------
Metric: response_match_score, Status: PASSED, Score: 0.5473684210526315, Threshold: 0.5
---------------------------------------------------------------------
Invocation Details:
+----+--------------------------+---------------------------+---------------------------+---------------------------+---------------------------+-----------------------------+------------------------+
|    | prompt                   | expected_response         | actual_response           | expected_tool_calls       | actual_tool_calls         | tool_trajectory_avg_score   | response_match_score   |
+====+==========================+===========================+===========================+===========================+===========================+=============================+========================+
|  0 | What did I buy recently? | Here's your recent        | Looks like your recent    | id=None                   | id='adk-8960eb53-2933-459 | Status: PASSED, Score:      | Status: PASSED, Score: |
|    | My customer ID is        | purchase history for      | orders include: *         | args={'customer_id':      | f-b306- 71e3c069e77e'     | 1.0                         | 0.5473684210526315     |
|    | CUST001.                 | Customer ID CUST001:  *   | **ORD-101 (2023-10-15):** | 'CUST001'} name='get_purc | args={'customer_id':      |                             |                        |
|    |                          | **Order ORD-101**         | Wireless Headphones for   | hase_history'             | 'CUST001'} name='get_purc |                             |                        |
|    |                          | (October 15, 2023):       | $120.00 - Status:         | partial_args=None         | hase_history'             |                             |                        |
|    |                          | Wireless Headphones,      | Delivered 🎧 *   **ORD-102 | will_continue=None        | partial_args=None         |                             |                        |
|    |                          | Status: delivered, Total: | (2023-11-01):** USB-C     |                           | will_continue=None        |                             |                        |
|    |                          | $120 *   **Order          | Cable, Phone Case for     |                           |                           |                             |                        |
|    |                          | ORD-102** (November 1,    | $35.00 - Status: Refunded |                           |                           |                             |                        |
|    |                          | 2023): USB-C Cable, Phone | 📱  Is there anything else |                           |                           |                             |                        |
|    |                          | Case, Status: refunded,   | I can help you with       |                           |                           |                             |                        |
|    |                          | Total: $35  Let me know   | regarding these orders?   |                           |                           |                             |                        |
|    |                          | if you have any other     |                           |                           |                           |                             |                        |
|    |                          | questions or need further |                           |                           |                           |                             |                        |
|    |                          | assistance! 🛍️            |                           |                           |                           |                             |                        |
+----+--------------------------+---------------------------+---------------------------+---------------------------+---------------------------+-----------------------------+------------------------+



********************************************************************
Eval Set Id: customer_service_eval
Eval Id: product_info_check
Overall Eval Status: PASSED
---------------------------------------------------------------------
Metric: tool_trajectory_avg_score, Status: PASSED, Score: 1.0, Threshold: 0.8
---------------------------------------------------------------------
Metric: response_match_score, Status: PASSED, Score: 0.6829268292682927, Threshold: 0.5
---------------------------------------------------------------------
Invocation Details:
+----+----------------------+---------------------------+---------------------------+---------------------------+---------------------------+-----------------------------+------------------------+
|    | prompt               | expected_response         | actual_response           | expected_tool_calls       | actual_tool_calls         | tool_trajectory_avg_score   | response_match_score   |
+====+======================+===========================+===========================+===========================+===========================+=============================+========================+
|  0 | Do you have wireless | Yes, we have wireless     | Yes, we do! 🎧 We have     | id=None                   | id='adk-4571d660-a92b-412 | Status: PASSED, Score:      | Status: PASSED, Score: |
|    | headphones in stock? | headphones in stock! They | noise-canceling wireless  | args={'product_name':     | a-a79e- 5c54f8b8af2d'     | 1.0                         | 0.6829268292682927     |
|    |                      | are priced at $120.00 and | headphones with a 20-hour | 'wireless headphones'} na | args={'product_name':     |                             |                        |
|    |                      | feature noise-canceling   | battery life available    | me='lookup_product_info'  | 'wireless headphones'} na |                             |                        |
|    |                      | with a 20-hour battery    | for $120.                 | partial_args=None         | me='lookup_product_info'  |                             |                        |
|    |                      | life. 🎧                   |                           | will_continue=None        | partial_args=None         |                             |                        |
|    |                      |                           |                           |                           | will_continue=None        |                             |                        |
+----+----------------------+---------------------------+---------------------------+---------------------------+---------------------------+-----------------------------+------------------------+



********************************************************************
Eval Set Id: customer_service_eval
Eval Id: refund_request
Overall Eval Status: PASSED
---------------------------------------------------------------------
Metric: tool_trajectory_avg_score, Status: PASSED, Score: 1.0, Threshold: 0.8
---------------------------------------------------------------------
Metric: response_match_score, Status: PASSED, Score: 0.6216216216216216, Threshold: 0.5
---------------------------------------------------------------------
Invocation Details:
+----+---------------------------+---------------------------+---------------------------+---------------------------+---------------------------+-----------------------------+------------------------+
|    | prompt                    | expected_response         | actual_response           | expected_tool_calls       | actual_tool_calls         | tool_trajectory_avg_score   | response_match_score   |
+====+===========================+===========================+===========================+===========================+===========================+=============================+========================+
|  0 | I want a refund for order | Your refund for order     | Your refund for order     | id=None args={'order_id': | id='adk-fb8ff1cc- cf87-41 | Status: PASSED, Score:      | Status: PASSED, Score: |
|    | ORD-102 because it was    | **ORD-102** due to        | **ORD-102** has been      | 'ORD-102', 'reason':      | f2-9b11-d4571b14287f'     | 1.0                         | 0.6216216216216216     |
|    | damaged.                  | "damaged" has been        | successfully processed!   | 'damaged'}                | args={'order_id':         |                             |                        |
|    |                           | successfully processed!   | You should see a full     | name='issue_refund'       | 'ORD-102', 'reason':      |                             |                        |
|    |                           | Refund amount: **$35.0**. | refund of $35.0 appear in | partial_args=None         | 'damaged'}                |                             |                        |
|    |                           | Your order status has     | your account shortly. We  | will_continue=None        | name='issue_refund'       |                             |                        |
|    |                           | been updated to           | apologize for the         |                           | partial_args=None         |                             |                        |
|    |                           | **refunded**.  Is there   | inconvenience! Is there   |                           | will_continue=None        |                             |                        |
|    |                           | anything else I can help  | anything else I can       |                           |                           |                             |                        |
|    |                           | you with today? 🛍️        | assist you with today? 😊  |                           |                           |                             |                        |
+----+---------------------------+---------------------------+---------------------------+---------------------------+---------------------------+-----------------------------+------------------------+

המשמעות היא שהנציג השתמש בכלים הנכונים וסיפק תשובה שדומה מספיק למה שציפיתם.

9. (אופציונלי: קריאה בלבד) – פתרון בעיות וניפוי באגים

הבדיקות ייכשלו. זו העבודה שלהם. אבל איך מתקנים אותן? בואו ננתח תרחישי כשל נפוצים ונראה איך אפשר לנפות באגים.

תרחיש א': כשל ב'מסלול'

השגיאה:

Result: FAILED
Reason: Criteria 'tool_trajectory_avg_score' failed. Score 0.0 < Threshold 1.0
Details:
EXPECTED: tool: lookup_order, then tool: issue_refund
ACTUAL:   tool: issue_refund

אבחון: הסוכן דילג על שלב האימות (lookup_order). זו שגיאה לוגית.

איך לפתור את הבעיה:

  • אל תנחשו: חוזרים אל ממשק האינטרנט של ADK (adk web).
  • שחזור: מקלידים בצ'אט את ההנחיה המדויקת מהבדיקה שנכשלה.
  • Trace: פתיחת תצוגת Trace. מעיינים בכרטיסייה 'גרף'.
  • תיקון ההנחיה: בדרך כלל צריך לעדכן את הנחיית המערכת. שינוי: "אתה סוכן מועיל". הוראה: "אתה סוכן מועיל. חשוב מאוד: חובה להפעיל את הפונקציה lookup_order כדי לאמת את הפרטים לפני שמפעילים את הפונקציה issue_refund."
  • התאמת הבדיקה: אם הלוגיקה העסקית השתנתה (למשל, אם כבר לא צריך אימות), הבדיקה שגויה. צריך לעדכן את הקובץ eval.test.json בהתאם למצב החדש.

תרחיש ב': הכשל 'ROUGE'

השגיאה:

Result: FAILED
Reason: Criteria 'response_match_score' failed. Score 0.45 < Threshold 0.8
Expected: "The refund has been processed successfully."
Actual:   "I've gone ahead and returned the money to your card."

אבחון: הנציג פעל בצורה נכונה, אבל השתמש במילים אחרות. הכלי ROUGE (חפיפה בין מילים) העניש את התוצאה.

איך פותרים את הבעיה:

  • האם זה שגוי? אם המשמעות נכונה, אל תשנו את ההנחיה.
  • שינוי הסף: מקטינים את הסף ב-test_config.json (לדוגמה, מ-0.8 ל-0.5).
  • שדרוג המדד: עוברים אל final_response_match_v2 בהגדרות. המודל קורא את שני המשפטים ומחליט אם המשמעות שלהם זהה.

10. ‫CI/CD עם Pytest ‏ (pytest)

pytest

פקודות CLI מיועדות לבני אדם. ‫pytest מיועד למכונות. כדי להבטיח את האמינות של סביבת הייצור, אנחנו עוטפים את ההערכות שלנו בחבילת בדיקות של Python. כך צינור ה-CI/CD (GitHub Actions, ‏ Jenkins) יכול לחסום פריסה אם הסוכן נחלש.

מה נכלל בקובץ הזה?

קובץ ה-Python הזה משמש כגשר בין רכיב ה-runner של CI/CD לבין כלי ההערכה של ADK. היא צריכה:

  • טעינת הסוכן: ייבוא דינמי של קוד הסוכן.
  • איפוס המצב: מוודאים שהזיכרון של הסוכן נקי כדי שהבדיקות לא יתערבבו זו בזו.
  • הרצת הערכה: קוראים ל-AgentEvaluator.evaluate() באופן פרוגרמטי.
  • Assert Success: אם ציון ההערכה נמוך, ה-build ייכשל.

קוד בדיקת השילוב

‫👈 פותחים את customer_service_agent/test_agent_eval.py. הסקריפט הזה משתמש ב-AgentEvaluator.evaluate כדי להריץ את הבדיקות שמוגדרות ב-eval.test.json.

‫👈 פותחים את customer_service_agent/test_agent_eval.py בעורך.

מזינים את הקוד הבא:

from google.adk.evaluation.agent_evaluator import AgentEvaluator
import pytest
import importlib
import sys
import os

@pytest.mark.asyncio
async def test_with_single_test_file():
    """Test the agent's basic ability via a session file."""
    # Load the agent module robustly
    module_name = "customer_service_agent.agent"
    try:
        agent_module = importlib.import_module(module_name)
        # Reset the mock data to ensure a fresh state for the test
        if hasattr(agent_module, 'reset_mock_data'):
            agent_module.reset_mock_data()
    except ImportError:
        # Fallback if running from a different context
        sys.path.append(os.getcwd())
        agent_module = importlib.import_module(module_name)
        if hasattr(agent_module, 'reset_mock_data'):
            agent_module.reset_mock_data()
    
    # Use absolute path to the eval file to be robust to where pytest is run
    script_dir = os.path.dirname(os.path.abspath(__file__))
    eval_file = os.path.join(script_dir, "eval.test.json")
    
    await AgentEvaluator.evaluate(
        agent_module=module_name,
        eval_dataset_file_path_or_dir=eval_file,
        num_runs=1,
    )

הפעלת Pytest

‫👈 בטרמינל, מריצים את הפקודה:

cd ~/adk_eval_starter
uv pip install pytest
uv run pytest customer_service_agent/test_agent_eval.py

תוצג תוצאה כמו:

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=============== 1 passed, 15 warnings in 12.84s ===============

11. סיכום

מעולה! הערכתם בהצלחה את נציג שירות הלקוחות באמצעות ADK Eval.

מה למדתם

ב-codelab הזה למדתם איך:

  • ‫✅ יצירת מערך נתונים מושלם כדי ליצור בסיס מידע משותף לסוכן.
  • ‫✅ הבנת הגדרת ההערכה כדי להגדיר קריטריונים להצלחה.
  • ‫✅ Run Automated Evaluations כדי לזהות רגרסיות בשלב מוקדם.

שילוב של ADK Eval בתהליך הפיתוח מאפשר לכם ליצור סוכנים בביטחון, כי אתם יודעים שכל שינוי בהתנהגות יתגלה על ידי הבדיקות האוטומטיות.