Agent Frameworks

تست‌گیری عامل‌های LLM: استراتژی‌های تزریق وابستگی در PydanticAI

مقدمه: چالش تست هوش مصنوعی

ساخت عامل‌های هوشمند با استفاده از مدل‌های زبانی بزرگ (LLMs) هیجان‌انگیز است، اما چالش‌های قابل توجهی را در مهندسی نرم‌افزار ایجاد می‌کند. برخلاف کدهای سنتی قطعی، عامل‌های LLM به APIهای خارجی، خروجی‌های تصادفی (stochastic) و مدیریت پیچیده وضعیت متکی هستند. این امر تست‌گیری را دشوار می‌سازد. چگونه منطق را تأیید می‌کنید وقتی «مغز» برنامه شما یک فراخوانی API غیرقطعی است؟ راه‌حل در الگوهای طراحی نرم‌افزار سخت‌گیرانه، به‌ویژه تزریق وابستگی (DI) نهفته است. PydanticAI، یک فریمورک مدرن پایتون برای ساخت برنامه‌های LLM، با استفاده از نشانه‌های نوع (type hints) و مدل‌های pydantic برای مدیریت تمیز وابستگی‌ها در این زمینه برتری دارد. با جداسازی کلاینت LLM از منطق کسب‌وکار خود، می‌توانید تست‌های واحد سریع و قابل اعتمادی بنویسید که هزینه‌ای برای توکن‌های API شما ندارند و از مشکلات شبکه ناپایدار رنج نمی‌برند.

چرا تزریق وابستگی برای عامل‌ها اهمیت دارد؟

در یک گردش کار عامل (agent) معمولی، ممکن است عاملی داشته باشید که در وب جستجو می‌کند، نتایج را پردازش می‌کند و سپس یک خلاصه تولید می‌کند. اگر ابزار جستجوی وب به‌صورت سخت‌کد (hard-coded) در عامل قرار داشته باشد، نمی‌توانید منطق خلاصه‌سازی را بدون برخورد واقعی با یک موتور جستجو تست کنید. تزریق وابستگی به شما اجازه می‌دهد این وابستگی‌ها را تعویض کنید. در محیط تولید، یک `WebSearchTool` واقعی را تزریق می‌کنید. در تست‌ها، یک `MockWebSearchTool` تزریق می‌کنید که داده‌های از پیش تعریف‌شده را برمی‌گرداند. این الگو اطمینان می‌دهد که تست‌های واحد شما دقیقاً بر منطق عامل خود تمرکز کنند، نه بر قابلیت اطمینان سرویس‌های خارجی.

راه‌اندازی یک عامل قابل تست

اول، بیایید یک عامل ساده که از یک وابستگی استفاده می‌کند را تعریف کنیم. از سیستم PydanticAI برای مدیریت وابستگی‌های ابزار استفاده خواهیم کرد.

from pydantic_ai import Agent
from pydantic_ai.models import Model
from pydantic_ai.toolsets import Toolset
from pydantic import BaseModel

class Fact(BaseModel):
    fact: str
    source: str

# تعریف یک Toolset سفارشی برای مدیریت وابستگی‌ها
class FactToolset(Toolset):
    model: Model
    facts: list[Fact]

    def __init__(self, model: Model, facts: list[Fact]):
        self.model = model
        self.facts = facts

    @property
    def get_facts(self):
        """فهرستی از حقایق شناخته‌شده را برمی‌گرداند."""
        return self.facts

# تعریف عامل
agent = Agent[FactToolset](
    model='openai:gpt-4o',
    system_prompt='شما یک راست‌کننده حقیقت هستید. از حقایق ارائه‌شده برای پاسخ به سؤالات استفاده کنید.',
)

async def main():
    # در تولید، حقایق واقعی را تزریق خواهید کرد
    toolset = FactToolset(model=agent.model, facts=[Fact(fact="آب خیس است", source="فیزیک")])
    
    try:
        result = await agent.run('آب خیس است؟', toolset=toolset)
        print(result.output)
    finally:
        await toolset.aclose()

