وقتی تعداد ابزارهای یک ایجنت افزایش مییابد، مدیریت نحوه فراخوانی و قالب ورودی آنها چالشبرانگیز میشود. اگر مدلی آرگومانی با ساختار اشتباه بفرستد و خطایی در سطح کد کنترلکننده رخ دهد، فرآیند اجرای ایجنت متوقف شده یا وارد رفتوبرگشتهای متنی مبهم با مدل میشود.
حل این مشکل در تفکیک ساختار داده از منطق اجرا است. با تعریف یک رجیستری نوعدار (Tool Registry)، پیش از آنکه کد ابزار فراخوانی شود، آرگومانها با یک اعتبارسنج سبک و بر مبنای JSON Schema ارزیابی میشوند. در صورت بروز مغایرت، مسیر دقیق خطا در قالب JSON Pointer گزارش میشود تا مدل بتواند در یک گام ورودی خود را تصحیح کند.
این رویکرد معماری پایه، وابستگی ایجنت به آزمون و خطا را کاهش داده و مرز مشخصی میان مشخصات ورودی، دادههای ورودی و کدهای اجرایی برقرار میکند.
ابزاری که ایجنت نتواند اعتبارسنجیاش کند، ابزاری است که ایجنت اصلاً نمیتواند فراخوانی کند. رجیستری و اعتبارسنج اسکیما را قبل از ساختن خود ابزارها پیادهسازی کنید.
اهداف یادگیری
- نگهداری یک رجیستری نوعدار (Typed registry) از «نام ابزار ← اسکیما ← کنترلکننده (Handler)» که دیسپچر (Dispatcher) بتواند یک بار آن را بخواند و بعد با خیال راحت به آن اعتماد کند.
- پیادهسازی زیرمجموعهای از استاندارد JSON Schema 2020-12 که کلیدواژههای مورد استفاده در 90 درصد فراخوانیهای واقعی ابزارها را پوشش دهد.
- بازگرداندن مسیرهای خطای دقیق در قالب JSON Pointer، تا مدل بتواند در یک رفتوبرگشت خطا را تصحیح کند.
- رد کردن ثبت مجدد بدون بازنویسی صریح (Explicit override)؛ چرا که بازنویسیهای بیصدا دقیقاً عامل اصلی ناهمگامشدن فهرست ابزارها در محیط عملیاتی (پروداکشن) هستند.
- خالص (Pure) نگهداشتن اعتبارسنج (بدون I/O، بدون وابستگی به زمان، بدون متغیرهای سراسری) تا بتوان آن را روی گزارشهای بازپخش (Replay logs) دوباره اجرا کرد.
چرا رجیستری مقدم بر ابزار است؟
یک ایجنت برنامهنویسی در سال 2026، ابزارهای ثبتشدهی بیشتری نسبت به گنجایش پنجرهی بافتار (Context Window) یک مدل در اختیار دارد. یک بستر آزمایشی (Harness) واقعی و جدی ممکن است 200 ابزار را ثبت کند و در هر نوبت گفتوگو (Turn)، بین 10 تا 40 ابزار را در اختیار مدل بگذارد. رجیستری، منبع حقیقت (Source of Truth) برای سه پرسش اساسی است: «چه ابزارهایی وجود دارند؟»، «آرگومانهای آنها چه ساختاری دارند؟» و «کدام کنترلکننده را باید صدا بزنم؟». با مشخص شدن پاسخ این سه پرسش، بقیهی بستر آزمایشی دیگر نیازی به حدس زدن ندارد.
اشتباهی که میخواهیم از آن پرهیز کنیم، ارائهی کنترلکنندهها بدون اسکیما، یا ارائهی اسکیماها بدون اعتبارسنجی است. هر دو بسیار رایج هستند. هر دوی این اشتباهات لایهی بعدی (دیسپچر در درس 23) را به یک بازی حدسوگمان تبدیل میکنند که تنها نشانهی شکست در آن، ردگیری محاسبات (Stack trace) ناشی از خطای کنترلکننده خواهد بود.
ساختار یک رکورد ابزار چگونه است؟
ToolRecord
name : str (unique, lowercase alphanumeric and underscore segments separated by dots, e.g., snake_case.segment.case)
description : str (one line, shown to the model)
schema : dict (JSON Schema 2020-12 subset)
handler : Callable (async or sync, returns Any)
idempotent : bool (dispatcher uses this for retry decisions)
timeout_ms : int (override per-tool dispatcher default)
اسکیما تنها فیلدی است که اعتبارسنج به آن دست میزند. کنترلکننده برای اعتبارسنج کاملاً مات و دستنخورده است. ما عمداً آنها را از هم جدا کردهایم. اسکیما داده است و کنترلکننده کد. ترکیب این دو وسوسهتان میکند که منطق اعتبارسنجی را داخل کنترلکننده بگذارید؛ یعنی همان باگی که جلوی آن را میگیریم.
زیرمجموعهی JSON Schema 2020-12
مستندات کامل مشخصات نسخهی 2020-12 یک مقالهی بلندبالاست. ما فقط به 8 کلیدواژه نیاز داریم.
type string / number / integer / boolean / object / array / null
properties map of property name -> schema
required list of property names
enum list of allowed primitive values
minLength integer, applies to strings
maxLength integer, applies to strings
pattern ECMA-262-compatible regex, applies to strings
items schema applied to every array element
همین مقدار برای پوشش دادن نیازهای واقعی یک API ابزار کاملاً کافی است. کلیدواژههایی که حذف کردهایم (مانند oneOf ،anyOf ،allOf ،$ref و عبارات شرطی) اگرچه در اسکیماهای پروداکشن معتبرند، اما اعتبارسنج را به یک پیمایشگر درخت با حلقهها و چرخههای تکرارشونده تبدیل میکنند. ما داریم یک رجیستری میسازیم، نه یک موتور JSON Schema.
مسیرهای خطا با JSON Pointer
وقتی اعتبارسنجی شکست میخورد، اعتبارسنج فهرستی از خطاها را برمیگرداند. هر خطا شامل یک مسیر بر پایهی JSON Pointer به درون ورودی است. یک اشارهگر، دنبالهای از نام ویژگیها و اندیسهای آرایه است که با اسلش شروع میشوند.
{"a": {"b": [1, 2, "x"]}}
^
/a/b/2
مدلها مسیرهای خطا را بسیار بهتر از جملات متنی میفهمند. اگر اسکیما مقدار args.user.email را الزامی بداند و مدل بهجای آن یک عدد صحیح بفرستد، خروجی خطا باید /user/email همراه با expected_type: string باشد. مدل این خطا را در فراخوانی بعدی، بدون نیاز به توضیحات متنی اضافی اصلاح میکند.
ثبت و بازنویسی
متد register(name, schema, handler, **opts) بهصورت پیشفرض از ثبت مجدد ابزار جلوگیری میکند. فراخواننده برای جایگزینی حتماً باید مقدار override=True را ارسال کند. این کار نوعی بهداشت عملیاتی است. اینکه دو بخش از کد بیصدا ابزاری با نام یکسان ثبت کنند، از آن دسته باگهایی است که پیدا کردنش در پروداکشن یک هفته زمان میبرد.
رجیستری سه متد خواندن در اختیارتان میگذارد: get(name) رکورد را برمیگرداند یا خطا میدهد؛ validate(name, args) مقدار Ok یا فهرستی از خطاها را ارائه میکند؛ و names() اسامی ابزارها را به ترتیب زمان ثبت بازمیگرداند.
اعتبارسنج چه هست و چه نیست؟
این اعتبارسنج یک پیمایش بازگشتی و تکمرحلهای روی درخت اسکیما است. خالص است؛ کنترلکنندهها را فراخوانی نمیکند؛ تبدیل نوع خودکار (Coercion) انجام نمیدهد (رشتهی "42" از آزمون اسکیمای عددی قبول نمیشود) و دادهها را هم بیصدا کوتاه نمیکند.
این اعتبارسنج یک مرز امنیتی نیست. یک کنترلکنندهی مخرب، حتی پس از تأیید در اعتبارسنجی هم میتواند رفتار نامناسبی داشته باشد. دیسپچر در درس 23 لایههای مهلت زمانی (Timeout) و ساندباکس (Sandbox) را اضافه خواهد کرد؛ کار رجیستری فقط بررسی ساختار است.
ساختار
راهنمای خواندن کد
فایل code/main.py کلاسهای ToolRegistry ،ToolRecord ،ValidationError و هشت تابع اعتبارسنجی را تعریف میکند. اعتبارسنج بر اساس schema["type"] تصمیمگیری میکند (یا اسکیمای دارای enum را بهعنوان بررسی enum بدون نوع پردازش میکند). هر اعتبارسنج نوع، یا یک لیست خالی بازمیگرداند یا لیستی از ValidationError. پیمایشگر سطح بالا هنگام پایین رفتن در درخت، خطاها را جمعآوری کرده و بخشهای مسیر را به ابتدای آنها میافزاید.
فایل code/tests/test_registry.py فرآیند ثبت، بازنویسی، اعتبارسنجی موفق، خطاهای اعتبارسنجی همراه با مسیر، و تکتک کلیدواژههای این زیرمجموعه را پوشش میدهد.
برای مطالعهی بیشتر
دو امکانی که پس از پایان این درس بهکارتان میآید، حل $ref بر اساس یک بلوک تعاریف محلی و استفاده از additionalProperties: false برای تعریف ساختار سختگیرانه است. هر دو کوچک هستند و هر دو با بزرگتر شدن فهرست ابزارها به بیش از 50 مورد، معمولاً اضافه میشوند. ما آنها را از این درس کنار گذاشتیم تا خواندن این فایل طولانی نشود.
در درس بعدی (درس 22)، بستر انتقال (Transport) از نوع JSON-RPC stdio را میسازیم که این رجیستری را در اختیار کلاینت مدل میگذارد. در درس پس از آن (درس 23)، هر دوی آنها را داخل یک دیسپچر مجهز به مهلت زمانی و تلاش مجدد بستهبندی خواهیم کرد.
