Pydantic AI کا استعمال کرتے ہوئے پروڈکشن گریڈ ایجنٹ کیسے بنایا جائے۔

مقامی LLM SDK کا استعمال کرتے ہوئے AI ایجنٹوں کی تعمیر اس وقت تک پروٹو ٹائپنگ کے لیے بہترین ہے جب تک کہ آپ کو ساختی آؤٹ پٹ، قابل جانچ کوڈ، اور پیداواری استحکام کی ضرورت نہ ہو۔

خلا خود کو پیشین گوئی کے طریقوں سے ظاہر کرتا ہے۔ اب جب کہ آپ کا نوٹ بک کوڈ کام کر رہا ہے، آئیے اسے پروڈکشن میں منتقل کریں اور پیچ کرنا شروع کریں۔ json.loads کے ارد گرد ایک کوشش/exc، مارک ڈاؤن ہیجز کو ہٹانے کے لیے ایک مددگار، فیلڈ کی اقسام کو چیک کرنے کے لیے چند اگر بیانات، دوبارہ کوشش کرنے کا لوپ، اور کال ایبلز پر ٹول کے ناموں کا نقشہ بنانے کے لیے ایک ڈسپیچ فنکشن۔ ان میں سے کوئی بھی اپنے آپ میں مشکل نہیں ہے۔ ایک ساتھ، وہ کوڈ بیس کا بڑا حصہ بن جاتے ہیں، اصل ایجنٹ کی منطق گلو کے نیچے غائب ہو جاتی ہے۔

اس مضمون میں، ہم چھ مسائل کو اس ترتیب سے دیکھتے ہیں کہ وہ کس ترتیب سے پیدا ہوتے ہیں اور یہ ظاہر کرتے ہیں کہ Pydantic AI کس طرح کوڈ کا استعمال کرتے ہوئے ہر مسئلے کو حل کرتا ہے۔

  1. غیر ساختہ آؤٹ پٹ کو کمزور تجزیہ درکار ہے۔ آؤٹ پٹ اسکیما انگریزی پرامپٹ سٹرنگ میں ہے جو اس لغت سے وابستہ نہیں ہے جس کی آپ کوڈ کی توقع ہے۔

  2. ٹول کی تعریفیں بوائلر پلیٹ سے بھری ہوئی ہیں۔ ہاتھ سے لکھے ہوئے JSON اسکیما کی 70 لائنوں تک اور تین ٹولز کے لیے ڈسپیچ کوڈ کے ساتھ، اسکیما اور فنکشن کے دستخطوں کو مطابقت پذیر رکھنے کے لیے کچھ نہیں ہے۔

  3. رن ٹائم سیاق و سباق کو منتقل کرنے کا کوئی صاف طریقہ نہیں ہے۔ جب ایک فریم ورک کسی ٹول کو کال کرتا ہے، تو یہ ڈیٹا بیس کنکشن یا یوزر آئی ڈی کو عالمی یا بندش تک پہنچائے بغیر پاس نہیں کر سکتا۔

  4. ٹیسٹ کے لیے ایک حقیقی LLM کال درکار ہے۔ تمام ٹیسٹوں پر پیسہ خرچ ہوتا ہے، سیکنڈ لگتے ہیں، نیٹ ورک تک رسائی درکار ہوتی ہے، اور فلیکس کی ضرورت ہوتی ہے۔

  5. دوبارہ کوشش کریں اور توثیق کی منطق دستی ہے۔ اس کا مطلب ہے کہ آپ اپنے تعینات کردہ ہر ایجنٹ پر وہی توثیق/دوبارہ کوشش/دوبارہ کوشش کرنے کے پیٹرن کو دوبارہ بناتے ہیں۔

  6. ماڈلز کو تبدیل کرنے کا مطلب ہے اپنے انٹیگریشن کوڈ کو دوبارہ لکھنا۔ ہر فراہم کنندہ کی SDK ظاہری شکل، ٹول فارمیٹ، اور ردعمل کا ڈھانچہ مختلف ہوتا ہے۔

ہم ایک چلتی ہوئی مثال استعمال کریں گے: رسید تجزیہ ایجنٹ۔ یہ اصل رسید کا متن لیتا ہے (تصویر کو متن کے طور پر اسکین کرکے حاصل کیا جاتا ہے)، مرچنٹ کیٹیگریز اور ایکسچینج ریٹ دیکھنے کے لیے ٹول کو کال کرتا ہے، اور ایک آبادی والا خلاصہ (مرچنٹ، خرچ کرنے کا زمرہ، آئٹمائزڈ بریک ڈاؤن اور اعتماد کا اسکور) واپس کرتا ہے جسے براہ راست بجٹ ڈیش بورڈ یا اخراجات کے ٹول میں استعمال کیا جا سکتا ہے۔ سٹرکچرڈ ان پٹ، سوالات کے لیے ٹول کالز، ڈاؤن اسٹریم سسٹمز کے لیے ٹائپ شدہ آؤٹ پٹ۔ وہ مسائل جو سطحی طور پر کافی عام معلوم ہوتے ہیں وہ مانوس معلوم ہوتے ہیں۔

آخر میں، آپ کے پاس ٹائپ شدہ آؤٹ پٹ کے ساتھ ایک ورکنگ ایجنٹ، انحصار انجیکشن ٹول، خودکار دوبارہ کوششوں کے ساتھ کاروباری اصول کی توثیق، اور بغیر کسی API کلید کے ملی سیکنڈ میں چلنے والے ٹیسٹوں کا ایک مجموعہ ہوگا۔

انڈیکس

Pydantic AI کی ایک مختصر تفصیل

Pydantic ایک Python پر مبنی ڈیٹا کی توثیق کی لائبریری ہے۔ آپ اپنے اسکیما کو ٹائپ اشارے کے ساتھ ایک باقاعدہ ازگر کلاس کے طور پر بیان کرتے ہیں، اور Pydantic اسے رن ٹائم پر نافذ کرتا ہے۔ یعنی، یہ ان قسموں کو مجبور کرتا ہے جو اہل ہیں، جو نہیں ہیں ان کو مسترد کرتی ہے، اور درست غلطی کو بڑھاتی ہے جو ناگوار فیلڈز کا نام دیتی ہے۔

  from pydantic import BaseModel, Field

  class Item(BaseModel):
      name: str
      amount: float = Field(gt=0)

  Item(name="Espresso", amount="2.50")  # → amount=2.5, coerced
  Item(name="Espresso", amount=-1)      # → ValidationError: amount must be > 0

Pydantic AI ایک ایجنٹ فریم ورک ہے جو LLM کی حدود کو سنبھالتا ہے۔ اس کا مطلب ہے کہ ماڈل کو فراہم کنندہ کی ڈیفالٹ اسکیما کی درخواست میں تبدیل کرنا، جو واپس کیا گیا ہے اسے پارس کرنا اور اس کی توثیق کرنا، فنکشن کے دستخطوں سے ٹول کی تعریفیں تیار کرنا، انحصار کو انجیکشن لگانا، اور اگر توثیق ناکام ہو جاتی ہے تو دوبارہ کوشش کرنا۔ ایجنٹ کی منطق Python میں رکھی گئی ہے۔ فریم ورک دو طرفہ ترجمہ ہینڈل کرتا ہے۔

شرطیں

یہ مضمون فرض کرتا ہے کہ آپ درج ذیل سے واقف ہیں:

  • Python 3.10+ – اشارے ٹائپ کریں، ڈیٹا کلاسز، async/await

  • LLM API بنیادی باتیں — OpenAI، Anthropic، یا اسی طرح کے SDKs کو کم از کم چند کالیں کیں۔

  • ایجنٹ کا تصور – سمجھیں کہ AI ایجنٹ کیا ہے (LLM + ٹولز + انفرنس لوپ)۔ اگر نہیں، تو AI ایجنٹ – بلڈر گائیڈ سے شروع کریں۔

Pydantic AI کے ساتھ کسی پیشگی تجربے کی ضرورت نہیں ہے۔ ہم زمین سے تعمیر کریں گے۔

مسئلہ: بغیر کسی فریم ورک کے ایجنٹوں کی تعمیر

ان مسائل کو بہتر طور پر اجاگر کرنے اور اس کی وضاحت کرنے کے لیے، ہم ایک چلتی ہوئی مثال استعمال کریں گے جسے رسید تجزیہ ایجنٹ کہا جاتا ہے۔ یہ اصل رسید کا متن لیتا ہے (تصویر کو متن کے طور پر اسکین کرکے حاصل کردہ متن)، اخراجات کی درجہ بندی کرتا ہے، تاجر کی معلومات کو تلاش کرتا ہے، اور ایک منظم خلاصہ واپس کرتا ہے۔ یہ آپ کو بیچنے والے کا نام، اخراجات کے زمرے، آئٹمائزڈ بریک ڈاؤن، اور ٹرسٹ سکور دیتا ہے۔ ڈاؤن اسٹریم سسٹمز (بجٹ ڈیش بورڈز، لاگت کی اطلاع دینے والے ٹولز) اس ساختی آؤٹ پٹ کو براہ راست استعمال کرتے ہیں۔

یہ ایک عام حقیقی دنیا کا نمونہ ہے: ساختہ ان پٹ، سوال کے لیے ٹول کال، ڈاؤن اسٹریم سسٹم میں ان پٹ آؤٹ پٹ۔ آئیے ایک نظر ڈالتے ہیں کہ ایک خام OpenAI SDK کال کے ذریعے عمارت کیسی دکھتی ہے۔

مسئلہ 1: غیر ساختہ آؤٹ پٹ کے لیے کمزور تجزیہ درکار ہے۔

بنیادی طور پر، LLM کے ساتھ تمام تعاملات ٹیکسٹ ان پٹ، ٹیکسٹ آؤٹ پٹ ہیں۔ آپ کے ماڈل کے ساتھ پورا معاہدہ (ان پٹ ڈیٹا، اہداف، اور مطلوبہ آؤٹ پٹ فارمیٹ) ایک ٹیکسٹ پرامپٹ میں موجود ہے۔ درستگی کو نافذ کرنے کے لیے کوئی اسکیما، ٹائپ سسٹم، یا کمپائلر نہیں ہے۔ انگریزی میں بیان کریں کہ آپ کیا چاہتے ہیں اور امید کرتے ہیں کہ ماڈل اس کے مطابق ہوگا۔

OpenAI SDK کا استعمال کرتے ہوئے یہاں ایک آسان عمل ہے: سسٹم پرامپٹس ان پٹ سیاق و سباق، کارروائی کی ہدایات فراہم کرتے ہیں، اور آؤٹ پٹ اسکیما تمام متن کے ایک حصے کے طور پر ظاہر ہوتا ہے۔

import json
from openai import OpenAI

client = OpenAI()