تست واحد با Mocks

اکنون، می‌توانیم یک تست واحد بنویسیم. کلید کار این است که خود `Model` را mock کنیم تا فراخوانی‌های واقعی API انجام نشود. PydanticAI برای این منظور یک `MockModel` ارائه می‌دهد.

import pytest
from pydantic_ai import Agent
from pydantic_ai.models import MockModel
from pydantic_ai.toolsets import Toolset
from pydantic import BaseModel
from pydantic_ai.messages import ModelResponse, TextPart, ToolCallPart

class Fact(BaseModel):
    fact: str
    source: str

class FactToolset(Toolset):
    def __init__(self, facts: list[Fact]):
        self.facts = facts

    def get_facts(self):
        return self.facts

# ایجاد عامل با یک مدل جایگزین. مدل واقعی بعداً تزریق خواهد شد.
agent = Agent[FactToolset](
    model='mock', 
    system_prompt='شما یک راست‌کننده حقیقت هستید.'
)

@pytest.mark.anyio
async def test_agent_facts():
    # 1. تعریف پاسخ مورد انتظار از LLM
    # LLM باید ابزار 'get_facts' را فراخوانی کند
    mock_response = ModelResponse(
        parts=[
            ToolCallPart(tool_name='get_facts', args={}),
            TextPart(content='بر اساس حقایق، آب خیس است.')
        ]
    )
    
    # 2. ایجاد یک MockModel که پاسخ از پیش تعریف‌شده ما را برمی‌گرداند
    mock_model = MockModel(messages=[mock_response])
    
    # 3. ایجاد toolset با داده‌های تست
    test_facts = [Fact(fact="آب خیس است", source="منبع تست")]
    toolset = FactToolset(facts=test_facts)
    
    # 4. اجرای عامل با مدل mock و toolset
    # نکته: ما مدل mock را در زمینه (context) عامل تزریق می‌کنیم
    async with agent.run_with_tools('آب خیس است؟', toolset=toolset, model=mock_model) as result:
        assert result.output == 'بر اساس حقایق، آب خیس است.'

بهترین روش‌ها برای تست‌های قوی

1. جداسازی وضعیت (State): اطمینان حاصل کنید که هر تست یک نمونه تازه از toolset ایجاد می‌کند. از وضعیت مشترک بین تست‌ها برای جلوگیری از آلودگی (contamination) خودداری کنید. 2. اعتبارسنجی ابزارها: در تست‌های خود، تأیید کنید که ابزارهای صحیح فراخوانی شده‌اند. می‌توانید `result.all_messages` را بررسی کنید تا اطمینان حاصل شود که اشیاء `ToolCallPart` با نام‌های خاص وجود دارند. 3. استفاده از خروجی‌های ساختاریافته: PydanticAI از خروجی‌های ساختاریافته (مدل‌های pydantic) پشتیبانی می‌کند. تست کنید که پاسخ LLM با schema شما مطابقت دارد. این برای پردازش داده‌های پایین‌دست (downstream) حیاتی است.

نتیجه‌گیری

پیاده‌سازی عامل‌های LLM نیازمند تغییر در تفکر از «اسکریپت‌نویسی» به «مهندسی» است. با بهره‌گیری از قابلیت‌های تزریق وابستگی PydanticAI، می‌توانید عامل‌هایی بسازید که نه تنها کارآمد هستند، بلکه قابل تست و قابل نگهداری نیز می‌باشند. ترکیب `MockModel` برای پاسخ‌های LLM و `Toolsets` سفارشی برای وابستگی‌های داده، به شما امکان می‌دهد منطق خود را از ماهیت غیرقطعی هوش مصنوعی جدا کنید. با جداسازی ابزارهای خود شروع کنید، مدل‌های خود را mock کنید و پایه‌ای برای برنامه‌های هوش مصنوعی قابل اعتماد بسازید که می‌توانند با اطمینان مقیاس‌پذیر شوند.
Share: