داستان هوشیار: پایه‌ریزی AI با Open WebUI در همکاران سیستم

طی چند سال اخیر هوش مصنوعی تونسته به یکی از ابزارهای اصلی در چرخه توسعه نرم‌افزار تبدیل بشه. تو این بخش می‌خوایم تعریف کنیم که این موج تو سازمان ما چطور شروع شد، ما چطور بهش جواب دادیم، و در نهایت چی شد که به سمت ارائه سرویس‌هایی به تیم‌های همکاران سیستم رفتیم که اسمش رو «هوشیار» گذاشتیم.

سرویس داخلی «هوشیار» اواسط سال ۱۴۰۴ با ارائه یک نسخه از Open WebUI شروع شد، اما به مرور زمان به توسعه ابزارهای دیگه‌ای تحت همین عنوان مثل «هوشیار جلسات»، «هوشیار اکسل» یا «هوشیار موزیک» هم منجر شد. توی این بلاگ پست می‌خوایم اختصاصی در مورد سرویس Open WebUI صحبت کنیم، و در پست‌های آینده به بقیه سرویس‌هایی که لانچ کردیم یا از ابتدا نوشتیم هم اشاره می‌کنیم.

هوش مصنوعی در سازمان‌ها

امروز تقریبا هیچ سازمانی نیست که سراغ هوش مصنوعی نرفته باشه یا حداقل بهش فکر نکرده باشه. دلیلش هم ساده‌ست: هوشواره‌ها میتونن با بررسی کردن کد کیفیت اونها رو افزایش بدن، کارها و نوشتن کدهای تکراری رو خودکار کنن، روی کارهای انجام شده تحلیل ارائه بدن و توی تصمیم‌گیری‌ها به ما کمک کنن. اما همین موج به همون اندازه که فرصت می‌سازه، چالش هم با خودش میاره.

مهم‌ترین‌شون از نگاه ما دوتا بود: حاکمیت (Governance) و یکپارچگی داده. وقتی کل شرکت شروع می‌کنه به فرستادن سوال و کد و سند به یه مدل زبانی، این سوال‌ها پیش میاد که داده کجا می‌ره، کی به چی دسترسی داره، و اصلا چطور میشه جلوی هرج‌ومرج رو گرفت. کنارش هم بحث مهارت (چطور از هوش مصنوعی استفاده کنیم) و یکپارچگی (چطور مدل‌ها رو به سامانه‌های داخلی، اسناد و دیتامون وصل کنیم) مطرحه.

معرفی تیم AID

تیم ما، AID (مخفف AI Development Empowerment که به اسم «توانش هوشمند» هم معروفه)، دقیقا جواب همکاران سیستم به همین موجیه که بالا گفتیم. یکی از اولین کارهایی که تیم ما بعد از تشکیلش انجام داد بررسی مجدد فرآیندها و چرخه توسعه نرم‌افزار بود. ما باید مطمئن می‌شدیم که با وجود تغییراتی که داره اتفاق میفته، فرآیندهای تولید نرم‌افزارمون هنوز منطقی هستن. شروع کردیم به پاسخ به این سوال که چطور با مطالعه وظایف تیم‌ها، می‌تونیم ورودی و خروجی‌ها و کارهایی که زیاد ازشون وقت می‌گیرن رو با استفاده از هوشواره‌ها حذف یا شتابدهی کنیم.

البته که ما درس‌آموزی‌های زیادی از مقالات خارجی داشتیم، اما چیزهایی که تو این مجموعه بلاگ پست‌ها تعریف می‌کنیم، تجربه‌ی دست‌اولِ همین تیمه؛ چیزهایی که خودمون راه انداختیم، به مشکل خوردیم و حلشون کردیم؛ نه جمع‌بندی مقاله‌های دیگه.

توانمندسازی تیم‌های توسعه و محصول با هوش مصنوعی

در همون هفته اول تشکیل تیم به این نتیجه رسیدیم برای اینکه این کار رو در سطح کل شرکت انجام بدیم، به یک پلتفرم چت متمرکز نیاز داریم. جایی که بتونیم با مدل‌های مختلف صحبت کنیم، با دیتا و سامانه‌های موجود در سازمان یکپارچش کنیم، و همچنین پرامپت و مدل‌های شخصی‌سازی شده رو تعریف و ارزیابی کنیم.

