پرش به محتوای اصلی
پرش به محتوای مقاله

درون خط لوله RAG؛ سازوکار تبدیل درخواست‌های متنی به نقشه‌ها

·۲ تیر ۱۴۰۵۸ دقیقه مطالعه
راهنما
ساخت عامل هوش مصنوعی محلی برای OSM: ۲۱ روز تکرار و بهبود
ساخت عامل هوش مصنوعی محلی برای OSM: ۲۱ روز تکرار و بهبود
اشتراک‌گذاری
واقعاً چه چیز جدید است؟

پیاده‌سازی یک حلقه بازخوردی (Feedback Loop) برای گسترش خودکار پرس‌وجو در صورت یافت نشدن نتیجه در داده‌های OSM؛ این یعنی گذار از یک مترجم ساده به یک عامل کاوشگر.

تصور کنید بخواهید تمام داده‌های جغرافیایی یک شهر را بدون پرداخت یک سنت به شرکت‌های ابری و با حفظ کامل حریم خصوصی استخراج کنید. برای این کار، تنها به یک کارت گرافیک معمولی با حافظه VRAM کمتر از ۸ گیگابایت نیاز دارید. این تمام چیزی است که برای اجرای یک عامل هوش مصنوعی کاملاً محلی، قادر به ترجمه زبان طبیعی به پرس‌وجوهای پیچیده جغرافیایی، مورد نیاز است.

این ادعای جان تشادا (Jan Tschada) است که فرآیند ۲۱ روزه تکرار و بهبود خود را برای ساخت یک عامل (Agent) — سیستمی که می‌تواند به‌طور مستقل ابزارها را مدیریت کند — مستند کرده است. هدف او پل زدن میان درخواست‌های مبهم کاربر و ساختارهای JSON دقیق در OpenStreetMap (OSM) بوده است. این تحلیل فنی عمیق، معماری، شکست‌ها و اصلاحاتی را بررسی می‌کند که در ساخت سیستمی به کار رفته که تماس‌های API، محدودیت‌های نرخ درخواست (Rate Limits) و نگرانی‌های مربوط به حریم خصوصی را کاملاً حذف می‌کند.

استخراج داده‌های جغرافیایی اغلب با مشکل «ظرافت معنایی» روبروست. برای مثال، اگر کاربر عبارت «مناطق محدود شده» را به کار ببرد، برچسب فنی صحیح کاملاً به بستر متن بستگی دارد: برای مناطق دریایی باید از seamark:type=restricted_area و برای مناطق خشکی از access=exclusion_zone استفاده شود. همان‌طور که در تحلیل قبلی ما درباره‌ی شکست‌های «کدنویسی بر اساس حس» (Vibe Coding) در عامل‌های محلی اشاره کردیم، این پروژه ثابت می‌کند که موفقیت در این حوزه نیازمند یک خط لوله ساختاریافته RAG است، نه تکیه بر شهود ساده‌ی مدل یا مهندسی پرامپت‌های پراکنده. در واقع، بررسی دقیق‌تر معماری‌های هیبریدی برای عامل‌های محلی نشان می‌دهد که چرا اتکای صرف به «وایب» در مقیاس واقعی با شکست مواجه می‌شود و نیاز به ساختارهای سخت‌گیرانه‌تر است.

ساخت عامل هوش مصنوعی محلی برای OSM: ۲۱ روز تکرار و بهبود

معماری فنی سیستم

بنیاد این سیستم یک عامل هوش مصنوعی محلی است که عمدتاً طی جلسات کاری شبانه در طول سه هفته توسعه یافته است. نقطه شروع، یک Wrapper قدرتمند برای مدل‌های زبانی بزرگ (LLM) با استفاده از کتابخانه llama-cpp-python است. تشادا برای اطمینان از جریان پاک داده‌ها، کلاسی به نام LocalLLMFunctionCaller را پیاده‌سازی کرد. این کلاس مدل را با یک پنجره زمینه (Context Window) مشخص مقداردهی اولیه می‌کند؛ این مقدار در ابتدا n_ctx=2048 بود و بعداً در محیط خط فرمان (CLI) به 8192 افزایش یافت تا مدل بتواند داده‌های بیشتری را هم‌زمان پردازش کند. همچنین دمای مدل (Temperature) روی 0.0 تنظیم شد تا خروجی‌ها کاملاً قطعی، غیرخلاقانه و بازتولیدپذیر باشند.

