6.6 KiB
6.6 KiB
Architecture — Loyalty Agent Service
System Overview
AI-powered assistant for managing a Loyalty Platform, built on the Model Context Protocol (MCP).
┌──────────────────┐
│ React/Vite UI │
│ :9333 │
└────────┬─────────┘
│ WebSocket / STOMP
▼
┌──────────────────┐
│ Loyalty Agent │
│ :9332 │
│ │
│ LLM Orchestrator │
│ MCP Client │
│ Conversation │
│ PostgreSQL │
└────────┬─────────┘
│ MCP SSE (/sse, /message)
▼
┌──────────────────┐
│ Loyalty MCP │
│ Server :9331 │
│ │
│ 25+ MCP Tools │
└────────┬─────────┘
│ REST + OAuth2 (Client Credentials)
▼
┌──────────────────┐
│ Loyalty Core API │
│ :8081 │
└──────────────────┘
Keycloak ────── OAuth2 Token Provider
PostgreSQL ──── Conversation Persistence
Ollama ──────── Local LLM (Qwen3.5:4b)
Technology Stack
| Technology | Version | Purpose |
|---|---|---|
| Java | 25 | Runtime |
| Spring Boot | 4.0.0 | Framework |
| Spring AI | 2.0.0 | LLM & MCP integration |
| Ollama | latest | Local LLM hosting |
| Qwen | 3.5:4b | Primary LLM model |
| PostgreSQL | 15+ | Conversation persistence |
| Keycloak | latest | OAuth2/OIDC provider |
| React + Vite | 19/6 | Frontend UI |
Module Architecture
loyalty-mcp-server (:9331)
MCP Server exposing Loyalty Platform capabilities as MCP tools via SSE transport.
Responsibilities:
- Register 25+ MCP tools (campaign, rule, customer, transaction, etc.)
- Validate tool inputs
- Proxy requests to Loyalty Core API with OAuth2 client credentials
- Return structured
Result<T>with agent instructions for LLM formatting - Handle errors (sanitize stack traces, structured error responses)
Key Classes:
CampaignTools/CampaignRuleTools— Primary tool registrationCampaignService/CampaignRuleService— Business logicRestClientConfig— OAuth2 REST client factoryResult<T>— Standard response wrapper with_agent_instruction
loyalty-agent (:9332)
AI Agent / BFF — Orchestrates LLM, MCP, conversations, and WebSocket.
Responsibilities:
- Accept user messages via WebSocket/STOMP
- Classify intent (regex-based, zero LLM latency)
- Route to appropriate executor (SimpleAgent, Workflow, DirectChat)
- Stream LLM responses back to frontend
- Persist conversations to PostgreSQL
Key Architecture:
AgentController (@MessageMapping /chat)
↓
LoyaltyAgentService
↓
AgentOrchestrator
├── Active Workflow? → WorkflowExecutor (multi-turn creation)
├── IntentClassifier → QUERY → SimpleAgentExecutor (ReAct + tools)
├── IntentClassifier → CONVERSATION → SimpleAgentExecutor (direct chat)
└── IntentClassifier → CREATE_* → WorkflowExecutor
Intent Classification (regex-based):
| Intent | Trigger Examples |
|---|---|
QUERY |
"tìm chiến dịch", "danh sách rule" |
CONVERSATION |
"xin chào", "cảm ơn" |
CREATE_CAMPAIGN |
"tạo chiến dịch mới" |
CREATE_RULE |
"thêm thể lệ" |
Tool Scope Enforcement:
| Scope | Allowed Operations |
|---|---|
READ_ONLY |
search, get, find, count, check |
CREATE_CAMPAIGN |
READ_ONLY + createCampaign + generateCampaignId |
CREATE_RULE |
READ_ONLY + createRule + generateRuleId |
Frontend (:9333)
React/Vite conversational UI with STOMP WebSocket.
Key Features:
- STOMP over WebSocket for real-time messaging
- Token streaming (character-by-character LLM output)
- Tool execution status indicators
- Conversation management (create, list, delete, rename)
- Offline message queue with exponential backoff reconnect
Security Architecture
Frontend ──(WebSocket)──> Agent ──(MCP SSE)──> MCP Server ──(OAuth2)──> Core API
│
▼
Keycloak
Principles:
- LLM is never an authorization boundary — tool scope is enforced by
AgentToolScopebefore tools reach the LLM - OAuth2 Client Credentials — MCP Server authenticates to Core API via Keycloak
- Tool result sanitization —
ToolInterceptorstrips Java stack traces - Input validation — All tool parameters validated before downstream calls
Data Flow
Chat Request Flow
1. User types message in React UI
2. STOMP message → /app/chat → AgentController
3. AgentController → LoyaltyAgentService.chat()
4. AgentOrchestrator classifies intent
5. Executor starts LLM streaming with tools
6. LLM selects tool → ToolInterceptor wraps & calls MCP
7. MCP Server executes tool → calls Core API
8. Tool result → LLM reasons → generates response
9. Response streamed back via STOMP → /user/queue/chat-events
10. React UI renders tokens in real-time
Resilience Patterns
| Pattern | Implementation |
|---|---|
| MCP auto-reconnect | ToolInterceptor.attemptReconnect() |
| WebSocket reconnect | StompService exponential backoff + jitter |
| Request timeout | SimpleAgentExecutor configurable via agent.request-timeout-seconds |
| Tool result truncation | ToolInterceptor caps results > 4000 chars |
| Error sanitization | ToolInterceptor strips stack traces |
| Graceful shutdown | server.shutdown: graceful on both modules |
| LLM search noise filtering | SearchUtils.sanitizeSearch() |