با وجود اینکه هوش مصنوعی سرعت توسعه ما رو خیلی بیشتر کرده و یک سامانه که قبلا چندین ماه طول می‌کشید رو می‌تونیم در عرض چند روز راه‌اندازی کنیم، قاعده ما هنوز اینه که راه‌حل های متن‌باز رو به اختراع دوباره چرخ ترجیح می‌دیم. ما فقط جاهایی سراغ ایجاد یک راه‌حل جدید رفتیم که راه‌حل‌های قبلی خیلی متناسب با کار ما نبودن. شتابدهی و افزایش کیفیت کارهای تولید و محصول نه فقط به ابزار، بلکه به آموزش هم نیاز داره. معمولا منابع آموزشی برای یک ابزار متن‌باز مورد قبول در جامعه نرم‌افزار، بیشتر از منابعی هستن که ما برای ابزارهای اختصاصی می‌سازیم.

جواب این سوال ما رو رسوند به Open WebUI که تو بخش بعدی مفصل ازش می‌گیم.

Open WebUI چیه (و یکپارچگی با OpenRouter و ابرآمد)

Open WebUI یه پلتفرم چت متن‌باز برای مدل‌های زبانی هست که قابلیت‌های مهمی مثل قابلیت اتصال به چندین ارائه دهنده، مدیریت کاربران و دایرکتوری در سازمان‌های بزرگ، جستجوی اسناد، ترمینال، پشتیبانی از ابزار و سرورهای MCP و ... داره.

با توجه به معیارهایی مثل هزینه، انعطاف توی انتخاب مدل، کنترل داده و قابلیت توسعه پذیری، Open WebUI بهترین گزینه برای ما بود و با کمک پروایدرهای هوش مصنوعی مثل ابرآمد، OpenRouter، و Z.AI Coding Plan تونستیم از همون شروع کار به بیشتر از ۳۴۰ مدل ابری دسترسی پیدا کنیم و این مدل‌ها رو در اختیار افراد در تیم‌های مختلف قرار بدیم.

به‌جای اینکه به هر ارائه‌دهنده جدا وصل بشیم، از OpenRouter به‌عنوان یه دروازه‌ی واحد استفاده کردیم که از اون طریق به کلی مدل مختلف دسترسی داریم. در کنار اون از مدل‌هایی که ابرآمد روی زیرساخت خودش اجرا می‌کنه و در اختیار ما قرار داده هم استفاده کردیم.

نکته‌ای که باید مد نظر داشته باشیم اینه که وقتی از OpenRouter استفاده می‌کنیم یعنی پرامپت‌ها از مرز شبکه‌ی ما خارج می‌شن و به دست ارائه دهنده‌های میانی و ثالث میرسن. این یه تنش واقعی در بحث کنترل داده‌ست که تو بخش ۱.۱ گفتیم. جلوتر میریم سراغش و می‌گیم که چطور مدیریتش کردیم.

راه‌اندازی در سطح شرکت و جا انداختنش

راه‌اندازی فنی یه چیزه، جا انداختنش تو کل شرکت کلا یه چیز دیگه‌ست. ما این کار رو در چند مرحله پیش بردیم:

·       سریع‌تر کردن کارهای تکراری: یکی از اولین کارهایی که انجام دادیم ساخت یک مدل به اسم Gherkin Generator بود که نوشتن سناریوهای تست رو برای تحلیلگرا راحت‌تر و سریع‌تر می‌کرد. تحلیلگرای ما یکی از اولین کاربران هوشیار بودن که بعد از اون توسعه‌دهنده‌ها هم شروع به استفاده از مدل‌های هوشیار کردن.

·       آنبوردینگ و آموزش: ما چندین جلسه کارگاه آموزشی تو حوزه‌های مختلف برگزار کردیم مثل ارائه «هوش مصنوعی در چرخه توسعه نرم‌افزار»، ارائه «مهندسی پرامپت و کانتکست»، ارائه «کدنویسی با ادیتورهای هوشمند»، و «آموزش توسعه Agentها از بنیان». این آموزش‌ها کمک خوبی به درک بهتر این ابزارها و فناوری‌ها کردن.

·       قهرمان‌های داخلی (Champions): ما توی هر تیم یک چمپیون تعریف کردیم. چمپیون‌ها به عنوان اولین رابط تیم ما با سایر تیم‌ها به هم‌تیمی‌هاشون کمک می‌کنن تا از جدیدترین ابزارها، تکنیک‌ها و بهترین شیوه‌های استفاده از هوش مصنوعی توی پروژه‌ها بهره‌مند بشن و سوالات و چالش‌های اولیه رو داخل تیم خودشون برطرف کنن. در واقع، Championها تسهیل‌گرانی هستن که فرآیند یادگیری و به‌کارگیری فناوری‌های هوشمند رو برای تیم خودشون هموار می‌کنن.