برای مدیریت تغییرات و نوسانات در پاسخ‌های LLM، یک ماژول اختصاصی به نام executor.py ایجاد شد. این ماژول از یک الگوریتم تطبیق آکولاد برای استخراج JSON استفاده می‌کند. به‌جای تکیه بر عبارات منظم (Regular Expressions) که در متون پیچیده خطا دارند، تابع extract_json عمق آکولادهای باز و بسته را می‌شمارد تا شیء JSON را به‌طور دقیق از هرگونه متن اضافی یا «حاشیه» (Fluff) اطراف آن جدا کند.

استراتژی‌های مهندسی پرامپت

خط لوله این سیستم از فایلی به نام prompts.py بهره می‌برد که شامل چهار سازنده (Builder) مجزا برای سطوح مختلف پیچیدگی است:

  • build_prompt(): یک تابع پایه برای فراخوانی‌های استاندارد مدل.
  • build_mcp_prompt(): طراحی شده برای فراخوانی ابزارها در سبک پروتکل زمینه مدل (Model Context Protocol).
  • build_osmfilter_prompt(): یک رویکرد Zero-shot (بدون مثال) برای تولید مستقیم فیلترهای OSM.
  • build_osmfilter_prompt_with_examples(): یک روش Few-shot (با چند مثال) که پرس‌وجوی کاربر را به همراه رشته‌ای از مثال‌های پیش‌فرمت شده به مدل ارائه می‌دهد. این متد در نهایت به هسته اصلی خط لوله RAG تبدیل شد.

لایه RAG و بردارهای معنایی

برای مدیریت مقیاس عظیم برچسب‌های OSM، عامل از استراتژی تولید بازیابی‌افزا (RAG) استفاده می‌کند که توسط مدل جاسازی (Embedding) bge-small-en-v1.5 پشتیبانی می‌شود. این مدل به‌طور استثنایی سبک است، تنها ۳۳ مگابایت فضا اشغال می‌کند و بردارهایی با ۳۸۴ بُعد تولید می‌کند که برای کاربردهای محلی ایده‌آل است.

جزئیات Ingestion (جذب داده‌ها):

  • جذب پایگاه دانش: سیستم مجموعه‌داده taginfo-wiki.db را می‌خواند. هر توصیف برچسب OSM به عنوان یک شیء JSON ذخیره می‌شود که شامل فیلدهای tgroup (گروه برچسب)، key (کلید)، value (مقدار)، description (توصیف)، implies (دلالت‌ها)، combination (ترکیبات)، linked (پیوندها)، status (وضعیت) و approval (تأییدیه) است.
  • کلاس LocalLLMEmbedder: این کلاس مسئول ایجاد بردارهای معنایی و ذخیره آن‌ها در SQLite است. این قابلیت به سیستم اجازه می‌دهد تا کل مجموعه برچسب‌های مستند OSM را به‌صورت محلی جست‌وجو کند. برای به حداکثر رساندن شتاب‌دهنده گرافیکی، تنظیم n_gpu_layers=-1 به کار رفته است.
  • پایگاه داده نمونه‌های فیلتر: فراتر از ویکی عمومی، تشادا جدولی به نام filter_examples ایجاد کرد. این جدول شامل پرس‌وجوی زبان طبیعی، درخت نحو انتزاعی (AST) مربوط به JSON، برچسب‌های استخراج‌شده و بردار معنایی (Embedding) دقیق آن پرس‌وجو است.
  • مکانیزم تجزیه (Parsing): یک تجزی‌کننده اختصاصی مبتنی بر ماشین-حالت (State-machine) نوشته شد تا مثال‌ها را از فایل‌های متنی ساده بخواند، بلوک‌های «User:» و «Assistant:» را شناسایی کند و با ردیابی آکولادهای JSON، حدود ۲۰۰ مثال را پردازش و ذخیره کند.

