معماری یه هارنس ایجنتیک حرفهای
خلاصهٔ کاملتر
نویسنده با یه استعارهٔ نظامی شروع میکنه: خلبان خوب تو یه جت خوب شاید یه دوئل هوایی رو ببره، ولی هیچکس یه عملیات هوایی رو اینجوری اداره نمیکنه — طراح مأموریت، اسکادرانهای موازی، بودجهٔ سوخت، جعبهسیاه و مرور بعد از عملیات دورِ خلبان میچینن. به گفتهٔ اون، ابزارهایی مثل Claude Code و Devin و Cursor دقیقاً همین کارو با حلقهٔ پایه میکنن. سؤال محوری مقاله اینه: چطور یه فراخوانی تنهای LLM رو به سیستمی تبدیل کنیم که برنامه بریزه، عمل کنه، از خطا برگرده و بتونه ثابت کنه کار درستو انجام داده؟
اولین قطعه، ابزارهای تایپداره. تو هارنس پایه آرگومانها دستی اعتبارسنجی میشدن که سریع فرومیپاشه: هر ابزار جدید منطق تکراری میخواد، مدل هیچ اسکیمای رسمیای نمیبینه و فقط شکل آرگومانها رو حدس میزنه. با تعریف آرگومانها بهشکل یه مدل Pydantic، هم اعتبارسنجی زمان اجرا داری، هم دقیقاً همون JSON Schema که APIهای Anthropic و OpenAI انتظار دارن، هم یه قلاب برای حساب هزینه از طریق cost_hint. نکتهٔ اصلی اینه که پلن بد باید زود و تو لایهٔ اعتبارسنجی بشکنه، نه وسط یه کوئری پایگاه داده.
قطعهٔ بعدی، پلنه که دیگه یه لیست تخت نیست بلکه گرافه. مثال مقاله مقایسهٔ چند شهره: برای سه شهر، نُه جستوجوی مستقل داریم که همه میتونن همزمان اجرا شن و یه نود جمعبندی که به هر نُهتاشون وابستهست. پس بهجای گرفتن یه اکشن در هر نوبت، کل گراف رو از Planner میگیریم و قبل از اجرا اعتبارسنجیش میکنیم، چون پلنر هم میتونه ساختار توهم بزنه: وابستگی به نودی که وجود نداره، یا حلقهای که هیچوقت تموم نمیشه.
اجراکننده یه پیمایشگر سطحبهسطحه: مجموعهٔ آماده رو حساب میکنه، همهشونو با asyncio.gather همزمان میفرسته و تا وقتی پیشرفتی هست تکرار میکنه. دو تصمیم کوچیک بیشترین وزن رو دارن — asyncio.to_thread ابزارهای همگام رو تو ثردپول میبره تا لازم نباشه همهچی رو async بازنویسی کنیم، و یه سمافور سقف همزمانی رو نگه میداره تا یه پلن پنجاهنودی یهو پنجاه فراخوانی LLM باز نکنه. زمان اجرا از «جمع تأخیرها» میره به «بیشترین تأخیر، بهعلاوهٔ جمعبندی».
برای حافظه هم بهجای ریختن کل تاریخچه تو پرامپت، حافظه لایهلایه میشه: حافظهٔ کاری (هدف فعلی و چند نتیجهٔ آخر)، حافظهٔ اپیزودیک (نتیجهٔ اجراهای مشابه قبلی) و حافظهٔ معنایی (دانش پسزمینه). فقط چند مورد مرتبط بازیابی میشه و کانتکست زیر یه سقف کاراکتری چیده میشه؛ اپیزودیک اولویت داره چون خطای گذشته روی کار مشابه معمولاً از یه حقیقت عمومی بهدردبخورتره. برای سنجش شباهت هم امبدینگ جملهای (all-MiniLM-L6-v2) اول امتحان میشه و Jaccard فقط پشتیبانه، چون Jaccard روی بازنویسی جمله کم میآره.
آخرین قطعه اعتبارسنجیه، اونهم پلهای: اول چکهای ساختاریِ قطعی که تقریباً مجانیان، بعد فقط برای بازماندهها داورِ LLM که توکن واقعی خرج میکنه.
def verify_report(report, goal, required_cities, provider) -> Verdict:
det = deterministic_check_report(report, required_cities)
if not det.passed:
return det
return llm_judge_report(report, goal, provider)اگه گزارش ناقص باشه، همون لایهٔ ارزون با دلیلی مثل «این شهرها جا افتادن» ردش میکنه و صفر توکن خرج میشه. کنارش، کار بین سه نقش تقسیم میشه: Planner که هدف و اسکیمای ابزارها رو میگیره و DAG برمیگردونه، Worker که فقط اجراش میکنه، و Critic که خروجی رو داوری میکنه — یعنی تولیدکننده هیچوقت برگهٔ خودشو تصحیح نمیکنه.
نکات کلیدی:
- ابزارهای تایپدار با Pydantic: یه تعریف، هم اعتبارسنجی هم اسکیما هم مستندات هم برآورد هزینه
- پلن بهشکل DAG، با اعتبارسنجی ساختار قبل از خرجکردن توکن
- اجرای موازیِ سطحبهسطح با asyncio.gather و سمافور برای مهار هزینه و نرخ درخواست
- حافظهٔ سهلایه (کاری، اپیزودیک، معنایی) با بازیابی محدود و برشِ صریح در سقف بودجه
- اعتبارسنجی پلهای: چک قطعی و مجانی اول، داورِ LLM فقط برای بازماندهها
- سه نقش جدا (Planner، Worker، Critic) تا تولیدکننده داورِ خودش نباشه