·       رشد استفاده: تو مدت خیلی سریعی از فقط چند کاربر به بیشتر از ۶۰۰ کاربر داخلی رسیدیم و تعداد توکن‌هایی که در هفته از طریق OpenRouter مصرف می‌کردیم، به چند میلیارد رسید. اگه بخوایم سایر ابزارهامون مثل Cursor، Claude، و GLM رو هم در نظر بگیریم، این عدد خیلی بزرگ‌تر هم می‌شه.

امنیت، حاکمیت و کنترل دسترسی

حالا میرسیم به همون چیزی که تو بخش ۱.۱ در موردش صحبت کردیم. وقتی یه ابزار AI رو در اختیار کل شرکت میذاری، اولین سوال جدی که برات پیش میاد اینه: کی به چی دسترسی داره و داده کجا میره؟ چطور جلوی Prompt Injection رو بگیریم؟

دسترسی: کی و چطور: Open WebUI از LDAP پشتیبانی می‌کنه. این یعنی کاربرا می‌تونن از همون اکانت شرکت‌شون برای ورود به هوشیار استفاده کنن و سطح دسترسی‌هاشون به مدل‌ها، پرامپت‌ها، و ابزارها مطابق تیم‌ها و واحدهاشون تعریف بشه.

وضعیت داده در دیپلوی Self-Hosted: از اونجایی که Open WebUI روی زیرساخت‌های خودمون هاست شده، تمامی داده‌ها روی PostgreSQL خودمون می‌مونه و یکپارچه‌سازی داده‌های تجمیعی از هوشیار با سایر دیتای سازمانی و داشبوردهای تحلیلی و همچنین یکپارچه‌سازی با داشبوردهای Apache DevLake به راحتی امکان‌پذیره.

یکپارچه‌سازی با OpenRouter: همون طور که گفتیم ما نه تنها با مدل‌های لوکال، بلکه به کمک OpenRouter از مدل‌های ابری هم استفاده می‌کنیم. اینجا باید تصمیم بگیریم که چه دیتایی رو برای مدل‌های بیرونی، و یا کدوم مدل‌ها میفرستیم. با کمک هوشیار می‌تونیم Functionهایی بنویسیم که ارسال یک سری داده خاص مثلا دیتاهای حساس رو قبل از ارسال به مدل‌های ابری متوقف کنیم. حتی در این سطح می‌تونیم دسترسی به مدل‌هایی که دارای Prompt Retention هستن و دیتای ارسال شده توسط کاربر رو ذخیره می‌کنن غیرفعال کنیم.

یکپارچه‌سازی Open WebUI با استک ما: MCP به‌عنوان ستون فقرات

میدونیم صرفا وصل بودن به مدل‌ها و یه صفحه چت که فقط بلد باشه حرف بزنه خیلی به درد کارهای واقعی شرکت نمیخوره. مهم اینه که مدل بتونه با کانتکست واقعی شرکت کار کنه و حتی اکشن واقعی بگیره: کد رو بخونه، تیکت جیرا رو ببینه، تو مستندات بگرده و یا حتی از طرف کاربر کاری انجام بده. توی نسخه‌های قبلی Open WebUI نیاز بود تا پارامتر Function Calling روی Native ست بشه تا به جای معرفی ابزارها در پرامپت کاربر، مستقیما از قابلیت Tools در API مدل‌های هوش مصنوعی استفاده کنیم. اما خوشبختانه این تنظیم در نسخه‌های جدیدتر Open WebUI روی همین حالت تنظیم شده و به‌طور خودکار همه مدل‌ها به‌صورت پیشفرض از حالت Agentic یا عاملی استفاده می‌کنن.

MCP چیه؟

قبل از اینکه بریم سراغ لیست سرورهایی که وصل کردیم، بد نیست خیلی خلاصه بگیم MCP اصلا چیه. MCP (Model Context Protocol) یه پروتکل باز و استانداردشده‌ست که راه ارتباط بین یه مدل زبانی و ابزارها، داده‌ها و سیستم‌های بیرونی رو یکسان می‌کنه. به زبون ساده، به‌جای اینکه برای هر سرویس (گیتلب، جیرا، کدبیس، …) یه راه اتصال جدا و مخصوص خودش بنویسیم، هرکدوم رو پشت یه «سرور MCP» میذاریم که چندتا ابزار (tool) استاندارد در اختیار مدل قرار میده؛ مدل هم بدون اینکه بدونه پشت پرده چه سرویسی هست، همون ابزارها رو صدا می‌زنه.