مکانیسم‌های جست‌وجوی شباهت

تابع search_filter_examples() ابتدا پرس‌وجوی کاربر را برداری کرده و سپس آن را از طریق np.frombuffer به یک آرایه NumPy تبدیل می‌کند. سپس شباهت کسینوسی (Cosine Similarity) را نسبت به مثال‌های ذخیره شده محاسبه می‌کند. معمولاً از یک حد نصاب min_score برابر با ۰.۶۵ برای حذف تطبیق‌های نامرتبط استفاده می‌شود و در نهایت ۱۰ نتیجه برتر (k=10) بازگردانده می‌شوند.

پیاده‌سازی ریاضی این تابع به صورت np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)) است. این فرمول تضمین می‌کند که فاصله ریاضی میان قصد کاربر و مثال‌های تاریخی موجود در پایگاه داده به‌طور دقیق اندازه‌گیری شود.

شکست‌ها و تکرارهای توسعه

بین روز اول و بیست‌ویکمین توسعه، پروژه سه مرحله پالایش مجزا را طی کرد. اولین مانع بزرگ در هنگام آزمایش نقطه ورود CLI (فایل func_cli.py) رخ داد.

فاجعه توکن توقف (Stop Token):
تشادا با شکست بحرانی مواجه شد که در آن مدل پاسخ‌های خالی برمی‌گرداند. با فعال کردن حالت --verbose برای بررسی خروجی خام، او متوجه شد که مدل یک خط خالی قبل از شیء JSON تولید می‌کند. این خط خالی باعث تحریک پارامتر stop=["\n\n"] می‌شد و باعث می‌گشت LLM پاسخ را پیش از نوشتن هرگونه JSON واقعی قطع کند.

راهکار او حذف کامل توکن توقف و تکیه بر max_tokens و تجزی‌کننده آکولاد بود. او همچنین یک سیستم پشتیبان (Fallback) اضافه کرد: اگر پاسخ خالی باشد، سیستم مقدار {} را برمی‌گرداند تا فراخواننده بتواند شکست را به‌طور نرم مدیریت کند.

بهبود اعتبارسنجی کاندیداها:
برای افزایش دقت، تشادا مرحله‌ای برای اعتبارسنجی کاندیداها با استفاده از پرامپت build_osmtags_validate_prompt() پیاده کرد. این کار تضمین می‌کند که LLM برچسب‌های نامرتبط را بر اساس بستر متن حذف کند. به مدل لیستی از کاندیداها (کلید، مقدار، توصیف) داده می‌شود و مدل باید آرایه‌ای از IDهای مرتبط را خروجی دهد. برای مثال، این مکانیسم مانع از آن می‌شود که وقتی پرس‌وجوی کاربر صراحتاً مربوط به خشکی است، برچسب seamark:type=restricted_area انتخاب شود.

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

حلقه عامل‌محور (Agentic Loop)

تکامل نهایی، یک مولد ایستا را از طریق یک حلقه منطقی ساده به یک کاوشگر تطبیقی تبدیل کرد. این حلقه که کمتر از ۵۰ خط کد دارد، توالی زیر را دنبال می‌کند:

۱. فراخوانی مولد فیلتر برای ایجاد یک فیلتر JSON.
۲. اجرای فیلتر با استفاده از تابع execute_osmfilter().
۳. بررسی تعداد ویژگی‌های یافت شده (Feature Count)؛ اگر صفر باشد، سیستم فرض می‌کند فیلتر بیش از حد سخت‌گیرانه (Restrictive) بوده است.
۴. فراخوانی تابع broaden_request(request) برای گسترش پارامترهای جست‌وجو و تکرار حلقه تا زمانی که نتیجه‌ای یافت شود یا به حداکثر تعداد تلاش‌ها (max_attempts) برسد.

