پیاده‌سازی RAG در مقیاس تولید: درس‌هایی از پردازش روزانه بیش از ۱۰,۰۰۰ آگهی

من یک خط لوله (pipeline) RAG برای یک سایت کاریابی ساختم. این سیستم در محیط استیجینگ (staging) خوب کار می‌کرد، اما در مواجهه با بار واقعی (real load) با مشکل روبرو شد. پردازش روزانه هزاران آگهی، چیزی فراتر از داشتن یک ذخیره‌ساز برداری (vector store) خوب نیاز دارد. شما باید بدانید سیستم دقیقاً کجا از کار می‌افتد.

در اینجا درس‌هایی که در مورد تکه‌بندی (chunking)، جاسازی‌ها (embeddings)، هزینه‌ها و قابلیت مشاهده‌پذیری (observability) آموختم، آورده شده است.

۱. استراتژی تکه‌بندی خود را حدس نزنید

بیشتر آموزش‌ها با تکه‌بندی به عنوان یک تنظیم ساده برخورد می‌کنند. در محیط تولید، استراتژی شما تعیین‌کننده دقت و هزینه است.

من سه روش را برای آگهی‌های شغلی تست کردم:

  • تکه‌های با اندازه ثابت (Fixed-size chunks): این روش شکست خورد. این روش بخش‌هایی مثل «نیازمندی‌ها» و «مزایا» را در نقاط تصادفی تقسیم می‌کرد که باعث ایجاد بازیابی نویزدار (noisy retrieval) می‌شد.
  • تکه‌بندی معنایی (Semantic chunking): این روش بهتر بود اما بی‌ثبات بود. برخی تکه‌ها خیلی طولانی و برخی دیگر خیلی کوتاه بودند.
  • تقسیم‌بندی بازگشتی کاراکترها با هم‌پوشانی (Recursive character splitting with overlap): این روش بهترین نتیجه را داشت. من بر اساس خطوط جدید و جملات تقسیم‌بندی کردم. از اندازه ۴۰۰ توکن با ۵۰ توکن هم‌پوشانی استفاده کردم. این کار تضمین می‌کند جملاتی که در دو تکه قرار می‌گیرند، متصل باقی بمانند.

نکته حرفه‌ای: قبل از تکه‌بندی، داده‌های خود را نرمال‌سازی کنید. منابع مختلف مانند Greenhouse یا Lever فرمت‌های متفاوتی برمی‌گردانند. ابتدا متن را پاکسازی کنید تا تکه‌کننده (chunker) شما با یک ساختار یکپارچه روبرو شود.

۲. جاسازی‌ها (Embeddings): هزینه در مقابل دقت

من مدل Llama 3.1 را از طریق Ollama در مقابل text-embedding-3-small شرکت OpenAI تست کردم. مدل محلی رایگان بود اما با اصطلاحات تخصصی حوزه مثل "equity compensation" مشکل داشت و نتایج نویزداری تولید می‌کرد. OpenAI هزینه بیشتری داشت اما تطبیق‌های دقیقی ارائه می‌داد. من OpenAI را انتخاب کردم زیرا بازیابی بد، در فراخوانی‌های بعدی LLM هزینه بیشتری خواهد داشت.

برای صرفه‌جویی در زمان، درخواست‌های خود را دسته‌بندی (batch) می‌کنم. من تا ۱۰۰ تکه را در یک فراخوانی ارسال می‌کنم. این کار تأخیر (latency) را کاهش داده و سرعت خط لوله را بالا نگه می‌دارد.

۳. موازنه در انتخاب ذخیره‌ساز برداری (Vector Store)

من برای نمونه‌سازی (prototyping) از Pinecone استفاده کردم چون راه‌اندازی سریعی دارد. با این حال، در مقیاس بالا، هزینه‌ها بسیار زیاد شد.

من به استفاده از pgvector در داخل PostgreSQL تغییر مسیر دادم.

  • راه‌اندازی آن کار بیشتری می‌طلبید.
  • مبالغ بسیار زیادی در هزینه‌ها صرفه‌جویی کرد.
  • ثبات تراکنشی (transactional consistency) را فراهم کرد. از آنجایی که جاسازی‌ها (embeddings) در همان پایگاه داده‌ای قرار دارند که داده‌های شغلی در آن هستند، شما یک منبع واحد از حقیقت (source of truth) خواهید داشت و نیازی به همگام‌سازی دو سیستم مختلف ندارید.

۴. کنترل هزینه‌های LLM

امتیازدهی به هر آگهی با GPT-4o گران است. من از سه تاکتیک برای کاهش هزینه‌ها استفاده کردم:

  • OpenAI Batch API: من کارهای امتیازدهی را در طول شب پردازش می‌کنم که تخفیف زیادی به همراه دارد.
  • حافظه پنهان (Caching): نتایج را برای پروفایل‌های تکراری کاندیداها کش می‌کنم.
  • لایه‌بندی مدل (Model tiering): من از GPT-4o-mini برای نقش‌های رایج مثل "Sales Representative" استفاده می‌کنم و فقط برای نقش‌های خاص که دقت در آن‌ها حیاتی است، از GPT-4o استفاده می‌کنم.

۵. اول قابلیت مشاهده‌پذیری (Observability) را بسازید

خط لوله من یک بار به صورت بی‌صدا (silent failure) از کار کرد. داده‌های بدشکل باعث ایجاد تکه‌های خالی شدند که سیستم بدون هیچ خطایی از آن‌ها عبور کرد.

من این مشکل را با اضافه کردن لاگ‌گذاری ساختاریافته (structured logging) همراه با یک شناسه همبستگی (correlation ID) حل کردم. این کار به من اجازه داد تا یک آگهی را از مرحله ورود داده (ingestion) تا امتیازدهی ردیابی کنم. در نهایت توانستم ببینم کدام منابع داده باعث بروز خطا می‌شوند.

بزرگترین درس: بیشتر مشکلات از داده‌های نامنظم ناشی می‌شوند، نه از هوش مصنوعی. ابتدا لوله‌کشی داده‌های خود را اصلاح کنید.

Source: https://dev.to/abdul___rehman/production-rag-at-scale-lessons-from-processing-10000-listings-daily-22gm

Optional learning community: https://t.me/GyaanSetuAi