دلیل اینکه سراغش رفتیم هم دقیقا همینه: یک پروتکل، کلی کانکتور. اگه فردا یه سرویس جدید هم بخوایم به هوشیار وصل کنیم، کافیه یه سرور MCP براش پیدا کنیم یا بنویسیم؛ نه اینکه دوباره از صفر یکپارچگی طراحی کنیم.

سرورهای MCP که استفاده می‌کنیم

تو هوشیار چندتا سرور MCP رو وصل کردیم که هرکدوم یه بخش از استک شرکت رو در اختیار مدل میگذارن:

·       GitLab MCP: دسترسی به ریپازیتوری‌ها، مرج‌ریکوئست‌ها و پایپلاین‌های CI رو در اختیار مدل می‌گذاره. با این سرور، مدل میتونه کد رو بخونه، تاریخچه کامیت‌ها رو ببینه و حتی روی MRها کار کنه.

·       Atlassian MCP: پل ارتباطی بین هوشیار و Jira/Confluence. تحلیلگرا و توسعه‌دهنده‌ها از همین مسیر میتونن تیکت‌ها و صفحات مستندات رو بدون خارج شدن از هوشیار به مدل بدن. یکی از جاهایی که این سرور کم داشت و مجبور شدیم دست به کار بشیم رو جلوتر مفصل تعریف می‌کنیم.

·       Storybook MCP: دسترسی به کتابخانه‌ی کامپوننت‌ها و داکس دیزاین‌سیستم داخلی‌مون؛ برای اینکه مدل موقع کار روی فرانت‌اند بدونه چه کامپوننتی از قبل وجود داره و چطور باید استفاده بشه.

·       SG CocoIndex: این پروژه یک فورک از CocoIndex Code هست که ما در همکاران سیستم قابلیت‌های مختلفی مثل MCP سرور برای اتصال به هوشیار، جستجو در برنچ‌ها با کمک ابزار ripgrep، جستجوی معنایی با کمک Embedding Model ها و ایندکس کردن کدها با روش HNSW، سیستم مدیریت حافظه، ایندکس کردن کد به‌صورت خودکار و دوره‌ای، و همچنین ارسال متریک‌هایی مانند تعداد خطوط کد به تفکیک زبان روی داشبورد Apache DevLake رو براش پیاده سازی کردیم. این پروژه در گیت‌هاب ما به‌صورت متن‌باز قرار گرفته. جلوتر بیشتر در مورد این ابزار صحبت می‌کنیم.

علاوه بر این سه‌تا، چندتا سرور MCP دیگه هم داریم که چون هرکدوم داستان خودشون رو دارن، جای مفصل‌تری برای تعریف کردنشون گذاشتیم: Sub-Agent MCP برای واگذاری کار به ایجنت‌های دیگه، RAGFlow برای جستجو تو اسناد، و سرورهای MCP جستجوی سورس‌کد. نکته‌ی مشترک همه‌شون همونیه که در بخش قبلی گفتیم: مسیر وصل شدن همه یکیه، فقط چیزی که پشتش هست فرق می‌کنه.

چالش‌ها و راه‌حل‌ها

راه‌اندازی Open WebUI از نظر فنی کار خاصی نبود؛ با یه داکر کامپوز راه میفتاد و میتونستیم کلی امکانات رو در اختیار تیم‌های فنی قرار بدیم. اما این موضوع علاوه بر نیازمندی‌هایی که ایجاد کرد و در بخش قبلی راجع بهشون صحبت کردیم، چالش‌هایی هم با خودش به همراه داشت که در ادامه به اون‌ها می‌پردازیم.

محدودیت SQLite

وقتی که تصمیم به راه‌اندازی Open WebUI گرفتیم، از دیتابیس پیش‌فرض که SQLite بود استفاده کردیم چون فکر نمیکردیم که این سرویس تا این حد در سازمان محبوب بشه و این انتخاب مشکل‌ساز بشه. بعد از مدت کوتاهی میزان استفاده از این سرویس که الان همه با اسم هوشیار میشناسنش به شدت زیاد شد. هرچه تعداد چت‌ها، مدل‌های کاستوم و دیتای آنالیتیکال بیشتر میشد، سرویس هم کندتر میشد؛ تا حدی که این کندی برای همه ملموس شده بود. علاوه بر این گاهی اوقات چت‌های طولانی دیگه باز نمیشدن و افراد عملا دسترسی‌شون به اون چت رو از دست می‌دادن.

