refactor: overhaul agent workflow engine with scoped tool execution, configurable LLM model profiles, and enhanced state management.
This commit is contained in:
544
sub_agent_deep_dive.md
Normal file
544
sub_agent_deep_dive.md
Normal file
@@ -0,0 +1,544 @@
|
||||
# Sub-Agent Architecture — Deep Dive (5 câu hỏi)
|
||||
|
||||
Dựa trên feedback và khảo sát codebase MCP server + loyalty-agent.
|
||||
|
||||
---
|
||||
|
||||
## 1. MCP Tool Design cho Loyalty Multi-Module
|
||||
|
||||
### Hiện trạng
|
||||
|
||||
```
|
||||
loyalty-mcp-server/
|
||||
└── tool/
|
||||
├── CampaignTools.java (7 tools: search, count, getById, getByIds, generateId, checkId, create)
|
||||
└── CampaignRuleTools.java (7 tools: search, count, getById, getByIds, generateId, checkId, create)
|
||||
```
|
||||
|
||||
Tổng: **14 tools**, tất cả expose qua 1 MCP SSE endpoint duy nhất (`/sse`). Agent nhận hết 14 tool definitions → context window bị ăn ~1000+ tokens chỉ cho tool schema.
|
||||
|
||||
### Loyalty có nhiều module hơn
|
||||
|
||||
Nhìn vào `client/api/` đã có sẵn `MemberTierApi.java`, `PointPoolApi.java`. Tương lai sẽ có:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ LOYALTY CORE MODULES │
|
||||
├──────────────┬──────────────┬──────────────┬────────────┤
|
||||
│ Reward │ Member │ Transaction │ Partner │
|
||||
│ │ │ │ │
|
||||
│ • Campaign │ • Profile │ • TxnCode │ • Merchant │
|
||||
│ • Rule │ • Tier │ • TxnHistory │ • Store │
|
||||
│ • Formula │ • Card │ • Counter │ • Offer │
|
||||
│ • Schedule │ • Segment │ • Statement │ │
|
||||
│ • Counter │ • Point Pool │ • Batch │ │
|
||||
│ • Alert │ • Enrollment │ │ │
|
||||
└──────────────┴──────────────┴──────────────┴────────────┘
|
||||
```
|
||||
|
||||
Nếu mỗi module có 5-7 tools → **tổng 30-50 tools**. 1 agent không thể xử lý tốt.
|
||||
|
||||
### Đề xuất: Tool Groups + Agent Scoping
|
||||
|
||||
**Phương án A — Grouped Tools trong 1 MCP Server (Đơn giản nhất)**
|
||||
|
||||
Giữ 1 MCP server, nhưng tools tổ chức theo package/group. Agent-side filter theo tên prefix:
|
||||
|
||||
```
|
||||
loyalty-mcp-server/
|
||||
└── tool/
|
||||
├── reward/
|
||||
│ ├── CampaignTools.java → tool names: reward_searchCampaigns, reward_getCampaignById...
|
||||
│ └── CampaignRuleTools.java → tool names: reward_searchRules, reward_getRuleById...
|
||||
├── member/
|
||||
│ ├── MemberTools.java → tool names: member_searchMembers, member_getMemberById...
|
||||
│ └── TierTools.java → tool names: member_searchTiers...
|
||||
└── transaction/
|
||||
└── TransactionTools.java → tool names: txn_searchTransactions...
|
||||
```
|
||||
|
||||
Agent-side filtering:
|
||||
|
||||
```java
|
||||
// Trong QueryAgent — chỉ load read tools của reward module
|
||||
List<ToolCallback> tools = toolInterceptor.getWrappedTools(sessionId, messageId)
|
||||
.stream()
|
||||
.filter(t -> {
|
||||
String name = t.getToolDefinition().name();
|
||||
return name.startsWith("reward_") && !name.contains("create") && !name.contains("generate");
|
||||
})
|
||||
.toList();
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> **Ưu điểm**: Không cần đổi MCP server architecture. Chỉ rename tools + filter ở agent.
|
||||
> **Nhược điểm**: Tất cả tool definitions vẫn load vào memory agent, chỉ filter khi truyền cho ChatClient.
|
||||
|
||||
---
|
||||
|
||||
**Phương án B — Multi-MCP Endpoint (Mạnh hơn, phức tạp hơn)**
|
||||
|
||||
Mỗi domain module là 1 MCP endpoint riêng. Agent chỉ connect tới endpoint cần thiết:
|
||||
|
||||
```yaml
|
||||
# application.yml — loyalty-agent
|
||||
spring:
|
||||
ai:
|
||||
mcp:
|
||||
client:
|
||||
sse:
|
||||
connections:
|
||||
reward:
|
||||
url: http://localhost:9331/sse # Reward tools only
|
||||
member:
|
||||
url: http://localhost:9331/member/sse # Member tools
|
||||
transaction:
|
||||
url: http://localhost:9331/txn/sse # Transaction tools
|
||||
```
|
||||
|
||||
Nhưng Spring AI MCP client hiện tại auto-discover **tất cả** connections. Để chọn lọc, cần custom `ToolCallbackProvider` filtering.
|
||||
|
||||
> [!NOTE]
|
||||
> **Khuyến nghị cho bước đầu**: Dùng **Phương án A** (1 MCP server, tool name prefix, agent-side filter). Đơn giản, hiệu quả, không cần thay đổi infrastructure.
|
||||
|
||||
---
|
||||
|
||||
**Phương án C — MCP Server per Module (Micro-MCP)**
|
||||
|
||||
Tách thành nhiều MCP server process:
|
||||
|
||||
```
|
||||
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
|
||||
│ reward-mcp │ │ member-mcp │ │ txn-mcp │
|
||||
│ :9331/sse │ │ :9332/sse │ │ :9333/sse │
|
||||
│ Campaign* │ │ Member* │ │ Transaction* │
|
||||
│ Rule* │ │ Tier* │ │ Counter* │
|
||||
└───────────────┘ └───────────────┘ └───────────────┘
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> Overkill cho giai đoạn hiện tại. Chỉ hợp lý khi mỗi module có team riêng hoặc deploy riêng.
|
||||
|
||||
### Tóm tắt: Phương án nào?
|
||||
|
||||
| Phương án | Độ phức tạp | Khi nào dùng |
|
||||
|---|---|---|
|
||||
| **A. Prefix + Filter** | ⭐ Thấp | **Nên bắt đầu từ đây** — 5-20 tools |
|
||||
| **B. Multi-MCP Endpoint** | ⭐⭐ Trung bình | 20-50 tools, cần isolation |
|
||||
| **C. Micro-MCP Server** | ⭐⭐⭐ Cao | 50+ tools, multi-team |
|
||||
|
||||
---
|
||||
|
||||
## 2. Shared Memory & Context Injection cho Creator Agent
|
||||
|
||||
### Vấn đề
|
||||
|
||||
User flow thực tế khi tạo campaign + rule:
|
||||
|
||||
```
|
||||
User: "danh sách chiến dịch" → QueryAgent, searchCampaigns
|
||||
User: "xem thể lệ Happy Birthday" → QueryAgent, searchRules
|
||||
User: "tạo thêm 1 rule mới tương tự" → CreatorAgent — CẦN BIẾT context trước đó!
|
||||
Cần biết: campaignId, rule template,
|
||||
formula type, existing rules...
|
||||
```
|
||||
|
||||
CreatorAgent cần:
|
||||
1. **Chat history** — biết user đang nói về campaign nào
|
||||
2. **Domain context** — query các thông tin liên quan (pools, tiers, counters...)
|
||||
3. **Validation data** — kiểm tra ID hợp lệ, trùng lặp...
|
||||
|
||||
### Kiến trúc Memory
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ SHARED MEMORY LAYER │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ InMemoryChatMemory (existing) │ │
|
||||
│ │ Key: conversationId (sessionId) │ │
|
||||
│ │ Shared across ALL agents │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ SessionContext (NEW) │ │
|
||||
│ │ Key: sessionId │ │
|
||||
│ │ Stores: activeCampaignId, lastQueryResult, │ │
|
||||
│ │ workflowState, userPreferences │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
│ │ │
|
||||
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
|
||||
│ Chat │ │ Query │ │ Creator │
|
||||
│ Agent │ │ Agent │ │ Agent │
|
||||
│ │ │ writes │ │ reads │
|
||||
│ │ │ context│ │ context │
|
||||
└─────────┘ └─────────┘ └─────────┘
|
||||
```
|
||||
|
||||
**Chat history (InMemoryChatMemory)** đã shared — tất cả agent dùng chung `conversationId` làm key. Khi user chuyển từ QueryAgent sang CreatorAgent, history vẫn giữ nguyên.
|
||||
|
||||
**SessionContext** mới — lưu structured data:
|
||||
|
||||
```java
|
||||
public class SessionContext {
|
||||
private String activeCampaignId; // Campaign đang được nói tới
|
||||
private String activeCampaignName; // Tên để inject vào prompt
|
||||
private String lastToolResult; // Kết quả tool gần nhất (compressed)
|
||||
private Map<String, Object> metadata; // Flexible key-value
|
||||
}
|
||||
```
|
||||
|
||||
### Context Enrichment cho Creator Agent
|
||||
|
||||
Khi Creator Agent bắt đầu tạo rule, nó cần query nhiều thông tin. Có 2 cách:
|
||||
|
||||
**Cách 1: LLM tự gọi tools (hiện tại)**
|
||||
|
||||
Để LLM quyết định gọi tools nào để lấy thông tin. Vấn đề: model nhỏ hay quên hoặc gọi sai.
|
||||
|
||||
**Cách 2: Code-driven Context Injection (khuyên dùng)**
|
||||
|
||||
```java
|
||||
public class CreatorAgent implements Agent {
|
||||
|
||||
@Override
|
||||
public void execute(ChatRequest request, String sessionId) {
|
||||
SessionContext ctx = sessionContextStore.get(sessionId);
|
||||
|
||||
// Auto-enrich: trước khi gọi LLM, query sẵn thông tin cần thiết
|
||||
StringBuilder enrichedContext = new StringBuilder();
|
||||
|
||||
if (ctx.getActiveCampaignId() != null) {
|
||||
// Auto-fetch campaign details
|
||||
Campaign campaign = campaignService.findById(ctx.getActiveCampaignId());
|
||||
enrichedContext.append("Campaign hiện tại: ")
|
||||
.append(campaign.getName())
|
||||
.append(" (ID: ").append(campaign.getId()).append(")\n");
|
||||
|
||||
// Auto-fetch existing rules
|
||||
List<CampaignRule> existingRules = ruleService.findByCampaignId(campaign.getId());
|
||||
enrichedContext.append("Rules hiện có: ").append(existingRules.size()).append("\n");
|
||||
|
||||
// Auto-fetch available pools
|
||||
List<PoolDefinition> pools = poolService.getAll();
|
||||
enrichedContext.append("Point pools: ")
|
||||
.append(pools.stream().map(PoolDefinition::getName).toList()).append("\n");
|
||||
}
|
||||
|
||||
chatClient.prompt()
|
||||
.system(CREATOR_SYSTEM_PROMPT)
|
||||
.user(enrichedContext + "\n\n" + request.prompt())
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Code-driven enrichment** tốt hơn "LLM tự gọi tool" vì:
|
||||
> 1. Deterministic — không phụ thuộc vào khả năng reasoning của model nhỏ
|
||||
> 2. Nhanh hơn — gọi API trực tiếp, không qua LLM → tool → LLM loop
|
||||
> 3. Đầy đủ hơn — developer biết rõ cần context gì
|
||||
|
||||
---
|
||||
|
||||
## 3. Multi-Ollama Endpoints (Thinking vs Non-Thinking)
|
||||
|
||||
### Setup
|
||||
|
||||
```
|
||||
┌──────────────────┐ ┌──────────────────┐
|
||||
│ Máy A (hiện tại)│ │ Máy B (mới) │
|
||||
│ 192.168.99.10 │ │ 192.168.99.XX │
|
||||
│ │ │ │
|
||||
│ qwen3.5:4b │ │ qwen2.5:3b │
|
||||
│ (thinking ON) │ │ (non-thinking) │
|
||||
│ GPU: ... │ │ GPU: ... │
|
||||
│ │ │ │
|
||||
│ Dùng cho: │ │ Dùng cho: │
|
||||
│ - Query Agent │ │ - Chat Agent │
|
||||
│ - Creator Agent │ │ - Formatter │
|
||||
└──────────────────┘ └──────────────────┘
|
||||
```
|
||||
|
||||
### Spring AI Config — Model Registry
|
||||
|
||||
```yaml
|
||||
# application.yml
|
||||
spring:
|
||||
ai:
|
||||
ollama:
|
||||
# Default (thinking model)
|
||||
base-url: http://192.168.99.10:11434
|
||||
chat:
|
||||
options:
|
||||
model: qwen3.5:4b
|
||||
temperature: 0.3
|
||||
num_ctx: 32768
|
||||
|
||||
# Custom — non-thinking endpoint
|
||||
agent:
|
||||
models:
|
||||
chat:
|
||||
base-url: http://192.168.99.XX:11434
|
||||
model: qwen2.5:3b
|
||||
temperature: 0.7
|
||||
query:
|
||||
base-url: http://192.168.99.10:11434
|
||||
model: qwen3.5:4b
|
||||
temperature: 0.3
|
||||
creator:
|
||||
base-url: http://192.168.99.10:11434
|
||||
model: qwen3.5:4b
|
||||
temperature: 0.2
|
||||
```
|
||||
|
||||
### Implementation
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class MultiModelConfig {
|
||||
|
||||
@Bean
|
||||
public OllamaApi thinkingOllamaApi() {
|
||||
return OllamaApi.builder()
|
||||
.baseUrl("http://192.168.99.10:11434")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OllamaApi fastOllamaApi() {
|
||||
return OllamaApi.builder()
|
||||
.baseUrl("http://192.168.99.XX:11434")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean("chatAgent.chatClient")
|
||||
public ChatClient chatAgentClient(
|
||||
@Qualifier("fastOllamaApi") OllamaApi api,
|
||||
ToolCallingManager toolCallingManager) {
|
||||
var model = OllamaChatModel.builder()
|
||||
.ollamaApi(api)
|
||||
.defaultOptions(OllamaChatOptions.builder()
|
||||
.model("qwen2.5:3b")
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.toolCallingManager(toolCallingManager)
|
||||
.build();
|
||||
return ChatClient.builder(model).build();
|
||||
}
|
||||
|
||||
@Bean("queryAgent.chatClient")
|
||||
public ChatClient queryAgentClient(
|
||||
@Qualifier("thinkingOllamaApi") OllamaApi api,
|
||||
ToolCallingManager toolCallingManager) {
|
||||
var model = OllamaChatModel.builder()
|
||||
.ollamaApi(api)
|
||||
.defaultOptions(OllamaChatOptions.builder()
|
||||
.model("qwen3.5:4b")
|
||||
.temperature(0.3)
|
||||
.build())
|
||||
.toolCallingManager(toolCallingManager)
|
||||
.build();
|
||||
return ChatClient.builder(model).build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> **Thay model dễ dàng**: Khi có model tốt hơn (ví dụ `qwen3:8b-no-think`), chỉ cần đổi trong config YAML, không sửa code.
|
||||
|
||||
---
|
||||
|
||||
## 4. Latency Optimization — Tránh Chain Agents
|
||||
|
||||
### Vấn đề
|
||||
|
||||
Chain 2 agents nối tiếp → latency gấp đôi:
|
||||
|
||||
```
|
||||
BAD: Query Agent (10s) → Formatter Agent (8s) = 18s tổng
|
||||
```
|
||||
|
||||
### Giải pháp: KHÔNG chain — mỗi agent tự hoàn chỉnh
|
||||
|
||||
```
|
||||
GOOD: Query Agent (10s) — tự format trong cùng 1 prompt = 10s tổng
|
||||
```
|
||||
|
||||
**Cách thực hiện**: Thay vì Formatter Agent riêng, nhúng formatting instruction vào `agent_instruction` (đã có sẵn!):
|
||||
|
||||
```java
|
||||
// Trong CampaignService.java (MCP server) — ĐÃ CÓ SẴN
|
||||
return Result.of(campaigns, """
|
||||
Trả lời tự nhiên bằng tiếng Việt. Không dùng markdown table.
|
||||
Hiển thị mỗi chiến dịch theo dạng danh sách đánh số:
|
||||
**[Tên]** (`[campaignId]`) — Owner: [ownerName]...
|
||||
""");
|
||||
```
|
||||
|
||||
`agent_instruction` pattern hiện tại **đã là formatter** — nằm trong tool result, không tốn thêm LLM call. Rất thông minh.
|
||||
|
||||
### Nơi nào thực sự cần parallel/async
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Creator Agent — Tạo Rule Flow │
|
||||
│ │
|
||||
│ 1. User: "tạo rule mới" │
|
||||
│ │
|
||||
│ 2. Context Enrichment (PARALLEL, code-driven, ~500ms): │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ getCampaign() │ │ getRules() │ │ getPools() │ │
|
||||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||||
│ └─────────────────┼─────────────────┘ │
|
||||
│ ▼ │
|
||||
│ 3. LLM Call (SINGLE, ~8-12s): │
|
||||
│ System: Creator prompt + enriched context │
|
||||
│ User: "tạo rule mới" │
|
||||
│ → LLM hỏi thêm hoặc confirm │
|
||||
│ │
|
||||
│ Total: ~9-13s (vs 17s+ hiện tại vì LLM phải tự gọi tool) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Parallel context fetching** (Java `CompletableFuture`):
|
||||
|
||||
```java
|
||||
// Context enrichment — chạy song song, mất ~500ms thay vì 3 LLM calls
|
||||
CompletableFuture<Campaign> campaignFuture = CompletableFuture.supplyAsync(
|
||||
() -> campaignService.findById(campaignId));
|
||||
CompletableFuture<List<CampaignRule>> rulesFuture = CompletableFuture.supplyAsync(
|
||||
() -> ruleService.findByCampaignId(campaignId));
|
||||
CompletableFuture<List<PoolDefinition>> poolsFuture = CompletableFuture.supplyAsync(
|
||||
() -> poolService.getAll());
|
||||
|
||||
// Wait all (parallel) — ~500ms
|
||||
CompletableFuture.allOf(campaignFuture, rulesFuture, poolsFuture).join();
|
||||
|
||||
// Inject into prompt
|
||||
String context = buildContext(campaignFuture.get(), rulesFuture.get(), poolsFuture.get());
|
||||
```
|
||||
|
||||
### Tóm tắt chiến lược latency
|
||||
|
||||
| Kỹ thuật | Tiết kiệm | Áp dụng |
|
||||
|---|---|---|
|
||||
| Tắt thinking cho Chat Agent | 17s → 2-3s | Chat Agent |
|
||||
| Formatter = `agent_instruction` | Bỏ 1 LLM call (~8s) | Query Agent |
|
||||
| Parallel context fetch | 3 API calls: 1.5s → 0.5s | Creator Agent |
|
||||
| Code-driven enrichment | Bỏ 2-3 tool-call loops (~20s) | Creator Agent |
|
||||
|
||||
---
|
||||
|
||||
## 5. On-Premise Fallback Strategy (Không dùng Cloud)
|
||||
|
||||
### Constraint: Bảo mật dữ liệu → chỉ chạy on-premise
|
||||
|
||||
Bỏ hoàn toàn phương án cloud. Thay vào đó:
|
||||
|
||||
### Strategy: Multi-Tier On-Premise Models
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ON-PREMISE MODEL TIERS │
|
||||
│ │
|
||||
│ Tier 1 (Fast, Light) Tier 2 (Strong, Slow) │
|
||||
│ ┌──────────────────┐ ┌──────────────────┐ │
|
||||
│ │ Máy B │ │ Máy A (GPU mạnh) │ │
|
||||
│ │ qwen2.5:3b │ │ qwen3.5:4b │ │
|
||||
│ │ non-thinking │ │ thinking │ │
|
||||
│ │ ~2-3s/response │ │ ~8-15s/response │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ Dùng cho: │ │ Dùng cho: │ │
|
||||
│ │ - Chat Agent │ │ - Query Agent │ │
|
||||
│ │ - Simple Q&A │ │ - Creator Agent │ │
|
||||
│ │ - Validation │ │ - Complex reasoning│ │
|
||||
│ └──────────────────┘ └──────────────────┘ │
|
||||
│ │
|
||||
│ Tương lai: Tier 3 (nếu có GPU mạnh hơn) │
|
||||
│ ┌──────────────────┐ │
|
||||
│ │ Máy C (A100/H100) │ │
|
||||
│ │ qwen3.5:14b │ │
|
||||
│ │ cho critical tasks │ │
|
||||
│ └──────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Khi nào escalate từ Tier 1 → Tier 2?
|
||||
|
||||
Không cần "confidence-based routing" phức tạp. Dùng **Intent-based routing** đơn giản:
|
||||
|
||||
```java
|
||||
// Router quyết định dựa trên intent, không dựa trên confidence
|
||||
switch (intent) {
|
||||
case CONVERSATION → chatAgent; // Tier 1 (fast model)
|
||||
case CAMPAIGN_QUERY,
|
||||
RULE_QUERY,
|
||||
UNKNOWN → queryAgent; // Tier 2 (thinking model)
|
||||
case CAMPAIGN_CREATE,
|
||||
RULE_CREATE → creatorAgent; // Tier 2 (thinking model)
|
||||
}
|
||||
```
|
||||
|
||||
### Retry Strategy (nếu model trả kết quả kém)
|
||||
|
||||
Thay vì fallback lên cloud, **retry cùng model với prompt khác**:
|
||||
|
||||
```java
|
||||
// Nếu tool result rỗng hoặc LLM trả "tôi không biết"
|
||||
if (isLowQualityResponse(response)) {
|
||||
// Retry 1: thêm few-shot example vào prompt
|
||||
// Retry 2: simplify câu hỏi
|
||||
// Retry 3: báo user "Không tìm thấy, vui lòng thử cách khác"
|
||||
}
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Không retry bằng model khác** — retry bằng **prompt khác** trên cùng model hiệu quả hơn cho small LLMs. Thêm 1 ví dụ cụ thể vào prompt thường giải quyết 80% trường hợp model trả sai.
|
||||
|
||||
---
|
||||
|
||||
## Tổng kết — Bước tiếp theo
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ KIẾN TRÚC ĐỀ XUẤT CUỐI │
|
||||
│ │
|
||||
│ Máy B (fast) Máy A (strong) │
|
||||
│ ┌────────────┐ ┌────────────┐ │
|
||||
│ │ qwen2.5:3b │ │ qwen3.5:4b │ │
|
||||
│ └─────┬──────┘ └─────┬──────┘ │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌──────────┐ ┌──────────────────┐ │
|
||||
│ │ChatAgent │ │ QueryAgent │ │
|
||||
│ │(no tools)│ │ (read tools, │ │
|
||||
│ │ │ │ prefix filter) │ │
|
||||
│ └──────────┘ └──────────────────┘ │
|
||||
│ ┌──────────────────┐ │
|
||||
│ │ CreatorAgent │ │
|
||||
│ │ (write tools, │ │
|
||||
│ │ code-driven │ │
|
||||
│ │ enrichment) │ │
|
||||
│ └──────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────┐ │
|
||||
│ │ Shared Memory Layer │ │
|
||||
│ │ InMemoryChatMemory + SessionContext │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────┐ │
|
||||
│ │ MCP Server (1 process) │ │
|
||||
│ │ Tools: reward_*, member_*, txn_* │ │
|
||||
│ │ Agent-side filtering by prefix │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Nếu anh thấy hướng này ổn, mình có thể tạo change proposal (`/opsx-propose`) để bắt đầu implement từng phase:
|
||||
|
||||
1. **Phase 1**: Agent interface + ChatAgent (tắt thinking, model riêng) — cải thiện "hello" ngay
|
||||
2. **Phase 2**: Tool prefix naming + QueryAgent (filtered tools) — giảm tool confusion
|
||||
3. **Phase 3**: CreatorAgent + SessionContext + code-driven enrichment — nâng chất lượng tạo campaign/rule
|
||||
4. **Phase 4**: Multi-Ollama config + ModelRegistry — hoàn thiện infra
|
||||
Reference in New Issue
Block a user