این حلقه، یک ترجمه تک‌مرحله‌ای (One-shot) را به یک سیستم پویا تبدیل می‌کند. هر تصمیم — از درخواست اولیه و مثال‌های بازیابی شده گرفته از فیلتر تولید شده و تعداد نتایج — برای مقاصد حسابرسی (Auditing) ثبت می‌شود.

وضعیت فعلی و درس‌های آموخته شده

در روز بیست‌ویکمین، سیستم برای اکثر پرس‌وجوهای ساده، فیلترهای JSON معتبر تولید می‌کند و روی یک GPU با VRAM کمتر از ۸ گیگابایت اجرا می‌شود. با این حال، تشادا به چندین چالش جاری اشاره می‌کند:

  • سنتز (Synthesis): ترکیب چندین برچسب (مانند maxspeed و surface) هنوز کامل نیست و جای بهبود دارد.
  • نفی (Negation): مدل با پرس‌وجوهایی مانند «بزرگراه‌ها نباشند» (not highways) کلنجار می‌رود.
  • تنوع (Diversity): پایگاه داده اولیه مثال‌ها به‌شدت به سمت برچسب‌های دریایی سوگیری داشت؛ افزودن مثال‌های خشکی (پارکینگ، مرزها، دسترسی‌ها) برای آگاهی از بستر متن ضروری بود.
  • اعتماد (Confidence): آستانه شباهت ۰.۴ برای درخواست شفاف‌سازی از کاربر، فعلاً یک حدس است و نیاز به تنظیم دقیق‌تر با داده‌های واقعی دارد.

درس‌های فنی کلیدی:

  • توکن‌های توقف: مگر اینکه ۱۰۰٪ مطمئن باشید مدل هرگز آن توکن را در محتوای معتبر تولید نمی‌کند، از آن‌ها دوری کنید. به‌جای آن از max_tokens و تجزی‌کننده‌های خارجی استفاده کنید.
  • فرمت Embedding: در حالی که رشته‌های JSON کار می‌کنند، اما جملات زبان طبیعی (مثلاً: "key=highway, value=primary, description: A major road") نتایج بسیار بهتری تولید کردند.
  • گزارش‌گیری (Logging): خروجی --verbose حیاتی‌ترین ابزار عیب‌یابی برای خط لوله‌های LLM محلی است تا از تلف کردن روزها زمان روی باگ‌های نامرئی جلوگیری شود.

برای توسعه‌دهندگان، این آزمایش ثابت می‌کند که LLMهای محلی می‌توانند ترجمه‌های فنی دامنه-محور را مدیریت کنند، به شرطی که توسط یک پایگاه داده باکیفیت از مثال‌ها پشتیبانی شوند. تغییر از پرامپت‌نویسی Zero-shot به یک حلقه عامل مبتنی بر RAG، پیش‌بینی‌ناپذیری مدل‌های کوچک محلی را به‌طور قابل توجهی کاهش داد.

قطعات پیاده‌سازی (Implementation Snippets)

برای کسانی که قصد پیاده‌سازی منطق مشابه را دارند، تابع جست‌وجوی RAG به‌گونه‌ای طراحی شده که به پایگاه داده SQLite متصل شده و روی Blob‌های ذخیره شده پیمایش کند:

# author: Jan Tschada
# SPDX-License-Identifer: Apache-2.0
def search_filter_examples(self, query: str, db_path: str = "taginfo-wiki.db", min_score=0.65, k=10):
    q_emb = self.create_embedding(query)
    q_vec = np.frombuffer(q_emb, dtype=np.float32)
    conn = sqlite3.connect(db_path)
    cursor = conn.cursor()
    rows = cursor.execute("SELECT natural_language, json_ast, tags, embedding FROM filter_examples WHERE embedding IS NOT NULL").fetchall()
    scored = []
    for natural_language, json_ast_str, tags_str, emb in rows:
        emb_vec = np.frombuffer(emb, dtype=np.float32)
        score = self.cosine(q_vec, emb_vec)
        if score >= min_score:
            scored.append((score, natural_language, json_ast_str, tags_str))
    scored.sort(reverse=True)
    return scored[:k]

رابط خط فرمان (CLI) پروژه به کاربران اجازه می‌دهد درخواست‌ها را به این صورت اجرا کنند: uv run osm-functions --request "Find only restricted areas" --model /path/to/model.gguf. در نقطه ورود ساده‌شده نهایی، LocalLLMFunctionCaller با n_ctx=8192 مقداردهی شده و LocalLLMEmbedder مدل data/bge-small-en-v1.5-q4_k_m.gguf را برای تضمین بازیابی با کارایی بالا بارگذاری می‌کند.

کاربران علاقه‌مند می‌توانند جریان‌های کاری عامل‌های جغرافیایی خود را با تست نحوه مدیریت ابهام برچسب‌ها یا پیاده‌سازی خط لوله RAG مشابه برای داده‌های نقشه‌ خصوصی ارزیابی کنند. تکرارهای آینده این پروژه قصد دارد عملگرهای مکانی (مثلاً «در فاصله ۵ کیلومتری یک بیمارستان») و ادغام با Wikidata برای غنی‌سازی ویژگی‌های OSM با حقایق بیشتر را اضافه کند.

گام بعدی شما

  • اگر توسعه‌دهنده هستید، از کتابخانه llama.cpp برای اجرای مدل‌های کوچک در محیط‌های محدود استفاده کنید.
  • برای کاهش توهمات مدل در داده‌های تخصصی، یک پایگاه داده از «مثال‌های موفق» (Few-shot examples) را در یک SQLite محلی پیاده‌سازی کنید.
  • در هنگام طراحی عامل‌ها، به‌جای تکیه بر پاسخ اول مدل، یک حلقه بازخوردی برای بررسی نتایج (مانند تعداد خروجی صفر در این پروژه) طراحی کنید.

اما داستان سخت‌افزاری این تحول حتی شگفت‌انگیزتر است — به تحلیل ما درباره‌ی تراشه‌های Blackwell مراجعه کنید.

چرا این موضوع مهم است؟

این رویکرد ثابت می‌کند که برای کاربردهای تخصصی، نیازی به مدل‌های میلیارد-پارامتری و APIهای گران‌قیمت نیست. تخصص در طراحی لایه RAG و حلقه بازخوردی، اجازه می‌دهد سخت‌افزارهای ارزان‌قیمت نتایجی در سطح صنعتی تولید کنند.

تأثیر برای ایران

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

·نگاه ما
تحریریه دات‌هوش

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

منابع

این گزارش با خط‌لولهٔ خودکار دات‌هوش از منابع معتبر جهانی تدوین و زیر نظر تحریریه منتشر شده است. روش کار ما

گفتگو

پنج‌شنبه‌های هوش‌محور

بسته‌ی هفتگی دات‌هوش

۵ خبر، ۲ ابزار، ۱ پرامپت در هر شماره. به‌زودی راه‌اندازی می‌شود — هر پنج‌شنبه صبح.

خبر کلیدی
ابزار کاربردی
پرامپت حرفه‌ای
تحلیل پژوهش
به‌زودی
زاویه‌ی ایرانی
به‌زودی
تمرین این هفته
به‌زودی

راهنماهای دات‌هوش

راهنماهای کاربردیِ دات‌هوش برای کار با هوش مصنوعی — از همین‌جا شروع کنید:

دات‌هوش

راهنمای فارسی هوش مصنوعی — با نگاه به ایران

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