همه اینها باعث شد تصمیم بگیریم از SQLite به Postgres مهاجرت کنیم. اما یه مشکل خیلی بزرگ وجود داشت و اون هم این بود که Open WebUI به‌طور رسمی مهاجرت به Postgres رو پشتیبانی نمیکرد و هیچ ابزار متن‌باز کارآمدی وجود نداشت که بتونه این کار رو برای ما انجام بده. همین باعث شد خودمون دست به کار بشیم و این ابزار رو از اول بنویسیم. با کمک Claude Code توسعه رو شروع کردیم و بعد از چند بار تست روی بکاپ‌هایی که داشتیم سراغ محیط پروداکشن رفتیم و موفق شدیم تمام داده رو به‌طور کامل و بدون از دست رفتن حتی یک ردیف به Postgres منتقل کنیم.

در نهایت این ابزار رو به‌صورت متن‌باز روی گیت‌هاب گذاشتیم و کامیونیتی هم ازش استفاده کرد.

نبود Sub-Agent در Open WebUI

با گذشت زمان و جا افتادن هوشیار در فرآیندهای سازمان، تیم‌های مختلف از جمله تیم‌های تولید نرم‌افزار و تیم محصول از ما میخواستن که مدل‌های کاستومی براشون بسازیم تا بتونه بخشی از فرآیند کارشون رو انجام بده. کارهایی مثل نوشتن سناریوهای BDD یا تحقیق درمورد روش‌های حل نیازمندی مشتری. زمانی که کارکرد این مدل‌ها پیچیده‌تر شد با مشکل پر شدن کانتکست مدل زبانی مواجه شدیم. بعضی اوقات کارکرد یه مدل شامل چند بخش میشد و مدل آروم آروم فراموش میکرد که ابتدای فرآیند چه اتفاقی افتاده و گاهی اوقات کانتکست مدل به‌طور کامل پر میشد و دیگه نمی‌تونست به ما خروجی بده. راه‌حل ما برای حل این موضوع این بود که از مفهومی به اسم Sub-Agent استفاده بکنیم تا بتونیم فرآیند رو به چندتا تسک کوچک‌تر بشکونیم و به Agent‌های مختلف بسپاریم. اما متوجه شدیم که متاسفانه Open WebUI از این قابلیت پشتیبانی نمی‌کنه و تصمیم گرفتیم خودمون دست به کار بشیم و قابلیت Sub-Agent رو به‌صورت یک MCP به سرویس هوشیار اضافه کنیم.

طراحی‌ای که برای این کار انتخاب کردیم ساده بود: یه سرور نوشتیم که توش هر Sub-Agent رو داخل یه فایل کانفیگ تعریف می‌کنیم و برای هرکدوم مدل، system prompt و ابزارهای مخصوص خودش رو مشخص می‌کنیم. وقتی سرور بالا میاد، هر ایجنتی که تعریف کردیم به‌صورت یه ابزار جداگانه در اختیار مدل اصلی هوشیار قرار میگیره؛ و هر وقت مدل اصلی بخواد بخشی از کار رو واگذار کنه، کافیه همون ابزار رو صدا بزنه و صورت‌مسئله رو بهش بده.

پشت اون ابزار، Sub-Agent با مدل و ابزارهای خودش به‌طور مستقل کار می‌کنه و در نهایت فقط نتیجه‌ی نهایی رو به مدل اصلی برمیگردونه. زیبایی ماجرا دقیقا همینجاست: مدل اصلی دیگه لازم نیست تمام جزئیات و ابزارهای اون Sub-Agent رو توی کانتکست خودش نگه داره. هر Sub-Agent توی کانتکست جدای خودش کار می‌کنه، بخش خودش رو تموم می‌کنه و یه خروجی تمیز تحویل میده. به این شکل کانتکست مدل اصلی سبک میمونه و دیگه با همون مشکل پر شدن کانتکست که ازش گفتیم مواجه نمی‌شیم.

یه مزیت دیگه هم داشت: چون هر ایجنت مدل، پرامپت و ابزارهای خودش رو داره، می‌تونیم به هر نقش دقیقا همون چیزی رو بدیم که لازم داره. مثلا به یه ایجنت پژوهشگر ابزار جستجو میدیم و به یه ایجنت نویسنده اصلا ابزاری نمیدیم. اینطوری نقش‌ها هم شفاف و مشخص باقی می‌مونن.

این ابزار رو هم مثل ابزار مهاجرت خودمون از صفر توسعه دادیم و سعی کردیم واقعا آماده‌ی استفاده در پروداکشن باشه، نه یه اسباب‌بازی. بعد هم به‌صورت متن‌باز روی گیت‌هاب قرارش دادیم تا هر کسی که به چنین چیزی نیاز داشت بتونه ازش استفاده کنه.

