forked from hesabix/arc
162 lines
13 KiB
Markdown
162 lines
13 KiB
Markdown
# سناریوی قابلیتهای runtime ایجنت — subagent، ابزار موازی، context پایدار
|
||
|
||
**تاریخ:** ۱۴۰۵/۰۵/۲۸ (۱۸ اوت ۲۰۲۶)
|
||
**وضعیت:** فازهای PRM-04 ۰–۱، TOOL-06 A–B+D، AGT-06 ۰–۲ پیاده شد — UI موازی و دانش-بهابزار موکول
|
||
**مرجع ممیزی:** [`AI_AGENT_SYSTEM_AUDIT.md`](AI_AGENT_SYSTEM_AUDIT.md) (`AGT-06`، `TOOL-06`، `PRM-04`)
|
||
**رقیب مرجع:** Cursor Task/subagent، Claude Code، ChatGPT agent teams، OpenAI Agents SDK
|
||
|
||
این سند پاسخ خطبهخط به سه سوال محصول است و برای هر شکاف فاز اجرا با معیار پذیرش میدهد. تا وقتی آیتم ممیزی `انجامشده` نشده، پیادهسازی شروع نشود مگر مالک صریح بخواهد.
|
||
|
||
---
|
||
|
||
## ۱. جمعبندی سریع
|
||
|
||
| قابلیت | در سیستم امروز | کجا | شکاف با سطح جهانی |
|
||
|--------|-----------------|-----|-------------------|
|
||
| ساخت و هدایت subagent | **هست (فاز ۰–۲)** | `spawn_subagent` / `await_subagent` / `cancel_subagent` روی حلقهٔ تو در تو analyzer | UI تایملاین و persist SQL هنوز نیست |
|
||
| چند ابزار در یک نوبت، موازی | **هست (read)** | مدل میتواند چند `tool_calls` بدهد؛ بکاند read را تا ۴تایی موازی و write را سریال اجرا میکند | UI موازیبودن را نشان نمیدهد؛ نوبت اول سوال `complex` با `tool_choice` روی یک ابزار plan قفل میشود |
|
||
| تکرار بینش/حافظه/دانش در هر سوال | **لایهٔ semi_static cacheپذیر** | `static` + `semi_static` (حافظه/بینش/کانکتور) + `dynamic` (datetime/دانش/…) | دانش سوالوابسته هنوز در system است اگر RAG لازم باشد |
|
||
|
||
**نتیجه برای فرض سوم:** فرض «این اطلاعات داخل تاریخچهٔ گفتوگو تکرار میشود» دقیق نیست. پیامهای ذخیرهشدهٔ جلسه فقط user/assistant/tool هستند. آنچه تکرار میشود **بلاک runtime سیستم** است که بدون آن مدل در درخواست بعدی آن را به یاد ندارد. راهحل درست حذف از تاریخچه نیست (آنجا نیست)؛ راهحل **لایهٔ cache نیمهپایدار** و ارسال مجدد فقط وقتی محتوا عوض شده است.
|
||
|
||
---
|
||
|
||
## ۲. یافتهٔ ۱ — Subagent
|
||
|
||
### ۲.۱ آنچه در کد هست
|
||
|
||
- یک حلقهٔ ایجنت در `chat_completion_stream` با بودجه، todo، continuation، write guard.
|
||
- Run persist در `ai_agent_runs` و ادامه با `POST .../runs/{run_id}/continue` (AGT-01). هنوز همان درخواست/همان حلقه است (AGT-02).
|
||
- هیچ ابزار `spawn_subagent` / `delegate_task` در registry نیست.
|
||
- `GAP-07` (چند شخصیت حسابدار/انباردار) با این قابلیت یکی نیست: آن نقش ثابت است؛ subagent اجرای موقت با هدف و allowlist است.
|
||
|
||
### ۲.۲ رفتار مطلوب (Cursor / Claude)
|
||
|
||
1. والد تشخیص میدهد کار چنددامنهای است (مثلاً فروش + موجودی + بدهکاران).
|
||
2. یک یا چند subagent با `goal`، `tool_allowlist`، `max_iterations`، `execution_mode=analyzer` ساخته میشود.
|
||
3. والد میتواند صبر کند، ادامه بدهد، یا subagent را قطع کند.
|
||
4. خروجی subagent یک envelope است (یافتهها + citations + خطا)؛ والد آن را نقد میکند و پاسخ نهایی را میسازد.
|
||
5. subagent حق write ندارد مگر والد صریحاً بسپارد (پیشفرض ممنوع).
|
||
|
||
### ۲.۳ فاز اجرا
|
||
|
||
| فاز | کار | معیار پذیرش | ریسک |
|
||
|-----|-----|-------------|------|
|
||
| ۰ — قرارداد ✓ | ابزار `spawn_subagent` + store درونپردازهای `ai_subagent_runs` | schema در `function_registry` + intent + l10n | بدون UI هنوز |
|
||
| ۱ — حلقهٔ تو در تو ✓ | `AIService` با `operation=subagent`، سقف نوبت ۴، allowlist، `approve_writes=False` | تست واحد: دو read در فرزند؛ write در فرزند fail-closed | انفجار هزینه اگر سقف نباشد |
|
||
| ۲ — کنترل والد ✓ | tool result شامل `subagent_id`؛ ابزار `await_subagent` / `cancel_subagent` | والد بعد از cancel دیگر token فرزند را مصرف نکند | race با SSE والد |
|
||
| ۳ — ادغام | envelope استاندارد + citation از فرزند به والد | پاسخ نهایی بدون عدد بدون منبع از فرزند | نشت PII بین دامنه |
|
||
| ۴ — UI | گام `trace_step` با kind=`subagent` در تایملاین | کاربر ببیند کدام زیر-اجرا زنده/تمام/قطع است | شلوغی God Widget |
|
||
|
||
سقف پیشنهادی محصول: حداکثر **۲** subagent همزمان، حداکثر **۴** نوبت ابزار برای هر کدام، فقط کانال چت درونبرنامه (تلگرام/CRM/ورکفلو نه در فاز ۱).
|
||
|
||
**سناریوی پذیرش دستی**
|
||
|
||
1. «گزارش فروش این ماه، موجودی کالاهای کم، و سه بدهکار برتر را یکجا بده.»
|
||
2. انتظار: والد plan میسازد؛ حداقل یک spawn؛ نتایج در پاسخ نهایی با منبع.
|
||
3. کاربر Stop روی والد: فرزند هم cancel شود.
|
||
4. سوال ساده «موجودی کالای X؟» نباید spawn کند.
|
||
|
||
---
|
||
|
||
## ۳. یافتهٔ ۲ — اجرای موازی ابزار
|
||
|
||
### ۳.۱ آنچه در کد هست (انجامشده)
|
||
|
||
- اگر مدل در **یک** پیام assistant چند `tool_calls` بدهد، `handle_function_calls_async` آنها را به `run_tool_calls_partitioned` میدهد.
|
||
- Read حداکثر `MAX_PARALLEL_READ_TOOLS = 4` با semaphore؛ هر write سد سریال است؛ ترتیب مدل حفظ میشود.
|
||
- Prompt مسیریابی صریحاً میگوید: «ابزارهای read-only مستقل را در یک نوبت parallel صدا بزن» (`ai_tool_routing_prompt.py`).
|
||
- `parallel_tool_calls=false` در OpenAI و `disable_parallel_tool_use` در Anthropic **ست نشده** — یعنی ارائهدهنده موازی را اجازه میدهد.
|
||
- تست: `tests/test_ai_tool_parallel.py`.
|
||
|
||
### ۳.۲ آنچه نیست
|
||
|
||
- UI: تایملاین ابزارها را پشتسرهم نشان میدهد حتی اگر موازی اجرا شده باشند.
|
||
- نوبت صفر سوال `complex`: `tool_choice` روی `create_session_plan` است → در آن نوبت موازی عملاً قفل است (عمدی).
|
||
- مدل گاهی هنوز سریالی کار میکند؛ این محدودیت مدل است نه بکاند.
|
||
|
||
### ۳.۳ فاز اجرا (تکمیل، نه ساخت از صفر)
|
||
|
||
| فاز | کار | معیار پذیرش |
|
||
|-----|-----|-------------|
|
||
| A — مشاهده ✓ | لاگ `AI_METRIC` با `tool_calls_in_round` و `parallel_read_count` | رویداد `tool_round_parallel` |
|
||
| B — eval ✓ | کیس طلایی «فروش+موجودی+بدهکار» assertion که حداقل دو tool در یک round باشد | `ai_eval_assertions` + `DEFAULT_EVAL_CASES` |
|
||
| C — UI | در trace، ابزارهای همنوبت با یک bundleId نمایش موازی | کاربر زمان انتظار را کوتاهتر حس کند |
|
||
| D — سقف ✓ | اگر مدل >۴ read بدهد، همان semaphore فعلی کافی است؛ در prompt سقف را بنویس | stampede DB رخ ندهد |
|
||
|
||
**سناریوی پذیرش دستی**
|
||
|
||
1. همان سوال چنددامنهای پس از ساختهشدن plan.
|
||
2. در لاگ سرور یک round با ≥۲ function همزمان (زمان شروع همپوشان).
|
||
3. Write (مثلاً ایجاد فاکتور) هرگز با write دیگر موازی نشود.
|
||
|
||
---
|
||
|
||
## ۴. یافتهٔ ۳ — بینش و context در هر سوال جدید
|
||
|
||
### ۴.۱ مسیر واقعی هر پیام کاربر
|
||
|
||
```
|
||
POST .../messages?stream=true
|
||
→ persist پیام user
|
||
→ build_system_prompt_stream
|
||
static_core = نقش + سیاست untrusted ← cacheپذیر
|
||
business_anchor = شناسه کسبوکار + تقویم ← cacheپذیر
|
||
semi_static = حافظه + بینش + کانکتور ← cacheپذیر جدا
|
||
execution/plan = حالت اجرا + todos
|
||
runtime = datetime، دانش، مهارت، پیوست، todo
|
||
→ تاریخچه جلسه (حداکثر ۴۰ پیام؛ در صورت تنگی بودجه خلاصه)
|
||
→ حلقهٔ مدل + ابزار
|
||
```
|
||
|
||
بینش (`format_insights_for_prompt`) حدود چند خط KPI است (فروش امروز/هفته، بدهکاران، کمموجود) و با TTL **۳۰۰ ثانیه** در حافظهٔ فرایند کش میشود تا SQL تکرار نشود. **متن همان KPI هر نوبت دوباره در system میآید.**
|
||
|
||
دانش فقط اگر `query_needs_knowledge(user_query)` باشد لود میشود — یعنی وابسته به سوال است و نباید cache بلندمدت شود.
|
||
|
||
تاریخچهٔ DB شامل بینش نیست. پس «تکرار در تاریخچه» رخ نمیدهد؛ «تکرار در ورودی مدل» رخ میدهد و برای HTTP stateless طبیعی است.
|
||
|
||
Prompt cache (`ai_prompt_cache.py`) دو breakpoint دارد: `static_core + business_anchor` و لایهٔ `semi_static` (حافظه/بینش/کانکتور). datetime و دانش در `dynamic_system` میمانند.
|
||
|
||
### ۴.۲ راهحل پیشنهادی (ترتیب امن)
|
||
|
||
نباید بینش را بهکلی حذف کرد: مدل بدون آن در سوال «وضعیت امروز چطوره؟» مجبور به چند tool اضافه میشود. هدف کاهش **توکن تکراری** است نه حذف قابلیت.
|
||
|
||
| فاز | کار | معیار پذیرش |
|
||
|-----|-----|-------------|
|
||
| ۰ — اندازه ✓ | در `context_usage` فیلدهای `runtime_tokens` / `insights_tokens` / `static_tokens` / `semi_static_tokens` | عدد واقعی در لاگ `AI_METRIC context_usage` |
|
||
| ۱ — لایهٔ نیمهپایدار ✓ | `semi_static` = بینش + حافظهٔ دستورات + کانکتور؛ hash محتوا در cache_key | برای Anthropic، `cache_read_tokens` در نوبت ۲+ همان جلسه > ۰ وقتی KPI عوض نشده |
|
||
| ۲ — دانش فقط با ابزار | دانش را از system درآور؛ `search_knowledge` اجباری وقتی RAG لازم است | سوال غیر دانشمحور runtime بدون بلوک دانش |
|
||
| ۳ — datetime جدا ✓ | ساعت همیشه dynamic بماند (نباید کل cache را بشکند) | breakpoint cache قبل از datetime |
|
||
| ۴ — اختیاری | اگر hash بینش عوض شد (فروش جدید)، یک خط «بینش بهروز شد» در runtime | مدل عدد کهنه ندهد |
|
||
|
||
**آنچه نباید کرد**
|
||
|
||
- چسباندن بینش به پیام assistant در تاریخچه (تاریخچه را آلوده میکند و با خلاصه قاطی میشود).
|
||
- حذف کامل بینش از نوبتهای بعدی بدون cache لایهٔ سیستم (مدل کور میشود).
|
||
- گذاشتن دانش بازیابیشدهٔ هر سوال داخل prefix ثابت (cache غلط و پاسخ کهنه).
|
||
|
||
**سناریوی پذیرش دستی**
|
||
|
||
1. دو سوال پشتسرهم در یک جلسه: «سلام» سپس «فروش امروز چقدر بود؟»
|
||
2. انتظار: نوبت دوم SQL بینش نزند اگر TTL نگذشته (کش ۳۰۰ثانیه).
|
||
3. پس از فاز ۱: usage نوبت دوم `cache_read` داشته باشد؛ متن بینش در پیامهای DB نباشد.
|
||
|
||
---
|
||
|
||
## ۵. ترتیب پیشنهادی اجرا
|
||
|
||
1. **PRM-04 فاز ۰–۱** ✓
|
||
2. **TOOL-06 تکمیل A–B** ✓ (+ سقف prompt فاز D)
|
||
3. **AGT-06 فاز ۰–۲** ✓ — فاز ۳–۴ (ادغام citation در UI / تایملاین) بعدی است.
|
||
|
||
وابستگی: subagent موازیِ چند فرزند روی همان `run_tool_calls_partitioned` سوار میشود؛ سقف ۲ همزمان و ۴ نوبت اعمال شده.
|
||
|
||
---
|
||
|
||
## ۶. چکلیست PR وقتی یکی از فازها پیاده شد
|
||
|
||
- [x] ابزار جدید: registry + permission + intent + l10n + write guard
|
||
- [x] تست واحد مسیر اصلی + مسیر شکست (cancel subagent، write در فرزند، cache miss)
|
||
- [x] کیس eval یا سناریوی دستی بالا
|
||
- [x] بهروز کردن وضعیت آیتم در ممیزی و یک سطر تاریخچه
|
||
- [x] متریک `AI_METRIC` برای spawn / parallel_round / cache_hit
|