تصور کنید صبح که از خواب بیدار میشوید، یک تیم از دستیاران متخصص که شبانه کار کردهاند در اختیار دارید: یکی ورودیهای شما را میخواند، دیگری حقایق کلیدی را خلاصه میکند، سومی موارد مهم را اولویتبندی میکند و چهارمی همه چیز را در یک گزارش روزانه تمیز قالببندی میکند. این دقیقاً تعریف یک سیستم چند عاملی است. این راهنما شما را در ساخت سیستمی راهنمایی میکند که در آن چهار عامل مبتنی بر پایتون، هر یک مسئولیت ویژهای را بر عهده دارند تا این گردش کار را به صورت خودکار اجرا کنند.
یک سیستم چند عاملی (Multi-Agent System) متشکل از چندین نرمافزار مستقل (عامل یا Agent) است که برای دستیابی به یک هدف مشترک با یکدیگر همکاری میکنند. برخلاف یک اسکریپت سنتی که مانند یک قطار روی ریلهای از پیش تعیین شده حرکت میکند و با کوچکترین تغییر در ورودی ممکن است خراب شود، یک عامل هوش مصنوعی更像 به راننده اتوبوس میماند. این عامل یک مقصد (هدف) دارد، اما میتواند بر اساس شرایط فعلی (دادهها) مسیر خود را انتخاب کند. اگر راهی مسدود باشد، راه دیگری پیدا میکند. عاملها معمولاً از الگویی به نام ReAct (استدلال به همراه عمل) پیروی میکنند که در آن در هر مرحله، عامل در مورد کاری که باید انجام دهد فکر میکند، اقدامی انجام میدهد، نتیجه را مشاهده میکند و تصمیم میگیرد که آیا به هدف خود رسیده است یا خیر.
شاید این سؤال پیش بیاید که چرا فقط از یک عامل قدرتمند که همه کارها را انجام میدهد استفاده نکنیم؟ این روش، الگوی "مدل خدا" نامیده میشود و مشکلات واقعی دارد. وقتی شما از یک مدل زبانه بزرگ (LLM) واحد بخواهید که در یک درخواست، دادهها را دریافت کند، خلاصه کند، اولویتبندی کند و قالببندی کند، در واقع بار فکری زیادی را یکباره به آن تحمیل کردهاید. مدلهای زبانی پنجره زمینه و توجه محدودی دارند. هرچه وظایف بیشتری را روی هم انباشته کنید، احتمال توهم زدن مدل، رد شدن از مراحل یا تولید خروجی ناسازگار بیشتر میشود.
یک سیستم چند عاملی این مشکل را از طریق "جداسازی نگرانیها" (Separation of Concerns) حل میکند. در این معماری:
در سیستم مورد بحث این راهنما، دادهها به ترتیب از طریق چهار عامل جریان مییابند که همگی توسط Docker Compose هماهنگ میشوند. هر عامل از یک حجم اشتراکی (Shared Volume) میخواند، ورودی خود را پردازش میکند، نتیجه را مینویسد و خارج میشود. این یک خط لوله همگام است: عاملها یکی پس از دیگری و به ترتیب اجرا میشوند. این سادهترین الگوی سیستم چند عاملی برای پیادهسازی و درک است.
مسئولیتهای هر عامل به شرح زیر است:
توجه کنید که تنها یکی از چهار عامل واقعاً یک مدل زبانی فرا میخواند. این طراحی عمدی است – شما باید فقط زمانی از یک مدل زبانی استفاده کنید که به استدلال یا درک زبان نیاز دارید. هر چیز دیگری باید کد قطعی (Deterministic) باشد. این روش ارزانتر، سریعتر و قابل پیشبینیتر است.
اگر تا به حال یک پروژه پایتون را با کسی به اشتراک گذاشتهاید و شنیدهاید که "روی ماشین من کار نمیکند"،你已经 مشکل اصلی را که Docker حل میکند درک کردهاید. Docker کد شما، وابستگیهای آن و یک سیستم عامل حداقلی را در یک واحد واحد به نام کانتینر بستهبندی میکند. وقتی آن کانتینر را اجرا میکنید، بدون توجه به ماشینی که روی آن در حال اجراست – لپتاپ شما، کامپیوتر همکارتان یا یک سرور ابری – دقیقاً به یک شکل رفتار میکند.
برای یک سیستم چند عاملی، این مسئله بدتر میشود زیرا هر عامل ممکن است به وابستگیهای متفاوتی نیاز داشته باشد. Docker با ارائه محیطی ایزوله برای هر عامل، از بروز تضاد بین وابستگیها جلوگیری میکند. ابزار Docker Compose نیز اجرای هماهنگ همه کانتینرها را تنها با یک فرمان ممکن میسازد. این امر تفاوت بین "اینم یک فایل README با 15 مرحله راهاندازی" و "دستور docker compose up را اجرا کن" است.
سیستم چند عاملی که در اینجا معرفی شد، با تفکیک وظایف و استفاده از کانتینرهای ایزوله، راهحلی modular، قابل اعتماد و قابل تکرار برای تبدیل شلوغی دیجیتال به یک خلاصه منظم روزانه ارائه میدهد. الگوهای اصلی آموختهشده – جداسازی نگرانیها، عاملهای کانتینری شده، ارتباط از طریق حجم اشتراکی و کدنویسی دفاعی در برابر APIهای خارجی – بسیار فراتر از این مورد استفاده خاص قابل به کارگیری هستند. این معماری پایهای مستحکم برای هر گردش کار هوش مصنوعی است که نیاز به قابلیت اطمینان و انعطافپذیری دارد.
یک اسکریپت سنتی پایتون از یک مسیر ثابت و از پیش تعیینشده پیروی میکند. این اسکریپتها مقداری ورودی میخوانند، آن را از طریق یک سری مراحل سختکدشده پردازش میکنند و در نهایت خروجی تولید میکنند. این مدل بسیار شکننده است؛ اگر حتی قالب ورودی کمی تغییر کند، اسکریپت معمولاً از کار میافتد. یک عامل هوشمند، اما، رویکردی کاملاً متفاوت دارد. این عامل بیشتر شبیه به یک راهنما عمل میکند که یک مقصد (هدف) مشخص دارد، اما میتواند بر اساس شرایط فعلی (دادهها) مسیر خود را انتخاب کند.
برای درک بهتر این تفاوت، میتوان اسکریپت سنتی را به یک قطار روی ریل تشبیه کرد. قطارها سریع و کارآمد هستند، اما تنها میتوانند به سمتی بروند که ریلها آن را هدایت میکنند. اگر مسیر مسدود شود، قطار متوقف میشود. در مقابل، یک عامل هوشمند更像 مانند راننده یک اتوبوس است. او یک مقصد نهایی دارد، اما اگر جادهای مسدود باشد، مسیر دیگری را پیدا میکند. این انعطافپذیری کلیدی، عاملها را برای مقابله با ورودیهای نامرتب و غیرقابل پیشبینی بسیار مناسبتر میسازد.
عاملهای هوشمند معمولاً از یک حلقه الگوبرداریشده به نام الگوی ReAct پیروی میکنند که مخفف Reasoning plus Acting (استدلال به علاوه عمل) است. در هر مرحله، عامل در مورد آنچه باید انجام دهد فکر میکند (استدلال)، یک عمل را انجام میدهد، نتیجه را مشاهده میکند و تصمیم میگیرد که آیا به هدف خود رسیده است یا خیر. اگر نه، به حلقه بازمیگردد و دوباره تلاش میکند. در عمل، این به آن معناست که یک عامل مبتنی بر مدل زبانی بزرگ میتواند بسیار بهتر از یک اسکریپت سنتی با ورودیهای نامرتب کنار بیاید. به عنوان مثال، اگر قالب یک خبرنامه تغییر کند، عامل خلاصهساز همچنان میتواند نکات کلیدی را استخراج کند زیرا درباره محتوا استدلال میکند، نه اینکه صرفاً یک ساختار سخت و ثابت را تجزیه کند.
ممکن است این سؤال پیش بیاید: چرا فقط از یک عامل قدرتمند که همه کارها را انجام میدهد استفاده نکنیم؟ این رویکرد، الگوی «مدل خدا» نامیده میشود و مشکلات واقعی دارد. هنگامی که شما از یک مدل زبانی بزرگ واحد میخواهید که دادهها را دریافت کند، خلاصه کند، اولویتبندی کند و همه را در یک دستور قالببندی کند، در واقع بار فکری بیش از حدی به آن تحمیل کردهاید. مدلهای زبانی پنجره متناهی و توجه محدودی دارند. هر چه وظایف بیشتری بر عهده آنها بگذارید، احتمال توهمزدگی، رد کردن مراحل یا تولید خروجی نامتناظر در مدل بیشتر میشود.
یک سیستم چندعاملی این مشکل را از طریق «جداسازی دغدغهها» حل میکند. در این معماری، هر عامل یک وظیفه مشخص و محدود دارد. این طراحی مزایای متعددی دارد:
یک نکته کلیدی در طراحی سیستمهای چندعاملی، استفاده هوشمندانه از مدلهای زبانی بزرگ است. در یک معماری بهینه، تنها زمانی از LLM استفاده میشود که واقعاً به استدلال یا درک زبان نیاز باشد. برای مثال، در سیستم خلاصهسازی روزانه، تنها یکی از چهار عامل (یعنی خلاصهساز) است که LLM را فرا میخواند. عوامل دیگر مانند جمعآوری کننده داده، اولویتبند و قالببند، کدهای پایتون ساده و قطعی هستند. این رویکرد نه تنها بسیار مقرون به صرفهتر و سریعتر است، بلکه نتایج قابل پیشبینیتری نیز ارائه میدهد.
به طور خلاصه، انتخاب بین اسکریپت سنتی و عامل هوشمند به ماهیت مشکل بستگی دارد. اسکریپتها برای کارهایی که مسیر پردازش کاملاً مشخص و قابل پیشبینی است، عالی هستند. اما برای مسائل پیچیدهتر با دادههای درهم و نامطمئن، که نیاز به قابلیت انعطاف و استدلال دارند، معماری چندعاملی مبتنی بر عاملهای هوشمند گزینه برتر محسوب میشود. این معماری با شکستن یک کار بزرگ به وظایف تخصصی کوچکتر، قابلیت اطمینان، نگهداشت پذیری و مقیاسپذیری سیستم را به طور چشمگیری افزایش میدهد.
اگر تا به حال یک پروژه پایتون را با شخصی به اشتراک گذاشتهاید و جمله معروف "روی سیستم من کار نمیکند" را شنیدهاید، دقیقاً با مشکلی آشنا هستید که داکر برای حل آن طراحی شده است. در یک سیستم چند عامله (Multi-Agent) مانند سیستمی که میسازیم، این مشکل حتی جدیتر میشود، چون هر عامل (Agent) ممکن است به نسخهها یا کتابخانههای متفاوتی وابسته باشد که در یک محیط مشترک با هم تضاد پیدا کنند. داکر با بستهبندی کد، وابستگیهای آن و یک سیستم عامل مینیمال در واحدی مستقل به نام «کانتینر» این مشکل را رفع میکند.
یک کانتینر داکر را میتوان مانند یک کانتینر حمل و نقل برای نرمافزار در نظر گرفت. محتویات آن (کد شما، کتابخانههای پایتون، ابزارهای سیستمی) در داخل آن مهر و موم شده و از محیط بیرون محافظت میشوند. وقتی شما این کانتینر را اجرا میکنید، فارغ از اینکه روی لپتاپ شما، کامپیوتر همکارتان یا یک سرور ابری در حال اجرا باشد، دقیقاً به یک شکل رفتار میکند. محیطهای مجازی پایتون تنها کتابخانههای پایتون را ایزوله میکنند، اما داکر کل محیط از جمله سیستم عامل، کتابخانههای سیستمی و دیگر ابزارها را ایزوله میسازد و همین باعث میشود که اجرای برنامه در هر ماشینی تضمین شده و یکسان باشد.
برای درک بهتر، باید با چند مفهوم اساسی داکر آشنا شوید:
برای این آموزش، استفاده از داکر اجباری نیست و میتوانید هر چهار عامل را به صورت اسکریپتهای ساده پایتون اجرا کنید. اما بدون داکر با چالشهای متعددی روبرو خواهید شد:
در مقابل، با استفاده از داکر، هر عامل محیط ایزوله خود را دارد، شما میتوانید تمام کانتینرها را با یک دستور (docker compose up) به صورت موازی اجرا کنید، نتایج در هر جایی یکسان خواهد بود و هر کانتینر میتواند مستقل از دیگری از نسخه خاصی از پایتون استفاده کند. برای یک پروژه شخصی، هر دو روش قابل استفاده هستند، اما اگر بخواهید این سیستم را با دیگران به اشتراک بگذارید، آن را روی سرور deploy کنید یا در ابر اجرا نمایید، داکر تفاوت بین "این یک فایل راهنما با ۱۵ مرحله تنظیم است" و "فقط دستور docker compose up را اجرا کنید" را ایجاد میکند.
داکر تصاویر را به صورت لایهای میسازد. هر دستور در یک داکرفایل یک لایه جدید ایجاد میکند. داکر این لایهها را کش میکند، بنابراین اگر لایهای از آخرین باری که تصویر ساخته شد تغییر نکرده باشد، داکر به جای ساختن مجدد، از نسخه کششده آن استفاده میکند. به دلایل عملکردی، داکرفایلها معمولاً به این ترتیب ساختار مییابند: لایه سیستم عامل پایه به ندرت تغییر میکند، لایه نصب وابستگیها زمانی تغییر میکند که فایل requirements.txt تغییر کند، و لایه کد برنامه با هر بار تغییر کد، عوض میشود. با قرار دادن دستور نصب وابستگیها قبل از کپی کردن کد برنامه، داکر تنها زمانی دستور pip install را دوباره اجرا میکند که واقعاً وابستگیهای شما تغییر کرده باشند و این کار باعث میشود فرآیند ساخت در ثانیه انجام شود، نه دقیقه.
به طور خلاصه، داکر با ارائه یک محیط ایزوله، قابل حمل و یکسان، پیچیدگیهای مدیریت وابستگیها و استقرار برنامهها، به ویژه سیستمهای چندجزئی مانند سیستم چند عامله ما، را به شدت کاهش میدهد. در بخشهای بعدی خواهیم دید که چگونه از داکر کامپوز برای به هم پیوند دادن عاملهایمان استفاده میکنیم.
قبل از نوشتن حتی یک خط کد، ضروری است که نقشه کلی نحوه اتصال قطعات به یکدیگر را ترسیم کنید. سیستم کامل از چهار Agent تشکیل شده است که به صورت یک خط لوله ترتیبی مرتب شدهاند و همگی توسط Docker Compose هماهنگ میشوند. دادهها به ترتیب از طریق Agent Ingstor، Agent Summarizer، Agent Prioritizer و Agent Formatter جریان مییابند. هر Agent از یک حجم اشتراکی میخواند، ورودی خود را پردازش میکند، نتیجه را مینویسد و خارج میشود.
این یک خط لوله همگام است: Agentها یکی پس از دیگری و به دنبال هم اجرا میشوند. این سادهترین الگوی چند عاملی برای پیادهسازی و درک است. از نظر مسئولیتها، Ingstor فایلهای خام را خوانده و ترکیب میکند، Summarizer نقاط کلیدی را استخراج میکند، Prioritizer موارد را بر اساس کلمات کلیدی فوریت امتیازدهی میکند و Formatter گزارش نهایی Markdown را تولید میکند. توجه کنید که تنها یکی از این چهار Agent واقعاً یک LLM را فرا میخواند. این طراحی عمدی است.
هر Agent در دایرکتوری خود با کد، Dockerfile و فایل requirements مختص به خود زندگی میکند. این جداسازی به این معنی است که میتوانید هر Agent را به طور مستقل بسازید، آزمایش کنید و به روز کنید. ساختار پروژه به این شکل خواهد بود که یک پوشه اصلی شامل پوشههای agents/ingestor، agents/summarizer، agents/prioritizer و agents/formatter میشود.
هر Agent از یک الگوی ساده یکسان پیروری میکند: خواندن یک فایل ورودی از حجم اشتراکی، انجام کار خود و نوشتن یک فایل خروجی. این یکنواختی، درک و توسعه سیستم را آسان میکند. به عنوان مثال، کد اصلی عامل Ingstor بسیار ساده است و تنها با استفاده از ماژولهای کتابخانه استاندارد پایتون، تمام فایلهای متنی را از پوشه ورودی خوانده و در یک فایل واحد ترکیب میکند. در مقابل، عامل Summarizer پیچیدهترین Agent است که از OpenAI SDK برای فراخوانی یک API LLM جهت تولید خلاصه استفاده میکند.
برای هر Agent یک Dockerfile ایجاد میکنید. این فایل دستورالعملهایی برای ساخت یک Image داکر شامل کد، وابستگیها و یک سیستم عامل حداقلی ارائه میدهد. به عنوان مثال، Dockerfile برای Agent Ingstor با یک تصویر پایه سبک پایتون شروع میشود، وابستگیها را نصب میکند و کد برنامه را کپی میکند. پس از ساخت Imageهای جداگانه برای هر Agent، از Docker Compose برای مرتبط کردن آنها استفاده میکنید.
فایل docker-compose.yml تمام کانتینرها را تعریف میکند، حجمهای اشتراکی را مونت میکند، متغیرهای محیطی را منتقل میکند و ترتیب اجرای صحیح را اعمال میکند. کلید این خط لوله ترتیبی، استفاده از تنظیمات وابستگی با شرط تکمیل موفقیتآمیز سرویس است. این تنظیم به داکر میگوید تا قبل از شروع بعدی، صبر کند تا کانتینر قبلی با کد خروجی صفر خارج شود. با اجرای دستور docker compose up --build، کل خط لوله به صورت خودکار و متوالی اجرا میشود.
در طول فرآیند ساخت و اجرا، ممکن است با چالشهایی مواجه شوید. یکی از خطاهای رایج، خروج کانتینر به دلیل خطای حافظه (OOM) است که معمولاً به دلیل پردازش فایلهای بزرگ توسط LLM رخ میدهد و با افزایش محدودیت حافظه در docker-compose.yml قابل رفع است. خطای دیگر "اجازه دسترسی رد شد" روی پوشه خروجی است که ممکن است به دلیل عدم تطابق مجوزهای volume mount رخ دهد.
خطاهای محدودیت نرخ از سمت OpenAI معمولاً توسط منطق تلاش مجازی که در کد Summarizer تعبیه شده است، مدیریت میشوند. همچنین اگر Agent Summarizer نتواند کلید API را پیدا کند، باید بررسی کنید که فایل .env در دایرکتوری صحیح قرار گرفته باشد. برای اطمینان از صحت عملکرد هر جزء قبل از کانتینریسازی، نوشتن تستهای واحد برای منطق اصلی هر Agent بسیار توصیه میشود.
مراحل ساخت این سیستم چند عاملی به طور خلاصه به این صورت است: ابتدا معماری و جریان داده ترسیم میشود. سپس ساختار پروژه ایجاد و کد هر یک از چهار Agent (Ingestor، Summarizer، Prioritizer، Formatter) به صورت مجزا نوشته میشود. در مرحله بعد، برای هر Agent یک Dockerfile ایجاد شده و Image مربوطه ساخته میشود. در نهایت، با استفاده از Docker Compose تمام کانتینرها به هم متصل و خط لوله با یک دستور واحد اجرا میگردد. این رویکرد مبتنی بر تفکیک وظایف و کانتینریسازی، سیستمی modular، قابل آزمایش و قابل اعتماد ایجاد میکند.
عامل Ingestor نقطه شروع خط لوله پردازش است. وظیفه اصلی آن خواندن تمام فایلهای متنی از پوشه ورودی و ترکیب آنها در یک فایل واحد است. این سادهترین عامل سیستم است که بدون نیاز به کتابخانههای خارجی یا فراخوانی API کار میکند. کد این عامل از ماژولهای استاندارد پایتون مانند os و logging استفاده میکند. تابع sorted(os.listdir()) تضمین میکند که فایلها به ترتیب الفبایی پردازش شوند و از وابستگی به سیستم فایل جلوگیری میکند. بلوک try/around هر خواندن فایل باعث میشود که یک فایل خراب کل خط لیره را متوقف نکند. اگر هیچ فایلی یافت نشود، عامل یک فایل خروجی خالی مینویسد تا عوامل بعدی بتوانند ورودی خالی را به طور مناسب پردازش کنند.
عامل Summarizer پیچیدهترین بخش سیستم است که از API هوش مصنوعی برای تولید خلاصه مختصر استفاده میکند. این تنها عاملی است که تماس شبکهای برقرار میکند و بنابراین در معرض خطاهای خارجی مانند قطعی API یا محدودیت نرخ قرار دارد. کلاینت OpenAI() به طور خودکار متغیر محیطی OPENAI_API_KEY را میخواند. برش text[:8000] مقدار متنی ارسالی به API را محدود میکند تا هزینه کاهش یابد و سرعت افزایش پیدا کند. مقدار temperature=0.3 خروجی را متمرکز و قطعی میکند که برای خلاصهسازی ایدهآل است. منطق بازخوانی خطاها به طور خاص RateLimitError را مدیریت میکند و با انتظار طولانیتر در هر بار (۵، ۱۰ و سپس ۱۵ ثانیه) از افزایش تصاعدی استفاده میکند.
عامل Prioritizer خلاصه تولید شده توسط LLM را گرفته و هر خط را بر اساس کلیدواژههای فوریت امتیازدهی میکند. این یک عامل مبتنی بر قاعده است که نیازی به فراخوانی LLM ندارد و بنابراین سریع، قطعی و رایگان عمل میکند. تابع امتیازدهی تعداد کلیدواژههای اولویتدار موجود در هر خط را میشمارد. خطوط امتیازدار به ترتیب نزولی مرتب میشوند تا موارد فوریتر ابتدا نمایش داده شوند. هر خط با امتیاز خود در پرانتز پیشوندگذاری میشود. در سیستمهای پیشرفتهتر میتوان این امتیازدهی کلیدواژهای را با یک رتبهبند مبتنی بر LLM جایگزین کرد، اما برای خلاصه روزانه، تطابق ساده کلیدواژه عملکرد قابل قبولی دارد.
عامل Formatter آخرین حلقه سیستم است که خطوط امتیازدار را خوانده و یک سند Markdown تمیز در دایرکتوری خروجی تولید میکند. توجه کنید که این عامل به /output مینویسد نه /data. این یک mount volume جداگانه در Docker Compose است. حجم /data برای ارتباط داخلی عوامل استفاده میشود، در حالی که حجم /output به پوشهای روی ماشین میزبان نگاشت میشود تا نتیجه نهایی قابل دسترسی باشد. متد split('] ', 1) با maxsplit=1 تضمین میکند که کاراکترهای براکت داخل محتوای واقعی باعث شکست تجزیه نشوند.
در این راهنما، یک سیستم چندعامله هوش مصنوعی از پایه ساخته شد. چهار عامل تخصصی پایتون ایجاد شده، هر یک با داکر کانتینری شده و با Docker Compose اورکستر شدهاند. الگوهای اصلی آموخته شده - شامل جداسازی مسئولیتها، عوامل کانتینری شده، ارتباط از طریق volume مشترک و کدنویسی دفاعی در برابر APIهای خارجی - فراتر از این مورد استفاده خاص قابل اعمال هستند. هر زمان که به یک گردش کار هوش مصنوعی قابل اعتماد، ماژولار و قابل تکثیر نیاز داشته باشید، این الگوها پایه محکمی فراهم میکنند. برای توسعه بیشتر، میتوانید از چارچوبهای همکاری عامل مانند CrewAI استفاده کنید، با مدلهای محلی آزمایش کنید یا معماریهای مبتنی بر رویداد را پیادهسازی نمایید.