Atlassian MCP کم داشت: مشارکت در پروژه متن‌باز

همون‌طور که قبل‌تر گفتیم، یکی از اولین مدل‌هایی که برای تحلیلگرا ساختیم Gherkin Generator بود؛ مدلی که با گرفتن سند نیازمندی از Confluence، سناریوهای BDD رو براشون تولید میکرد. اما تحلیلگرا معمولا کارشون فقط نوشتن سناریو برای یه سند تازه نبود؛ خیلی وقت‌ها یه سند نیازمندی که از قبل وجود داشت آپدیت میشد، و تحلیلگر نیاز داشت سناریوهای Gherkin رو هم متناسب با همون تغییرات به‌روز کنه؛ نه از صفر بنویسه.

برای اینکه مدل بتونه دقیقا بفهمه بین دو نسخه از سند چی تغییر کرده، نیاز داشتیم هم محتوای نسخه‌ی قبلی صفحه‌ی Confluence رو در اختیارش بگذاریم و هم محتوای نسخه‌ی فعلی رو، تا مدل خودش تغییرات رو تشخیص بده و سناریوهای Gherkin رو متناسب باهاش آپدیت کنه. اینجا بود که به یه محدودیت خوردیم: سرور MCP که برای Atlassian استفاده میکردیم، یعنی پروژه‌ی متن‌باز mcp-atlassian، فقط میتونست آخرین نسخه‌ی یه صفحه‌ی Confluence رو برگردونه. هیچ ابزاری برای گرفتن یه نسخه‌ی تاریخی و مشخص از صفحه وجود نداشت، و بدون اون نمیتونستیم محتوای نسخه‌ی قبلی رو به مدل بدیم.

چون این قابلیت هیچ‌جای دیگه‌ای هم متن‌باز پیاده نشده بود، تصمیم گرفتیم به‌جای دور زدن مشکل، خودمون این قابلیت رو بسازیم و به‌صورت upstream به همون پروژه مشارکت بدیم. حاصلش شد ابزار جدیدی به اسم get_page_history که با گرفتن شناسه‌ی صفحه و شماره‌ی نسخه، محتوای همون نسخه‌ی تاریخی رو برمیگردونه؛ چه برای احراز هویت با توکن/basic auth (از طریق API نسخه ۱ کانفلوئنس) و چه برای OAuth (از طریق API نسخه ۲). این تغییر رو در قالب PR شماره ۹۰۵ فرستادیم و بعد از ریویو توسط نگهدارنده‌ی پروژه، merge شد.

بعد از merge شدن این PR، Gherkin Generator ما میتونه از همون مسیر همیشگی MCP، هم نسخه‌ی قبلی و هم نسخه‌ی فعلی یه صفحه‌ی Confluence رو بگیره، تغییرات بین‌شون رو تشخیص بده، و سناریوهای Gherkin رو دقیقا متناسب با همون تغییرات به‌روز کنه، نه اینکه کل سند رو از اول بازنویسی کنه.

Takeaway: وقتی یه سرور MCP متن‌باز یه قابلیت رو کم داره، همیشه لازم نیست دور بزنیمش یا فورکش کنیم؛ گاهی سریع‌ترین و پایدارترین راه این‌ه که خودمون اون قابلیت رو بسازیم و به آپ‌استریم پروژه برگردونیم؛ هم ما ازش استفاده می‌کنیم، هم کل کامیونیتی.

نبود پشتیبانی از Standard Input and Output (stdio)

یکی دیگه از چالش‌هایی که برای ما بوجود اومد این بود که پشتیبانی Open WebUI از MCP طوری بود که از stdio پشتیبانی نمیکرد و فقط transportهای مبتنی بر HTTP رو قبول میکرد، ولی یکسری از MCPهایی که بهشون نیاز داشتیم فقط به‌صورت stdio قابل استفاده بودن. راه‌حل این مشکل اما بسیار ساده بود. یک ابزار متن‌باز با اسم MCP Proxy بود که میتونست MCPها رو با هر transportی که داشتن بگیره و به‌صورت HTTP در اختیارمون بذاره. حالا فقط کافی بود تا یک Dockerfile بسازیم و بعد از کانفیگ کردن اون MCP با این کامند به‌صورت Streamable HTTP اون رو serve کنیم و ازش توی هوشیار استفاده کنیم:

mcp-proxy --host=0.0.0.0 --port=8080 npx something-mcp

ضعف جستجوی اسناد

