اگر از مدلهای زبانی برای تولید کدهای عملیاتی استفاده میکنید، احتمالا میدانید که پرامپتهای انگلیسی هر چقدر هم دقیق باشند، باز هم منجر به توهمات و تصمیمات طراحی متناقض میشوند. راهکار این مشکل نه در مهندسی پرامپتهای پیچیدهتر، بلکه در پیادهسازی زبانهای تخصصی دامنه (Domain-Specific Languages یا DSL) است تا فضای خروجی هوشمند مصنوعی را محدود کنیم.
به باور مارتین فاولر (Martin Fowler)، با تغییر نقش مدل زبانی بزرگ (LLM) — که مثل کتابخانهداری است که میلیاردها صفحه را خوانده و حالا با همان لحن کتابها جواب میدهد — از یک کدنویس عمومی به یک «مترجم» برای یک سینتکس محدود، میتوان شکاف میان کد تولیدی و سیستمهای آماده برای تولید (Production-ready) را پر کرد. این استراتژی اجازه میدهد تا قابلیت اطمینان در تولید کد به حالت شبهقطعی برسد. این رویکرد در واقع پاسخی به چالشهای عملیاتی است؛ چراکه همانطور که در تحلیلهای قبلی دیدیم، محدودیتهای حافظه در مدلهای محلی میتواند منجر به ناکارآمدی عاملهای هوشمند در پروژههای کدنویسی گسترده شود.
این رویکرد مشکلی بنیادی به نام «ناممکن بودن مشخصات اولیه» (Upfront Specification Impossibility) را حل میکند. طبق تحلیلهای مطرح شده، اکثر طراحیهای سیستمهای مقیاسبزرگ نه نقشههای دقیق، بلکه فرضیاتی هستند که در طول پیادهسازی تکامل مییابند. ساخت سیستمهای بزرگ شامل تصمیمات طراحی کوچک و بیشماری است که نمیتوان همه آنها را از پیش دانست یا صرفاً از طریق یک مشخصات سطحبالا (High-level Spec) هدایت کرد. محدودیتهای واقعی، موازنهها (Trade-offs) و موارد خاص (Edge Cases) تنها زمانی کشف میشوند که پیادهسازی پیش برود. بنابراین، پاسخ طبیعی، تکرار است: اصلاح مشخصات، تولید کد، بررسی نتیجه و بازگرداندن آموختهها به چرخه بعدی. این حلقه زمانی بیشترین اثر را دارد که هر دور، یک تغییر کوچک و قابلبازبینی تولید کند.
نقش مدلسازی دامنه در پایداری
در پوشش پیشین ما دربارهی موازنههای میزبانی شخصی در برابر استفاده از APIها، دیدیم که هزینه و تأخیر تعیینکننده انتخابهای زیرساختی هستند. به همین ترتیب، انتخاب نحوه تعامل با یک LLM — چه از طریق جاوا و چه از طریق یک DSL سفارشی — تعیینکننده میزان قابلاعتماد بودن سیستم نهایی است. این موضوع با طراحی دامنه-محور (Domain Driven Design یا DDD) گره خورده است که بر ایجاد یک مدل مفهومی مشترک از یک دامنه در کد تمرکز دارد.
این «زبان مشترک» (Ubiquitous Language) به تیم واژگانی میدهد تا از طریق آن ارتباط برقرار کرده و کدبیس را تکامل دهد. یک DSL در واقع سینتکس محدودی برای بیان مفاهیم و عملیاتهای این دامنه است. از این منظر، بخش بزرگی از توسعه نرمافزار صرفاً فرآیند ساخت یک مدل دامنه و استفاده از آن برای پیشبرد سیستم است. در اینجا LLM بسته به اینکه مدل دامنه از پیش در کدبیس وجود داشته باشد یا خیر، دو نقش متمایز ایفا میکند.
چرا محدودیتها باعث افزایش دقت میشوند؟
زبانهای عمومی مثل جاوا راههای بسیار زیادی برای بیان یک قصد واحد دارند که منجر به پراکندگی، تنوع بیش از حد و در نهایت خطا میشود. اما DSLهایی مثل SQL برای پرسوجوی پایگاهداده، PlantUML، Mermaid و Graphviz برای مدلسازی بصری، یا Kubernetes YAML برای زیرساختهای ابری، به دلیل محدود بودنِ عمدی موفق هستند. این زبانها طراحی شدهاند تا مجموعهای خاص از مفاهیم را در یک دامنه مشخص بیان کنند.
طبق گزارشی در وبسایت martinfowler.com، دلایل موفقیت DSLها در کنار LLMها سه مورد است:
- کارایی در یادگیری با نمونه اندک (Few-Shot Efficiency): چون سینتکس محدود است و تغییرات زائد را حذف میکند، چند مثال ساده در پنجره متنی (Context) کافی است تا مدل خروجی صحیحی تولید کند. مدلهای پیشرو پیش از این با دادههای PlantUML یا رابطهای Fluent در جاوا به شدت آموزش دیدهاند، به این معنی که از نقطه صفر شروع نمیکنند. این توانایی مدلها در پردازش الگوهای پیچیده، ریشه در مکانیسمهای ریاضیاتی توزیع توجه (Attention) دارد که هستهی پردازش در مدلهای GPT و Claude است. البته هنوز مشخص نیست مدلهای کوچکتر و محدودتر در مواجهه با DSLهای کاملاً نوآورانه چگونه عمل میکنند.
- اعتبارسنجی قطعی (Deterministic Validation): این زبانها معمولاً یک اعتبارسنج قطعی مثل پارسر، JSON schema، چککننده تایپ یا کامپایلر همراه خود دارند. این یعنی یک عامل (Agent) میتواند در یک حلقه خودکار «تولید و بررسی»، یک کاندید تولید کند، آن را از اعتبارسنج رد کند و در صورت خطا، بدون دخالت انسان آن را اصلاح نماید.
- خطاهای سطح دامنه: بهجای گزارشهای مبهم خطا (Stack Traces) که در اعماق کد تولیدی دفن شدهاند، اعتبارسنج بازخوردی در قالب مفاهیم دامنه میدهد؛ مثلاً: «شما نمیتوانید پیش از انتخاب مشتری، یک عملیات را تعریف کنید».
باید توجه داشت که این راهکار یک فرمول جادویی برای همه نیست. مزیت آن تنها زمانی است که DSL به اندازه کافی کوچک و محدود باقی بماند تا چند مثال Few-shot بتوانند نحوه استفاده از آن را برسانند. همچنین هزینه اولیه قابلتوجهی برای طراحی و نگهداری زبان و مدل معنایی (Semantic Model) آن وجود دارد. بازدهی واقعی در DSLهایی متمرکز است که به خوبی ساختاریافته باشند و توسط یک اعتبارسنج پشتیبانی شوند.
مورد مطالعاتی: ارائههای غنی از نمودار
فاولر برای آموزش سیستمهای توزیعشده، ابزاری برای تولید اسلایدهای پاورپوینت ساخت. او مکرراً نیاز داشت عملیاتهای پیچیده در یک کلاستر را توضیح دهد. اگرچه نمودارهای توالی (Sequence Diagrams) UML مفید بودند، اما نمایش یک نمودار کامل در حالی که جریان پیامها توضیح داده میشود، مؤثر نیست. او راهی میخواست تا نمودارهای توالی را گامبهگام نشان دهد.
بهجای اینکه از LLM بخواهد مستقیماً فایل پاورپوینت بسازد، یک رویکرد دولایه پیاده کرد:
۱. توسعه PlantUML: او PlantUML را با نشانگرهای [step] گسترش داد. برای مثال، پرامپتی برای تولید یک خوشه سه گرهی (آتن، بیزانتیوم و سیرن) منجر به کدی میشود که در آن نشانگرها ترتیب را مشخص میکنند: [step] Alice -> athens: "title", "After Dawn" و سپس [step] athens -> athens: save() و به همین ترتیب. او بهطور خاص یادداشتهایی برای نمایش وضعیتها اضافه کرد، مانند state: title: After Dawn برای آتن. این کار باعث میشود ابزار برای هر مرحله علامتگذاری شده، یک اسلاید مجزا بسازد.
۲. مشخصات YAML: یک فایل YAML کوچک برای توصیف ساختار ارائه و نمودارهای مورد استفاده طراحی کرد. پرامپتی ساده مثل «یک اسلاید YAML برای نمودار quorum-write با عنوان Quorum Write Example بساز»، یک مشخصه معتبر تولید میکند که به این شکل است: - slide: title: "Quorum Write Example" diagram: "quorum-write".
چون مشخصات YAML ابزار به عنوان بستر (Context) در پرامپت ارائه شده بود، LLM یک مشخصه معتبر تولید کرد که مستقیماً توسط ابزار پردازش شد. در این مثال، LLM دو نقش ایفا کرد: ابتدا به عنوان همطراح برای شکل دادن به توسعههای YAML و PlantUML، و سپس به عنوان رابطی برای تبدیل درخواستهای انگلیسی به مشخصات فنی معتبر.
چارچوب Tickloom: مقیاسبندی برای سیستمهای توزیعشده
برای دامنههای پیچیدهتر، سینتکس ساده کافی نیست و به یک مدل معنایی کامل نیاز است. فاولر Tickloom را توسعه داد؛ چارچوبی برای سادهسازی ساخت و تست الگوریتمهای توزیعشده مثل Raft یا Paxos.
پیادهسازی این سیستمها دشوار است چون محیطهای ناهمگام (Asynchronous) فضای تصمیمگیری عظیمی ایجاد میکنند. مدلهای رشتهبندی (Threading)، الگوهای شبکه، هماهنگی ذخیرهساز، رفتار تکرار (Retry) و معناشناسی زمانبندی معمولاً در کد درهم میپیچند. این امر منجر به یک «بحران اعتبارسنجی» میشود؛ فضای حالتِ درهمتنیدگیهای ممکن — که ناشی از زمانبندی رشتهها، تأخیرهای شبکه، توقف پردازشها و انحراف ساعت (Clock Skew) است — چنان وسیع است که اعتبارسنجی سیستماتیک تقریباً غیرممکن میشود. به همین دلیل است که تستهای Jepsen حتی در سیستمهای تکاملیافته و میدانآزموده نیز باگهای شدیدی پیدا میکنند.
Tickloom این مشکل را با تحمیل یک محیط اجرای قطعی از طریق یک مدل معنایی حل میکند:
- حلقه تیک (Tick Loop): هر گره در یک حلقه تکرشتهای اجرا میشود. هر فراخوانی
tick()ساعت منطقی را یک واحد جلو میبرد. - ترتیب قطعی: کارهای معلق با ترتیبی ثابت پردازش میشوند: ابتدا شبکه، سپس باس پیام، سپس پردازش و در نهایت ذخیرهساز.
- زمان منطقی: زمان با «تیک» اندازهگیری میشود، نه میلیثانیه.
- هماهنگی سادهشده: کلاس پایه
Replicaدانش داخلی از همتایان، پخش پیام (Broadcast) و کورومها (Quorums) را فراهم میکند.
در این محیط، مدل معنایی به عنوان بستر مبنیسازی (Grounding) عمل میکند. وقتی از LLM خواسته شود یک «ذخیره کلید-مقدار مبتنی بر کوروم» پیاده کند، دیگر نیازی نیست لایه شبکه را اختراع کند؛ بلکه منطق پروتکل را با استفاده از واژگان ثابت پر میکند: Replica ،quorumRequest ،countResponseIf ،MessageType و Handler. برای مثال، LLM میتواند استراتژی «آخرین نویسنده برنده است» (LWW) را پیاده کند، به گونهای که یک درخواست GET کلاینت، مقادیر را از اکثریت جمعآوری کرده و موردی با بالاترین برچسب زمانی را برگرداند.
حل شکاف اعتبارسنجی با DSLهای داخلی
پیادهسازی الگوریتم یک طرف سکه است و تست سناریوهای شکست طرف دیگر. باگهای ظریف در ترتیبهای خاص رخ میدهند؛ مثلاً وقتی یک write قبل از تغییر کورومِ خواننده تکثیر شود، یا یک پارتیشن شبکه در لحظهای اشتباه ترمیم گردد. نوشتن این سناریوها مستقیماً روی یک testkit نیازمند مدیریت futures و حلقههای دستی tick() است که از نظر مکانیکی متراکم است و بازبینی آن دشوار است.
یک مثال از سناریوی انحراف ساعت در کد خام شامل تنظیم دستی پردازشها (ATHENS, BYZANTIUM, CYRENE)، تعیین زمان پردازش (1000L و 2000L) و تیک زدن دستی تا تکمیل writeها است. در این حالت، قصد واقعی — «باب از طریق بیزانتیوم مینویسد، آلیس از طریق آتن مینویسد، یک خواننده مقدار باب را میبیند چون ساعت بیزانتیوم جلوتر بود» — زیر انبوهی از جزئیات مکانیکی دفن میشود. این موضوع برای LLM دشوار است، زیرا دهها تصمیم جانبی (مثل کدگذاری بایتها یا اورلودهای factory) وجود دارد که میتواند در آنها دچار توهم (Hallucination) — یعنی وقتی مدل با اطمینان چیزی میگوید که اصلاً وجود ندارد — شود.
فاولر این مشکل را با ساخت یک DSL داخلی در جاوا حل کرد. این DSL از «رابطهای پیشرونده» (Progressive Interfaces) استفاده میکند؛ یعنی کد اصلاً کامپایل نمیشود اگر کاربر بخواهد پیش از تعریف توپولوژی، یک مرحله (Step) را اعلام کند یا پیش از انتخاب کلاینت، یک اکشن تعریف نماید. در نهایت، این DSL به یک نمایش میانی (IR) تبدیل میشود — یک Scenario ساخته شده از Steps، که هر گام شامل یک Action و به صورت اختیاری ClusterEvents (خطاها) است.
نمونههای سناریوهای مبتنی بر DSL
هنگام شبیهسازی یک «خوانش غیرخطی کوروم» (با ارجاع به بخش ۱۰.۶ کتاب DDIA)، LLM یک سناریوی اخباری تولید میکند که بر این موارد متمرکز است:
- تعریف توپولوژی: نگاشت سرورها (ATHENS, BYZANTIUM, CYRENE) و کلاینتها (WRITER, ALICE, BOB) به اتصالات مشخص.
- شبیهسازی خطا: استفاده از توابع بیانگر و انگلیسیمانند برای خطاها:
partition(BYZANTIUM).from(CYRENE)،reconnect(BYZANTIUM)وdelay(INTERNAL_SET_REQUEST).from(ATHENS).to(BYZANTIUM, CYRENE).byTicks(100). - اعتبارسنجی رفتاری: استفاده از
.expectResponse(v -> VNEW.equals(v))برای تعریف نتیجه مورد انتظار سناریو.
چون این DSL در جاوا ساخته شده، کامپایلر میزبان بهطور رایگان گرامر را اعتبارسنجی میکند. اگر LLM در یک مرحله دچار خطا شود، کامپایلر خطای دقیقی را به آن نقطه غیرقانونی ارجاع میدهد و AI میتواند خود را اصلاح کند. این کار فضای توهم را به شدت کاهش میدهد و به بازبینها اجازه میدهد بهجای بررسی خطبهخط کد، توصیف یک آزمایش را حسابرسی کنند.
گردشکار دومرحلهای هوش مصنوعی
ادغام DSLها با هوش مصنوعی در دو مرحله متمایز رخ میدهد:
۱. فاز همطراحی: انسان و LLM بهصورت تکرارشونده درباره انتزاعها ایدهپردازی میکنند. در اینجا LLM شریکی است که جایگزینها را طرحریزی کرده و طراحی را نقد میکند. این فاز بازخوردمحور است، زیرا تصمیمات طراحی (مثل رابطهای پیشرونده) با امتحان کردن آنها در موارد واقعی و شناسایی نقاط دشوار کشف میشوند. در این مرحله انسانe کنترل را در دست دارد تا مالک تصمیمات کلیدی معنایی باشد.
۲. فاز رابط: پس از تثبیت DSL، مدل به یک رابط زبان طبیعی تبدیل میشود. مدل درخواستهای انگلیسی — مثلاً «سناریوی بازتولید خوانش بخش ۱۰.۶ DDIA را بنویس» — را به واژگان محدود DSL ترجمه میکند. در اینجا انتزاع هم به عنوان بستر (Context) پرامپت و هم به عنوان مهارکنندهای (Harness) که نتیجه را چک میکند، عمل میکند.
DSL به عنوان منبع واحد حقیقت
گرایشی رو به رشد وجود دارد که «پرامپت» را منبع اصلی حقیقت (Source of Truth) بدانند، اما فاولر معتقد است این یک اشتباه است. در گردشکار مبتنی بر DSL، خودِ برنامه تولید شده دارایی ماندگار است.
چون یک DSL متراکم است، قدرت بیان بالایی دارد و کدهای تکراری (Boilerplate) ندارد، قصد اصلی راهکار را بهصورت خوانا ثبت میکند. اگر یک سناریوی شکست Tickloom از یک درخواست زبان طبیعی تولید شده باشد، کد حاصل از پیش در واژگان دامنه بیان شده است. اگر سناریو ماه ها بعد نیاز به تغییر داشته باشد، نیازی به بازیابی پرامپت اولیه و تولید مجدد همه چیز نیست؛ زیرا خودِ DSL بستر کافی را برای LLM فراهم میکند تا قصد نویسنده را بفهمد و کد را تکامل دهد. دارایی ماندگار، نه پرامپت، بلکه خودِ DSL و مدل معنایی آن است.
گام بعدی شما
- بررسی کنید آیا در پروژههای فعلیتان میتوانید یک زیرزبان (Sub-language) ساده برای تعریف پیکربندیها ایجاد کنید تا وابستگی به پرامپتهای طولانی کم شود.
- مطالعه مستندات PlantUML یا Mermaid برای درک اینکه چگونه مدلهای زبانی در تبدیل متن به نمودار عمل میکنند.
- آزمایش این ایده: بهجای درخواست کد کامل، از LLM بخواهید ابتدا یک «پروتکل تبادل داده» (مشخصات محدود) بنویسد و سپس بر اساس آن کد تولید کند.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما دربارهی تراشههای Blackwell مراجعه کنید.




گفتگو