<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>نوشته های محمد ناصر صدرمنش</title><link>https://virgool.ir/feed/@sadrmanesh</link><description>مهندس محصول هوش مصنوعی در همه‌جو. درباره‌ی پردازش زبان فارسی، RAG، ارزیابی جست‌وجو و عامل‌های هوش مصنوعی برای تجارت الکترونیک می‌نویسم. سازنده‌ی persian-rag.</description><language>fa</language><pubDate>2026-11-11 12:49:17</pubDate><image><url>https://files.virgool.ir/upload/users/4966792/avatar/R470JR.jpg</url><title>محمد ناصر صدرمنش</title><link>https://virgool.ir/@sadrmanesh</link></image><item><title>طراحی یک بنچمارک کوچک بازیابی فارسی برای سنجش مقاومت در برابر املاهای چندگانه: معرفی persian-rag-bench</title><link>https://virgool.ir/@sadrmanesh/طراحی-یک-بنچمارک-کوچک-بازیابی-فارسی-برای-سنجش-مقاومت-در-برابر-املاهای-چندگانه-معرفی-persian-rag-bench-e6glsmwzazq5</link><description>مقدمهدر یادداشت قبلی‌ام درباره‌ی «ی عربی، نیم‌فاصله و PDF برعکس» توضیح دادم که چرا جست‌وجوی فارسی در سامانه‌های پرسش‌وپاسخ مبتنی بر بازیابی (RAG) بی‌صدا خراب می‌شود. این یادداشت درباره‌ی روی دیگر همان سکه است: چطور بفهمیم یک خط لوله‌ی بازیابی فارسی در برابر این مشکلات مقاوم است، و وقتی تغییرش می‌دهیم بدتر نشده است؟پاسخ من یک بنچمارک کوچک و تکرارپذیر به نام persian-rag-bench است که آن را به‌صورت عمومی در Figshare (با DOI) و Hugging Face منتشر کرده‌ام. در ادامه طراحی آن، داده‌ها، معیارها، نحوه‌ی استفاده و محدودیت‌هایش را شرح می‌دهم.چرا یک بنچمارک کوچک؟بنچمارک‌های بزرگ فارسی وجود دارند؛ مثلاً زیرمجموعه‌ی فارسی MIRACL یا PQuAD. این‌ها برای مقایسه‌ی کلی مدل‌ها ارزشمندند، اما برای یک کار روزمره‌ی مهندسی مناسب نیستند: آزمون پسرفت (regression check). وقتی یک قاعده‌ی یکسان‌سازی را عوض می‌کنید یا اندازه‌ی تکه‌ها را تغییر می‌دهید، می‌خواهید در چند ثانیه و بدون GPU بفهمید چیزی خراب شده یا نه.بنچمارک‌های بزرگ همچنین یک ضعف خاص دارند: معمولاً پرسش‌هایی که عمداً با املای متفاوت از متن نوشته شده باشند را به‌طور جداگانه علامت نمی‌زنند. پس نمی‌توان مستقیماً پرسید «سامانه‌ی من با املاهای چندگانه چطور کنار می‌آید؟».بنابراین سه هدف طراحی داشتم:سرعت و تکرارپذیری: اجرای کامل روی یک لپ‌تاپ معمولی، حتی با یک embedder آفلاین.برچسب صریح برای املای چندگانه: پرسش‌های املایی قابل گزارش جداگانه باشند.سادگی قالب: دو فایل JSONL که با هر ابزاری خوانده شوند.و یک «غیرهدف» صریح: این بنچمارک برای رتبه‌بندی کلی مدل‌ها ساخته نشده و برای این کار بیش از حد کوچک است.ساختار دادهبنچمارک دو بخش دارد: پیکره (corpus) با ۳۴ ردیف و فیلدهای id، title و text، و پرسش‌ها (qa) با ۴۲ ردیف و فیلدهای id، question، relevant_ids و tags. جدول زیر همین را خلاصه می‌کند:corpus: تعداد ردیف: 34؛ فیلدها: id، title، textqa: تعداد ردیف: 42؛ فیلدها: id، question، relevant_ids، tagsپیکره (corpus)پیکره ۳۴ متن کوتاه دانشنامه‌ای است درباره‌ی شاعران فارسی‌زبان، شهرها، بناهای تاریخی، جشن‌ها، خوراک‌ها و موضوعات روزمره مثل اتاق، هیئت مدیره و بورس. طول متن‌ها بین ۱۸ تا ۳۵ کلمه است (میانگین حدود ۲۷ کلمه، با شمارش ساده‌ی فاصله‌ها). یک نمونه:{&#34;id&#34;: &#34;hafez&#34;, &#34;title&#34;: &#34;حافظ&#34;, &#34;text&#34;: &#34;حافظ شیرازی شاعر قرن هشتم هجری است. ...&#34;}کوتاه بودن متن‌ها عمدی است: هر متن تقریباً یک واقعیت اصلی دارد و ابهام درباره‌ی اینکه کدام متن جواب پرسش است کم می‌شود.پرسش‌ها (qa)۴۲ پرسش طبیعی یا عبارت کوتاه جست‌وجو، هر کدام با شناسه‌ی متن‌های مرتبط. بر اساس شمارش خودم روی فایل:۴۱ پرسش دقیقاً یک متن مرتبط دارند و یک پرسش دو متن مرتبط.۲۶ متن از ۳۴ متن پیکره دست‌کم یک بار هدف یک پرسش‌اند؛ ۸ متن باقی‌مانده فقط نقش متن رقیب (distractor) را دارند.۱۲ پرسش برچسب spelling دارند.پرسش‌ها هم به شکل پرسش کامل‌اند («آرامگاه حافظ در کدام شهر است؟») و هم به شکل عبارت کوتاه جست‌وجو («قیمت بلیط»)، چون کاربران واقعی هر دو را می‌نویسند.طراحی پرسش‌های املاییقلب این بنچمارک ۱۲ پرسش املایی است. هر کدام یک کلمه را با املایی متفاوت اما رایج نسبت به متن نوشته‌اند. هیچ‌کدام از این املاها «غلط» نیستند؛ هر دو شکل در متن‌های فارسی واقعی دیده می‌شوند. الگوهای پوشش داده‌شده:ت/ط و تفاوت حروف هم‌آوا: طهران به‌جای تهران، اطاق به‌جای اتاق، بلیط و باطری.همزه و یای میانی: هیات به‌جای هیئت، رییس، مساله به‌جای مسئله.همزه روی کرسی یا ی: پائیز، آئین.برخی کلمه‌ها عمداً بیش از یک بار آمده‌اند، یک بار به شکل عبارت کوتاه («طهران») و یک بار درون جمله‌ای بلندتر («پرجمعیت‌ترین شهر طهران»)، تا بتوان دید سامانه فقط وقتی کلمه تنهاست موفق می‌شود یا در بافت هم.نکته‌ی مهم این است که این مشکل با یکسان‌سازی حروف (مثل تبدیل ی عربی به فارسی) حل نمی‌شود، چون اینجا خود حروف فرق دارند. بنابراین بنچمارک دقیقاً چیزی را می‌سنجد که یکسان‌سازی ساده از آن غافل است.معیارهابنچمارک با معیارهای استاندارد بازیابی گزارش می‌شود:Hit@k: سهم پرسش‌هایی که دست‌کم یک متن مرتبط در k نتیجه‌ی اول آمده است. Hit@1 سخت‌گیرانه‌ترین شکل آن است.MRR@10: میانگین معکوس رتبه‌ی اولین متن مرتبط در ده نتیجه‌ی اول.nDCG@10: کیفیت ترتیب ده نتیجه‌ی اول با در نظر گرفتن جایگاه.Hit@1 (spelling): همان Hit@1 فقط روی ۱۲ پرسش املایی.چون تقریباً هر پرسش فقط یک متن مرتبط دارد، MRR و nDCG رفتار نزدیکی دارند. معیار تفکیک‌کننده‌ی اصلی، شکاف میان Hit@1 کل و Hit@1 املایی است: اگر سامانه روی کل پرسش‌ها خوب است اما روی پرسش‌های املایی افت شدید دارد، مشکل دقیقاً در مقاومت املایی است.یک نکته‌ی آماریبا ۴۲ پرسش، جابه‌جا شدن فقط یک پرسش Hit@k را حدود ۰٫۰۲۴ تغییر می‌دهد (۱ تقسیم بر ۴۲). روی زیرمجموعه‌ی ۱۲ پرسشی املایی، هر پرسش حدود ۰٫۰۸۳ اثر دارد. پس اختلاف‌های کوچک را نباید بیش از حد تفسیر کرد. این بنچمارک برای دیدن پسرفت‌های واضح ساخته شده، نه اختلاف‌های ظریف.نتیجه‌های مرجعنتیجه‌های زیر از persian-rag نسخه‌ی 0.3.1 با embedder آفلاین مبتنی بر hashing (بُعد ۵۱۲) و اندازه‌ی تکه‌ی ۴۰۰ به دست آمده‌اند، همان‌طور که در مستندات بنچمارک پروژه منتشر شده است. به‌طور خلاصه: BM25 ساده به Hit@1 برابر 0.881 و Hit@1 املایی 0.583 می‌رسد، و هر دو پیکربندی «BM25 با بازنویسی پرسش» و «ترکیبی» به Hit@1 برابر 0.952 و Hit@1 املایی 0.833 می‌رسند. جزئیات در جدول زیر است:sparse (BM25): Hit@1: 0.881؛ Hit@5: 0.881؛ MRR@10: 0.881؛ nDCG@10: 0.879؛ Hit@1 (املایی): 0.583sparse + rewrite: Hit@1: 0.952؛ Hit@5: 0.976؛ MRR@10: 0.964؛ nDCG@10: 0.967؛ Hit@1 (املایی): 0.833hybrid: Hit@1: 0.952؛ Hit@5: 0.976؛ MRR@10: 0.964؛ nDCG@10: 0.967؛ Hit@1 (املایی): 0.833خوانش این جدول آموزنده است. BM25 ساده روی کل پرسش‌ها 0.881 می‌گیرد که بد به نظر نمی‌رسد، اما روی پرسش‌های املایی به 0.583 می‌رسد؛ یعنی تقریباً در ۵ پرسش از ۱۲ پرسش املایی جواب درست در رتبه‌ی اول نیست. افزودن بازنویسی پرسش (ساختن شکل‌های املایی دیگر و ادغام نتیجه‌ها) Hit@1 املایی را به 0.833 می‌رساند. بدون برچسب spelling، این تفاوت در میانگین کل گم می‌شد.نحوه‌ی استفادهبارگذاری از Hugging Facefrom datasets import load_dataset&#xA;&#xA;corpus = load_dataset(&#34;sadrmanesh/persian-rag-bench&#34;, &#34;corpus&#34;, split=&#34;test&#34;)&#xA;qa = load_dataset(&#34;sadrmanesh/persian-rag-bench&#34;, &#34;qa&#34;, split=&#34;test&#34;)اجرای بنچمارک پروژهgit clone github.com/sadrmanesh/persian-rag &amp;&amp; cd persian-rag&#xA;pip install -e .&#xA;python benchmarks/run_benchmark.pyاین اسکریپت همه‌ی حالت‌های بازیابی را با و بدون بازنویسی پرسش اجرا می‌کند.استفاده با سامانه‌ی خودتاناگر خط لوله‌ی دیگری دارید، کافی است:متن‌های corpus را با همان یکسان‌سازی‌ای که در سامانه‌ی اصلی‌تان دارید ایندکس کنید.هر پرسش qa را با همان یکسان‌سازی جست‌وجو کنید.شناسه‌های بازیابی‌شده را با relevant_ids مقایسه و معیارها را محاسبه کنید.نتیجه را یک بار برای همه‌ی پرسش‌ها و یک بار فقط برای پرسش‌هایی که tags آن‌ها شامل spelling است گزارش کنید.بهترین کاربرد آن قرار دادنش در CI است: هر تغییر در یکسان‌سازی، تکه‌بندی یا مدل embedding باید Hit@1 املایی را حفظ کند.محدودیت‌هاکوچک و دست‌نویس: با یک یا چند متن مرتبط برای هر پرسش، امتیازها سریع به سقف می‌رسند و برای مقایسه‌ی مدل‌های قوی تفکیک‌کننده نیستند.دامنه‌ی محدود: متن‌ها کوتاه‌اند و بیشتر دانش عمومی درباره‌ی ایران و فرهنگ فارسی‌اند؛ متن‌های حقوقی، فنی یا فروشگاهی را نمایندگی نمی‌کنند.سازنده و ارزیاب یکی است: بنچمارک را خودم ساخته‌ام و همان پروژه‌ای که آن را ساخته، روی آن ارزیابی شده است. این خطر بیش‌برازش به بنچمارک را دارد.تنها ۱۲ پرسش املایی: پوشش الگوهای املایی کامل نیست.متن پیش‌یکسان‌سازی نشده: داده عمداً یکسان‌سازی نشده است. هر دو طرف را به یک روش یکسان‌سازی کنید.جمع‌بندییک بنچمارک لازم نیست بزرگ باشد تا مفید باشد؛ باید پرسش درستی بپرسد. persian-rag-bench یک پرسش مشخص می‌پرسد: آیا خط لوله‌ی بازیابی فارسی شما وقتی کاربر «طهران» یا «اطاق» می‌نویسد هم متن درست را پیدا می‌کند؟ با برچسب جداگانه برای پرسش‌های املایی، این پرسش در هر اجرای CI در چند ثانیه پاسخ داده می‌شود. پیشنهاد و مشارکت برای گسترش پرسش‌ها، به‌خصوص در حوزه‌های تخصصی‌تر، را با کمال میل می‌پذیرم.منابع و لینک‌هادیتاست در Figshare، DOI: 10.6084/m9.figshare.34913889دیتاست در Hugging Face: huggingface.co/datasets/sadrmanesh/persian-rag-benchگزارش فنی persian-rag در Zenodo، DOI: 10.5281/zenodo.23923535کد منبع persian-rag: github.com/sadrmanesh/persian-ragمستندات بنچمارک: sadrmanesh.github.io/persian-rag/benchmarksکد و مستندات پروژه: huggingface.co/datasets/sadrmanesh/persian-rag-bench</description><category>محمد ناصر صدرمنش</category><author>محمد ناصر صدرمنش</author><pubDate>Wed, 04 Nov 2026 20:03:02 +0330</pubDate></item><item><title>مهارت‌های قابل ارزیابی برای عامل‌های برنامه‌نویس در تجارت الکترونیک: مطالعه‌ی موردی وب‌هوک‌های تکراری و تحویل دوباره</title><link>https://virgool.ir/@sadrmanesh/مهارت-های-قابل-ارزیابی-برای-عامل-های-برنامه-نویس-در-تجارت-الکترونیک-مطالعه-ی-موردی-وب-هوک-های-تکراری-و-تحویل-دوباره-yd2aapwr7ecm</link><description>مقدمهعامل‌های برنامه‌نویس (coding agents) امروز بخش زیادی از کد فروشگاه‌های اینترنتی را می‌نویسند یا تغییر می‌دهند: افزونه‌ی ووکامرس، اتصال درگاه پرداخت، پردازش وب‌هوک سفارش. این عامل‌ها دانش عمومی خوبی دارند، اما دانش «تجربی» یک مهندس ارشد تجارت الکترونیک را ندارند؛ چیزهایی مثل اینکه چرا نباید روی شناسه‌ی تلاش تحویل dedupe کرد یا چرا امضای وب‌هوک باید روی بایت‌های خام بررسی شود.یکی از راه‌های انتقال این دانش، «مهارت» (skill) است: پوشه‌ای با یک فایل SKILL.md که نام، توضیح و دستورالعمل دارد و عامل وقتی کار با توضیح آن مطابقت داشت، آن را بارگذاری می‌کند. من مجموعه‌ای متن‌باز از این مهارت‌ها به نام commerce-agent-skills ساخته‌ام. در این یادداشت دو موضوع را توضیح می‌دهم: اینکه چطور می‌شود مهارت را مثل کد قابل آزمون کرد، و یک مطالعه‌ی موردی فنی درباره‌ی رایج‌ترین خطای پنهان در پردازش وب‌هوک، یعنی تحویل دوباره پس از timeout.مهارت چیست و با «قاعده» چه فرقی دارد؟این مجموعه از قالب Agent Skills پیروی می‌کند: یک پوشه، یک فایل SKILL.md با فیلدهای name و description، و در صورت نیاز فایل‌های مرجع و اسکریپت‌های کوچک. عامل ابتدا فقط نام و توضیح را می‌خواند و تصمیم می‌گیرد مهارت به کار مربوط است یا نه؛ بدنه‌ی مهارت و فایل‌های مرجع فقط وقتی لازم شد بارگذاری می‌شوند. این طراحی باعث می‌شود پنجره‌ی متنی عامل با دانش نامربوط پر نشود.کنار هر مهارت یک «قاعده» (rule) هم هست: یک فایل Markdown کوتاه که می‌توان آن را همیشه در متن عامل نگه داشت. تفاوت را این‌طور خلاصه می‌کنم: مهارت برای یک کار است، قاعده برای همه‌ی کارهای یک حوزه.نسخه‌ی 0.3.3 مجموعه ۲۹ مهارت دارد؛ از توسعه‌ی افزونه‌ی ووکامرس و امنیت افزونه‌ی وردپرس تا پرداخت Stripe، درگاه‌های ایرانی مانند زرین‌پال، موجودی چندانباره، مرجوعی و بازپرداخت، قیمت‌گذاری B2B، بومی‌سازی فارسی و راست‌به‌چپ، دسترس‌پذیری و کارایی ویترین، و طراحی ابزارهای MCP برای فروشگاه. (طراحی ابزارهای نوشتن امن در یک سرور MCP را در یادداشت «الگوی پیش‌نمایش، تأیید و ثبت در یک سرور MCP برای ووکامرس» شرح داده‌ام.)اصل محوری: با مهارت مثل کد رفتار کنیدمتنی که قرار است رفتار یک عامل را شکل دهد، اگر آزمون نداشته باشد، به‌مرور خراب می‌شود: توضیحش با محتوایش ناهماهنگ می‌شود، اصطلاحی از قلم می‌افتد یا یک تغییر ظاهراً بی‌خطر نکته‌ای کلیدی را حذف می‌کند. برای جلوگیری از این، سه لایه‌ی کنترل در نظر گرفته‌ام.۱. وظیفه‌های ارزیابی و نمونه‌ی حل‌شدههر مهارت یک فایل evals/tasks.json دارد. هر وظیفه یک صورت مسئله و دو فهرست بررسی است:must_include: رشته‌هایی که پاسخ باید داشته باشد، معمولاً نام API یا فیلد.must_not_include: رشته‌هایی که پاسخ نباید داشته باشد، معمولاً نشانه‌ی یک اشتباه رایج.برای هر وظیفه یک نمونه‌ی حل‌شده هم هست که همه‌ی بررسی‌ها را پاس می‌کند. در آخرین نسخه‌ی شاخه‌ی اصلی مخزن، مجموع وظیفه‌های ارزیابی ۶۲ مورد است.۲. اعتبارسنج سخت‌گیراعتبارسنج نسخه‌ها، بخش‌های لازم، سرفصل‌های قاعده، پوشش دسته‌ها و سبک نگارش را بررسی می‌کند. مهم‌ترین بررسی این است: اگر رشته‌ای در must_include باشد که در خود مهارت، قاعده یا مرجع‌هایش آموزش داده نشده، اعتبارسنج خطا می‌دهد. یعنی نمی‌شود از عامل انتظار چیزی را داشت که مهارت به او یاد نداده است.۳. اجراکننده‌ی ارزیابیاسکریپت run-evals پاسخ‌هایی را که از یک عامل جمع کرده‌اید امتیاز می‌دهد. امتیاز هر وظیفه نسبت بررسی‌های پاس‌شده به کل بررسی‌هاست و وظیفه فقط وقتی پاس است که همه‌ی بررسی‌ها پاس شوند. فایل پاسخ گم‌شده صفر می‌گیرد.node scripts/run-evals.mjs&#xA;node scripts/run-evals.mjs --answers ./answers --min-score 0.8فرمان اول نمونه‌های حل‌شده را بررسی می‌کند. فرمان دوم پاسخ‌ها را از مسیر answers/&lt;skill&gt;/&lt;task-id&gt;.md می‌خواند و اگر امتیاز زیر آستانه باشد با کد خروج ۱ پایان می‌یابد، پس می‌توان آن را در CI گذاشت.یک نکته‌ی صادقانه: این بررسی‌ها از نوع تطبیق زیررشته‌اند. پاس شدن آن‌ها درستی پاسخ را فراتر از رشته‌هایی که نام‌برده شده ثابت نمی‌کند. اجراکننده هم خودش وظیفه‌ها را به عامل زنده نمی‌فرستد؛ پاسخ‌هایی را که شما جمع کرده‌اید امتیاز می‌دهد.مطالعه‌ی موردی: وب‌هوک تکراری پس از timeoutدو حالت هم‌زمان و شکست نیمه‌کاره که در ادامه آمده‌اند، از بازخورد خوانندگان به مجموعه اضافه شدند؛ این تجربه را در نوشته‌ای درباره‌ی بازخورد خوانندگان توضیح داده‌ام.صورت مسئلهفرض کنید فروشگاه شما وب‌هوک «سفارش پرداخت شد» را دریافت می‌کند. گیرنده رویداد را ذخیره می‌کند، سفارش را به‌روز می‌کند، دستور ارسال (fulfillment) را می‌سازد و ایمیل تأیید می‌فرستد. اما فرستنده پیش از رسیدن پاسخ 2xx دچار timeout می‌شود و همان رویداد را دوباره می‌فرستد؛ اغلب با یک شناسه‌ی تحویل جدید.اگر گیرنده خودتوان (idempotent) نباشد، نتیجه دو بار ارسال، دو ایمیل یا دو بار کم شدن موجودی است. این رایج‌ترین نوع تکرار در عمل است و بی‌صدا رخ می‌دهد.چرا راه‌حل‌های ساده کار نمی‌کنند؟dedupe روی شناسه‌ی تحویل. در ووکامرس، هدر X-WC-Webhook-Delivery-ID برای هر تلاش یک شناسه‌ی لاگ تازه است. پس تحویل دوباره با این کلید «جدید» دیده می‌شود. به همین دلیل در وظیفه‌ی ارزیابی این رشته در فهرست must_not_include آمده است.پاسخ 200 پیش از ذخیره. اگر فرایند بعد از پاسخ و پیش از نوشتن در پایگاه داده از کار بیفتد، رویداد برای همیشه گم می‌شود.انجام همه‌ی کارها پیش از پاسخ. گیرنده‌ی کندی که رویداد را اعمال می‌کند و ایمیل را می‌فرستد و بعد پاسخ می‌دهد، خودش timeout را دعوت می‌کند.الگوی درست: صندوق ورودی پایداررویه‌ای که مهارت order-and-webhook-reliability آموزش می‌دهد چنین است:بدنه‌ی خام را پیش از هر تجزیه‌ی JSON بخوانید.امضا را با مقایسه‌ی زمان‌ثابت بررسی کنید و درخواست بدون امضا یا با امضای نامعتبر را پیش از نگاه کردن به نوع رویداد رد کنید.رویداد را در یک صندوق ورودی پایدار (durable inbox) ذخیره کنید که روی یک کلید پایدار یکتا باشد.فقط پس از موفقیت ذخیره پاسخ 2xx بدهید.تغییر را فقط اگر این رویداد از نسخه‌ی ذخیره‌شده جدیدتر است اعمال کنید.اگر رویداد ناقص یا خارج از ترتیب بود، سفارش را از منبع حقیقت بخوانید و همان را بنویسید.کلید یکتای عملی: شناسه‌ی رویداد ارائه‌دهنده، اگر در تلاش‌های دوباره ثابت می‌ماند؛ در غیر این صورت هش موضوع، شناسه‌ی منبع و نسخه‌ی منبع (در ووکامرس date_modified_gmt). هر ردیف صندوق وضعیتی دارد: stored، applied یا ignored.نکته‌ی ظریف: اثرهای جانبیکلید یکتا به‌تنهایی کافی نیست. تحویل دوباره باید ردیف را در وضعیت applied پیدا کند، 2xx برگرداند و هیچ اثر جانبی اجرا نکند. این فقط وقتی برقرار است که هر اثر جانبی (ایمیل، موجودی، ارسال) در همان تراکنشی ثبت شود که ردیف را applied می‌کند، یا خودش روی شناسه‌ی رویداد کلید خورده باشد. شبه‌کد زیر فقط یک نمونه‌ی مفهومی برای توضیح ایده است و کد خود مخزن نیست:BEGIN&#xA;  row = inbox.insert_or_get(event_key)&#xA;  if row.status == &#34;applied&#34;: COMMIT; return 2xx&#xA;  apply_order_change(event)&#xA;  outbox.add(&#34;send_email&#34;, key=event_key)&#xA;  outbox.add(&#34;create_fulfillment&#34;, key=event_key)&#xA;  row.status = &#34;applied&#34;&#xA;COMMIT&#xA;return 2xxیک کارگر جداگانه صف خروجی (outbox) را پردازش می‌کند و چون هر ورودی روی شناسه‌ی رویداد کلید خورده، تکرار آن هم بی‌اثر است.دو حالت دشوارتر: تحویل هم‌زمان و شکست نیمه‌کارهتحویل هم‌زمان. گاهی همان رویداد در یک لحظه به دو سرور یا دو کارگر می‌رسد. اگر گیرنده اول صندوق را بخواند، چیزی پیدا نکند و بعد درج و اعمال کند (الگوی «بررسی، سپس درج»)، هر دو درخواست از بررسی رد می‌شوند و سفارش دو بار اعمال می‌شود. راه درست این است که رویداد با یک درج اتمی روی کلید یکتا «تصاحب» شود، مثلاً INSERT ... ON CONFLICT DO NOTHING، و فقط درخواستی که ردیف را واقعاً ساخته تغییر را اعمال کند. درخواست دیگر 2xx می‌دهد و هیچ کاری انجام نمی‌دهد. قفل کردن کل جدول لازم نیست؛ کلید یکتا تصمیم می‌گیرد.شکست نیمه‌کاره. تصاحب نباید یک شکست را پنهان کند. اگر تحویل اول ردیف صندوق را درج کند و پیش از commit شدن تغییر سفارش از کار بیفتد، تحویل دوباره‌ای که فقط وجود ردیف را ببیند و 2xx بدهد، رویداد را برای همیشه گم می‌کند و سفارش هرگز «پرداخت‌شده» نمی‌شود. راه‌حل این است که تغییر در همان تراکنشی اعمال شود که ردیف را applied می‌کند، تا خرابی هر دو را rollback کند؛ ردیفی که پس از timeout هنوز در وضعیت pending مانده، قابل تلاش دوباره است و تحویل بعدی آن را دوباره تصاحب و کار را کامل می‌کند. پاک کردن ردیف صندوق راه‌حل نیست. افزون بر این، برای هر تحویل یک ورودی ممیزی با کلید شناسه‌ی رویداد ثبت می‌شود که می‌گوید آن تحویل چه کرد؛ مثلاً اول «failed: rolled back before apply» و بعد «applied». نتیجه: تاریخچه‌ی سفارش دقیقاً یک تغییر وضعیت و هر دو تلاش را نشان می‌دهد.وظیفه‌های ارزیابی متناظراین سناریو در نسخه‌ی 0.3.2 به شکل وظیفه‌ی redelivery-after-timeout اضافه شد. صورت مسئله همین داستان است و پاسخ باید چهار مفهوم applied، 2xx، side effect و timeout را داشته باشد و نباید به X-WC-Webhook-Delivery-ID تکیه کند. همان نسخه توضیح این حالت را به خود مهارت، فهرست بررسی و «دام‌ها»یش و یک خط متناظر به قاعده اضافه کرد؛ چون اعتبارسنج اجازه نمی‌دهد وظیفه چیزی بخواهد که مهارت یاد نداده.پس از آن دو وظیفه‌ی دیگر هم به همین مهارت اضافه شد. وظیفه‌ی concurrent-duplicate-delivery از پاسخ می‌خواهد مفاهیم unique key، ON CONFLICT، concurrent و 2xx را داشته باشد و نه به X-WC-Webhook-Delivery-ID تکیه کند و نه «قفل کل جدول» را پیشنهاد دهد. وظیفه‌ی redelivery-after-partial-failure مفاهیم pending، same transaction، audit، exactly one و rolled back را می‌خواهد و پاک کردن ردیف صندوق را رد می‌کند. به این ترتیب این مهارت اکنون پنج وظیفه‌ی ارزیابی دارد.جزئیات خاص ووکامرسامضای ووکامرس به شکل base64(hmac_sha256(secret, raw_body)) است. تجزیه‌ی JSON و امضای دوباره‌ی JSON.stringify هرگز مطابقت نمی‌کند، چون فرستنده بایت‌های اصلی را امضا کرده است.ووکامرس پس از بیش از پنج شکست پیاپی وب‌هوک را غیرفعال می‌کند (فیلتر woocommerce_max_webhook_delivery_failures با مقدار پیش‌فرض ۵).پینگ فعال‌سازی رویداد سفارش نیست؛ 2xx بدهید و سفارشی نسازید.اگر پایگاه داده در دسترس نیست، 503 برگردانید تا فرستنده دوباره بفرستد؛ کلید یکتا تکرار را امن می‌کند.نصب و استفادهnpx @sadrmanesh/commerce-agent-skills init&#xA;npx @sadrmanesh/commerce-agent-skills add order-and-webhook-reliability stripe-paymentsفرمان اول همه‌ی مهارت‌ها را در .agents/skills/ و همه‌ی قاعده‌ها را در .agents/rules/ کپی می‌کند. فرمان دوم فقط مهارت‌های نام‌برده را نصب می‌کند.محدودیت‌هاارزیابی‌ها مبتنی بر زیررشته‌اند؛ پاسخی می‌تواند رشته‌ها را داشته باشد و همچنان اشتباه باشد. خواندن پاسخ‌های ناموفق و نمونه‌ای از پاسخ‌های موفق لازم است.نمونه‌های حل‌شده امتیاز یک عامل زنده نیستند.رویه‌ی صندوق ورودی فرض می‌کند پایگاه داده تراکنش یا قید یکتایی دارد.جمع‌بندیدانش تجربی تجارت الکترونیک را می‌توان به شکلی بسته‌بندی کرد که عامل‌های برنامه‌نویس از آن استفاده کنند، اما این بسته‌بندی باید مثل کد آزمون داشته باشد. مطالعه‌ی موردی وب‌هوک تکراری نشان می‌دهد چرا: درس اصلی، یعنی «dedupe روی نسخه‌ی منبع، نه شناسه‌ی تحویل، و اثرهای جانبی داخل همان تراکنش»، در یک جمله جا می‌شود، اما اگر از متن مهارت حذف شود، عامل با اطمینان کامل کدی می‌نویسد که مشتری را دو بار ایمیل می‌کند.منابع و لینک‌هاکد منبع: github.com/sadrmanesh/commerce-agent-skillsگزارش فنی در Zenodo، DOI: 10.5281/zenodo.23883598نسخه‌ی نرم‌افزار در Zenodo، DOI: 10.5281/zenodo.23944676مستندات: sadrmanesh.github.io/commerce-agent-skillsدیتاست مهارت‌ها و ارزیابی‌ها: huggingface.co/datasets/sadrmanesh/commerce-agent-skillsقالب Agent Skills: agentskills.io/specificationکد و مستندات پروژه: github.com/sadrmanesh/commerce-agent-skills</description><category>محمد ناصر صدرمنش</category><author>محمد ناصر صدرمنش</author><pubDate>Wed, 04 Nov 2026 20:01:51 +0330</pubDate></item><item><title>ارزیابی کیفیت جست‌وجوی فروشگاهی با داوران مدل زبانی: از شکاف برچسب‌ها تا آزمون معناداری</title><link>https://virgool.ir/@sadrmanesh/ارزیابی-کیفیت-جست-وجوی-فروشگاهی-با-داوران-مدل-زبانی-از-شکاف-برچسب-ها-تا-آزمون-معناداری-dlbqejez238j</link><description>مقدمه: «جست‌وجوی جدید واقعاً بهتر است؟»هر تیمی که روی جست‌وجوی یک فروشگاه اینترنتی کار می‌کند، دیر یا زود با این پرسش روبه‌رو می‌شود. از BM25 به جست‌وجوی برداری رفته‌اید، یک مرحله‌ی رتبه‌بندی دوباره (reranker) اضافه کرده‌اید یا بازنویسی پرسش را فعال کرده‌اید. عددهای ارزیابی آفلاین هم کمی بالا رفته‌اند. اما آیا این بهبود واقعی است، یا حاصل نویز، برچسب‌های ناقص و یک داور یک‌طرفه؟در این یادداشت روشی را توضیح می‌دهم که برای پاسخ دادن به همین پرسش به کار می‌برم و آن را در یک ابزار متن‌باز پایتون به نام llm-search-eval پیاده کرده‌ام (کد منبع در GitHub). تمرکز یادداشت روی ایده‌هاست، نه روی خود ابزار: این ایده‌ها در هر سامانه‌ی ارزیابی جست‌وجو قابل پیاده‌سازی‌اند.ارزیابی آفلاین معمولاً کجا خراب می‌شود؟ارزیابی آفلاین جست‌وجو یعنی مجموعه‌ای از پرسش‌ها، فهرستی از محصولات و برچسب‌هایی که می‌گویند هر محصول برای هر پرسش چقدر مرتبط است. سامانه‌ها روی همین پرسش‌ها اجرا می‌شوند و نتیجه‌شان با معیارهای بازیابی اطلاعات سنجیده می‌شود. در عمل، این روش معمولاً در پنج جا می‌لنگد:برچسب‌ها تمام می‌شوند. مجموعه‌ی طلایی (gold set) که فصل پیش برچسب خورده، محصولاتی را که بازیاب جدید امروز پیدا می‌کند پوشش نمی‌دهد. نتیجه‌های بدون برچسب بی‌صدا «نامرتبط» حساب می‌شوند و سامانه‌ی جدید بدتر از واقعیت دیده می‌شود.میانگین‌ها نویز را پنهان می‌کنند. دو درصد بهبود nDCG روی پنجاه پرسش به‌راحتی می‌تواند تصادفی باشد.یک مدل، یک نظر است. یک داور مدل زبانی سوگیری‌های خودش را دارد و معمولاً کسی بررسی نمی‌کند که چقدر با برچسب‌گذارهای انسانی هم‌نظر است.هزینه بی‌صدا بالا می‌رود. داوری هزاران جفت «پرسش ـ محصول» با یک مدل بزرگ گران است و معمولاً تا رسیدن صورت‌حساب کسی متوجه نمی‌شود.کاتالوگ‌های چندزبانه نامرتب‌اند. متن فارسی ترکیبی از حروف عربی و فارسی، چند نظام رقم و نیم‌فاصله است و تطبیق ساده را خراب می‌کند.اصل اول: برچسب طلایی مقدم است، داور فقط شکاف‌ها را پر می‌کنداولین تصمیم طراحی این است که برچسب انسانی همیشه بر داوری مدل اولویت دارد. داور مدل زبانی فقط جفت‌هایی را داوری می‌کند که در نتیجه‌های برتر یکی از سامانه‌ها آمده‌اند و برچسب طلایی ندارند. به این ترتیب مشکل «شکاف برچسب» حل می‌شود، بدون اینکه برچسب‌های قابل‌اعتماد انسانی کنار گذاشته شوند.نکته‌ی مهم دیگر این است که همه‌ی سامانه‌ها روی یک مخزن برچسب مشترک سنجیده شوند. اگر محصولی برای سامانه‌ی B داوری شده، همان برچسب برای سامانه‌ی A هم به کار می‌رود. در غیر این صورت مقایسه منصفانه نیست.برچسب‌ها درجه‌بندی‌شده‌اند: از ۰ (نامرتبط) تا ۳ (کاملاً مرتبط). برای معیارهای دودویی، درجه‌ی ۲ و بالاتر به‌طور پیش‌فرض «مرتبط» حساب می‌شود.اصل دوم: یک داور کافی نیست؛ هیئت داوران و توافقبه‌جای یک مدل، می‌توان چند مدل را به‌عنوان «هیئت داوران» به کار برد و رأی‌ها را با یکی از قاعده‌های اکثریت، میانگین، میانه، کمینه یا بیشینه ترکیب کرد. اگر یکی از داوران خطا بدهد (مثلاً timeout)، جفت از دست نمی‌رود، به شرط اینکه حداقل تعداد رأی لازم جمع شده باشد.اما هیئت داوران تنها وقتی ارزش دارد که توافق آن‌ها اندازه‌گیری شود. آماره‌هایی که در این روش گزارش می‌شوند:سهم اتفاق آرا: جفت‌هایی که همه‌ی داوران درجه‌ی یکسان داده‌اند.کاپای فلایس (Fleiss&#39; kappa): توافق اصلاح‌شده برای شانس، برای هر تعداد داور.آلفای کریپندورف (Krippendorff&#39;s alpha): با نسخه‌های اسمی، ترتیبی و فاصله‌ای. برای درجه‌های ۰ تا ۳، نسخه‌ی ترتیبی آموزنده‌ترین است و رأی‌های گم‌شده را هم تحمل می‌کند.کاپای کوهن دوبه‌دو: ساده و وزن‌دار درجه‌دوم برای هر جفت داور.به‌عنوان یک قاعده‌ی سرانگشتی، کاپا یا آلفای بالای ۰٫۸ توافق قوی، بین ۰٫۶ و ۰٫۸ توافق قابل‌توجه، و زیر ۰٫۴ نشانه‌ی این است که دستورالعمل داوری یا مدل‌ها نیاز به اصلاح دارند.واسنجی در برابر برچسب‌های انسانیپرسش مهم‌تر این است که داور چقدر با انسان هم‌نظر است. در حالت واسنجی (calibration)، جفت‌هایی که برچسب طلایی دارند هم به داور فرستاده می‌شوند. امتیازها همچنان با برچسب طلایی محاسبه می‌شوند و داوری‌های اضافه فقط برای جدول توافق به کار می‌روند. این جدول شامل توافق دقیق، توافق با اختلاف حداکثر یک درجه، توافق دودویی در آستانه‌ی ارتباط، کاپای کوهن و کاپای وزن‌دار، سوگیری میانگین (مثبت یعنی داور از انسان سهل‌گیرتر است) و ماتریس درهم‌ریختگی است. برای کنترل هزینه می‌توان فقط یک نمونه‌ی تصادفی تکرارپذیر از جفت‌ها را واسنجی کرد.اصل سوم: هر مقایسه یک آزمون معناداری لازم داردمعیارهای کلاسیک بازیابی اطلاعات پایه‌ی کارند: nDCG@k با بهره‌ی نمایی (2^rel − 1) یا خطی، دقت@k، بازخوانی@k، MRR و MAP. اما گزارش میانگین به‌تنهایی کافی نیست.هر سامانه با سامانه‌ی پایه با یک آزمون تصادفی‌سازی جفتی دوطرفه (sign-flip) روی مقدار هر پرسش از معیار اصلی مقایسه می‌شود. این آزمون فرض نرمال بودن ندارد و انتخاب رایج در ارزیابی بازیابی اطلاعات است. کنار آن، برای هر میانگین و هر اختلاف، فاصله‌ی اطمینان بوت‌استرپ و شمار برد، تساوی و باخت پرسش‌ها گزارش می‌شود.یک جزئیات روش‌شناختی مهم: پرسشی که در مخزن برچسب هیچ محصول مرتبطی ندارد، معیارهایش تعریف‌نشده است. چنین پرسشی به‌جای اینکه صفر حساب شود از میانگین کنار گذاشته می‌شود و تعداد پرسش‌های واقعاً شمرده‌شده (n) برای هر میانگین ثبت می‌شود.اصل چهارم: داوری دوبه‌دو و خنثی کردن سوگیری جایگاهوقتی دو سامانه از نظر nDCG نزدیک‌اند یا برچسب‌ها کم‌اند، داوری دوبه‌دو (pairwise) یک نظر دوم مفید است. در این روش داور برای هر پرسش، k نتیجه‌ی برتر دو سامانه را کنار هم می‌بیند و می‌گوید خریدار کدام فهرست را ترجیح می‌دهد: A، B یا تساوی.مدل‌های زبانی سوگیری جایگاه دارند و گاهی گزینه‌ی اول را ترجیح می‌دهند. برای خنثی کردن آن، هر پرسش دو بار داوری می‌شود: یک بار A اول و یک بار B اول. سامانه فقط وقتی پرسش را می‌برد که هر دو ترتیب هم‌نظر باشند؛ اگر دو ترتیب برنده‌ی متضاد انتخاب کنند، پرسش «ناسازگار» ثبت می‌شود که خودش معیاری از پایداری داور است. نتیجه با نرخ برد، فاصله‌ی ویلسون و آزمون علامت گزارش می‌شود.اصل پنجم: هزینه و منشأ هر داوری باید ثبت شوددو موضوع عملی اغلب نادیده گرفته می‌شوند:هزینه. مصرف توکن برای هر مدل شمرده می‌شود، قیمت را کاربر تعیین می‌کند و می‌توان برای هزینه، توکن یا تعداد داوری سقف گذاشت. پیش از اجرا هم می‌توان تعداد فراخوانی، توکن و هزینه را تخمین زد. یک حافظه‌ی نهان مبتنی بر محتوا باعث می‌شود اجرای دوباره هزینه‌ی تکراری نداشته باشد.منشأ (provenance). نتیجه‌ی داور مدل زبانی به سه چیز بستگی دارد: مدل، ارائه‌دهنده و متن پرامپت. از نسخه‌ی 0.4.0 هر پرامپت یک نسخه‌ی دستی و یک هش دارد: ۱۲ نویسه‌ی اول SHA-256 روی متن کامل پرامپت. این هش بخشی از کلید حافظه‌ی نهان است، پس داوری‌ای که با پرامپت دیگری انجام شده هرگز از حافظه‌ی نهان برنمی‌گردد. در تغییرات منتشرنشده‌ی بعدی، نسخه‌ی تاریخ‌دار مدلی که ارائه‌دهنده واقعاً پاسخ داده هم ثبت می‌شود و اگر در یک اجرا چند نسخه از یک مدل دیده شود، گزارش هشدار می‌دهد که امتیازها مستقیماً قابل مقایسه نیستند. دلیلش این است که ارائه‌دهنده‌ها گاهی مدل را بی‌صدا به‌روز می‌کنند.اصل ششم: فارسی شهروند درجه‌یک استمشکلات خاص متن فارسی در جست‌وجو را پیش‌تر در یادداشت «ی عربی، نیم‌فاصله و PDF برعکس» شرح داده‌ام؛ اینجا فقط به اثر آن‌ها بر ارزیابی می‌پردازم.برای پرسش‌های فارسی، پیش از تطبیق، «ي» و «ك» عربی به «ی» و «ک» فارسی تبدیل می‌شوند، ارقام فارسی و عربی یکسان می‌شوند، اعراب، کشیده و نویسه‌های جهت حذف می‌شوند و نیم‌فاصله مرز کلمه حساب می‌شود. زبان پرسش اگر مشخص نشده باشد از روی خط تشخیص داده می‌شود و گزارش، معیار اصلی را به تفکیک زبان نشان می‌دهد. پرامپت داور هم زبان پرسش را ذکر می‌کند و ترجمه یا نویسه‌گردانی میان فارسی و انگلیسی را تطابق حساب می‌کند؛ مثلاً پرسش «گوشی سامسونگ» با محصولی به نام Samsung Galaxy.یک مثال عملیمخزن پروژه یک کاتالوگ کوچک دوزبانه‌ی لوازم الکترونیکی با سه سامانه برای مقایسه دارد. با داور شبیه‌ساز (mock)، که قطعی است و برای CI و اجرای آفلاین ساخته شده، می‌توان بدون هیچ هزینه‌ای اجرا کرد:pip install llm-search-eval&#xA;llm-search-eval run \&#xA;  --queries  examples/electronics/queries.jsonl \&#xA;  --products examples/electronics/products.jsonl \&#xA;  --qrels    examples/electronics/qrels.csv \&#xA;  --system   bm25=examples/electronics/runs/bm25.jsonl \&#xA;  --system   semantic=examples/electronics/runs/semantic.csv \&#xA;  --system   hybrid=examples/electronics/runs/hybrid.trec \&#xA;  --judge mock --output-dir reports/در این داده‌ی نمونه، nDCG@10 برای bm25 برابر 0.5305، برای semantic برابر 0.9865 و برای hybrid برابر 0.9767 است. این عددها فقط برای نمایش کارکرد ابزار روی یک داده‌ی کوچک نمونه‌اند و نباید به‌عنوان مقایسه‌ی عمومی روش‌ها خوانده شوند.برای داور واقعی کافی است داور، قیمت و سقف بودجه را مشخص کنید:llm-search-eval run ... --judge openai=gpt-4o-mini \&#xA;  --cache .judge-cache.jsonl --price gpt-4o-mini=0.15,0.60 --max-cost 2خروجی شامل گزارش Markdown و JSON، یک داشبورد HTML مستقل (بدون سرور و CDN)، گزارش هزینه و فایل همه‌ی داوری‌هاست.از ارزیابی تا دروازه‌ی کیفیت در CIارزیابی وقتی بیشترین ارزش را دارد که خودکار باشد. با فرمان gate یا GitHub Action پروژه می‌توان قاعده‌هایی مثل «nDCG@10 نباید از فلان کف پایین‌تر برود» یا «نسبت به سامانه‌ی فعلی نباید افت کند» تعریف کرد. اگر تغییری در رتبه‌بندی این قاعده‌ها را نقض کند، build شکست می‌خورد و نتیجه در یک کامنت به‌روزشونده روی pull request نمایش داده می‌شود. اجراها را هم می‌توان مستقیماً از Elasticsearch، OpenSearch، Algolia، Meilisearch، Typesense یا هر API جست‌وجوی HTTP/JSON گرفت.محدودیت‌هاداور مدل زبانی جایگزین برچسب‌گذار انسانی نیست؛ واسنجی فقط میزان هم‌نظری را نشان می‌دهد و آن را تضمین نمی‌کند.آزمون معناداری روی مجموعه‌ای کوچک از پرسش‌ها توان آماری کمی دارد. «معنادار نبودن» به معنای «برابر بودن» نیست.داور شبیه‌ساز فقط پوشش واژه‌ها را می‌سنجد و برای آزمون خط لوله ساخته شده، نه برای قضاوت درباره‌ی کیفیت.مقایسه‌ی دوبه‌دو هیچ معیار نقطه‌ای را تغییر نمی‌دهد و باید کنار آن خوانده شود.جمع‌بندیبرای اینکه بگوییم جست‌وجوی جدید واقعاً برای خریداران بهتر است، چهار چیز لازم است: پر کردن شکاف برچسب‌ها با داوری که توافقش با انسان سنجیده شده، مقایسه‌ی همه‌ی سامانه‌ها روی یک مخزن برچسب مشترک، یک آزمون معناداری جفتی برای هر اختلاف، و ثبت هزینه و منشأ هر داوری. هیچ‌کدام از این‌ها پیچیده نیست، اما نبود هر کدام می‌تواند تصمیم اشتباهی را با عددهای قانع‌کننده توجیه کند.منابع و لینک‌هاکد منبع llm-search-eval: github.com/sadrmanesh/llm-search-evalنسخه‌ی نرم‌افزار در Zenodo، DOI: 10.5281/zenodo.23944666مستندات: sadrmanesh.github.io/llm-search-evalداشبورد نمونه: sadrmanesh.github.io/llm-search-eval/demo/report.htmlنسخه‌ی قابل اجرا در مرورگر (Hugging Face Space): huggingface.co/spaces/sadrmanesh/llm-search-eval-demoداده‌ی نمونه: huggingface.co/datasets/sadrmanesh/llm-search-eval-electronicsکد و مستندات پروژه: github.com/sadrmanesh/llm-search-eval</description><category>محمد ناصر صدرمنش</category><author>محمد ناصر صدرمنش</author><pubDate>Wed, 04 Nov 2026 20:00:47 +0330</pubDate></item><item><title>طراحی ابزارهای نوشتن امن برای عامل‌های هوش مصنوعی در فروشگاه‌های آنلاین: الگوی پیش‌نمایش، تأیید و ثبت در یک سرور MCP</title><link>https://virgool.ir/@sadrmanesh/طراحی-ابزارهای-نوشتن-امن-برای-عامل-های-هوش-مصنوعی-در-فروشگاه-های-آنلاین-الگوی-پیش-نمایش-تأیید-و-ثبت-در-یک-سرور-mcp-cq3l5vszr2bi</link><description>چکیدهبا گسترش پروتکل Model Context Protocol (MCP)، اتصال عامل‌های مبتنی بر مدل‌های زبانی بزرگ به سامانه‌های عملیاتی مانند فروشگاه‌های آنلاین ساده شده است. اما دسترسی خواندنی و دسترسی نوشتنی از نظر ریسک هم‌ارز نیستند: عاملی که می‌تواند قیمت را تغییر دهد، سفارش را تکمیل کند یا مبلغی را بازگرداند، روی داده و پول مشتریان واقعی اثر مستقیم دارد. این مقاله طراحی ابزارهای نوشتن را در woocommerce-mcp، یک سرور متن‌باز MCP برای مدیریت فروشگاه‌های ووکامرس، شرح می‌دهد و تحلیل می‌کند. این سرور در نسخه 0.4.0 دارای 47 ابزار است که 28 ابزار آن خواندنی و 19 ابزار آن نوشتنی است. سازوکارهای اصلی عبارت‌اند از: حالت پیش‌فرض فقط خواندنی برای هر فروشگاه، چرخه دومرحله‌ای پیش‌نمایش و تأیید که در آن پیش‌نمایش با اجرای همان کد واقعی ابزار روی یک کلاینت برنامه‌ریز ساخته می‌شود، شناسه previewId که به‌صورت یک اثر انگشت بی‌حالت از نام ابزار، شناسه فروشگاه و آرگومان‌ها محاسبه می‌شود و تأیید یک درخواست متفاوت را رد می‌کند، و یک گزارش ممیزی با قالب JSON که تصمیم‌ها را ثبت می‌کند و نتایج خواندن را ثبت نمی‌کند. این سازوکارها با سه ریشه خطر «عاملیت بیش از حد» در فهرست OWASP تطبیق داده شده‌اند. در بخش بحث، محدودیت‌های طرح، از جمله اینکه شناسه پیش‌نمایش به محتوای درخواست گره می‌خورد و نه به وضعیت فروشگاه، بررسی و یک بررسی پیش‌شرط به‌عنوان کار آینده پیشنهاد می‌شود.کلیدواژه‌ها: عامل هوش مصنوعی، Model Context Protocol، ووکامرس، امنیت ابزار، انسان در حلقه، گزارش ممیزی، عاملیت بیش از حدAbstractThe Model Context Protocol (MCP) makes it straightforward to connect agents built on large language models to operational systems such as online stores. Read access and write access, however, carry very different risks: an agent that can change a price, complete an order, or record a refund acts directly on real customers&#39; data and money. This paper describes and analyzes the design of the write tools in woocommerce-mcp, an open-source MCP server for operating WooCommerce stores, which in version 0.4.0 exposes 47 tools, 28 reads and 19 writes. The design rests on a read-only default per store; a two-step preview and confirm cycle in which the preview is produced by running the real tool code against a planning client; a previewId, a stateless fingerprint of the tool name, store id and canonicalized arguments that causes a confirmation with different arguments to be refused; and a JSON Lines audit log that records decisions but not read results. The mechanisms are mapped to the three root causes of Excessive Agency in the OWASP Top 10 for LLM Applications. The discussion states the limitations openly, in particular that the preview id binds the request and not the store state, and proposes a precondition check as future work.Keywords: AI agents, Model Context Protocol, WooCommerce, tool safety, human in the loop, audit logging, excessive agency1. مقدمهپروتکل Model Context Protocol یک قرارداد باز برای ارتباط میان برنامه‌های میزبان مدل زبانی و سرورهایی است که «ابزار»، «منبع» و «پرامپت» در اختیار مدل می‌گذارند [1]. در این قرارداد، ابزار تابعی است که مدل می‌تواند آن را فراخوانی کند و نتیجه آن را در ادامه استدلال خود به کار ببرد [2]. همین سادگی باعث شده است که اتصال یک عامل به سامانه‌ای مانند فروشگاه ووکامرس از نظر فنی کار دشواری نباشد: رابط REST ووکامرس مستند است [3] و یک سرور MCP می‌تواند هر نقطه پایانی آن را در قالب یک ابزار عرضه کند.دشواری اصلی پس از این مرحله آغاز می‌شود. ابزارهای خواندنی، مانند گزارش فروش یا فهرست محصولات کم‌موجودی، در بدترین حالت اطلاعات نادرست یا ناقص برمی‌گردانند. ابزارهای نوشتنی اما وضعیت یک سامانه زنده را تغییر می‌دهند: قیمت محصولی که مشتریان در حال خرید آن هستند، وضعیت سفارشی که ارسال آن آغاز شده، یا بازپرداختی که ممکن است از طریق درگاه پرداخت انجام شود. خطای مدل، برداشت نادرست از دستور کاربر، یا محتوای دست‌کاری‌شده‌ای که عامل از داده‌های فروشگاه می‌خواند، در این ابزارها به پیامد واقعی تبدیل می‌شود.این مقاله تجربه طراحی woocommerce-mcp را گزارش می‌کند [9] و نسخه گسترش‌یافته و علمی‌تر یادداشت انگلیسی نویسنده درباره همین موضوع است [11]. این سرور متن‌باز با مجوز MIT منتشر شده و در نسخه 0.4.0 دارای 47 ابزار، شامل 28 ابزار خواندنی و 19 ابزار نوشتنی، است. تمرکز مقاله بر 19 ابزار نوشتنی و تصمیم‌های طراحی پیرامون آن‌هاست. سهم این مقاله سه بخش دارد: (الف) توصیف یک الگوی عملی پیش‌نمایش، تأیید و ثبت برای ابزارهای نوشتنی MCP، (ب) معرفی یک سازوکار بی‌حالت برای گره‌زدن تأیید به محتوای پیش‌نمایش، و (ج) تحلیل صادقانه تضمین‌ها و محدودیت‌های این طرح.2. بیان مسئلهمسئله را می‌توان این‌گونه صورت‌بندی کرد: یک عامل مبتنی بر مدل زبانی از طریق یک سرور MCP به یک یا چند فروشگاه زنده متصل است. یک انسان، مثلاً مدیر فروشگاه، به عامل دستور می‌دهد و انتظار دارد تغییرات تنها با آگاهی و تأیید او انجام شوند. هدف طراحی ابزارهایی است که سه ویژگی داشته باشند:حداقل دامنه اثر: هیچ تغییری بدون تصمیم صریح مالک فروشگاه ممکن نباشد و سطح دسترسی برای هر فروشگاه جداگانه تعیین شود.تطابق تأیید با محتوا: آنچه اعمال می‌شود همان چیزی باشد که انسان دیده و تأیید کرده است.قابلیت ممیزی: پس از وقوع، بتوان مشخص کرد چه چیزی تلاش شد، چه کسی آن را درخواست کرد و نتیجه چه بود، بدون اینکه داده‌های مشتریان در گزارش انباشته شوند.مشخصه MCP در بخش ابزارها تصریح می‌کند که برای اعتماد و ایمنی، همواره باید انسانی در حلقه باشد که بتواند فراخوانی ابزار را رد کند [2]. این توصیه در سطح برنامه میزبان مطرح شده است. پرسش این مقاله آن است که خود سرور، مستقل از رفتار میزبان، چه سهمی در تحقق این اصل می‌تواند داشته باشد. اصول کلی‌تر طراحی ابزارهای MCP برای فروشگاه را در قالب یک مهارت قابل‌ارزیابی برای عامل‌های برنامه‌نویس نیز در یادداشتی دیگر شرح داده‌ام.3. پیشینه و کارهای مرتبطModel Context Protocol. مشخصه MCP قالب پیام‌ها، چرخه عمر اتصال، و انواع قابلیت‌های سرور را تعریف می‌کند [1]. در بخش ابزارها، علاوه بر لزوم حضور انسان در حلقه، بر نمایش روشن ابزارهای در دسترس و درخواست تأیید برای عملیات حساس از سوی برنامه میزبان تأکید شده است [2]. این مشخصه سازوکار مشخصی برای پیش‌نمایش یا گره‌زدن تأیید به محتوای فراخوانی در سطح پروتکل تعریف نمی‌کند و این کار را به پیاده‌سازی‌ها می‌سپارد.عاملیت بیش از حد. پروژه OWASP در فهرست ده خطر مهم برنامه‌های مبتنی بر مدل زبانی [5]، خطر LLM06:2025 را «عاملیت بیش از حد» (Excessive Agency) نام‌گذاری کرده است [4]. بر اساس این منبع، ریشه این خطر در سه عامل است: کارکرد بیش از حد (ابزارهایی بیش از نیاز کار)، مجوز بیش از حد (دسترسی بیش از نیاز به سامانه‌های پایین‌دستی) و خودمختاری بیش از حد (انجام اقدامات پرپیامد بدون تأیید مستقل). همین منبع راهکارهایی مانند محدود کردن ابزارها، اعمال اصل حداقل دسترسی و الزام به تأیید انسانی برای اقدامات پرپیامد را پیشنهاد می‌کند. طرح ارائه‌شده در این مقاله را می‌توان یک پیاده‌سازی مشخص از این راهکارها برای حوزه تجارت الکترونیک دانست.احراز هویت و مجوز. برای دسترسی راه دور، سرور از دو استاندارد IETF بهره می‌برد: بررسی وضعیت توکن در OAuth 2.0 (RFC 7662) [6] و فراداده منبع محافظت‌شده OAuth 2.0 (RFC 9728) [7] که کلاینت‌های MCP از آن برای یافتن سرور مجوز استفاده می‌کنند.رابط ووکامرس. همه عملیات از طریق رابط REST ووکامرس انجام می‌شود [3]. بنابراین هر ابزار نوشتنی در نهایت به یک درخواست HTTP با روش و مسیر مشخص تبدیل می‌شود. این ویژگی امکان نمایش دقیق «درخواستی که ارسال خواهد شد» را فراهم می‌کند و پایه سازوکار پیش‌نمایش است.4. روش و طراحی4.1. مدل تهدیددارایی‌های مورد حفاظت عبارت‌اند از: داده‌های کاتالوگ و قیمت، وضعیت سفارش‌ها، وجوه بازپرداخت‌شده، داده‌های شخصی مشتریان و کلیدهای دسترسی فروشگاه. تهدیدهایی که طرح به آن‌ها می‌پردازد:ت1. اقدام ناخواسته عامل: عامل به دلیل برداشت نادرست یا تزریق دستور از طریق داده‌هایی که می‌خواند، اقدامی نوشتنی انجام دهد که انسان قصد آن را نداشته است.ت2. ناهمخوانی تأیید و اجرا: انسان یک تغییر را ببیند و تأیید کند، اما عامل در فراخوانی نهایی آرگومان‌های متفاوتی بفرستد.ت3. نمایش ابزار بیش از نیاز: ابزاری که در یک استقرار لازم نیست، در دسترس عامل باشد، یا خطای پیکربندی آن را بی‌صدا در دسترس قرار دهد.ت4. دسترسی شبکه‌ای غیرمجاز: سرور HTTP بدون احراز هویت در معرض شبکه قرار گیرد، یا توکنی با دسترسی خواندنی برای نوشتن به کار رود.ت5. انباشت داده شخصی: داده‌های مشتریان که عامل می‌خواند، در گزارش‌ها یا خروجی‌ها ذخیره یا آشکار شوند.خارج از دامنه این طرح: به خطر افتادن میزبان یا کلاینت MCP، به خطر افتادن خود سرور ووکامرس، و تغییرات هم‌زمانی که انسان یا سامانه دیگری مستقیماً در فروشگاه اعمال می‌کند (این مورد در بخش 6 بررسی می‌شود).4.2. حالت پیش‌فرض فقط خواندنی (پاسخ به ت1 و ت3)هر فروشگاه تا زمانی که صریحاً خلاف آن تنظیم نشود فقط خواندنی است. در حالت تک‌فروشگاه این کار با WOOCOMMERCE_READ_ONLY=false و در حالت چندفروشگاهی با readOnly: false در ورودی هر فروشگاه انجام می‌شود. در غیر این صورت، ابزارهای نوشتنی پیش از هر ارتباطی با فروشگاه با خطای read_only متوقف می‌شوند و حتی اجرای آزمایشی نیز رد می‌شود. انگیزه این پیش‌فرض آن است که رایج‌ترین کاربرد اولیه یک عامل فروشگاهی گزارش‌گیری است و به دسترسی نوشتنی نیازی ندارد.افزون بر این، فهرست ابزارهای مجاز و ممنوع (WOOCOMMERCE_TOOLS_ALLOW و WOOCOMMERCE_TOOLS_DENY) نام یا الگو می‌پذیرند. فهرست ممنوع بر فهرست مجاز اولویت دارد و الگویی که با هیچ ابزاری منطبق نشود خطای پیکربندی محسوب می‌شود، بنابراین یک اشتباه تایپی هنگام راه‌اندازی آشکار می‌شود و نه به‌صورت بی‌صدا. هر فروشگاه نیز می‌تواند allowTools و denyTools اختصاصی داشته باشد که در هر فراخوانی بررسی می‌شوند. ابزار پنهان‌شده اساساً در فهرست ابزارها نمایش داده نمی‌شود.4.3. چرخه پیش‌نمایش و تأیید (پاسخ به ت1)در فروشگاهی که نوشتن در آن مجاز است نیز اولین فراخوانی یک ابزار نوشتنی تغییری ایجاد نمی‌کند. در حالت پیش‌فرض، ابزار درخواست REST دقیقی را که ارسال خواهد کرد بازمی‌گرداند:{&#xA; &#34;status&#34;: &#34;confirmation_required&#34;,&#xA; &#34;store&#34;: &#34;default&#34;,&#xA; &#34;previewId&#34;: &#34;pv_4f1c9a2e7b3d0856&#34;,&#xA; &#34;message&#34;: &#34;The change was NOT applied. Review the request below with the user, then call this tool again with the same arguments, confirm: true, and this previewId.&#34;,&#xA; &#34;request&#34;: { &#34;method&#34;: &#34;PUT&#34;, &#34;path&#34;: &#34;orders/1015&#34;, &#34;query&#34;: {}, &#34;body&#34;: { &#34;status&#34;: &#34;completed&#34; } }&#xA;}تغییر تنها زمانی اعمال می‌شود که ابزار با همان آرگومان‌ها و confirm: true دوباره فراخوانی شود. پارامتر dryRun: true نیز همیشه فقط پیش‌نمایش تولید می‌کند، حتی اگر الزام تأیید غیرفعال شده باشد.نکته طراحی مهم در این بخش، روش تولید پیش‌نمایش است. پیش‌نمایش با یک منطق جداگانه ساخته نمی‌شود، بلکه همان کد واقعی ابزار روی یک «کلاینت برنامه‌ریز» اجرا می‌شود. در این کلاینت، درخواست‌های خواندنی طبق روال به فروشگاه ارسال می‌شوند و نخستین درخواست نوشتنی متوقف و به‌عنوان درخواست برنامه‌ریزی‌شده برگردانده می‌شود. در نتیجه، برای نمونه، پیش‌نمایش یک تغییر گروهی قیمت با قیمت‌های واقعی فعلی محاسبه می‌شود و پیش‌نمایش نمی‌تواند از کدی که تغییر را اعمال می‌کند فاصله بگیرد.متن پیام بازگشتی نیز بخشی از طراحی است. این پیام هم برای انسان و هم برای عامل نوشته شده و به صراحت می‌گوید که تغییری اعمال نشده و گام بعدی چیست. به بیان دیگر، خروجی ابزار بخشی از رابط کاربری آن است.4.4. گره‌زدن تأیید به پیش‌نمایش با previewId (پاسخ به ت2)در نسخه‌های پیش از 0.4.0، پیش‌نمایش و نوشتن تأییدشده هر کدام یک سطر جداگانه با آرگومان‌های یکسان در گزارش ممیزی داشتند و تطبیق آن‌ها تنها به‌صورت دستی ممکن بود. پرسشی که پس از انتشار مطرح شد، این بود که آیا گزارش ممیزی ثابت می‌کند عامل دقیقاً چه چیزی را تأیید کرده است. پاسخ به این پرسش به سازوکار previewId در نسخه 0.4.0 انجامید.هر پیش‌نمایش اکنون یک previewId دارد که به این صورت تعریف می‌شود:previewId = &#34;pv_&#34; + hex(SHA-256(tool + &#34;\n&#34; + store + &#34;\n&#34; + canonicalJson(args)))[0:16]در این رابطه، canonicalJson آرگومان‌ها را با کلیدهای مرتب‌شده و حذف اعضای تعریف‌نشده سریال‌سازی می‌کند تا ترتیب کلیدها در نتیجه اثری نداشته باشد. تابع درهم‌ساز SHA-256 طبق استاندارد FIPS 180-4 تعریف شده است [8]. فراخوانی تأیید می‌تواند این شناسه را بازگرداند. سرور شناسه را از ابزار، فروشگاه و آرگومان‌های خود فراخوانی تأیید دوباره محاسبه می‌کند و در صورت عدم تطابق، نوشتن با خطای preview_mismatch رد می‌شود و هیچ درخواستی به فروشگاه ارسال نمی‌شود. در این حالت، پیش‌نمایش و نوشتن یک شناسه مشترک در گزارش ممیزی خواهند داشت.دقت در بیان ماهیت این شناسه ضروری است. previewId یک اثر انگشت است، نه یک راز و نه یک توکن یک‌بارمصرف. سرور هیچ سابقه‌ای از پیش‌نمایش‌های صادرشده نگه نمی‌دارد، بنابراین بررسی نمی‌کند که پیش‌نمایش واقعاً درخواست شده باشد، چه مدت از آن گذشته باشد یا قبلاً استفاده شده باشد. آرگومان‌های یکسان همیشه شناسه یکسانی تولید می‌کنند، پس شناسه پس از راه‌اندازی دوباره سرور معتبر می‌ماند و میان نشست‌های HTTP نیز کار می‌کند. استفاده از آن نیز اختیاری است: فراخوانی تأیید بدون شناسه همچنان کار می‌کند و سطر ممیزی آن شناسه‌ای را که آرگومان‌هایش تولید می‌کردند ثبت می‌کند.انتخاب اثر انگشت بی‌حالت آگاهانه بوده است. این سازوکار مهم‌ترین شکاف، یعنی تأیید چیزی متفاوت با آنچه انسان دیده است، را می‌بندد و در عوض به ذخیره‌سازی، قواعد انقضا یا وابستگی به نشست نیازی ندارد.4.5. گزارش ممیزی (پاسخ به ت5 و الزام ممیزی)با تنظیم WOOCOMMERCE_AUDIT_LOG به یک مسیر فایل یا stderr، هر تلاش برای نوشتن به یک سطر JSON با یکی از پنج نتیجه زیر تبدیل می‌شود: applied، dry_run، confirmation_required، refused و failed. هر سطر شامل زمان، نام ابزار، فروشگاه، آرگومان‌ها، کلاینت احراز‌هویت‌شده (در صورت وجود)، روش و مسیر درخواست برای پیش‌نمایش‌ها، کد خطا در صورت بروز خطا، و previewId است. اگر فراخواننده previewId فرستاده باشد، فیلد previewMatch نیز ثبت می‌شود.دو تصمیم در این بخش آگاهانه بوده است. نخست، گزارش فقط آرگومان‌های نوشتن را ثبت می‌کند و هرگز نتایج خواندن را ثبت نمی‌کند، تا داده‌های مشتریان که عامل می‌خواند در فایل گزارش انباشته نشوند. دوم، شکست در نوشتن گزارش یک بار روی stderr اعلام می‌شود اما فراخوانی ابزار را شکست نمی‌دهد. این تصمیم دسترس‌پذیری را بر کامل بودن گزارش مقدم می‌داند و در بخش 6 به آن بازمی‌گردیم.4.6. جداسازی چندفروشگاهی (پاسخ به ت1 و ت3)هر فروشگاه کلیدهای دسترسی، کلید فقط خواندنی و فهرست ابزارهای خود را دارد. بنابراین یک سرور می‌تواند هم‌زمان روی فروشگاه اصلی فقط خواندنی و روی نسخه آزمایشی قابل نوشتن باشد. مستندات پروژه همچنین توصیه می‌کند برای فروشگاه‌های فقط خواندنی از کلید ووکامرس با سطح Read استفاده شود، تا محدودیت در دو لایه مستقل (سرور و خود فروشگاه) اعمال شود.4.7. دسترسی راه دور و سطح مجوز (پاسخ به ت4)سرور به‌صورت محلی روی stdio اجرا می‌شود. در حالت HTTP، از توکن‌های ثابت حامل یا بررسی وضعیت توکن در OAuth 2.0 [6] پشتیبانی می‌کند. توکن‌های کامل دامنه woocommerce:write و توکن‌های خواندنی تنها دامنه woocommerce:read را دارند و هرگز نمی‌توانند فروشگاه را تغییر دهند. بدون احراز هویت، سرور HTTP از اتصال به نشانی‌ای غیر از loopback خودداری می‌کند.بر اساس مستندات امنیتی پروژه، هر نوشتن به ترتیب از چهار بررسی عبور می‌کند: داشتن دامنه woocommerce:write در توکن (در HTTP با احراز هویت)، فقط خواندنی نبودن فروشگاه، وجود confirm: true (مگر آنکه الزام تأیید غیرفعال شده باشد)، و مجوز نوشتن کلید ووکامرس.4.8. تطبیق با خطر عاملیت بیش از حدکارکرد بیش از حد: فهرست ابزارهای مجاز و ممنوع در سطح سرور و هر فروشگاه، و خطای پیکربندی برای الگوی بی‌اثر.مجوز بیش از حد: حالت پیش‌فرض فقط خواندنی، دامنه‌های woocommerce:read و woocommerce:write، و توصیه به کلید Read.خودمختاری بیش از حد: چرخه پیش‌نمایش و تأیید، previewId و رد با preview_mismatch، و گزارش ممیزی.5. پیاده‌سازیسرور با TypeScript برای Node.js نسخه 20 و بالاتر نوشته شده و از کتابخانه‌های رسمی MCP برای سرور استفاده می‌کند [9]. منطق مشترک همه ابزارهای نوشتنی در یک لایه ثبت ابزار (src/tools/registry.ts) متمرکز است و هر ابزار تنها کار اصلی خود را پیاده‌سازی می‌کند. ترتیب پردازش یک فراخوانی نوشتنی در این لایه چنین است: تعیین فروشگاه، بررسی مجاز بودن ابزار برای آن فروشگاه، بررسی دامنه توکن، محاسبه previewId، بررسی حالت فقط خواندنی، و سپس یا اجرای واقعی (در صورت تأیید و تطابق شناسه) یا اجرا روی کلاینت برنامه‌ریز برای تولید پیش‌نمایش. در هر یک از این مسیرها، نتیجه با یکی از پنج وضعیت تعریف‌شده در گزارش ممیزی ثبت می‌شود و خطاهای ردشده (فقط خواندنی، نبود مجوز، ابزار غیرفعال و عدم تطابق پیش‌نمایش) با وضعیت refused از سایر خطاها جدا می‌شوند.محاسبه شناسه در ماژول src/tools/preview-id.ts با ماژول node:crypto انجام می‌شود. کلاینت برنامه‌ریز، به‌جای ارسال درخواست نوشتنی، یک استثنای مخصوص می‌سازد که درخواست برنامه‌ریزی‌شده را در خود دارد. لایه ثبت ابزار این استثنا را دریافت و به پاسخ پیش‌نمایش تبدیل می‌کند. اگر اجرای ابزار بدون نیاز به نوشتن پایان یابد، برای نمونه در یک مجموعه تغییرات خالی، پاسخ با وضعیت no_change برگردانده می‌شود. در پیش‌نمایش‌ها، رازهای پیکربندی‌شده مانند راز وب‌هوک پیش از نمایش پنهان می‌شوند.سایر محدودیت‌های ایمنی در سطح ابزارها پیاده‌سازی شده‌اند:محدودیت عملیات گروهی: به‌روزرسانی گروهی محصولات و تغییر قیمت حداکثر 100 محصول در هر فراخوانی را می‌پذیرند. تغییر قیمت ابتدا قیمت‌های فعلی را می‌خواند و قیمت قدیم و جدید هر محصول را برمی‌گرداند.بازپرداخت: ابزار woocommerce_create_refund به‌صورت پیش‌فرض یک بازپرداخت دستی ثبت می‌کند و پولی جابه‌جا نمی‌کند. بازگرداندن وجه از طریق درگاه پرداخت به apiRefund: true صریح نیاز دارد و توضیح ابزار به عامل اعلام می‌کند که بازپرداخت قابل بازگشت نیست.پنهان‌سازی داده شخصی: با WOOCOMMERCE_REDACT_PII=true یا redactPii برای هر فروشگاه، نام‌ها، شرکت‌ها، نشانی‌ها، کدهای پستی، نام‌های کاربری، نشانی‌های IP و مشخصات مرورگر به [redacted] تبدیل می‌شوند. از ایمیل تنها نویسه نخست و دامنه، و از تلفن تنها دو رقم آخر باقی می‌ماند. شهر، استان و کشور حفظ می‌شوند تا گزارش‌ها و دسته‌بندی مشتریان کار کنند. یک فراخوانی نمی‌تواند پنهان‌سازی را برای فروشگاهی که آن را فعال کرده است خاموش کند.بسته از طریق npm با فرمان npx -y @sadrmanesh/woocommerce-mcp و به‌صورت ایمیج Docker در دسترس است و در MCP Registry رسمی ثبت شده است. مستندات طراحی نوشتن امن در [10] آمده است.6. تحلیل و بحث6.1. آنچه طرح تضمین می‌کندبا فرض درستی پیکربندی، طرح این ویژگی‌ها را فراهم می‌کند: (1) در فروشگاه فقط خواندنی هیچ درخواست نوشتنی به فروشگاه نمی‌رسد، (2) در حالت پیش‌فرض، هیچ نوشتنی بدون یک فراخوانی دوم با confirm: true اعمال نمی‌شود، (3) اگر فراخوانی تأیید previewId را همراه داشته باشد، آرگومان‌های اعمال‌شده دقیقاً همان آرگومان‌های پیش‌نمایش هستند، و (4) هر تلاش نوشتن، از جمله تلاش‌های ردشده و ناموفق، در صورت فعال بودن گزارش ممیزی ثبت می‌شود.6.2. محدودیت‌هاگره خوردن به درخواست و وضعیت فروشگاه. از نسخه 0.5.0، ابزارهایی که برای محاسبه تغییر از فروشگاه می‌خوانند، از رکوردهای خوانده‌شده (قیمت، موجودی و date_modified) اثر انگشت می‌گیرند و آن را در previewId می‌گذارند. هنگام تأیید، سرور همان رکوردها را دوباره می‌خواند و اگر چیزی تغییر کرده باشد، با خطای stale_preview نوشتن را رد می‌کند و چیزی به فروشگاه فرستاده نمی‌شود. این یک قفل نیست، چون REST API ووکامرس نوشتن شرطی ندارد، و فقط داده‌هایی را پوشش می‌دهد که تغییر به آن‌ها وابسته است.نبود حالت. چون سرور سابقه پیش‌نمایش‌ها را نگه نمی‌دارد، نمی‌تواند ثابت کند که پیش‌نمایشی واقعاً به انسان نمایش داده شده است. عاملی که آرگومان‌ها را می‌داند می‌تواند شناسه را خود محاسبه کند یا مستقیماً با confirm: true و بدون شناسه فراخوانی کند. بنابراین previewId در برابر عامل متخاصم تضمینی نمی‌دهد و نقش آن تشخیص ناهمخوانی و ایجاد پیوند قابل ممیزی است. حضور واقعی انسان در حلقه همچنان به برنامه میزبان وابسته است [2].گزارش ممیزی اختیاری. گزارش ممیزی تا زمانی که WOOCOMMERCE_AUDIT_LOG تنظیم نشود خاموش است. این انتخاب راه‌اندازی را ساده می‌کند، اما برای فروشگاهی که نوشتن در آن فعال است، فراموش کردن آن آسان است. همچنین شکست در نوشتن گزارش، فراخوانی را متوقف نمی‌کند، پس کامل بودن گزارش تضمین نمی‌شود.پیش‌نمایش‌های پنهان‌نشده. پیش‌نمایش نوشتن‌ها پنهان‌سازی داده شخصی را اعمال نمی‌کند، زیرا آرگومان‌های خود فراخواننده را بازمی‌گرداند. افزون بر این، پنهان‌سازی نام‌ها در متن آزاد مانند یادداشت سفارش تنها زمانی انجام می‌شود که به شکل نشانی ایمیل باشند. سطرهای گزارش ممیزی نیز آرگومان‌های ابزار را در بر دارند که ممکن است شامل یادداشت مشتری یا قیمت باشد، و مستندات پروژه توصیه می‌کند فایل گزارش با همان دقت داده‌های فروشگاه نگهداری شود.نبود ارزیابی تجربی. این مقاله یک گزارش طراحی است و ارزیابی کمی، مانند اندازه‌گیری رفتار عامل‌ها یا مطالعه کاربری، در آن انجام نشده است. ادعاهای مقاله به رفتار تعریف‌شده در کد و مستندات محدود است.6.3. درس‌های طراحیاز این تجربه چند اصل کلی‌تر قابل استخراج است: کوچک‌ترین دامنه اثر را پیش‌فرض قرار دهید، پیش‌نمایش را از همان مسیر کد واقعی بسازید، خروجی ابزار را برای عامل بنویسید، تأیید را به محتوا گره بزنید، تصمیم‌ها را ثبت کنید و نه داده‌ها را، و درباره تضمین‌های سامانه دقیق باشید. همچنین تجربه نشان داد که «تأیید یک تغییر توسط انسان» و «اثبات بعدی آنچه تأیید شد» دو نیازمندی متفاوت‌اند و طراحی اولیه تنها اولی را پوشش می‌داد.7. نتیجه‌گیری و کارهای آیندهاین مقاله نشان داد که سرور MCP، مستقل از برنامه میزبان، می‌تواند بخش مهمی از ایمنی ابزارهای نوشتنی را بر عهده بگیرد: با بسته نگه داشتن پیش‌فرض دسترسی، با جدا کردن پیش‌نمایش از اجرا، با گره‌زدن تأیید به محتوای پیش‌نمایش، و با ثبت تصمیم‌ها در یک گزارش قابل خواندن. سازوکار previewId نمونه‌ای از یک راه‌حل بی‌حالت است که شکاف میان آنچه انسان دیده و آنچه اعمال می‌شود را بدون افزودن ذخیره‌سازی می‌بندد، و محدودیت‌های آن به صراحت قابل بیان است.کارهای آینده عبارت‌اند از:حالت با حالت (stateful) اختیاری: پیش‌نمایش‌هایی که منقضی می‌شوند و تنها یک بار قابل استفاده‌اند، برای تیم‌هایی که به این تضمین نیاز دارند و هزینه ذخیره‌سازی آن را می‌پذیرند.حل تغییرات نسبی در زمان پیش‌نمایش: دستوری مانند «افزایش ده‌درصدی» در زمان پیش‌نمایش به مقدار مطلق، مانند «تغییر قیمت از 20.00 به 22.00»، تبدیل شود تا پیش‌نمایش دقیقاً همان چیزی را نشان دهد که نوشته خواهد شد.بررسی پیش‌شرط: بررسی پیش‌شرط در نسخه 0.5.0 پیاده‌سازی شد. گام بعدی یک حالت اختیاری با نگهداری وضعیت است که در آن پیش‌نمایش‌ها منقضی می‌شوند و فقط یک بار قابل استفاده‌اند.سخت‌تر کردن فراموشی گزارش ممیزی در فروشگاه‌هایی که نوشتن در آن‌ها فعال است.ارزیابی تجربی رفتار عامل‌ها در برابر این سازوکارها، از جمله سناریوهای تزریق دستور از طریق داده‌های فروشگاه.منابع[1] Model Context Protocol. Specification, version 2025-11-25. modelcontextprotocol.io/specification/2025-11-25[2] Model Context Protocol. Specification, version 2025-11-25: Server features, Tools. modelcontextprotocol.io/specification/2025-11-25/server/tools[3] WooCommerce. WooCommerce REST API Documentation, WP REST API v3. woocommerce.github.io/woocommerce-rest-api-docs[4] OWASP Gen AI Security Project. (2025). LLM06:2025 Excessive Agency. genai.owasp.org/llmrisk/llm062025-excessive-agency[5] OWASP Gen AI Security Project. (2025). OWASP Top 10 for LLM Applications 2025. genai.owasp.org/llm-top-10[6] Richer, J. (Ed.). (2015). OAuth 2.0 Token Introspection (RFC 7662). IETF. rfc-editor.org/rfc/rfc7662[7] Jones, M. B., Hunt, P., &amp; Parecki, A. (2025). OAuth 2.0 Protected Resource Metadata (RFC 9728). IETF. rfc-editor.org/rfc/rfc9728[8] National Institute of Standards and Technology. (2015). Secure Hash Standard (SHS) (FIPS PUB 180-4). csrc.nist.gov/pubs/fips/180-4/upd1/final[9] Sadrmanesh, N. (2026). woocommerce-mcp (Version 0.4.0) [Source code]. GitHub. github.com/sadrmanesh/woocommerce-mcp[10] Sadrmanesh, N. (2026). woocommerce-mcp documentation: Safe writes. sadrmanesh.github.io/woocommerce-mcp/safe-writes[11] Sadrmanesh, N. (2026). Preview, confirm, audit: designing safe write tools for AI agents on a live store. LinkedIn. linkedin.com/pulse/preview-confirm-audit-designing-safe-write-tools-ai-live-sadrmanesh-jwy5f[12] Sadrmanesh, N. (2026). woocommerce-mcp technical report. Zenodo. DOI: 10.5281/zenodo.23897615[13] Sadrmanesh, N. (2026). woocommerce-mcp [Software]. Zenodo. DOI: 10.5281/zenodo.23841156کد و مستندات پروژه: github.com/sadrmanesh/woocommerce-mcp</description><category>محمد ناصر صدرمنش</category><author>محمد ناصر صدرمنش</author><pubDate>Wed, 04 Nov 2026 19:59:48 +0330</pubDate></item><item><title>ی عربی، نیم‌فاصله و PDF برعکس: سه دشمن پنهان جست‌وجوی فارسی در هوش مصنوعی!</title><link>https://virgool.ir/@sadrmanesh/ی-عربی-نیم-فاصله-و-pdf-برعکس-سه-دشمن-پنهان-جست-وجوی-فارسی-در-هوش-مصنوعی-m5rkfa6hoswr</link><description>چرا چت‌بات‌های هوش مصنوعی متن فارسی را درست پیدا نمی‌کنند؟این روزها خیلی از شرکت‌ها و سازمان‌ها چت‌بات‌هایی دارند که از روی سندهای خودشان جواب می‌دهند: دستیار پشتیبانی‌ای که آیین‌نامه‌ها را می‌خواند، چت‌باتی که از دل قراردادها و گزارش‌ها جواب پیدا می‌کند، یا ابزاری که به سؤال کارمندان دربارهٔ مستندات داخلی پاسخ می‌دهد. اگر با یکی از این‌ها به فارسی کار کرده باشید، احتمالاً دیده‌اید که گاهی جوابی را که مطمئنید در سندها هست پیدا نمی‌کند، یا با اطمینان کامل جوابی بی‌ربط می‌دهد.خود من وقتی داشتم یک سامانهٔ پرسش‌وپاسخ روی سندهای فارسی می‌ساختم، دقیقاً به همین وضع خوردم. روی متن انگلیسی همه‌چیز بی‌دردسر کار می‌کرد، اما روی فارسی سندی که دقیقاً جواب سؤال را داشت گاهی اصلاً پیدا نمی‌شد. بدترین بخش ماجرا این بود که هیچ خطایی دیده نمی‌شد: نه پیغامی، نه هشداری. سامانه فقط آرام و بی‌صدا بد کار می‌کرد. وقتی رد مشکل را گرفتم، فهمیدم ایراد نه از مدل هوش مصنوعی است و نه از پایگاه داده؛ ایراد از خود متن فارسی بود و از اینکه ابزارهای عمومی هیچ درکی از آن ندارند.در این نوشته سه عامل اصلی این مشکل را توضیح می‌دهم و بعد سراغ راه‌حل‌هایی می‌روم که در عمل جواب می‌دهند.این چت‌بات‌ها چطور کار می‌کنند؟چت‌باتی که از روی سندها جواب می‌دهد معمولاً دو مرحله دارد. اول، مثل یک موتور جست‌وجو، تکه‌های مرتبط با سؤال را از میان سندها پیدا می‌کند. بعد، آن تکه‌ها را همراه سؤال به مدل زبانی (همان مغز چت‌بات) می‌دهد تا جواب را بنویسد. به این روش در دنیای فنی RAG می‌گویند، کوتاه‌شدهٔ Retrieval-Augmented Generation، یعنی «تولید متن با کمک بازیابی».نکتهٔ کلیدی اینجاست: کیفیت جواب نهایی تقریباً به‌طور کامل به مرحلهٔ اول، یعنی جست‌وجو، بستگی دارد. اگر تکهٔ درست پیدا نشود، بهترین مدل دنیا هم جواب درست نمی‌دهد. پس هر چیزی که جست‌وجوی فارسی را خراب کند، کل چت‌بات را خراب کرده است.سه دشمن پنهان: یک کلمه، چند شکلبرای ما که فارسی می‌خوانیم، «کتاب» همیشه «کتاب» است. اما برای کامپیوتر، دو کلمه فقط وقتی یکی‌اند که حرف‌به‌حرف و کد‌به‌کد یکی باشند. متن فارسی پر از کلمه‌هایی است که ظاهرشان یکی است ولی کدهایشان نه. سه تا از این دشمن‌ها از همه پنهان‌تر و رایج‌ترند:ی و ک عربی که روی صفحه با ی و ک فارسی فرقی ندارند؛نیم‌فاصله که یک کلمه را به چند شکل مختلف درمی‌آورد؛PDF برعکس که «کتاب» را به «باتک» تبدیل می‌کند.دشمن اول: ی و ک عربیصفحه‌کلیدها، سیستم‌عامل‌ها و سایت‌های مختلف گاهی به‌جای «ی» و «ک» فارسی، «ي» و «ك» عربی می‌نویسند. روی صفحه تقریباً هیچ فرقی دیده نمی‌شود، اما «كتاب» با کاف عربی و «کتاب» با کاف فارسی دو رشتهٔ کاملاً متفاوت‌اند. اگر سند با یکی نوشته شده باشد و کاربر با دیگری جست‌وجو کند، جست‌وجوی کلیدواژه‌ای هیچ تطابقی پیدا نمی‌کند. همین داستان برای شکل‌های مختلف الف (أ، إ، ٱ) هم تکرار می‌شود.دشمن دوم: نیم‌فاصلهنیم‌فاصله (ZWNJ) یکی از ظریف‌ترین بخش‌های خط فارسی است. «می‌روم» را می‌شود با نیم‌فاصله، با فاصلهٔ کامل («می روم») یا چسبیده («میروم») نوشت. «کتاب‌ها» و «کتاب ها» و «کتابها» هم همین‌طور. هر کدام از این‌ها برای ابزاری که متن را به کلمه‌ها می‌شکند، چیز متفاوتی است. در یکی «می» و «روم» دو کلمهٔ جدا هستند و در دیگری یک کلمه.دشمن سوم: PDF برعکسو اما عجیب‌ترین دشمن: خیلی از تولیدکننده‌های PDF متن فارسی را به «ترتیب نمایشی» ذخیره می‌کنند، یعنی به همان ترتیبی که حروف از چپ‌به‌راست روی صفحه چیده شده‌اند. نتیجه این است که ابزارهای استاندارد استخراج متن، به‌جای «کتاب» تحویلتان می‌دهند «باتک». متنی که با این شکل ایندکس شود عملاً برای جست‌وجو بی‌مصرف است و هیچ هشداری هم نمی‌بینید، مگر اینکه خودتان خروجی را با چشم نگاه کنید.دشمن‌های کوچک‌تر: ارقام، علائم و املاهای چندگانهکنار این سه دشمن اصلی، چند مزاحم کوچک‌تر هم هستند. متن فارسی ممکن است «۱۴۰۳» را با ارقام فارسی، «١٤٠٣» را با ارقام عربی یا «1403» را با ارقام لاتین داشته باشد. ویرگول و علامت سؤال هم گاهی فارسی‌اند (، ؟) و گاهی لاتین.بعضی کلمه‌ها در فارسی بیش از یک املای پذیرفته دارند: «اتاق» و «اطاق»، «هیئت» و «هیات»، «مسئله» و «مساله»، «تهران» و «طهران». هیچ‌کدام غلط نیست، ولی اگر سند «اطاق» نوشته باشد و کاربر «اتاق» بپرسد، جست‌وجوی کلیدواژه‌ای سند را از دست می‌دهد. این مشکل را با یکسان‌سازی حروف هم نمی‌شود حل کرد، چون اینجا خود حروف فرق دارند.هر کدام از این مشکل‌ها به‌تنهایی کوچک به نظر می‌رسد. اما در یک مجموعهٔ واقعی همه با هم پیش می‌آیند و نتیجه‌شان همان چت‌باتی است که جواب را پیدا نمی‌کند، بی‌آنکه بگوید چرا.راه‌حل‌ها: پیش از جست‌وجو، متن را بشناسیدخبر خوب این است که هیچ‌کدام از این مشکل‌ها حل‌نشدنی نیستند. فقط باید پیش از اینکه متن به موتور جست‌وجو برسد، مرحله‌هایی مخصوص زبان فارسی در مسیر گذاشته شود. من این راه‌حل‌ها را در یک کتابخانهٔ متن‌باز پایتون به اسم persian-rag پیاده کرده‌ام، اما ایده‌ها عمومی‌اند و در هر سامانه‌ای قابل پیاده‌سازی‌اند.۱. یکسان‌سازی متن، اما قابل‌تنظیماولین و مهم‌ترین قدم، یکسان‌سازی (normalization) است: «ي» و «ى» به «ی»، «ك» به «ک» و شکل‌های مختلف الف به «ا» تبدیل شوند، نیم‌فاصلهٔ پیشوند «می» و پسوندهایی مثل «ها» و «ترین» درست شود، ارقام یکدست شوند و فاصله‌گذاری اطراف علائم مرتب شود. نکتهٔ مهم این است که همین کار باید هم روی سندها و هم روی پرسش کاربر انجام شود تا هر دو به یک شکل واحد برسند.دو اصل را در این مرحله مهم می‌دانم. اول، هیچ قاعده‌ای نباید اجباری باشد؛ پروژه‌ای ممکن است ارقام لاتین بخواهد یا نخواهد به نیم‌فاصله‌ها دست زده شود. یکسان‌سازی‌ای که نشود کنترلش کرد، دیر یا زود جایی متن را خراب می‌کند. دوم، یکسان‌سازی باید «خودتوان» (idempotent) باشد، یعنی اگر دو بار روی یک متن اجرا شود، نتیجه با یک بار اجرا فرقی نکند.برای نمونه، در پیاده‌سازی من خروجی یکسان‌سازی چنین است:normalize(&#34;كتاب هاي مي روم 123&#34;)&#xA;# کتاب‌های می‌روم ۱۲۳۲. تشخیص و تعمیر متن برعکس PDFمتن برعکس را می‌شود به‌طور خودکار تشخیص داد. دو نشانهٔ ساده کار را انجام می‌دهد: اینکه کلمه‌ها با کدام شکل‌های چسبان حروف شروع و تمام می‌شوند، و اینکه کلمه‌های پرتکرار فارسی رو به جلو دیده می‌شوند یا برعکس. اگر سطری برعکس تشخیص داده شد، برگردانده می‌شود؛ اما باید حواس‌تان باشد که عددها و متن لاتین جهت چپ‌به‌راست خودشان را نگه دارند، وگرنه «2024» به «4202» تبدیل می‌شود.۳. جست‌وجوی ترکیبی: هم کلمه، هم‌معنادو روش اصلی برای جست‌وجو داریم. روش کلیدواژه‌ای یا BM25 (الگوریتم کلاسیک رتبه‌بندی بر اساس تطابق کلمه‌ها) که در پیدا کردن اسم‌ها، اصطلاح‌ها و عبارت‌های دقیق عالی است. و روش برداری که با embedding (نمایش عددی معنای متن) کار می‌کند و می‌تواند جمله‌های هم‌معنا با کلمه‌های متفاوت را هم پیدا کند.هیچ‌کدام به‌تنهایی کافی نیست. راه رایج این است که هر دو اجرا شوند و رتبه‌بندی‌هایشان با Reciprocal Rank Fusion یا RRF (ادغام رتبه‌ها بر اساس جایگاه هر سند در هر فهرست) ترکیب شود. مزیت RRF این است که به مقیاس امتیازها کاری ندارد و فقط به رتبه‌ها نگاه می‌کند.۴. جست‌وجو با املاهای مختلفبرای مشکل «اتاق/اطاق» یکسان‌سازی جواب نمی‌دهد، چون هیچ‌کدام غلط نیست. راه‌حل این است که از روی پرسش کاربر، شکل‌های املایی دیگر ساخته شود، با هر کدام جداگانه جست‌وجو شود و نتیجه‌ها دوباره با RRF ادغام شوند. پرسش اصلی باید وزن بیشتری بگیرد تا همیشه اولویت داشته باشد.۵. ایندکس را به مدلش قفل کنیدیک خطای بی‌صدای دیگر: ایندکس را با یک مدل embedding می‌سازید و بعداً با مدل دیگری جست‌وجو می‌کنید. بردارها با هم سازگار نیستند، اما ممکن است ابعادشان یکی باشد و هیچ خطایی نگیرید؛ فقط نتیجه‌ها بی‌معنی می‌شوند. راه‌حل ساده است: نام مدل را کنار ایندکس ذخیره کنید و اگر مدل دیگری سراغش آمد، خطا بدهید. بهتر است سامانه با صدای بلند شکست بخورد تا اینکه آرام اشتباه کند.این راه‌حل‌ها چقدر اثر دارند؟برای اندازه‌گیری، یک مجموعهٔ آزمون کوچک فارسی ساختم (persian-rag-bench که طراحی آن را در یادداشتی جداگانه توضیح داده‌ام): ۳۴ متن و ۴۲ پرسش که ۱۲ تایشان عمداً با املای متفاوت از متن نوشته شده‌اند.در جست‌وجوی کلیدواژه‌ای BM25، جست‌وجو با املاهای مختلف معیار Hit@1 (سهم پرسش‌هایی که جواب درست در رتبهٔ اول آمده) را از 0.881 به 0.952 رساند.انتخاب مدل embedding هم اثر بزرگی دارد. دو مدل چندزبانه را مقایسه کردم؛ معیار nDCG@10 است (کیفیت ترتیب ده نتیجهٔ اول):مجموعهٔ آزمون خودم: paraphrase-multilingual-MiniLM-L12-v2 برابر 0.8637 و multilingual-e5-small برابر 0.9881زیرمجموعهٔ فارسی MIRACL (dev): paraphrase-multilingual-MiniLM-L12-v2 برابر 0.4424 و multilingual-e5-small برابر 0.7461PQuAD (validation): paraphrase-multilingual-MiniLM-L12-v2 برابر 0.6437 و multilingual-e5-small برابر 0.9242عدد MIRACL روی یک پیکرهٔ محدود از ۶٬۴۰۵ متن داوری‌شده برای ۶۳۲ پرسش به دست آمده، نه روی کل پیکرهٔ MIRACL، پس با عددهای منتشرشدهٔ آن قابل مقایسه نیست.محدودیت‌هااین عددها را باید با احتیاط خواند:مجموعهٔ آزمون را خودم ساخته‌ام و کوچک است. با ۴۲ پرسش، جابه‌جا شدن فقط یک پرسش Hit@k را حدود ۲٫۴ واحد درصد تغییر می‌دهد.امتیازها روی این مجموعه نزدیک سقف‌اند و برای مقایسهٔ مدل‌های قوی چندان تفکیک‌کننده نیست.ارزیابی املایی فقط بر ۱۲ پرسش دستی تکیه دارد.در وزن‌دهی شکل‌های املایی هنوز یک ایراد باز هست: هر شکل وزن ۱ می‌گیرد و پرسش اصلی وزن ۲، اما مجموع وزن چند شکل می‌تواند از پرسش اصلی بیشتر شود.برای تعمیر متن PDF، رتبه‌بندی دوباره و دقت ارجاع‌ها هنوز ارزیابی عددی ندارم.جمع‌بندیاگر چت‌بات یا موتور جست‌وجوی شما روی متن فارسی کار می‌کند، پیش از عوض کردن مدل، این سه دشمن پنهان را بررسی کنید: حروف عربی، نیم‌فاصله و PDF برعکس. یکسان‌سازی دقیق متن در هر دو طرف، تعمیر PDF، جست‌وجوی ترکیبی و در نظر گرفتن املاهای مختلف، اغلب بیش از هر تغییر دیگری کیفیت جواب‌ها را بالا می‌برد.منابع و لینک‌هاگزارش فنی persian-rag در Zenodo، DOI: 10.5281/zenodo.23923535نسخهٔ نرم‌افزار persian-rag در Zenodo، DOI: 10.5281/zenodo.23944663کد منبع: github.com/sadrmanesh/persian-ragبنچمارک persian-rag-bench در Figshare، DOI: 10.6084/m9.figshare.34913889کد و مستندات پروژه: github.com/sadrmanesh/persian-rag</description><category>محمد ناصر صدرمنش</category><author>محمد ناصر صدرمنش</author><pubDate>Wed, 04 Nov 2026 19:57:35 +0330</pubDate></item></channel></rss>