یکی از نیازمندی‌هایی که بعد از مدتی برای تیم‌های مختلف پیش اومد این بود که کاربر همیشه یه لینک Confluence مشخص نداشت که هوشیار با MCP مستقیم بگیردش. بعضی وقتا لازم بود کاربر بین اسناد مختلف جستجو کنه، یا سندی که باهاش کار داشت اونقدر بزرگ بود که کل متنش توی کانتکست مدل جا نمیشد و باید فقط بخشی ازش استفاده میشد. جالب اینکه این مورد دوم هم دوباره همون پر شدن کانتکست بود که سر Sub-Agent باهاش درگیر بودیم، منتها این بار به‌خاطر حجم خود سند بود نه طولانی شدن مراحل کار. همه اینا مشخصا به یک راه‌حل اشاره می‌کنن و اونم استفاده از Knowledge Base در Open WebUI هست. بعد از اینکه شروع به استفاده از این قابلیت در هوشیار کردیم، متوجه شدیم که قابلیت جستجو اصلا با کیفیت مدنظر ما کار نمی‌کنه و خیلی وقتا اطلاعات بی‌ربط پیدا می‌کنه و اطلاعات مهم رو نادیده میگیره. بعد از بررسی تنظیمات متوجه شدیم قابلیتی در knowledge baseها هست به اسم full context mode که کل سند رو در اختیار مدل قرار میده. این گزینه مشکل پیدا نشدن نکات رو حل میکرد، اما برای مشکل دیگه، یعنی پر شدن کانتکست مدل برای اسناد طولانی، راه‌حلی نبود. این موضوع باعث شد به فکر یه راه‌حل بهتر برای این مسئله باشیم و همونطور که احتمالا تا الان حدس زدید، تصمیم به راه‌اندازی یک سرویس RAG گرفتیم.

حالا که تصمیم به استفاده از RAG گرفته شده بود، باید گزینه‌های موجود رو بررسی میکردیم تا یک پروژه‌ی متن‌باز پیدا کنیم که بتونه نیازمندی‌های ما رو برطرف کنه ولی اونقدری سنگین نباشه که از پس نگهداری و فراهم کردن resourceهاش برنیایم. همینطور این ابزار باید قابلیت integrate شدن با data sourceهایی مثل Confluence رو میداشت. در نهایت بعد از جستجوهای فراوان به RAGFlow رسیدیم.

پروژه RAGFlow یک ابزار متن‌باز هست که اسناد رو با کیفیت خیلی بهتری پردازش و ایندکس می‌کنه و موقع جستجو فقط بخش‌های مرتبط رو برمیگردونه، ضمن اینکه امکان integrate شدن با منابعی مثل Confluence رو هم داره. بعد از راه‌اندازیش، از طریق MCP به هوشیار وصلش کردیم تا کاربرها بتونن بین اسنادشون جستجو کنن و بدون اینکه کانتکست مدل پر بشه، فقط بخش‌های مرتبط در اختیارش قرار بگیره.

جستجوی سورس‌کد

یکی از پرتقاضاترین چیزهایی که تیم‌های تولید نرم‌افزار ازمون میخواستن این بود که هوشیار بتونه تو خودِ کدبیس بگرده و جواب بده، نه فقط تو مستندات. مثلا تستر تیم میخواد بدون اینکه نیاز باشه Cursor یا Claude رو باز کنه روی محیط هوشیار در مورد یک موضوعی از کدبیس سوال بپرسه یا حتی از روی کدبیس تست‌های خوب بسازه. این موضوع در ظاهر ساده بود ولی مسیرش برای ما پر از فراز و نشیب شد، مخصوصا سر فضای دیسک و سرعت جستجو. تو این بخش سفرِ ایندکس کردن کد و کارِ روی پرفورمنسش رو تعریف می‌کنیم که در نهایت از طریق MCP به هوشیار وصل شد.

ChunkHound + DuckDB: مشکل فضای دیسک و پایداری

برای اولین نسخه‌ی جستجوی کد رفتیم سراغ ChunkHound که یک نرم‌افزار متن‌بازه، و MCP server داخلی خودش رو داره. این پروژه برای ذخیره بردارهای Embedding از DuckDB استفاده می‌کنه. همه‌چیز خوب پیش رفت تا وقتی خواستیم کل کدبیس رو ایندکس کنیم و دیدیم که ایندکس داره نزدیک به ۱ ترابایت دیسک میخوره.

