A scalable B2B pipeline that ingests raw customer requests, analyses them with an AI agent into structured data (urgency 1–10, category, summary), caches LLM responses, and fires automated actions (webhook / e-mail) for high-urgency items.
┌───────────┐ raw text / JSON
client ──▶│ Ingest │ POST /api/v1/ingest
│ API │
└─────┬─────┘
│ persist (Postgres)
▼
┌───────────┐ structured output (urgency, category, summary)
│ AI Agent │ Spring AI ▸ Anthropic ─ Redis cache (skip repeats)
└─────┬─────┘
│ persist analysis
▼
┌───────────┐ urgency ≥ threshold?
│ Action │ ── Webhook ▸ customer endpoint
│ Dispatcher│ └─ Email ▸ alert inbox
└───────────┘
│
▼
Next.js dashboard ◀── GET /api/v1/metrics, /ingestions
| Layer | Tech |
|---|---|
| Backend | Spring Boot 3.4, Java 17, Spring AI (Anthropic) |
| Data & cache | PostgreSQL (Flyway), Redis (LLM response cache + rate limiting) |
| Frontend | Next.js (App Router), TailwindCSS, Recharts |
Clean, layered, provider-agnostic:
domain/— entities (Ingestion,Analysis,ActionExecution) + enums. No framework leakage.ai/— theAnalyzerport and its Anthropic implementation (AnthropicAnalyzer). Structured output is enforced by Spring AI'sBeanOutputConverteragainst theAiAnalysisrecord, so the model can only return valid{urgencyScore, category, summary}.action/— theActionDispatcherport + async webhook/e-mail handlers.service/— theIngestionServiceuse case orchestrating the flow. The external LLM call runs outside any DB transaction so a slow response never holds a pooled connection.repository/— Spring Data JPA + read projections for the dashboard.api/— REST controllers, DTOs, and a global error handler.
- LLM response cache — identical inputs are hashed (SHA-256); a hit skips the model call entirely (cost + latency win), and the
fromCacheflag is surfaced in metrics. - Rate limiting — a per-tenant fixed-window counter (atomic
INCR) that is correct across instances.
docker compose up -d # Postgres :5432, Redis :6379, Mailhog :8025cd backend
export ANTHROPIC_API_KEY=sk-ant-... # required
# optional: export TRIAGE_MODEL=claude-haiku-4-5 (cheaper, high-volume)
# optional: export TRIAGE_WEBHOOK_URL=https://example.com/hook
# optional: export TRIAGE_ALERT_EMAIL=alerts@example.com
mvn spring-boot:run # Maven 3.9+ and JDK 17 required
# (no wrapper bundled — generate one with `mvn -N wrapper:wrapper` if you prefer ./mvnw)The default model is claude-opus-4-8; set TRIAGE_MODEL to switch.
cd frontend
cp .env.local.example .env.local
npm install && npm run dev # http://localhost:3000Ingest (JSON):
curl -X POST http://localhost:8080/api/v1/ingest \
-H "Content-Type: application/json" \
-H "X-Tenant-Id: acme" \
-d '{"content":"Our checkout has been down for 2 hours, customers cannot pay!","source":"email"}'{
"ingestionId": "…",
"analysisId": "…",
"urgencyScore": 9,
"category": "BUG_REPORT",
"summary": "Checkout outage for ~2h blocking all payments; needs immediate attention.",
"fromCache": false,
"actionsTriggered": true
}Ingest (raw text): POST /api/v1/ingest with Content-Type: text/plain.
Metrics: GET /api/v1/metrics · Recent feed: GET /api/v1/ingestions?limit=25
| Env var | Default | Purpose |
|---|---|---|
ANTHROPIC_API_KEY |
— | Anthropic API access (required) |
TRIAGE_MODEL |
claude-opus-4-8 |
Analysis model |
TRIAGE_WEBHOOK_URL |
— | Webhook target for urgent items |
TRIAGE_ALERT_EMAIL |
— | Alert inbox for urgent items |
DB_URL / DB_USER / DB_PASSWORD |
local Postgres | Database connection |
REDIS_HOST / REDIS_PORT |
localhost:6379 |
Cache & rate limiter |
Urgency threshold, cache TTL, and rate limit live under triage.* in application.yml.
This is an MVP template — production would add auth/multi-tenant isolation, an outbox for exactly-once actions, retries/DLQ, and observability.