اختبار الوكلاء القائم على التأكيدات
فحص استدعاءات الأدوات، والخطوات الوسيطة، وبنية المخرج النهائي
اختبار الوكلاء القائم على التأكيدات درس مجاني في AI Agents على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في AI Agents، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة AI Agents 4 دروس في المجموع.
تجاوز مطابقة النصوص حرفيًا
نظرًا إلى أن مخرجات LLM غير حتمية، فإن اختبارها باستخدام assert response == 'exact text' يكون هشًا. بدلًا من ذلك، اكتب تأكيدات تتحقق من بنية الاستجابة ومقصدها من دون الاعتماد على الصياغة الحرفية.
التأكيد على إجراء استدعاءات الأدوات
بالنسبة إلى الوكلاء الذين يستدعون الدوال، فإن التأكيد الأكثر موثوقية هو التحقق من أن الوكيل اختار استدعاء الأداة الصحيحة. وهذا تحقق بنيوي لا يعتمد على الصياغة الحرفية لتفكير LLM.
import json
from unittest.mock import patch, MagicMock
@patch('myagent.client.chat.completions.create')
def test_agent_calls_search_tool(mock_create):
# Mock: agent decides to call search_web
tool_call = MagicMock()
tool_call.function.name = 'search_web'
tool_call.function.arguments = json.dumps({'query': 'Python tutorials'})
mock_create.return_value = MagicMock(
choices=[MagicMock(message=MagicMock(tool_calls=[tool_call]))]
)
response = mock_create() # simulating the agent call
tc = response.choices[0].message.tool_calls
assert tc is not None
assert len(tc) > 0
assert tc[0].function.name == 'search_web'
# --- demo: give unittest.mock.patch a real dotted path to patch ---
import sys
import types
_myagent = types.ModuleType('myagent')
_myagent.client = types.SimpleNamespace(
chat=types.SimpleNamespace(completions=types.SimpleNamespace(create=lambda *a, **k: None))
)
sys.modules['myagent'] = _myagent
test_agent_calls_search_tool()
print('test_agent_calls_search_tool: PASS')
التأكيد على اسم الأداة الصحيح
بالإضافة إلى التحقق من وجود استدعاءات الأدوات، تحقّق من أن اسم الأداة المحددة يطابق التوقعات. فهذا يكشف الحالات التي يختار فيها الوكيل الأداة الخطأ لاستعلام معين.
import json
from unittest.mock import MagicMock
def extract_tool_calls(response) -> list:
message = response.choices[0].message
if not message.tool_calls:
return []
return [
{
'name': tc.function.name,
'args': json.loads(tc.function.arguments)
}
for tc in message.tool_calls
]
# In a test:
# calls = extract_tool_calls(mock_response)
# assert calls[0]['name'] == 'get_weather'
# assert calls[0]['args']['city'] == 'Paris'
print('Tool name and argument assertions are the most reliable agent tests')التأكيد على معاملات الأداة
بعد التحقق من اسم الأداة، افحص صحة المعاملات. فلا يكفي أن يختار الوكيل الأداة الصحيحة، بل يجب أيضًا أن يملأها بالمعاملات الصحيحة المستخلصة من طلب المستخدم.
import json
from unittest.mock import patch, MagicMock
@patch('myagent.client.chat.completions.create')
def test_weather_tool_gets_correct_city(mock_create):
tool_call = MagicMock()
tool_call.function.name = 'get_weather'
tool_call.function.arguments = json.dumps({'city': 'Tokyo', 'unit': 'celsius'})
mock_create.return_value = MagicMock(
choices=[MagicMock(message=MagicMock(tool_calls=[tool_call]))]
)
response = mock_create()
args = json.loads(response.choices[0].message.tool_calls[0].function.arguments)
assert args['city'] == 'Tokyo'
assert args.get('unit') in ['celsius', 'fahrenheit', None] # flexible
# --- demo: give unittest.mock.patch a real dotted path to patch ---
import sys
import types
_myagent = types.ModuleType('myagent')
_myagent.client = types.SimpleNamespace(
chat=types.SimpleNamespace(completions=types.SimpleNamespace(create=lambda *a, **k: None))
)
sys.modules['myagent'] = _myagent
test_weather_tool_gets_correct_city()
print('test_weather_tool_gets_correct_city: PASS')
التحقق من مخرجات JSON باستخدام JSON Schema
عندما يعيد وكيلك JSON منظمًا، تحقّق من المخرجات مقابل JSON Schema للتأكد من وجود جميع الحقول المطلوبة ومن امتلاكها الأنواع الصحيحة. وتجعل مكتبة jsonschema ذلك سهلًا.
# pip install jsonschema
import jsonschema
AGENT_RESPONSE_SCHEMA = {
'type': 'object',
'required': ['answer', 'sources', 'confidence'],
'properties': {
'answer': {'type': 'string', 'minLength': 1},
'sources': {
'type': 'array',
'items': {'type': 'string', 'format': 'uri'}
},
'confidence': {'type': 'number', 'minimum': 0, 'maximum': 1}
}
}
def test_agent_output_schema(agent_output: dict):
try:
jsonschema.validate(instance=agent_output, schema=AGENT_RESPONSE_SCHEMA)
print('Schema validation passed')
except jsonschema.ValidationError as e:
raise AssertionError(f'Invalid agent output: {e.message}')التأكيد على وجود الكلمات المفتاحية
بالنسبة إلى الاستجابات النصية التي قد تختلف صياغتها الحرفية، تحقّق من ظهور المفاهيم أو الكلمات الأساسية في المخرجات. فهذا أسلوب مرن وذو دلالة في الوقت نفسه — إذ يجب أن تتضمن إجابة الوكيل المصطلحات ذات الصلة على الأقل.
def assert_keywords_present(text: str, keywords: list, require_all: bool = True):
lower_text = text.lower()
found = [kw.lower() in lower_text for kw in keywords]
if require_all:
missing = [kw for kw, f in zip(keywords, found) if not f]
assert not missing, f'Missing keywords: {missing}'
else:
assert any(found), f'None of {keywords} found in: {text[:100]}'
# Tests
response = 'The capital city of France is Paris, located in western Europe.'
assert_keywords_present(response, ['paris', 'france', 'capital'])
print('All keywords present!') # passes
assert_keywords_present(response, ['spain', 'france'], require_all=False)
print('At least one keyword present!') # passesالتأكيد على تنسيق الاستجابة: التحقق من النوع
تتسم تأكيدات النوع بالسرعة والموثوقية. تحقّق من أن الوكيل يعيد dict وليس None، وأن حقول القوائم هي قوائم، وأن الحقول الرقمية تقع ضمن النطاقات الصحيحة.
def test_agent_returns_valid_structure(agent_result):
# Type checks
assert isinstance(agent_result, dict), 'Result must be a dict'
assert isinstance(agent_result.get('answer'), str), 'answer must be a string'
assert isinstance(agent_result.get('steps'), list), 'steps must be a list'
# Non-empty checks
assert len(agent_result['answer']) > 0, 'answer must not be empty'
assert len(agent_result['steps']) >= 1, 'must have at least one step'
# Range checks
confidence = agent_result.get('confidence', 0)
assert 0.0 <= confidence <= 1.0, 'confidence must be 0-1'
print('Structural assertions are fast and reliable')التأكيد على finish_reason
يخبرك الحقل finish_reason بسبب توقف النموذج عن التوليد. ويساعد التأكيد عليه في اكتشاف المشكلات: تشير القيمة 'stop' إلى إجابة مكتملة، وتشير 'tool_calls' إلى رغبة الوكيل في استدعاء أداة، وتشير 'length' إلى اقتطاع المخرجات.
from unittest.mock import MagicMock
def test_agent_stops_cleanly(mock_response):
finish_reason = mock_response.choices[0].finish_reason
assert finish_reason in ('stop', 'tool_calls'), \
f'Unexpected finish_reason: {finish_reason}'
def test_no_truncation(mock_response):
finish_reason = mock_response.choices[0].finish_reason
assert finish_reason != 'length', \
'Response was truncated — increase max_tokens'
# Example mock for a clean stop
mock = MagicMock()
mock.choices = [MagicMock(finish_reason='stop')]
test_agent_stops_cleanly(mock)
print('finish_reason: stop — clean termination')التأكيد على عدد الخطوات في حلقة
ينبغي للوكيل الذي يعمل داخل حلقة أن يكتمل خلال عدد معقول من الخطوات. أكّد أن الوكيل ينتهي ضمن حد أقصى لعدد التكرارات — فهذا يكشف الحلقات اللانهائية التي يفترض أن يمنعها الحارس max_iterations.
def test_agent_completes_in_bounded_steps(mock_agent):
result = mock_agent.run('Search for the weather in Paris')
# Agent should complete within 5 steps
assert result['steps_taken'] <= 5, \
f'Agent took too many steps: {result["steps_taken"]}'
# Agent should produce a final answer, not exit on timeout
assert result['status'] == 'completed', \
f'Agent did not complete: {result["status"]}'
assert result['answer'] is not None
print('Bounding step count prevents runaway agents from passing tests')تهيئة الاختبارات لمُدخلات متعددة
تتيح لك @pytest.mark.parametrize في pytest تشغيل الاختبار نفسه باستخدام مُدخلات متعددة. وهذا مثالي لاختبار توجيه وكيلك لأنواع الاستعلامات المختلفة إلى الأدوات الصحيحة.
import pytest
from unittest.mock import patch, MagicMock
import json
@pytest.mark.parametrize('query,expected_tool', [
('What is the weather in Tokyo?', 'get_weather'),
('Calculate 15% of 200', 'calculator'),
('Search for Python books', 'web_search'),
('What time is it in Berlin?', 'get_time'),
])
@patch('myagent.client.chat.completions.create')
def test_agent_tool_routing(mock_create, query, expected_tool):
tool_call = MagicMock()
tool_call.function.name = expected_tool
tool_call.function.arguments = json.dumps({'input': query})
mock_create.return_value = MagicMock(
choices=[MagicMock(message=MagicMock(tool_calls=[tool_call]))]
)
response = mock_create()
actual = response.choices[0].message.tool_calls[0].function.name
assert actual == expected_toolكتابة دوال مساعدة مخصصة للتأكيدات
مع نمو مجموعة اختبارات وكيلك، استخرج أنماط التأكيد الشائعة في دوال مساعدة. فهذا يجعل الاختبارات أقصر وأسهل قراءة وصيانة عند تغير تنسيق استجابة الوكيل.
import json
def assert_tool_called(response, tool_name: str, required_args: dict = None):
message = response.choices[0].message
assert message.tool_calls, 'Expected tool call but got plain text'
names = [tc.function.name for tc in message.tool_calls]
assert tool_name in names, f'Expected {tool_name}, got {names}'
if required_args:
for tc in message.tool_calls:
if tc.function.name == tool_name:
args = json.loads(tc.function.arguments)
for key, val in required_args.items():
assert args.get(key) == val, \
f'Arg {key}: expected {val}, got {args.get(key)}'
# Clean test using the helper:
# assert_tool_called(response, 'get_weather', {'city': 'Paris'})
# --- demo ---
from unittest.mock import MagicMock
tool_call = MagicMock()
tool_call.function.name = 'get_weather'
tool_call.function.arguments = json.dumps({'city': 'Paris'})
response = MagicMock(choices=[MagicMock(message=MagicMock(tool_calls=[tool_call]))])
assert_tool_called(response, 'get_weather', {'city': 'Paris'})
print('assert_tool_called passed: agent called get_weather with city=Paris')
اختبار المعرفة: اختبار الوكلاء القائم على التأكيدات
اختبر مدى فهمك لاستراتيجيات التأكيد في اختبارات الوكلاء.
ملخص: اختبار الوكلاء القائم على التأكيدات
أصبحت لديك الآن مجموعة متكاملة من أدوات التأكيد لاختبارات الوكلاء:
- تحقّق من أن
tool_callsغير فارغ عندما ينبغي للوكيل استخدام أداة - أكّد اسم الأداة الصحيح باستخدام
tc.function.name == 'expected_tool' - تحقّق من معاملات الأداة بتحليل
tc.function.argumentsبصيغة JSON - استخدم
jsonschema.validate()للتحقق من المخرجات المنظمة - استخدم عمليات التحقق من وجود الكلمات المفتاحية للتأكيدات النصية المرنة
- تحقّق من
finish_reasonوعدد الخطوات للوكلاء الذين يعملون داخل حلقات - استخدم
@pytest.mark.parametrizeلسيناريوهات المُدخلات المتعددة
الأسئلة الشائعة
هل درس «اختبار الوكلاء القائم على التأكيدات» مجاني؟
نعم — نص درس «اختبار الوكلاء القائم على التأكيدات» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة AI Agents، انتقل إلى CoddyKit PRO. تتضمن دورة AI Agents 4 دروس في المجموع.
ماذا ستتعلم في «اختبار الوكلاء القائم على التأكيدات»؟
فحص استدعاءات الأدوات، والخطوات الوسيطة، وبنية المخرج النهائي تتمرن على AI Agents مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ AI Agents؟
لا تُشترط خبرة سابقة. AI Agents على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.
كم من الوقت يستغرق درس «اختبار الوكلاء القائم على التأكيدات»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس AI Agents هذا؟
نعم. كل درس في AI Agents يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- لماذا يختلف اختبار الوكلاء؟
- محاكاة استدعاءات LLM في الاختبارات
- اختبار الوكلاء القائم على التأكيدات
- اختبارات التكامل لمسارات الوكلاء