این موضوع مسئله خیلی مهمیه برای اینکه Embedding کدهای ما در حالت بهینش حتی نباید یک درصد این مقدار هم جا نیاز داشته باشه. اگرچه خیلی بعدتر تیم ChunkHound این مسئله رو حل کردن، اما مشکلات دیگه ChunkHound مثل عدم پایداری قابلیت‌هاش در نهایت باعث شد که ما بذاریمش کنار و به فکر راه‌حل دیگه‌ای باشیم. به عنوان مثال توی نسخه ۵ این سرویس یک سری قابلیت‌هایی که برای ما مهم بودن مثل Streamable HTTP که مهم‌ترین روش ارتباطی با هوشیار بود، و یا ایندکس و جستجو کردن فایل‌هایی که برای ChunkHound ناشناخته بودن مثل Feature فایل‌های گرکین که ما خیلی برای تحلیل‌هامون ازشون استفاده می‌کنیم کاملا از جستجوهاش حذف شده بودن.

فورک کردن CocoIndex و افزودن قابلیت‌هایی مثل HNSW

با اون مشکل دیسک، رفتیم دنبال یه جایگزین و رسیدیم به CocoIndex. راه‌حلی که CocoIndex به اسم CocoIndex Code داشت به عنوان یک ابزار CLI که برنامه‌نویس‌ها رو سیستم‌شون نصبش کنن راه‌حل خوبی بود، اما به عنوان یک سرویس با پروسس گرم که بتونیم به هوشیار وصلش کنیم کارایی نداشت. سرعت جستجو بسیار پایین بود، و هر کسی باید برای خودش رو سیستم یک دیتابیس ایندکس جدا نگهداری می‌کرد. این موضوع باعث شد که ما به کمک CocoIndex و ایده‌هاش، سرویس خودمون رو با یک دیتابیس دیگه به اسم LanceDB که از جستجوی سریع HNSW پشتیبانی می‌کرد توسعه بدیم.

خروجی کار در نهایت به‌صورت متن‌باز روی گیت‌هاب منتشر شد. این پروژه تحت عنوان SG CocoIndex قابلیت‌های زیر رو داره:

  • یک سرویس گرم و همیشه در حال اجرا که ایندکس‌ها رو آپدیت نگه میداره و MCP سرور با انتقال HTTP همواره در دسترسه.

  • قابلیت جستجو نه فقط روی برنچ اصلی رو داره بلکه میتونه روی هر برنچی جستجو انجام بده.

  • با Git یکپارچه هست و به‌صورت دوره‌ای تغییرات رو میگیره و ایندکس می‌کنه.

  • دیگه مشکل حافظه‌ای که رو ChunkHound باهاش دست و پنجه نرم می‌کردیم رو نداره و به اندازه معقولی فضا می‌گیره.

  • وقتی روی کانتینرها اجرا می‌شه به منابع روی کانتینر توجه می‌کنه و به همون اندازه از حافظه استفاده می‌کنه.

  • یک سری سنجه‌های مختلف مثل تعداد خطوط کد به تفکیک زبان‌های برنامه‌نویسی رو روی داشبورد Apache DevLake ما منتشر می‌کنه.

تأثیر و میزان استفاده در قالب اعداد

حالا که سفر رو تعریف کردیم، بذارید با عدد نشون بدیم که هوشیار به عنوان یک سرویس داخلی واقعا چقدر استفاده میشه. همه این دیتا از آنالیتیکس خودِ Open WebUI تا لحظه‌ای که این بلاگ پست رو نوشتیم اومده:

  • تعداد کاربرها: بیشتر از ۶۰۰ نفر کاربر داخلی فعال

  • کل توکن مصرف‌شده: متوسط ۵ میلیارد توکن در هفته

  • مدل‌های کاستوم ساخته‌شده توسط کاربرها: بیش از ۱۱۰ مدل کاستوم ساخته شده برپایه سایر مدل‌های محلی و ابری

  • یه خروجی واقعی (outcome): افزایش سرعت و کیفیت کارها از ۳۰ درصد تا بیشتر از ۱۰ برابر

جمع‌بندی و قدم‌های بعدی

اگه بخوایم کل این مسیر رو تو یه جمله جمع کنیم: Open WebUI تو چند روز یه رابط چت AI برای کل شرکت بهمون داد، ولی تبدیلش به چیزی که واقعا با استک ما کار کنه، یعنی برخورد با یه سری چالش و وصله‌کردن خلأها با دست خودمون؛ و ستون فقرات همه‌ این یکپارچگی‌ها MCP بود.

چیزهایی که متن‌باز کردیم:

و یادمون نره که کارِ HNSW روی CocoIndex فعلا یه فورک داخلیه و هنوز Upstream نشده.