def analyze_receipt(receipt_text: str) -> dict:
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": """Analyze this receipt and return JSON:
{
    "merchant": "string",
    "category": "one of: food, transport, utilities, entertainment, shopping, other",
    "total": float,
    "currency": "string",
    "items": [{"name": "string", "amount": float}],
    "is_business_expense": bool,
    "confidence": float between 0 and 1
}"""},
            {"role": "user", "content": receipt_text}
        ]
    )

    raw = response.choices[0].message.content

    # Parse the response
    try:
        if raw.startswith("```"):
            raw = raw.split("\n", 1)[1].rsplit("```", 1)[0]
        result = json.loads(raw)
    except json.JSONDecodeError:
        raise ValueError(f"LLM returned invalid JSON: {raw[:200]}")

    # Validate fields manually
    allowed_categories = {"food", "transport", "utilities", "entertainment", "shopping", "other"}
    if result.get("category") not in allowed_categories:
        result["category"] = "other"

    return result

کوڈ صاف ہے، پڑھنے میں آسان ہے، اور میرے لیپ ٹاپ پر کام کرتا ہے۔ لیکن بنیادی مسئلہ یہ ہے کہ ایل ایل ایم (ان پٹ، ٹارگٹ اور آؤٹ پٹ فارمیٹ) کے ساتھ پورا معاہدہ غیر ساختہ تار میں ہے۔ کسی بھی فریق کی طرف سے معاہدے کا کوئی نفاذ نہیں ہے۔

جیسا کہ ہم اسے پیداوار میں دھکیلتے ہیں، مسائل سامنے آنا شروع ہو جاتے ہیں۔

  • فوری ہے اسکیما انگریزی میں ہے۔ سسٹم پرامپٹ قدرتی زبان میں آؤٹ پٹ فارمیٹ کی وضاحت کرتے ہیں۔ ایسی کوئی چیز نہیں ہے جو اس وضاحت کو اس سے جوڑتی ہو: dict آپ کا کوڈ درحقیقت توقع کرتا ہے: اگر آپ پرامپٹ میں کوئی فیلڈ شامل کرتے ہیں اور ڈاؤن اسٹریم پروسیسنگ کو بھول جاتے ہیں، تو آپ کو پروڈکشن تک کوئی غلطی نہیں ملے گی۔

  • LLM ہمیشہ صاف JSON واپس نہیں کرتا ہے۔ آؤٹ پٹ کو اس کے ساتھ لپیٹیں: ```json ``` باڑ سے پہلے/بعد میں وضاحتی متن شامل کریں، ٹریلنگ کوما شامل کریں، یا ٹائم آؤٹ پر جزوی جواب دیں۔ پارسنگ کوڈ ایک کیس (باڑ) کو سنبھالتا ہے لیکن دوسرے کو نہیں۔

  • کوئی حقیقی تصدیق نہیں ہے۔ ہے total کیا یہ اصل میں ایک نمبر ہے یا ایل ایل ایم نے سٹرنگ واپس کی ہے؟ "$45.99"? ہے confidence کیا یہ 0 اور 1 کے درمیان ہے یا اسے واپس کیا گیا ہے؟ 95 (فیصد)؟ ہے items کیا فہرست میں صحیح کلیدوں کے ساتھ لغات موجود ہیں؟ آپ کو یہ سب دستی طور پر چیک کرنا ہوگا۔

  • ناکامی خاموش یا مہلک ہوسکتی ہے۔ زمرہ متبادل (result["category"] = "other") ان مسائل کو چھپاتا ہے جن کی وجہ سے دوبارہ کوشش کرنی چاہئے۔ کہ json.loads اگر یہ ناکام ہو جاتا ہے تو، بحالی کے راستے کے بغیر ایک استثنا اٹھایا جاتا ہے۔

آپ کو واقعی جس چیز کی ضرورت ہے وہ پرامپٹ سٹرنگ کی انگریزی وضاحت نہیں ہے، بلکہ آپ کے کوڈ میں ایک بار ان پٹ ڈیٹا سٹرکچر کے طور پر آؤٹ پٹ اسکیما کی وضاحت کرنے کا ایک طریقہ ہے۔ سکیما LLM اور استعمال کرنے والے کوڈ دونوں کے لیے سچائی کا واحد ذریعہ ہونا چاہیے۔

ہمیں ایک ایسے فریم ورک کی بھی ضرورت ہے جو قسم کی تعریفوں کے خلاف LLM کے ردعمل کی توثیق کرے، اسکیما کو خود بخود نافذ کرے اور مماثلت نہ ہونے کی صورت میں مناسب غلطیوں کو بڑھائے۔

آخر میں، ہمیں بغیر کسی دستی منطق کے توثیق کی ناکامیوں پر دوبارہ کوشش کرنے کی ضرورت ہے۔ اگر آؤٹ پٹ اسکیما سے مماثل نہیں ہے تو، LLM میں توثیق کی خرابی کے پیغامات کو دوبارہ ڈسپلے کریں تاکہ آپ خود کو درست کر سکیں۔

یعنی آؤٹ پٹ فارمیٹ ہونا چاہیے: معاہدہ ایک قسم کے نظام کے طور پر اظہار کرنے کے بجائے۔ تجویز انگریزی میں بیان کیا گیا ہے۔

Pydantic AI اس مسئلے کو کیسے حل کرتا ہے۔

Pydantic AI آپ کو اپنے آؤٹ پٹ کو Pydantic ماڈل کے طور پر بیان کرنے کی اجازت دیتا ہے۔ فریم ورک اسکیما جنریشن، پرامپٹ انجیکشن، JSON پارسنگ، توثیق اور دوبارہ کوششوں کو ہینڈل کرتا ہے۔ اور یہ سب ایک ماڈل کی تعریف سے ماخوذ ہے۔

from pydantic import BaseModel, Field
from pydantic_ai import Agent
from enum import Enum


class SpendingCategory(str, Enum):
    FOOD = "food"
    TRANSPORT = "transport"
    UTILITIES = "utilities"
    ENTERTAINMENT = "entertainment"
    SHOPPING = "shopping"
    OTHER = "other"


class LineItem(BaseModel):
    name: str
    amount: float


class ReceiptAnalysis(BaseModel):
    merchant: str
    category: SpendingCategory
    total: float = Field(gt=0)
    currency: str = Field(min_length=3, max_length=3)
    items: list[LineItem]
    is_business_expense: bool
    confidence: float = Field(ge=0, le=1)


receipt_agent = Agent(
    "openai:gpt-4o",
    output_type=ReceiptAnalysis,
    system_prompt="Analyze the provided receipt and extract structured details.",
)

result = receipt_agent.run_sync("CAFE PARIS\n€12.50\nCroissant x2 €5.00\nEspresso €2.50\nCroque Monsieur €5.00")
print(result.output)
# merchant="CAFE PARIS" category= total=12.5 ...

تو یہاں کیا فرق ہے؟

سب سے پہلے، ایک سکیما ایک ماڈل ہے. ReceiptAnalysis فیلڈز، اقسام اور رکاوٹوں کی وضاحت کریں۔ Pydantic AI اسے LLM کے لیے موزوں JSON اسکیما میں تبدیل کرتا ہے اور اس کے خلاف جواب کی توثیق کرتا ہے۔ یہ ایک تعریف ہے جو ہر جگہ استعمال ہوتی ہے۔

دوسرا، کوڈ کا کوئی تجزیہ نہیں ہے۔ اپنی مارک ڈاون باڑ کو نہ ہٹائیں، ہمیں کال کریں۔ json.loadsیا پکڑو JSONDecodeError. فریم ورک اس سب کا خیال رکھتا ہے۔

اس کے علاوہ، تصدیق حقیقی ہے. Field(ge=0, le=1) کو confidence کی قدر کا مطلب ہے 95 اسے مسترد کیا جاتا ہے اور خاموشی سے قبول نہیں کیا جاتا ہے۔ SpendingCategory Enum کا مطلب ہے کہ متبادل ماسکنگ کے بغیر صرف درست زمرے ہی قبول کیے جاتے ہیں۔

آخر میں، دوبارہ کوششیں خودکار ہیں۔ اگر LLM آؤٹ پٹ واپس کرتا ہے جو توثیق میں ناکام ہو جاتا ہے، Pydantic AI توثیق کی غلطیوں کو ماڈل کو واپس بھیجتا ہے اور اسے خود ٹھیک کرنے کی درخواست کرتا ہے۔ دستی دوبارہ کوشش کرنے کا کوئی لوپ نہیں ہے۔

یہ فنکشن ReceiptAnalysis آبجیکٹ: بھرا ہوا اور تصدیق شدہ، IDE خودکار تکمیل کے لیے موزوں۔ نہیں dict مجھے امید ہے کہ آپ کے پاس صحیح چابی ہے۔

سطح کے نیچے کیا ہوتا ہے۔

تعریف کرتے وقت output_type=ReceiptAnalysisPydantic AI ہر ایجنٹ کے عمل میں کئی اہم کام انجام دیتا ہے:

راستے میں، ہم Pydantic ماڈل سے JSON اسکیما تیار کرتے ہیں اور اسے LLM درخواست میں داخل کرتے ہیں۔ ماڈل فراہم کنندہ پر منحصر ہے، یہ ڈیفالٹ ساختہ آؤٹ پٹ/ٹول کال میکانزم ہو سکتا ہے (OpenAI’s response_formatانتھروپک وغیرہ کے اوزار استعمال کریں) ایل ایل ایم جانتا ہے۔ درست طریقے سے یہ کس ڈھانچے میں تیار کیا جائے گا؟

واپسی پر، ہم LLM سے خام جواب لیتے ہیں، اسے Pydantic ماڈل کے خلاف پارس کرتے ہیں، اور مکمل توثیق (قسم جبر، فیلڈ کی رکاوٹیں، اور enum ممبرشپ) چلاتے ہیں۔ اگر توثیق ناکام ہو جاتی ہے، تو یہ ایک غلطی کا پیغام بات چیت میں واپس بھیجتا ہے اور LLM سے آؤٹ پٹ کو ٹھیک کرنے کے لیے کہتا ہے (خودکار طور پر دوبارہ کوشش کی قابل ترتیب حد تک)۔

┌──────────────────────────────────────────────────────────────────────┐
│                    Pydantic AI — Structured Output Flow              │
│                                                                      │
│  ┌────────────────┐         ┌──────────────────────────────────┐     │
│  │  Your Code     │         │  Pydantic AI Framework           │     │
│  │                │         │                                  │     │
│  │  output_type = │────────>│  1. Generate JSON schema from    │     │
│  │  ReceiptAnalysis         │     ReceiptAnalysis model        │     │
│  │                │         │                                  │     │
│  └────────────────┘         │  2. Inject schema into LLM      │     │
│                             │     request (provider-native     │     │
│                             │     format: response_format,     │     │
│                             │     tool_call, etc.)             │     │
│                             │              │                   │     │
│                             └──────────────┼───────────────────┘     │
│                                            ▼                         │
│                             ┌──────────────────────────────────┐     │
│                             │           LLM                    │     │
│                             │  Sees schema → produces JSON     │     │
│                             └──────────────┬───────────────────┘     │
│                                            │                         │
│                                            ▼                         │
│                             ┌──────────────────────────────────┐     │
│                             │  Pydantic AI Framework           │     │
│                             │                                  │     │
│                             │  3. Parse raw LLM response       │     │
│                             │  4. Validate against model:      │     │
│                             │     - Type checks                │     │
│                             │     - Field constraints (ge, le) │     │
│                             │     - Enum membership            │     │
│                             │              │                   │     │
│                             │         ┌────┴────┐              │     │
│                             │         │         │              │     │
│                             │      PASS ✓    FAIL ✗            │     │
│                             │         │         │              │     │
│                             │         ▼         ▼              │     │
│                             │  Return typed  Send validation   │     │
│                             │  object        error back to LLM │     │
│                             │                for self-correct   │     │
│                             │                (auto-retry)       │     │
│                             └──────────────────────────────────┘     │
│                                            │                         │
│                                            ▼                         │
│                             ┌──────────────────────────────────┐     │
│                             │  Your Code receives:             │     │
│                             │  result.output → ReceiptAnalysis │     │
│                             │  (typed, validated, ready to use)│     │
│                             └──────────────────────────────────┘     │
└──────────────────────────────────────────────────────────────────────┘

آپ اپنے معاہدے کی وضاحت ایک بار ازگر کی کلاس کے طور پر کرتے ہیں۔ فریم ورک LLM باؤنڈری کے دونوں اطراف پر کارروائی کرتا ہے، ماڈل کو بتاتا ہے کہ کیا پیدا کرنا ہے اور اس بات کو یقینی بناتا ہے کہ یہ تیار کیا گیا ہے۔

رسید ایجنٹوں کو فروخت کنندگان کے زمرے تلاش کرنے، شرح مبادلہ کی جانچ پڑتال اور اخراجات کی تاریخ کے بارے میں استفسار کرنے کے لیے ٹولز کی ضرورت ہوتی ہے۔

یہ ہے کہ خام فنکشن کال کی طرح دکھتی ہے:

tools = [
    {
        "type": "function",
        "function": {
            "name": "lookup_merchant_category",
            "description": "Look up the spending category for a merchant name",
            "parameters": {
                "type": "object",
                "properties": {
                    "merchant_name": {
                        "type": "string",
                        "description": "The merchant name from the receipt"
                    }
                },
                "required": ["merchant_name"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_exchange_rate",
            "description": "Get current exchange rate between two currencies",
            "parameters": {
                "type": "object",
                "properties": {
                    "from_currency": {
                        "type": "string",
                        "description": "Source currency code (e.g., EUR)"
                    },
                    "to_currency": {
                        "type": "string",
                        "description": "Target currency code (e.g., USD)"
                    }
                },
                "required": ["from_currency", "to_currency"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_spending_history",
            "description": "Get spending totals by category for a date range",
            "parameters": {
                "type": "object",
                "properties": {
                    "category": {
                        "type": "string",
                        "description": "Spending category"
                    },
                    "days": {
                        "type": "integer",
                        "description": "Number of past days to query"
                    }
                },
                "required": ["category", "days"]
            }
        }
    }
]


# Then you ALSO need to write the dispatch logic:
def handle_tool_call(tool_call):
    name = tool_call.function.name
    args = json.loads(tool_call.function.arguments)

    if name == "lookup_merchant_category":
        return lookup_merchant_category(args["merchant_name"])
    elif name == "get_exchange_rate":
        return get_exchange_rate(args["from_currency"], args["to_currency"])
    elif name == "get_spending_history":
        return get_spending_history(args["category"], args["days"])
    else:
        raise ValueError(f"Unknown tool: {name}")

میں نے تین ٹولز کے لیے JSON اسکیما اور ڈسپیچ کوڈ کی تقریباً 70 لائنیں لکھیں۔ اسکیما اصل فنکشن دستخط سے منقطع ہے۔ اگر آپ کسی فنکشن میں پیرامیٹر کا نام تبدیل کرتے ہیں اور اسکیما کو اپ ڈیٹ کرنا بھول جاتے ہیں، تو یہ رن ٹائم پر خود بخود ٹوٹ جائے گا۔

حل کیسا لگتا ہے۔

اس کے بجائے، ٹول کی تعریف فنکشن سے ہی اخذ کی جانی چاہیے۔ فنکشن کا نام، ڈاکسٹرنگ، اور ٹائپ کے اشارے پہلے سے ہی بیان کرتے ہیں کہ ٹول کیا کرتا ہے اور اس کے لیے کیا دلائل لیتے ہیں۔ بہت ہو گیا۔

یہ خود بخود مطابقت پذیر بھی رہنا چاہئے۔ پیرامیٹر کا نام تبدیل کرنا یا اس کی قسم کو تبدیل کرنا دوسری فائل کو چھوئے بغیر LLM کو بھیجے گئے اسکیما کو اپ ڈیٹ کرتا ہے۔

اور کوئی شپنگ نہیں ہونا چاہئے. فریم ورک کو صحیح فنکشن کو براہ راست کال کرنا چاہئے۔ کال ایبلز کے لیے کوئی دستی if/elif چین میپنگ سٹرنگ کے نام نہیں ہیں۔

Pydantic AI اس مسئلے کو کیسے حل کرتا ہے۔

Pydantic AI میں، ٹولز صرف ڈیکوریٹرز کے ساتھ کام کرتے ہیں۔ فریم ورک فنکشن کے دستخط اور Docstring سے JSON اسکیما تیار کرتا ہے اور ڈسپیچ کو خود بخود ہینڈل کرتا ہے۔

from pydantic_ai import Agent, RunContext

receipt_agent = Agent(
    "openai:gpt-4o",
    output_type=ReceiptAnalysis,
    system_prompt="Analyze the provided receipt and extract structured details.",
)


@receipt_agent.tool_plain
def lookup_merchant_category(merchant_name: str) -> str:
    """Look up the spending category for a merchant name."""
    # Your actual implementation
    categories_db = {"CAFE PARIS": "food", "UBER": "transport", "NETFLIX": "entertainment"}
    return categories_db.get(merchant_name.upper(), "other")


@receipt_agent.tool_plain
def get_exchange_rate(from_currency: str, to_currency: str) -> float:
    """Get current exchange rate between two currencies."""
    # Your actual implementation — call an API, hit a cache, etc.
    rates = {"EUR_USD": 1.08, "GBP_USD": 1.27}
    return rates.get(f"{from_currency}_{to_currency}", 1.0)


@receipt_agent.tool_plain
def get_spending_history(category: str, days: int) -> dict:
    """Get spending totals by category for a date range."""
    # Your actual implementation
    return {"category": category, "total": 142.50, "transaction_count": 12}

بس۔ کوئی JSON اسکیما لغت نہیں ہے۔ کوئی ڈسپیچ فنکشن نہیں ہے۔ وہی تین ٹولز ~70 کے بجائے ~25 لائن پر ہیں۔

یہ ہے کہ فریم ورک کیا کرتا ہے:

  • قسم کے اشارے سے اسکیما تیار کریں: merchant_name: str بن جاتا ہے۔ {"type": "string"} JSON اسکیما میں۔ ڈاکسٹرنگ ایک ٹول ہے۔ description. پیرامیٹر کے نام جائیداد کے نام بن جاتے ہیں۔ یہ سب اس سے اخذ کیا گیا ہے جو آپ پہلے ہی لکھ چکے ہیں۔

  • آٹو ڈسپیچ: جب LLM کال کرتا ہے۔ get_exchange_rateفریم ورک براہ راست سجے ہوئے فنکشن کی طرف جاتا ہے۔ کوئی سٹرنگ میچنگ اور مینوئل میپنگ نہیں۔

  • مطابقت پذیری کی ضمانت: نام تبدیل کریں from_currency کو source_currency یہ فنکشن کے دستخط میں شامل ہے اور اسکیما اگلے رن پر خود بخود اپ ڈیٹ ہوجاتا ہے۔ بھولنے کی کوئی دوسری جگہ نہیں ہے۔

مسئلہ 3: رن ٹائم سیاق و سباق کو منتقل کرنے کا کوئی صاف طریقہ نہیں ہے۔

خودکار ڈسپیچ (مسئلہ 2) کے ساتھ، فریم ورک صارف کے افعال کے بجائے ٹول فنکشن کو کال کرتا ہے۔ چونکہ اب آپ کا کال سائٹ پر کنٹرول نہیں ہے، اس لیے آپ بس نہیں گزر سکتے۔ db یا user_id ایک اضافی دلیل کے طور پر۔

اور یہ وہ نہیں ہے جو ایل ایل ایم کو پیش کرنا ہے۔ فریم ورک کے لیے سائیڈ چینلز کی ضرورت ہوتی ہے تاکہ وہ رن ٹائم انحصار کو ان ٹولز تک منتقل کر سکے جنہیں یہ آپ کی جانب سے کال کرتا ہے۔

اس طریقہ کار کے بغیر، درج ذیل نتائج سامنے آئیں گے:

# Option A: Global state (untestable, unsafe)
db = get_database_connection()
current_user = None  # Set somewhere else... hopefully before tools run

def get_spending_history(category: str, days: int) -> dict:
    # Uses global `db` and `current_user` — how do you test this?
    # How do you run two users concurrently?
    return db.query(
        "SELECT sum(amount) FROM transactions WHERE user_id = ? AND category = ? AND date > ?",
        current_user.id, category, days_ago(days)
    )


# Option B: Closure-based (awkward, deeply nested)
def make_tools(db, user):
    def get_spending_history(category: str, days: int) -> dict:
        return db.query(...)  # Captures db and user from enclosing scope

    def lookup_merchant_category(merchant_name: str) -> str:
        return db.query(...)  # Same closure trick

    return [get_spending_history, lookup_merchant_category]

# Every time you add a dependency, you restructure the closure nesting

دونوں نقطہ نظر جانچ کو مشکل بناتے ہیں۔ آپ اپنے کوڈ کو دوبارہ بنائے بغیر فرضی ڈیٹا بیس یا ٹیسٹ صارف کو آسانی سے تبدیل نہیں کر سکتے۔

حل کیسا لگتا ہے۔

ایک بہتر حل کے لیے آپ کو یہ بتانے کی ضرورت ہے کہ آپ کو اس ٹول سے کیا ضرورت ہے۔ LLM کے ذریعہ فراہم کردہ ٹول آرگیومینٹس سے الگ ان پٹ ضروریات کے طور پر انحصار (DB، HTTP کلائنٹ، صارف سیشن) کو ظاہر کریں۔

مزید برآں، اسے ڈیفینیشن ٹائم کے بجائے رن ٹائم پر انجیکشن لگانا چاہیے۔ ایجنٹ کو چلاتے وقت ایک مخصوص مثال پاس کریں، نہ کہ ٹول کی وضاحت کرتے وقت۔ یہ ٹول کی تعریفوں کو خالص اور دوبارہ قابل استعمال رکھتا ہے۔

اور جانچ کے لیے انحصار کا تبادلہ۔ اپنے ٹول کوڈ کو تبدیل کیے بغیر اصلی ڈیٹا بیس کو ان میموری موک ڈیٹا بیس سے تبدیل کریں یا اصلی صارف کو ٹیسٹ فکسچر سے تبدیل کریں۔

Pydantic AI اس مسئلے کو کیسے حل کرتا ہے۔

Pydantic AI میں اعلی درجے کا انحصار انجیکشن سسٹم ہے۔ تم ہو deps_type ایجنٹ اور ٹولز یہ انحصار ان پٹ فارم کے ذریعے حاصل کرتے ہیں۔ RunContextکوئی عالمی یا بندش نہیں:

from dataclasses import dataclass
from pydantic_ai import Agent, RunContext


@dataclass
class ReceiptDeps:
    db: DatabaseClient
    user_id: str
    http_client: HttpClient


receipt_agent = Agent(
    "openai:gpt-4o",
    output_type=ReceiptAnalysis,
    deps_type=ReceiptDeps,
    system_prompt="Analyze the provided receipt and extract structured details.",
)


@receipt_agent.tool
def get_spending_history(ctx: RunContext[ReceiptDeps], category: str, days: int) -> dict:
    """Get spending totals by category for a date range."""
    return ctx.deps.db.query(
        "SELECT sum(amount), count(*) FROM transactions WHERE user_id = ? AND category = ? AND date > ?",
        ctx.deps.user_id, category, days_ago(days)
    )


@receipt_agent.tool
def get_exchange_rate(ctx: RunContext[ReceiptDeps], from_currency: str, to_currency: str) -> float:
    """Get current exchange rate between two currencies."""
    response = ctx.deps.http_client.get(f"/rates/{from_currency}/{to_currency}")
    return response.json()["rate"]


# At runtime — pass real dependencies
result = receipt_agent.run_sync(
    "CAFE PARIS\n€12.50\nCroissant x2",
    deps=ReceiptDeps(
        db=get_database_connection(),
        user_id="user_123",
        http_client=HttpClient(base_url="https://api.exchangerate.host"),
    ),
)

# In tests — swap with mocks, no code changes to tools
result = receipt_agent.run_sync(
    "CAFE PARIS\n€12.50\nCroissant x2",
    deps=ReceiptDeps(
        db=InMemoryDb(fake_transactions),
        user_id="test_user",
        http_client=MockHttpClient(fixed_rate=1.08),
    ),
)

یہ آپ کو کیا دیتا ہے:

  • ٹولز اس بات کا اعلان کرتے ہیں کہ انہیں کس چیز کی ضرورت ہے، نہ کہ اسے کیسے حاصل کرنا ہے۔ ctx.deps.db یہ داخل ہے۔ IDE اس کے لیے طریقہ کار کو خود بخود مکمل کرے گا اور ٹائپ چیکر کسی غلط استعمال کو پکڑے گا۔ ٹول کو معلوم یا پرواہ نہیں ہے کہ آیا یہ حقیقی پوسٹگریس کنکشن ہے یا ٹیسٹ موک کنکشن۔

  • کوئی عالمی، کوئی بندش نہیں: مندرجہ ذیل جگہوں پر انحصار کو واضح طور پر متعارف کرایا گیا ہے: run_sync() گھنٹہ دو ساتھی صارفین کو دو الگ الگ اجازتیں ملتی ہیں۔ ReceiptDeps ایک ایسی مثال جس میں کوئی مشترکہ تغیر پذیر حالت نہیں ہے۔

  • ٹیسٹ آسان ہے۔ تبادلہ DatabaseClient کے لیے InMemoryDbاور تبادلہ HttpClient کے لیے MockHttpClient. ٹول کوڈ تبدیل نہیں ہوتا ہے۔ ماڈیول سطح کی حالت تک پہنچنے کے لیے کوئی بندر پیچ، انحصار انجیکشن فریم ورک، یا ٹیسٹ فکسچر نہیں ہے۔

  • LLM انحصار کی جانچ نہیں کرتا ہے۔ RunContext یہ ٹول پیرامیٹر کے طور پر سامنے نہیں آیا ہے۔ ایل ایل ایم صرف ہے۔ category اور days. فریم ورک خود بخود اسے اسکیما سے ہٹا دیتا ہے۔

مسئلہ 4: ٹیسٹ کے لیے ایک حقیقی LLM کال درکار ہے۔

آپ اس بات کو یقینی بنانا چاہتے ہیں کہ آپ کا ایجنٹ غیر ملکی کرنسی کی رسیدیں، بیچنے والے کے نام غائب، اور مبہم زمروں جیسے اہم معاملات کو ہینڈل کرتا ہے۔ تاہم، تمام ٹیسٹ اصل API کو مارتے ہیں۔

def test_foreign_currency_receipt():
    # This test:
    # - Costs money (API call)
    # - Takes 2-5 seconds
    # - Is non-deterministic (might pass today, fail tomorrow)
    # - Requires network access (breaks in CI without secrets)
    result = analyze_receipt("CAFÉ PARIS\n€12.50\nCroissant x2")
    assert result["currency"] == "EUR"
    assert result["category"] == "food"  # Might return "dining" instead — flaky!

آپ اسے CI میں قابل اعتماد طریقے سے نہیں چلا سکتے۔ آپ اپنا API بجٹ ختم کیے بغیر 50 ایج کیس ٹیسٹ نہیں چلا سکتے۔ آپ کا اختتام کوئی ٹیسٹ یا ناکام انٹیگریشن ٹیسٹوں کے ساتھ نہیں ہوتا ہے۔

حل کیسا لگتا ہے۔

سب سے پہلے، LLM کو ایک تعییناتی متبادل فنکشن سے تبدیل کریں۔ مقصد یہ ہے کہ جانچ کو تیز، مفت، اور دوبارہ قابل دہرانے کے قابل پیشین گوئی اور کنٹرول شدہ جوابات واپس کریں۔

اگلا، اپنے ایجنٹ کی منطق کو برقرار رکھیں۔ آپ کے ٹیسٹوں کو اصل ٹول ڈسپیچ، توثیق، اور آؤٹ پٹ پارس کرنا چاہیے۔ صرف ماڈل جعلی ہے۔

آخر میں، کارروائیوں پر اصرار کریں، ایل ایل ایم بیانات پر نہیں۔ تصدیق کریں کہ صحیح ٹول کو صحیح دلائل کے ساتھ بلایا گیا ہے اور یہ کہ آؤٹ پٹ متوقع ڈھانچے سے میل کھاتا ہے۔

Pydantic AI اس مسئلے کو کیسے حل کرتا ہے۔

Pydantic AI کے ذریعہ فراہم کردہ TestModel اور FunctionModel. یہ ایک ڈراپ ان ماڈل کا متبادل ہے جو آپ کو نیٹ ورک کال کیے بغیر بالکل "LLM” کی واپسی کو کنٹرول کرنے کی اجازت دیتا ہے۔

from pydantic_ai import Agent
from pydantic_ai.models.test import TestModel
from pydantic_ai.models.function import FunctionModel


# TestModel — returns a predictable, schema-valid response automatically
def test_receipt_analysis_structure():
    """Test that the agent returns a valid ReceiptAnalysis object."""
    with receipt_agent.override(model=TestModel()):
        result = receipt_agent.run_sync(
            "CAFE PARIS\n€12.50\nCroissant x2",
            deps=ReceiptDeps(
                db=InMemoryDb(fake_transactions),
                user_id="test_user",
                http_client=MockHttpClient(fixed_rate=1.08),
            ),
        )
        # TestModel fills fields with valid dummy data matching the schema
        assert isinstance(result.output, ReceiptAnalysis)
        assert 0 <= result.output.confidence <= 1


# FunctionModel — you control the exact response for specific scenarios
def test_foreign_currency_triggers_exchange_rate_tool():
    """Test that a EUR receipt causes the agent to call get_exchange_rate."""

    def mock_model(messages, info):
        # Simulate the LLM deciding to call the exchange rate tool
        return ModelResponse(
            tool_calls=[ToolCall(name="get_exchange_rate", args={"from_currency": "EUR", "to_currency": "USD"})]
        )

    with receipt_agent.override(model=FunctionModel(mock_model)):
        result = receipt_agent.run_sync(
            "CAFE PARIS\n€12.50\nCroissant x2",
            deps=ReceiptDeps(
                db=InMemoryDb(fake_transactions),
                user_id="test_user",
                http_client=MockHttpClient(fixed_rate=1.08),
            ),
        )
        # Assert the exchange rate tool was actually invoked
        tool_calls = [msg for msg in result.all_messages() if hasattr(msg, "tool_name")]
        assert any(tc.tool_name == "get_exchange_rate" for tc in tool_calls)

یہ مفید ہے کیونکہ یہ تیز اور مفت ہے۔ کوئی API کالز، نیٹ ورک یا ٹوکن استعمال نہیں کیے جاتے ہیں۔ ٹیسٹ ملی سیکنڈ میں چلتے ہیں۔

یہ تعیین پسند بھی ہے۔ اس کا مطلب ہے کہ ایک ہی ان پٹ کے لیے، آپ کو ہر بار ایک ہی آؤٹ پٹ ملتا ہے۔ LLM درجہ حرارت یا الفاظ میں تبدیلی کی وجہ سے کوئی غیر مستحکم ٹیسٹ نہیں ہیں۔

اصل ایجنٹ منطق بھی چلتی رہتی ہے۔ ٹول ڈسپیچ، انحصار انجیکشن، اور آؤٹ پٹ کی توثیق سبھی کو انجام دیا جاتا ہے۔ صرف ماڈل کو تبدیل کیا گیا ہے۔

یہ CI دوستانہ بھی ہے۔ ایک CI ماحول کو API کیز کی ضرورت نہیں ہے، انتظام کرنے کے لیے کوئی راز نہیں ہے، یا شرح کی حد نہیں ہے۔

یہ کنٹرول کی دو سطحوں کی بھی اجازت دیتا ہے۔ تم ہو TestModel "کیا پلمبنگ کام کرتی ہے؟" ٹیسٹ اور آپ FunctionModel "کیا ایجنٹ صحیح فیصلے کر رہے ہیں؟" یہ ایک ٹیسٹ ہے جو مخصوص LLM رویے کو اسکرپٹ کرتا ہے۔

مسئلہ 5: دوبارہ کوشش کریں اور دستی طور پر لکھی گئی توثیق کی منطق

اگر LLM غلط جواب دیتا ہے، تو آپ کو دوبارہ کوشش کرنی چاہیے۔ تاہم، دوبارہ کوشش کی منطق تیزی سے پیچیدہ ہو جاتی ہے۔

def analyze_receipt_with_retry(receipt_text: str, max_retries: int = 3) -> dict:
    for attempt in range(max_retries):
        try:
            response = client.chat.completions.create(...)
            raw = response.choices[0].message.content
            result = json.loads(strip_markdown(raw))

            # Validate
            if not isinstance(result.get("total"), (int, float)):
                raise ValueError("total must be numeric")
            if result.get("confidence", 0) > 1 or result.get("confidence", 0) < 0:
                raise ValueError("confidence must be 0-1")
            if result.get("category") not in ALLOWED_CATEGORIES:
                raise ValueError(f"invalid category: {result.get('category')}")

            return result

        except (json.JSONDecodeError, ValueError, KeyError) as e:
            if attempt == max_retries - 1:
                raise
            # Should we feed the error back to the LLM? Modify the prompt?
            # How do we track which attempts failed and why?
            continue

    raise RuntimeError("Should not reach here")

ہر ایجنٹ کو جو آپ تعینات کرتے ہیں اسی دوبارہ کوشش/تصدیق/دوبارہ پرامپٹ پیٹرن کی ضرورت ہوتی ہے، اور آپ اسے ہر بار دوبارہ لکھتے ہیں۔ LLM میں توثیق کی غلطیوں کو فیڈ کرنے کی منطق تاکہ یہ خود کو درست کر سکے پیچیدگی کی ایک اور پرت کا اضافہ کر دیتی ہے۔

حل کیسا لگتا ہے۔

سب سے پہلے، توثیق اعلانیہ ہونا ضروری ہے. یعنی، اس کی وضاحت آؤٹ پٹ اسکیما کے ذریعے کی جاتی ہے، نہ کہ ہاتھ سے لکھے ہوئے اگر بیانات پورے کوڈ میں بکھرے ہوں۔

دوسرا، دوبارہ کوششیں خودکار ہونی چاہئیں۔ اگر آؤٹ پٹ توثیق کرنے میں ناکام ہو جاتا ہے، تو فریم ورک کو خود کی اصلاح کو فعال کرنے کے لیے LLM کو ایک غلطی کا پیغام واپس دکھانا چاہیے۔

اور آپ کی اپنی مرضی کے مطابق توثیق کو ایک دوسرے کے ساتھ صاف طور پر باندھنا چاہئے. کاروباری قواعد کے لیے جو ٹائپ چیکنگ سے آگے بڑھتے ہیں (مثال کے طور پر "اگر زمرہ 'دیگر' ہے، تو اعتماد 0.8" سے کم ہونا چاہیے)، آپ کو دوبارہ کوشش کرنے والے لوپ کو دوبارہ لکھے بغیر توثیق کرنے والوں کو شامل کرنے کے قابل ہونا چاہیے۔

Pydantic AI اس مسئلے کو کیسے حل کرتا ہے۔

سکیما لیول کی توثیق پہلے سے ہی Pydantic ماڈل میں سنبھال لی گئی ہے (مسئلہ 1 دیکھیں)۔ تاہم، کاروباری منطق کی تصدیق کے لیے، Pydantic AI فراہم کرتا ہے: result_validator. یہ ایک ڈیکوریٹر ہے جو تجزیہ کرنے کے بعد چلتا ہے اور خودکار دوبارہ کوششوں کو متحرک کر سکتا ہے۔

from pydantic_ai import Agent, RunContext, ModelRetry


receipt_agent = Agent(
    "openai:gpt-4o",
    output_type=ReceiptAnalysis,
    deps_type=ReceiptDeps,
    system_prompt="Analyze the provided receipt and extract structured details.",
    retries=3,  # Max retry attempts on validation failure
)


@receipt_agent.result_validator
def validate_receipt_analysis(ctx: RunContext[ReceiptDeps], result: ReceiptAnalysis) -> ReceiptAnalysis:
    """Business logic validation — runs after schema validation passes."""

    # Rule: if total doesn't match sum of items, ask LLM to fix it
    items_sum = sum(item.amount for item in result.items)
    if abs(result.total - items_sum) > 0.01:
        raise ModelRetry(
            f"Total ({result.total}) doesn't match sum of items ({items_sum}). "
            f"Please recheck the receipt and correct either the total or the item amounts."
        )

    # Rule: low confidence + "other" category likely means the LLM gave up — retry
    if result.category == SpendingCategory.OTHER and result.confidence < 0.5:
        raise ModelRetry(
            "Category is 'other' with low confidence. Look more carefully at the "
            "merchant name and items to determine a more specific category."
        )

    return result

اگر توثیق ناکام ہوجاتی ہے، تو درج ذیل ہوتا ہے:

┌─────────────────────────────────────────────────────────────┐
│  Automatic Retry Flow                                       │
│                                                             │
│  LLM response                                               │
│       │                                                     │
│       ▼                                                     │
│  Schema validation (Pydantic model)                         │
│       │                                                     │
│       ├── FAIL → error message sent back to LLM → retry     │
│       │                                                     │
│       ▼                                                     │
│  result_validator (your business rules)                     │
│       │                                                     │
│       ├── ModelRetry raised → message sent to LLM → retry   │
│       │                                                     │
│       ▼                                                     │
│  PASS → return typed result                                 │
└─────────────────────────────────────────────────────────────┘
  • اسکیما کی خلاف ورزیاں (غلط قسمیں، گمشدہ فیلڈز، گنتی کی مماثلتیں) خود بخود Pydantic کے ذریعے پکڑی جاتی ہیں۔ توثیق کی غلطیوں کو سیاق و سباق کے طور پر LLM کو واپس بھیجا جاتا ہے تاکہ اسے مطلع کیا جاسکے۔ کیا ٹھیک کریں

  • کاروباری قوانین کی کسی بھی خلاف ورزی پر آپ کو پکڑ لیا جائے گا۔ result_validator. ModelRetry صحیح جواب کی حوصلہ افزائی کے لیے اپنے LLM کو حسب ضرورت پیغامات بھیجیں۔

  • آپ کے کوڈ میں دوبارہ کوشش کرنے کا کوئی لوپ نہیں ہے۔ کہ retries=3 پیرامیٹر کوششوں کی زیادہ سے زیادہ تعداد کو کنٹرول کرتا ہے۔ فریم ورک لوپس، دوبارہ اشارہ، اور ایرر میسج فارمیٹس کو ہینڈل کرتا ہے۔

مسئلہ 6: ماڈلز کو تبدیل کرنے کا مطلب ہے انٹیگریشن کوڈ کو دوبارہ لکھنا

ایجنٹ OpenAI کے ساتھ کام کرتا ہے۔ اب میں اسے مقامی طور پر Anthropic (میرے استعمال کے معاملے کے لیے سستا) یا اولاما (ڈیٹا کی رازداری کے لیے) کا استعمال کرتے ہوئے چلانا چاہتا ہوں۔ ہر فراہم کنندہ کے پاس مختلف SDKs، ٹول کال فارمیٹس، اور رسپانس اسٹرکچر ہوتے ہیں۔

# OpenAI
response = openai_client.chat.completions.create(
    model="gpt-4o",
    messages=messages,
    tools=tools  # OpenAI tool format
)
tool_calls = response.choices[0].message.tool_calls

# Anthropic — completely different API shape
response = anthropic_client.messages.create(
    model="claude-sonnet-4-20250514",
    messages=messages,
    tools=anthropic_tools  # Different format than OpenAI!
)
tool_use_blocks = [b for b in response.content if b.type == "tool_use"]

# Google — yet another shape
response = genai_client.generate_content(
    contents=messages,
    tools=google_tools  # Yet another format!
)
function_calls = response.candidates[0].content.parts

بالآخر، فراہم کنندہ کے مخصوص کوڈ کے راستے، اڈاپٹر پرتیں، اور ایجنٹ منطق انضمام گلو کے نیچے دب جاتے ہیں۔

حل کیسا لگتا ہے۔

ایک بار اپنے ایجنٹ کی منطق کی وضاحت کرکے شروع کریں۔ ٹولز، آؤٹ پٹ کی قسمیں، سسٹم پرامپٹس، اور توثیق کا ماڈل آزاد ہونا چاہیے۔

مزید برآں، آپ کو SDK کالز، ٹول سکیما، یا رسپانس پارسنگ کو دوبارہ لکھنے کے بجائے سٹرنگز کو تبدیل کر کے ماڈلز کو تبدیل کرنے کے قابل ہونا چاہیے۔

اور آپ کو سپلائر کے لیے مخصوص تفصیلات چھپانے کی ضرورت ہے۔ فریم ورک کو ہر فراہم کنندہ کے ذریعہ متوقع فارمیٹ میں یونیورسل ایجنٹ کی تعریف کا ترجمہ کرنا چاہیے۔

Pydantic AI اس مسئلے کو کیسے حل کرتا ہے۔

ایجنٹ کی تعریفیں مکمل طور پر ماڈل ایگنوسٹک ہیں۔ ایک ماڈل صرف ایک تار کا شناخت کنندہ ہے۔ اگر آپ ماڈل کو تبدیل کرتے ہیں، تو باقی سب کچھ وہی رہتا ہے۔

# Your agent definition — tools, output type, deps, validators — all unchanged
receipt_agent = Agent(
    "openai:gpt-4o",  # ← this is the only line that changes
    output_type=ReceiptAnalysis,
    deps_type=ReceiptDeps,
    system_prompt="Analyze the provided receipt and extract structured details.",
)

# Switch to Anthropic — same agent, same tools, same output type
receipt_agent = Agent(
    "anthropic:claude-sonnet-4-20250514",
    output_type=ReceiptAnalysis,
    deps_type=ReceiptDeps,
    system_prompt="Analyze the provided receipt and extract structured details.",
)

# Switch to a local model via Ollama
receipt_agent = Agent(
    "ollama:llama3.1",
    output_type=ReceiptAnalysis,
    deps_type=ReceiptDeps,
    system_prompt="Analyze the provided receipt and extract structured details.",
)

# Or make it configurable at runtime
import os

receipt_agent = Agent(
    os.getenv("RECEIPT_AGENT_MODEL", "openai:gpt-4o"),
    output_type=ReceiptAnalysis,
    deps_type=ReceiptDeps,
    system_prompt="Analyze the provided receipt and extract structured details.",
)

فریم ورک پردے کے پیچھے کیا ہینڈل کرتا ہے:

  • ٹول سکیما ترجمہ: آپ کا @receipt_agent.tool افعال کو OpenAI میں تبدیل کیا جاتا ہے۔ tools رسمی، بشریات tools فارمیٹ یا گوگل function_declarations - آپ اپنے منتخب سپلائر سے کیا توقع کرتے ہیں۔ آپ کو کبھی فرق نظر نہیں آئے گا۔

  • ردعمل کو معمول پر لانا: کیا ماڈل واپس آئے گا؟ choices[0].message.tool_calls (اوپن اے آئی)، content[].type == "tool_use" (انسانیت) یا candidates[0].content.parts (گوگل)، فریم ورک اسے ایک مستقل اندرونی نمائندگی میں معمول بناتا ہے۔

  • فراہم کنندہ کی مخصوص خصوصیات کو شفاف طریقے سے سنبھالا گیا: ہر فراہم کنندہ ساختی آؤٹ پٹ موڈ، اسٹریمنگ، اور ٹوکن کیلکولیشن کو مختلف طریقے سے لاگو کرتا ہے۔ فریم ورک آپ کے کوڈ میں فرق کو ظاہر کیے بغیر اپناتا ہے۔

ایک ایجنٹ کی تعریف، تمام ماڈلز۔ کنفیگریشن یا ماحولیاتی متغیر کے ذریعے تبدیل کریں۔

ختم

اس مضمون کے تمام مسائل ایک ہی جگہ سے پیدا ہوتے ہیں۔ LLM کال ٹیکسٹ ان پٹ، ٹیکسٹ آؤٹ پٹ، اور دونوں طرف کا کوڈ ان پٹ ہے۔ مقامی SDK نقطہ نظر ہاتھ سے لکھے ہوئے گلو کے ساتھ اس خلا کو پُر کرتا ہے: باڑ کے اسٹرائپرز، اگر چیک چینز، ڈسپیچ ٹیبلز، دوبارہ کوشش کرنے والے لوپس وغیرہ۔ ہر ٹکڑا آسان ہے۔ ایک ساتھ استعمال کیا جاتا ہے، وہ اپنے ارد گرد ایجنٹ لاجکس کو آگے بڑھاتے ہیں، اور آپ ان سب کے مالک ہیں۔

Pydantic AI حدود کو ایک اعلان کردہ معاہدہ بنا کر خلا کو پورا کرتا ہے۔ ہر سیکشن کے مندرجات درج ذیل ہیں:

مسئلہ خام SDK فیڈانٹک AI
ساختی پیداوار اسکیما انگریزی میں بیان کیا گیا اور براہ راست تجزیہ کیا گیا۔ output_type=ReceiptAnalysis - اسکیما جنریشن، ردعمل کی تصدیق
آلے کی تعریف JSON اسکیما + ڈسپیچ کی ~70 لائنیں۔ @agent.tool_plain داخل کردہ فنکشن کے بارے میں
رن ٹائم سیاق و سباق عالمی یا نیسٹڈ بندش deps_type + RunContextانجکشن فی رن
ٹیسٹ اصل API کالز: سست، مہنگی، اور غیر مستحکم TestModel / FunctionModelکوئی نیٹ ورک نہیں
دوبارہ کوشش کریں اور توثیق کریں۔ ہینڈ رولڈ لوپس، غلطیاں ختم کر دی گئیں۔ فیلڈ کی رکاوٹیں + ModelRetryخرابی کو ماڈل میں فیڈ کر دیا گیا ہے۔
ماڈل کی تبدیلی فراہم کنندہ کے لیے مخصوص SDKs اور پارسنگ کوڈ 1 تار: "openai:gpt-4o""anthropic:claude-sonnet-4-20250514"

ہمارا مکمل شدہ رسید ایجنٹ ایک Pydantic ماڈل، چند ان پٹ فنکشنز، ایک ڈیپس ڈیٹا کلاس، اور ایک تصدیق کنندہ ہے۔ کوئی تصریف نہیں، کوئی ڈسپیچنگ نہیں، کوئی دوبارہ کوشش نہیں کی گئی۔

Pydantic AI دستاویزات مختصر اور پڑھنے کے قابل ہیں۔ یہاں ہر چیز API کے حوالہ سے نقشہ بناتی ہے۔

Scroll to Top