arc/docs/AI_CHAT_PRODUCT_ISSUES.md
2026-08-21 14:14:21 +00:00

302 lines
27 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# مشکلات محصولی چت هوش مصنوعی — فهرست کار زنده
**نسخه سند:** 1.2
**تاریخ:** ۱۴۰۵/۰۵/۳۰ (۲۱ اوت ۲۰۲۶)
**وضعیت:** زنده — پس از هر اصلاح، وضعیت آیتم را عوض کنید و یک سطر در [تاریخچه](#تاریخچه-بهروزرسانی) بگذارید.
**مرجع معماری:** [`AI_AGENT_SYSTEM_AUDIT.md`](AI_AGENT_SYSTEM_AUDIT.md)
**مرجع subagent:** [`AI_AGENT_RUNTIME_CAPABILITIES_SCENARIO.md`](AI_AGENT_RUNTIME_CAPABILITIES_SCENARIO.md)
> این سند **لیست کار محصولی** است، نه ممیزی سطح جهانی. تمرکز: چیزهایی که کاربر الان در رابط چت می‌بیند و خراب است. ممیزی (`AGT-*`, `TOOL-*`, `UX-*`) را عوض نکنید مگر همان آیتم واقعاً بسته شود؛ اینجا شناسه‌های `CHAT-*` برای صف اصلاح روزانه‌اند.
---
## چگونه از این سند استفاده کنیم
1. از [صف اصلاح پیشنهادی](#صف-اصلاح-پیشنهادی) شروع کنید.
2. هر آیتم یک شناسه `CHAT-xx` دارد. وضعیت فقط از این مجموعه: `باز` · `در حال اصلاح` · `انجام‌شده` · `موکول` · `نپذیرفته`.
3. بعد از فیکس: وضعیت + تاریخ + یک خط «چه شد» در همان آیتم، و یک سطر در تاریخچه.
4. تست پذیرش دستی را قبل از بستن آیتم روی یک جلسهٔ واقعی فارسی اجرا کنید.
**منابع این نسخه**
- سورس حلقهٔ ایجنت، API چت، و UI Flutter (۱۸ اوت ۲۰۲۶).
- گفت‌وگوی ممیزی ۱۷–۱۸ اوت: [`ممیزی سیستم ایجنت`](858f8cb5-06c0-41f1-959c-b78f8f14b9f9) — subagent بک‌اند ساخته شد، **فاز UI عمداً موکول شد**.
- گزارش مستقیم کاربر روی شش مشکل زیر.
---
## خلاصهٔ اجرایی
بک‌اند قابلیت‌های ایجنت (ابزار، تأیید نوشتن، spawn زیر-ایجنت، تایم‌لاین تفکر) را دارد؛ **قرارداد نمایش و بازیابی خطا هنوز محصولی نیست.** نتیجه برای کاربر فارسی:
| # | آنچه کاربر می‌بیند | ریشه در کد (کوتاه) |
|---|---------------------|---------------------|
| ۱ | ابزار خطا می‌دهد و ایجنت گیر می‌کند | خطای خام انگلیسی؛ سقف ۲ شکست یکسان حلقه را می‌بندد؛ ابزار غایب از کاتالوگ ۴۸تایی |
| ۲ | زیر-ایجنت نامرئی است | spawn فقط در حافظهٔ فرایند؛ هیچ `trace_step` و هیچ پنل UI |
| ۳ | جدول بی‌معنی با `trace_id` / `step_id` / `kind` | UI هر لیست Map را جدول می‌کشد؛ `_reasoning_trace` فیلتر نشده |
| ۴ | متن به‌صورت `** بولد **` دیده می‌شود | نرمال‌سازی فقط روی پاسخ نهایی؛ پنل تفکر و RTL پوشش ناقص |
| ۵ | «در حال فکر کردن» انگلیسی است | `reasoning_content` مدل بومی انگلیسی است؛ mismatch فقط log می‌شود |
| ۶ | دکمهٔ تأیید → لودینگ بی‌پایان + توهم | تأیید = پیام جعلی کاربر + حلقهٔ کامل جدید؛ `wait=true` پیش‌فرض spawn استریم را قفل می‌کند |
---
## صف اصلاح پیشنهادی
ترتیب برای بیشترین اثر روی تجربهٔ کاربر، با کمترین ریسک امنیتی:
| اولویت | شناسه | کار | تخمین |
|--------|--------|------|--------|
| P0 | CHAT-03 | فیلتر متادیتای داخلی از جدول خودکار | کوچک — Flutter |
| P0 | CHAT-06 | تأیید نوشتن بدون پیام جعلی و بدون گیر کردن استریم | متوسط — API + Flutter |
| P0 | CHAT-04 | رندر markdown بولد در پاسخ و تفکر | کوچک — Flutter |
| P1 | CHAT-05 | تفکر قابل‌نمایش هم‌زبان کاربر | متوسط — prompt + سیاست نمایش |
| P1 | CHAT-01 | خطای ابزار قابل بازیابی برای مدل | متوسط — بک‌اند |
| P1 | CHAT-02 | پنل زیر-ایجنت (دیدن / قطع) | بزرگ — Flutter + SSE |
| P2 | CHAT-07+ | موارد کمکی صحت سیستم (پایین سند) | انجام‌شده |
---
# مشکلات گزارش‌شدهٔ کاربر
## CHAT-01 — ابزارها خطا می‌دهند و مدل نمی‌تواند رفع کند
- وضعیت: انجام‌شده
- اولویت: P1
- مالک: backend
- فایل‌ها: `function_registry.py` (`call_function`)، `ai_service.py` (`handle_function_calls_async`)، `ai_tool_result.py` (`compact_tool_result_for_llm`)، `ai_goal_assessment.py` (`ToolCallTracker`)، `ai_tool_intent.py`
- شواهد از کد:
1. هر استثنا به `{"error": str(e)}` تبدیل می‌شود؛ پیام اغلب انگلیسی و بدون `code` / `hint` / فیلدهای مجاز است.
2. `compact_tool_result_for_llm` برای خطا فقط `error` و `message` را نگه می‌دارد — schema پارامتر از دست می‌رود.
3. `AGENT_MAX_IDENTICAL_TOOL_FAILURES = 2`: دو بار همان tool+args → حلقه **بسته** می‌شود (AGT-08). از دید کاربر «نتوانست رفع کند».
4. کاتالوگ هر نوبت حداکثر ۴۸ ابزار است. اگر intent دسته را اشتباه بزند، مدل نامی را صدا می‌زند که در registry نیست → `Function 'X' not found`.
5. نام ناشناخته با registry **write فرض می‌شود** (`is_write_function` fail-closed). مدل ممکن است برای ابزار خیالی کارت تأیید بگیرد.
6. `spawn_subagent` با `wait=True` پیش‌فرض تا ۹۰ ثانیه والد را قفل می‌کند؛ از نظر کاربر ابزار «هنگ» کرده.
- شواهد از سوابق: در موج‌های ۱۷–۱۸ اوت، AGT-08 عمداً تکرار شکست را قطع کرد تا حلقه بی‌نهایت نشود؛ مسیر **ترمیم آرگومان** (مثلاً «این فیلد اجباری است، دوباره با X صدا بزن») ساخته نشد.
- معیار پذیرش:
- خطای ابزار برای مدل همیشه JSON پایدار باشد: `ok=false`, `code`, `message_fa`, `hint_fa`, `retryable`, در صورت امکان `expected_args`.
- نام ابزار ناموجود → `UNKNOWN_TOOL` با نزدیک‌ترین نام‌های مجاز، نه Permission/Approval.
- پس از خطای قابل‌رفع، مدل حداقل یک بار با آرگومان اصلاح‌شده تلاش کند؛ اگر باز شکست خورد، به کاربر فارسی توضیح بدهد نه اینکه ساکت حلقه را ببندد.
- تست: کیس طلایی «ابزار با آرگومان ناقص» → retry با آرگومان کامل؛ کیس «نام غلط» → hint نه approval.
- پیشنهاد اصلاح:
1. لایهٔ `normalize_tool_error(exc, function_name, schema)` قبل از برگرداندن به مدل.
2. اگر نام در registry نیست، هرگز write/approval نشود.
3. در prompt پس از خطا: «آرگومان قبلی را تکرار نکن؛ از hint استفاده کن.»
4. لاگ `AI_METRIC tool_error` با `code` برای مشاهده‌پذیری.
- یادداشت اصلاح: ۱۴۰۵/۰۵/۲۸ — `normalize_tool_error` / `unknown_tool_result` قبل از write-guard؛ compact JSON فیلدهای `hint_fa` و `expected_args` را نگه می‌دارد؛ نام ناموجود دیگر approval نمی‌گیرد. باگ `unexpected keyword argument 'db'` در `_create_handler` برای قرارداد `(args, context)` رفع شد.
---
## CHAT-02 — زیر-ایجنت برای کاربر دیده و مدیریت نمی‌شود
- وضعیت: انجام‌شده
- اولویت: P1
- مالک: flutter + backend
- فایل‌ها: `ai_subagent.py`، `ai_function_extensions_subagent.py`، ویجت‌های `lib/widgets/ai/` (هیچ پنل subagent نیست)، `ai_agent_trace_timeline.dart`
- آنچه امروز هست (فاز ۰–۲ AGT-06):
- ابزارهای `spawn_subagent` / `await_subagent` / `cancel_subagent`.
- store فقط `_runs` درون‌حافظهٔ فرایند؛ با ری‌استارت API از بین می‌رود.
- سقف ۲ همزمان، ۴ نوبت، timeout ۹۰ ثانیه، بدون write در فرزند.
- پیش‌فرض `wait=False`: والد استریم را قفل نمی‌کند؛ برای ادغام صریح `await_subagent`.
- یادداشت اصلاح: ۱۴۰۵/۰۵/۲۸ — `trace_step kind=subagent` با `explore_target=subagent_id`؛ دکمهٔ قطع در تایم‌لاین؛ `POST /sessions/{id}/subagents/{sid}/cancel`؛ پیش‌فرض wait=false.
---
## CHAT-03 — جدول داخلی `trace_id` / `step_id` / `kind` در انتهای پیام
- وضعیت: انجام‌شده
- اولویت: P0
- مالک: flutter
- فایل‌ها: `ai_tool_envelope.dart` (`extractToolTableSpecsFromResults`)، `ai_visualization_spec.dart` (`AITableSpec.tryFromRecords`)، `ai_chat_message_body.dart`، `ai_trace.py` (`merge_trace_into_function_results`)
- علت قطعی:
1. پس از TOOL-04، اگر پاسخ markdown جدول نداشته باشد، UI **بزرگ‌ترین لیست Map** داخل `function_results` را جدول می‌کشد.
2. کلیدهای داخلی که skip می‌شوند فقط این‌ها هستند: `_agent_trace`, `_agent_budget`, `_agent_todos`, `_agent_run`.
3. `merge_trace_into_function_results` علاوه بر `_agent_trace`، لایهٔ تفکر را در `_reasoning_trace` می‌گذارد — **لیستی از گام‌ها با فیلدهای `trace_id`, `step_id`, `kind`, `state`, `layer`, `visibility`**.
4. `extractToolRecordsFromResult` اگر مقدار یک List از Map باشد همان را ردیف جدول می‌کند.
5. نتیجه دقیقاً جدولی است که کاربر گزارش کرده. `_citations` و `_activated_skills` هم می‌توانند جدول بی‌ربط بسازند.
- معیار پذیرش:
- هیچ کلید `_…` و هیچ لیست trace/citation/skill به‌صورت جدول داده نشان داده نشود.
- جدول فقط از envelope ابزار دامنه (`records`/`items`/…) با ستون‌های کسب‌وکاری (نام، مبلغ، تاریخ، کد) بیاید.
- ستون‌ها برچسب فارسی داشته باشند نه کلید خام API.
- تست واحد: `function_results` شامل `_reasoning_trace` با ۲+ گام → `extractToolTableSpecsFromResults` خالی یا فقط رکورد فاکتور نمونه.
- پیشنهاد اصلاح:
1. skip: `_reasoning_trace`, `_citations`, `_activated_skills` و هر کلید با پیشوند `_`.
2. اگر کلیدهای غالب ردیف `trace_id`/`step_id`/`kind` بود، جدول ساخته نشود.
3. allowlist ستون کسب‌وکار یا نگاشت `key → label_fa`.
- یادداشت اصلاح: ۱۴۰۵/۰۵/۲۸ — skip همهٔ کلیدهای `_…` از جمله `_reasoning_trace`؛ اگر ستون‌ها شبیه trace باشند جدول ساخته نمی‌شود.
---
## CHAT-04 — بولد به‌صورت `** متن **` در رابط
- وضعیت: انجام‌شده
- اولویت: P0
- مالک: flutter
- فایل‌ها: `ai_chat_message_body.dart` (`normalizeAssistantMarkdown`)، `ai_thinking_scroll_box.dart`، `ai_agent_trace_timeline.dart` (`_BodyContent`)
- علت:
1. نرمال‌سازی فاصله داخل `** … **` فقط روی **پاسخ نهایی** اعمال می‌شود.
2. پنل تفکر (`AIThinkingScrollBox`) و تایم‌لاین همان متن خام را به `MarkdownBody` می‌دهند.
3. مدل‌های reasoning اغلب `** متن **` با فاصله می‌نویسند (CommonMark این را بولد نمی‌داند).
4. با RTL فارسی، `flutter_markdown` گاهی `**` را جدا از کلمه پارس می‌کند؛ فاصلهٔ یونیکد فارسی/`\u200c` در regex فعلی (`[ \t]*`) نیست.
- معیار پذیرش:
- در پاسخ نهایی، پنل تفکر، و body گام‌های trace، `**متن**` و `** متن **` هر دو بولد دیده شوند.
- بلوک کد و `` `inline` `` دست نخورند.
- نمونهٔ فارسی با نیم‌فاصله در تست ویجت.
- پیشنهاد اصلاح:
1. `normalizeAssistantMarkdown` را به یک util مشترک ببرید و در هر `MarkdownBody` چت صدا بزنید.
2. regex را به فاصلهٔ یونیکد و ZWNJ گسترش دهید.
3. در صورت باقی‌ماندن باگ RTL، renderer جایگزین یا پیش‌پردازش قوی‌تر.
- یادداشت اصلاح: ۱۴۰۵/۰۵/۲۸ — `normalizeAssistantMarkdown` به util مشترک رفت؛ پنل تفکر و تایم‌لاین هم نرمال می‌شوند؛ فاصلهٔ یونیکد/ZWNJ پوشش داده شد.
---
## CHAT-05 — متن «در حال فکر کردن» انگلیسی است وقتی زبان کاربر فارسی است
- وضعیت: انجام‌شده
- اولویت: P1
- مالک: backend (+ flutter اگر سیاست نمایش عوض شود)
- فایل‌ها: `ai_language_prompt.py`، `ai_service.py` (استریم `reasoning_content` → `trace_step kind=reasoning`)، `ai_provider.py`، `ai_thinking_scroll_box.dart`
- علت:
1. بلوک زبان از مدل می‌خواهد reasoning را فارسی بنویسد.
2. کانال **native** مدل‌های reasoning (o-series / gpt-5 / thinking آنتروپیک) تقریباً همیشه انگلیسی است و از system prompt پیروی نمی‌کند.
3. همان توکن‌ها زنده به UI استریم می‌شوند.
4. `reasoning_language_mismatch` فقط `logger.warning` است — ترجمه، حذف، یا جایگزینی با خلاصهٔ فارسی انجام نمی‌شود.
- معیار پذیرش:
- اگر زبان مؤثر چت `fa` باشد، متن داخل باکس تفکر برای کاربر فارسی باشد (یا باکس native انگلیسی نشان داده نشود).
- عناوین گام (`title_key`) از قبل l10n هستند — حفظ شوند.
- اگر مدل فقط انگلیسی think کند: یا خلاصهٔ فارسی از narration/tool، یا باکس جمع‌شده با برچسب «در حال تحلیل» بدون پاراگراف انگلیسی.
- پیشنهاد اصلاح:
1. کوتاه‌مدت: برای `fa` کانال native را در UI نشان ندهید؛ فقط `narrative` / `thought` فارسی.
2. میان‌مدت: اگر mismatch، یک جملهٔ فارسی از آخرین tool/plan به‌جای raw CoT.
3. هرگز native English را در تاریخچهٔ قابل‌نمایش persist نکنید اگر زبان جلسه فارسی است (یا در لایهٔ `visibility=internal` بماند).
- یادداشت اصلاح: ۱۴۰۵/۰۵/۲۸ — `visible_reasoning_markdown` برای `fa` متن لاتین/انگلیسی native را با جملهٔ فارسی جایگزین می‌کند و همان در trace persist می‌شود.
---
## CHAT-06 — تأیید کاربر → لودینگ بی‌پایان و ادامهٔ توهم‌آمیز ایجنت
- وضعیت: انجام‌شده
- اولویت: P0
- مالک: flutter + backend
- فایل‌ها: `ai_chat_dialog.dart` (`_confirmWriteApproval`)، `ai_write_approval_banner.dart`، `adapters/api/v1/ai/chat.py` (persist پیام کاربر + `approve_writes`)، `ai_write_guard.py`، `ai_subagent.py` (`wait` پیش‌فرض)
- مسیر فعلی:
```
کاربر تأیید می‌زند
→ بنر پاک می‌شود (_clearWriteApprovalState)
→ _sending = true (لودینگ)
→ POST پیام جدید با متن:
«کاربر عملیات پیشنهادی را تأیید کرد. لطفاً همان عملیات را اجرا کن.»
skipUserBubble=true (در UI دیده نمی‌شود، در DB ذخیره می‌شود)
→ حلقهٔ کامل ایجنت از صفر، با approve_writes=true
```
- چرا لودینگ بی‌پایان حس می‌شود:
1. `consume` تا رویداد `done` یا بستن استریم صبر می‌کند؛ `_sending` همان‌قدر true می‌ماند.
2. اگر مدل `spawn_subagent(wait=true)` بزند، ژنراتور والد تا ۹۰ ثانیه chunk ابزاری نمی‌دهد؛ UI فقط heartbeat می‌بیند.
3. زیر-ایجنت نامرئی است (CHAT-02) → کاربر فقط اسپینر می‌بیند.
4. اگر استریم بدون `done` قطع شود یا cancel به‌اشتباه `return` شود، `_sending` ممکن است true بماند.
- چرا توهم پیش می‌آید:
1. تأیید به‌جای «ادامهٔ همان run با `approval_id`» یک **نوبت جدید کاربر** است. مدل متن را دستور تازه‌ای می‌بیند.
2. اگر همان write را دوباره صدا نزند، هیچ چیز اجرا نمی‌شود ولی ممکن است موفقیت را روایت کند.
3. اگر write دیگری صدا بزند → `APPROVAL_MISMATCH` یا اجرای آرگومان ذخیره‌شده؛ مدل گیج می‌شود.
4. تاریخچه حالا پیام جعلی «لطفاً اجرا کن» دارد؛ نوبت‌های بعدی روی همان توهم سوار می‌شوند.
- معیار پذیرش:
- دکمهٔ تأیید **پیام کاربر جدید نسازد** (نه در UI نه در DB).
- سرور همان `approval_id` / کارت ذخیره‌شده را اجرا کند، سپس در صورت نیاز یک نوبت کوتاه سنتز بدهد.
- از زدن تأیید تا اولین رویداد SSE (وضعیت «در حال اجرا» یا نتیجهٔ ابزار) کمتر از ~۲ ثانیه حس شود؛ بنر تا اتمام اجرا در حالت loading بماند نه اینکه اول پاک شود.
- اگر اجرا شکست خورد، بنر برگردد و خطای فارسی نشان داده شود؛ مدل حق ندارد موفقیت را حدس بزند.
- تست: تأیید `create_invoice` → یک بار handler واقعی؛ بدون ردیف user اضافی در `ai_chat_messages`.
- پیشنهاد اصلاح:
1. API جدا: `POST .../messages/{id}/approve` یا `POST .../runs/{run_id}/approve` با `approval_id`.
2. UI: `approveWrites` روی همان run، بدون `contentOverride`.
3. تا `done`، `writeApprovalLoading` روی بنر بماند.
4. spawn در حین اجرای تأیید یا `wait=false` باشد یا رویدادهای فرزند به استریم والد بیاید (CHAT-02).
- یادداشت اصلاح: ۱۴۰۵/۰۵/۲۸ — تأیید silent: پیام کاربر persist نمی‌شود؛ LLM turn با `[user_approved_writes]`؛ `user_query` از آخرین پیام واقعی؛ بنر تا پایان استریم می‌ماند؛ کلاینت‌های قدیمی با متن جعلی هم silent حساب می‌شوند.
---
# موارد کمکی برای کارکرد صحیح سیستم
این‌ها را کاربر جداگانه نگفته؛ از همان بررسی کد و سوابق برای جلوگیری از برگشت باگ‌ها لازم‌اند.
### CHAT-07 — جدول خودکار بیش از حد تهاجمی است
- وضعیت: انجام‌شده · اولویت: P2 · مالک: flutter
- هر لیست ≥۲ ردیف و ≥۲ ستون اسکالر جدول می‌شود؛ کلید خام انگلیسی برچسب ستون است.
- جدول را فقط از envelope با `ok=true` و کلید لیست شناخته‌شده بسازید؛ حداکثر یک جدول per پیام مگر مدل ` ```table ` بدهد.
- یادداشت اصلاح: ۱۴۰۵/۰۵/۳۰ — فقط envelope موفق (`ok=true` یا `_envelope`) و کلیدهای لیست شناخته‌شده؛ برچسب ستون فارسی برای فیلدهای رایج.
### CHAT-08 — کلیدهای داخلی `function_results` قرارداد واحد ندارند
- وضعیت: انجام‌شده · اولویت: P2 · مالک: backend + flutter
- امروز: `_agent_trace`, `_reasoning_trace`, `_citations`, `_activated_skills`, `_agent_budget`, `_agent_todos`, `_agent_run`.
- یک ثابت مشترک (یا پیشوند `_` اجباری + allowlist نمایش) در Python و Dart.
- یادداشت اصلاح: ۱۴۰۵/۰۵/۲۸ — UI هر کلید با پیشوند `_` را از جدول خودکار حذف می‌کند.
### CHAT-09 — نام ابزار ناشناخته نباید write/approval شود
- وضعیت: انجام‌شده · اولویت: P1 · مالک: backend
- `is_write_function(name, registry)` برای نام غایب `True` برمی‌گرداند → کارت تأیید برای تابعی که وجود ندارد.
- جدا کنید: `unknown` / `read` / `write`. unknown → CHAT-01.
- یادداشت اصلاح: ۱۴۰۵/۰۵/۲۸ — در مسیر چت، نام غایب از registry قبل از write-guard به `UNKNOWN_TOOL` می‌رود. MCP fail-closed برای نام ناشناخته حفظ شد.
### CHAT-10 — پیش‌فرض `wait=true` در spawn استریم والد را خفه می‌کند
- وضعیت: انجام‌شده · اولویت: P1 · مالک: backend
- حتی بدون پنل UI، `wait=false` + await صریح مانع لودینگ مرده می‌شود.
- وابسته به CHAT-02 و CHAT-06.
- یادداشت اصلاح: ۱۴۰۵/۰۵/۲۸ — پیش‌فرض `wait=False`.
### CHAT-11 — Stop / cancel استریم باید `_sending` را همیشه پایین بیاورد
- وضعیت: انجام‌شده · اولویت: P1 · مالک: flutter
- در `_runAssistantStream` اگر `CancelToken.isCancel` باشد گاهی فقط `return` است؛ مسیر تأیید/جایگزینی استریم را بررسی کنید که اسپینر گیر نکند.
- یادداشت اصلاح: ۱۴۰۵/۰۵/۲۸ — در مسیر cancel اگر همان token جاری باشد `_sending=false` می‌شود.
### CHAT-12 — persist زیر-ایجنت و بازیابی پس از رفرش
- وضعیت: انجام‌شده · اولویت: P2 · مالک: backend
- همان فاز ۳ سناریوی runtime. تا SQL نباشد، پنل UI بعد از refresh خالی است.
- یادداشت اصلاح: ۱۴۰۵/۰۵/۳۰ — جدول `ai_subagent_runs` + hydrate از GET `/subagents` روی آخرین پیام assistant.
### CHAT-13 — پیام pause تأیید نباید به‌عنوان پاسخ نهایی تاریخچه بماند
- وضعیت: انجام‌شده · اولویت: P2 · مالک: backend
- `build_approval_pause_content` اگر `accumulated_content` خالی باشد همان متن pause persist می‌شود. بعد از approve موفق، آن حباب باید با نتیجهٔ واقعی جایگزین یا علامت «منتظر تأیید» بخورد.
- یادداشت اصلاح: ۱۴۰۵/۰۵/۳۰ — پرچم `_awaiting_approval`؛ persist بعدی همان ردیف را جایگزین می‌کند؛ UI حباب pause را قبل از استریم تأیید برمی‌دارد.
### CHAT-14 — مشاهده‌پذیری خطا و زبان تفکر
- وضعیت: انجام‌شده · اولویت: P2 · مالک: backend
- `reasoning_language_mismatch` و `tool_error` را به متریک/داشبورد ببرید تا بدون خواندن جلسه بفهمیم نرخ انگلیسی‌بودن تفکر و نرخ شکست ابزار چقدر است.
- یادداشت اصلاح: ۱۴۰۵/۰۵/۳۰ — `log_ai_event` برای هر دو + GET `/admin/ai/ops-metrics`.
---
## نقشهٔ اتصال به ممیزی
| CHAT | آیتم ممیزی مرتبط | توضیح |
|------|------------------|--------|
| CHAT-01 | TOOL-01، AGT-08، TOOL-04 | کاتالوگ ۴۸تایی + قطع حلقه پس از شکست + خطای بدون schema |
| CHAT-02 | AGT-06 فاز ۴ | بک‌اند هست؛ UI نیست |
| CHAT-03 | TOOL-04 یادداشت «جدول از رکورد ابزار» | اثر جانبی همان فیکس |
| CHAT-04 | UX-02 / رندر پیام | نرمال‌سازی ناقص |
| CHAT-05 | PRM زبان + کانال reasoning | prompt هست؛ enforcement نیست |
| CHAT-06 | SEC-01، AGT-01 | approval_id هست؛ مسیر UI هنوز «پیام جدید» است |
---
## سناریوهای پذیرش دستی (قبل از بستن P0)
1. **جدول داخلی:** سوالی بپرسید که مدل چند ابزار بزند. انتهای پاسخ نباید جدول با ستون `trace_id`/`step_id`/`kind` باشد. اگر جدول فاکتور/کالا آمد، ستون‌ها فارسی و معنادار باشند.
2. **بولد:** از مدل بخواهید چند عنوان را برجسته کند. در حباب پاسخ و در باکس تفکر نباید `**` خام دیده شود.
3. **تأیید نوشتن:** در حالت با تأیید، ایجاد یک موجودیت آزمایشی. پس از تأیید: بدون پیام کاربر اضافه در تاریخچه؛ لودینگ تمام شود؛ موجودیت واقعاً ساخته شود؛ مدل «ساخته شد» بدون tool نگوید.
4. **تفکر فارسی:** یک سوال حسابداری فارسی. باکس تفکر یا فارسی است یا بدون پاراگراف انگلیسی جمع شده.
5. **زیر-ایجنت (پس از CHAT-02):** سوال چنددامنه‌ای. کارت فرزند دیده شود؛ قطع دستی کار کند؛ سوال «موجودی کالای X» کارت نسازد.
---
## تاریخچهٔ به‌روزرسانی
| تاریخ | نسخه | چه تغییر کرد |
|--------|------|----------------|
| ۱۴۰۵/۰۵/۲۷ | 1.0 | سند اولیه از روی شش گزارش کاربر + بررسی سورس و سوابق ممیزی ۱۷–۱۸ اوت |
| ۱۴۰۵/۰۵/۲۸ | 1.1 | CHAT-01…06 و CHAT-09…11 انجام شد: قرارداد handler `(args, context)`، خطای ابزار ساخت‌یافته، تأیید silent، فیلتر جدول داخلی، markdown مشترک، تفکر فارسی، پنل/قطع subagent |
| ۱۴۰۵/۰۵/۳۰ | 1.2 | CHAT-07/12/13/14: جدول envelope-only، persist زیر-ایجنت، جایگزینی pause تأیید، متریک خطا/زبان |
| ۱۴۰۵/۰۵/۳۰ | 1.3 | استریم CRM/تیکت (CHN-02) و replay بافر هنگام poll چند-ورکر |
<!-- الگو:
| ۱۴۰۵/۰۵/۲۸ | 1.1 | CHAT-03 انجام‌شده — skip `_reasoning_trace` و کلیدهای `_` |
-->