refactor: remove manual process management from start-all.ps1 and add memory architecture planning document
This commit is contained in:
244
memory_analysis.md
Normal file
244
memory_analysis.md
Normal file
@@ -0,0 +1,244 @@
|
||||
# Memory & Tool Architecture Plan v3
|
||||
|
||||
*Cập nhật sau review: Approach C vẫn là hướng đúng, nhưng bản production nên là **Approach C+**. Trọng tâm không chỉ là dynamic tool loading, mà là policy catalog rõ ràng, router multi-label, DTO nhỏ, memory budget theo model, và state có TTL/idempotency.*
|
||||
|
||||
---
|
||||
|
||||
## 1. Kết luận ngắn
|
||||
|
||||
**Không nên load toàn bộ tools cho mọi request.** Khi domain Loyalty mở rộng từ prototype 14 tools lên 80+ tools, tool schema sẽ ăn phần lớn context window, nhất là các create tool dùng DTO sâu như `CampaignRule`.
|
||||
|
||||
**Hướng nên làm:** Dynamic Tool Loading + Domain Knowledge Registry, nhưng không filter bằng substring `contains`. Cần chuyển thành capability policy rõ ràng:
|
||||
|
||||
- Router phân loại `Action + Set<Domain> + confidence + workflow phase`
|
||||
- Tool policy catalog khai báo domain, operation, risk, phase, aliases
|
||||
- Per-request capability bundle chỉ expose tools cần thiết
|
||||
- Create/update tools dùng DTO nhỏ theo từng bước, không expose full domain model
|
||||
- Memory budget theo model context window, không dùng một con số 8K chung
|
||||
- Workflow/draft state có TTL, cleanup, idempotency, optimistic locking nếu chạy production
|
||||
|
||||
---
|
||||
|
||||
## 2. Evidence hiện tại trong repo
|
||||
|
||||
| Evidence | Ý nghĩa |
|
||||
|----------|---------|
|
||||
| `loyalty-agent/src/main/java/dev/sonpx/loyalty/agent/core/classifier/IntentClassifier.java` đang là rule-based, single-label | Dễ drop tool cần thiết khi user hỏi vừa campaign vừa rule, hoặc query phụ trong workflow |
|
||||
| `loyalty-agent/src/main/java/dev/sonpx/loyalty/agent/core/executor/AgentToolScope.java` đang suy capability từ tool name | Mở rộng bằng `contains("campaign")`/`contains("rule")` sẽ dễ false positive/false negative |
|
||||
| `loyalty-agent/src/main/java/dev/sonpx/loyalty/agent/core/executor/SimpleAgentExecutor.java` hiện gọi tools bằng `READ_ONLY` | Chưa có routing scope theo domain/action ở runtime |
|
||||
| `loyalty-agent/src/main/java/dev/sonpx/loyalty/agent/core/memory/InMemoryChatMemory.java` dùng estimate token và remove từng message | Có thể cắt lệch một turn user/assistant/tool, không phải whole-turn eviction |
|
||||
| `loyalty-agent/src/main/java/dev/sonpx/loyalty/agent/core/config/AgentProperties.java` mặc định `maxMemoryTokens = 8000` | Không an toàn cho mọi agent vì chat model có context 8K, query/creator model 32K |
|
||||
| `loyalty-agent/src/main/java/dev/sonpx/loyalty/agent/core/workflow/WorkflowExecutor.java` dùng `ConcurrentHashMap` active workflows | Có cleanup khi success/cancel, nhưng chưa có TTL nền và chưa bền khi restart |
|
||||
| Không thấy `loyalty-mcp-server/.../draft/DraftSessionManager.java` | Ghi chú cũ trong `context.md`/plan về DraftSessionManager 1h TTL là stale, không được coi là fact |
|
||||
| `loyalty-mcp-server/src/main/java/dev/sonpx/loyalty/mcp/reward/model/CampaignRule.java` có nhiều field và nested DTO | Full `createRule(CampaignRule dto)` làm tool schema rất lớn |
|
||||
|
||||
---
|
||||
|
||||
## 3. Problem statement đúng
|
||||
|
||||
### Prototype hiện tại
|
||||
|
||||
14 MCP tools vẫn fit context khá thoải mái. Vấn đề memory chưa phải bottleneck lớn nhất.
|
||||
|
||||
### Khi scale full domain
|
||||
|
||||
Nếu expose 80+ tools, đặc biệt có nhiều create/update tool với nested DTO:
|
||||
|
||||
```text
|
||||
32K context
|
||||
- System prompt: ~400 tokens
|
||||
- 80 tool definitions: ~20K-30K tokens
|
||||
- Conversation memory: ~4K-8K tokens
|
||||
- Tool results/response: ~3K-5K tokens
|
||||
=> Dễ overflow hoặc làm model 4B chọn sai tool
|
||||
```
|
||||
|
||||
Vì vậy bài toán chính là **tool selection + schema size**, không chỉ conversation memory.
|
||||
|
||||
---
|
||||
|
||||
## 4. Target Architecture: Approach C+
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
U["User message"] --> R["Intent router"]
|
||||
R --> D["Action + domains + confidence + phase"]
|
||||
D --> P["ToolPolicyCatalog"]
|
||||
P --> B["Capability bundle"]
|
||||
B --> L["LLM call with selected tools"]
|
||||
D --> K["DomainKnowledgeRegistry"]
|
||||
K --> L
|
||||
L --> T["MCP tools"]
|
||||
L --> M["Chat memory"]
|
||||
|
||||
W["Workflow state"] --> D
|
||||
W --> B
|
||||
```
|
||||
|
||||
### Core rule
|
||||
|
||||
Mỗi request chỉ được thấy tools đúng domain/action/phase. Unknown intent không được load `ALL`; chỉ load discovery/read-only tools có rủi ro thấp.
|
||||
|
||||
---
|
||||
|
||||
## 5. Implementation Plan
|
||||
|
||||
### Phase 1: Policy catalog + safer routing
|
||||
|
||||
1. Tạo `ToolPolicyCatalog`
|
||||
- Mỗi tool khai báo: `name`, `domains`, `operation`, `risk`, `workflowPhases`, `aliases`, `requiresConfirmation`
|
||||
- Default deny nếu tool chưa có policy
|
||||
- Không suy domain bằng substring tool name
|
||||
|
||||
2. Nâng `IntentClassifier`
|
||||
- Output không chỉ là một enum intent
|
||||
- Dùng structure kiểu:
|
||||
|
||||
```java
|
||||
record RoutingDecision(
|
||||
Action action,
|
||||
Set<Domain> domains,
|
||||
double confidence,
|
||||
WorkflowPhase phase,
|
||||
boolean sideQuery
|
||||
) {}
|
||||
```
|
||||
|
||||
3. Update `SimpleAgentExecutor`
|
||||
- Thay `AgentToolScope.READ_ONLY` cố định bằng capability bundle sinh từ `RoutingDecision`
|
||||
- Nếu confidence thấp: load discovery/read-only tools, không load create/update/delete tools
|
||||
|
||||
4. Test cần có
|
||||
- Contract test: mọi `@McpTool` phải có policy hoặc bị reject có chủ đích
|
||||
- Routing test: campaign query, rule query, campaign+rule query, unknown query, create workflow side-query
|
||||
- Safety test: unknown/create-like prompt không được expose create tool khi confidence thấp
|
||||
|
||||
### Phase 2: Shrink tool schemas
|
||||
|
||||
1. Không expose full model như `CampaignRule` cho create/update
|
||||
- Tạo command DTO nhỏ: `CreateRuleDraftCommand`, `SetRuleFormulaCommand`, `SetRuleCriteriaCommand`, `SubmitRuleCommand`
|
||||
- Chia theo workflow phase để mỗi step chỉ cần schema nhỏ
|
||||
|
||||
2. Formula nên chọn theo type
|
||||
- Không load cả 8 formula schemas cùng lúc nếu user đang chọn một formula type
|
||||
- Dùng discriminated union hoặc staged tool: chọn formula type trước, load schema cụ thể sau
|
||||
|
||||
3. Test cần có
|
||||
- Đo token/schema size trước-sau cho selected tools
|
||||
- Golden tests cho create rule flow
|
||||
- Test validation: thiếu required field phải hỏi lại, không tự bịa value
|
||||
|
||||
### Phase 3: Memory budget theo model
|
||||
|
||||
1. Tách budget theo agent/model
|
||||
- Chat 8K context không nên giữ `maxMemoryTokens = 8000`
|
||||
- Query/creator 32K có thể giữ lớn hơn, nhưng phải trừ tool schema + result budget
|
||||
|
||||
2. Sửa eviction theo whole turn
|
||||
- Không remove lẻ từng message nếu nó làm mất cặp user/assistant/tool result
|
||||
- Giữ system message, prune theo turn hoặc summary
|
||||
|
||||
3. Test cần có
|
||||
- Memory không vượt budget sau nhiều turn
|
||||
- Tool-call/result message không bị cắt lệch khiến transcript invalid
|
||||
|
||||
### Phase 4: Workflow/draft persistence
|
||||
|
||||
1. Không dựa vào `ConcurrentHashMap` cho production
|
||||
- Thêm TTL nền và cleanup định kỳ nếu vẫn single-node
|
||||
- Nếu multi-node: dùng DB/Redis/shared store
|
||||
|
||||
2. State cần có
|
||||
- `conversationId`, `workflowId`, `version`, `expiresAt`, `owner`, `idempotencyKey`
|
||||
- Optimistic locking/CAS để tránh double submit hoặc update đè
|
||||
|
||||
3. Test cần có
|
||||
- Expired workflow bị cleanup
|
||||
- Duplicate submit dùng cùng idempotency key không tạo hai record
|
||||
- Hai request song song không overwrite state sai
|
||||
|
||||
### Phase 5: DomainKnowledgeRegistry
|
||||
|
||||
1. Bắt đầu bằng static registry có version/source
|
||||
- Mỗi snippet ghi rõ domain, source file/spec, owner, last reviewed date
|
||||
- Inject theo domain/action, không inject toàn bộ
|
||||
|
||||
2. Khi nào mới cần RAG
|
||||
- Tổng domain knowledge thực sự vượt khả năng maintain bằng static snippets
|
||||
- Knowledge thay đổi thường xuyên
|
||||
- Có nhu cầu search tài liệu dài/không cấu trúc
|
||||
|
||||
RAG không giải quyết tool bloat. RAG chỉ giải quyết domain knowledge retrieval.
|
||||
|
||||
---
|
||||
|
||||
## 6. Edge Cases cần cover
|
||||
|
||||
| Edge case | Risk | Guardrail |
|
||||
|-----------|------|-----------|
|
||||
| User hỏi nhiều domain trong một câu: "campaign X có rule nào?" | Single-label router chỉ load campaign hoặc rule | Router trả `Set<Domain>` |
|
||||
| User đang create flow nhưng hỏi phụ: "có campaign nào active?" | Scope create thiếu read-only tools cần thiết | `sideQuery=true` cho phép bounded read-only fallback |
|
||||
| Tool name trùng hoặc ambiguous: `campaignRule` chứa cả campaign và rule | Substring filter load sai tools | ToolPolicyCatalog explicit |
|
||||
| Unknown intent nhưng prompt có từ "tạo" | Lỡ expose create tool | Unknown chỉ discovery/read-only; create cần confidence + workflow phase |
|
||||
| Create tool schema quá lớn | Context overflow, model chọn sai field | Staged command DTOs, schema measurement gate |
|
||||
| Memory 8K trên model context 8K | Không còn chỗ cho prompt/tools/response | Per-model budget formula |
|
||||
| Prune lẻ message | Mất tool result hoặc assistant turn, transcript invalid | Whole-turn eviction |
|
||||
| Restart service giữa workflow | Mất active workflow | Persist state trước production |
|
||||
| User submit hai lần | Double create | Idempotency key + CAS/version |
|
||||
| Tool result bị truncate giữa JSON | Model nhận JSON invalid | Truncate structured/result-aware, không substring raw JSON |
|
||||
| DomainKnowledgeRegistry stale | Bot trả sai nghiệp vụ | Version/source/owner/review date + tests |
|
||||
|
||||
---
|
||||
|
||||
## 7. Notes for implementation
|
||||
|
||||
- Ưu tiên Spring AI built-in tool filtering/resolver nếu đang dùng được trong version hiện tại; chỉ tự build filter layer khi cần thêm policy/risk/phase.
|
||||
- Không dùng `ALL` trong production path trừ admin/debug mode có guard riêng.
|
||||
- Tool policy phải fail closed: tool mới thêm mà chưa khai báo policy thì test fail.
|
||||
- Với destructive hoặc state-changing tools, cần confirmation/risk flag riêng, không chỉ dựa vào intent.
|
||||
- Nên log `routingDecision`, selected tool names, schema token estimate, latency, tool-call success/failure để benchmark.
|
||||
- Quyết định RAG/multi-agent phải dựa trên số liệu: selected tool count, schema token size, tool accuracy, latency, prompt overflow rate.
|
||||
- Tất cả con số token hiện tại là estimate. Trước khi implement rộng, cần đo schema thực tế bằng tokenizer/model target.
|
||||
|
||||
---
|
||||
|
||||
## 8. Roadmap cập nhật
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
title Memory & Tool Architecture Roadmap v3
|
||||
dateFormat YYYY-MM-DD
|
||||
section Phase 1: Routing Safety
|
||||
ToolPolicyCatalog + fail-closed tests :p1a, 2026-07-23, 2d
|
||||
RoutingDecision multi-domain/action :p1b, after p1a, 2d
|
||||
Runtime capability bundle :p1c, after p1b, 1d
|
||||
section Phase 2: Schema Reduction
|
||||
Create/update command DTOs :p2a, after p1c, 3d
|
||||
Formula staged loading :p2b, after p2a, 2d
|
||||
Schema token measurement gate :p2c, after p2b, 1d
|
||||
section Phase 3: Memory
|
||||
Per-model memory budgets :p3a, after p1c, 1d
|
||||
Whole-turn eviction :p3b, after p3a, 2d
|
||||
section Phase 4: Workflow Production
|
||||
TTL cleanup :p4a, after p3b, 1d
|
||||
Persistent workflow store :p4b, after p4a, 3d
|
||||
Idempotency + optimistic locking :p4c, after p4b, 2d
|
||||
section Phase 5: Knowledge
|
||||
Versioned DomainKnowledgeRegistry :p5a, after p2c, 2d
|
||||
Benchmark RAG/multi-agent necessity :p5b, after p5a, 2d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Final verdict
|
||||
|
||||
Approach C là đúng cho prototype và giai đoạn scale gần, nhưng chưa đủ để gọi là production best practice nếu chỉ implement bằng `contains()` và single-intent routing.
|
||||
|
||||
Best practice nên chốt là **Approach C+**:
|
||||
|
||||
1. Dynamic tool loading theo policy explicit
|
||||
2. Router multi-label theo action/domain/phase
|
||||
3. Small command DTOs thay vì full domain DTO
|
||||
4. Memory budget theo model + whole-turn eviction
|
||||
5. Workflow state có TTL/persistence/idempotency
|
||||
6. Domain knowledge có version/source, chưa cần RAG cho tới khi có số liệu chứng minh
|
||||
Reference in New Issue
Block a user