Project Status · Java AI Agent Application

Dawn AI

基于 Java 17 + Spring Boot 3.2 + Spring AI 1.1.4 的生产级 AI Agent 应用。 融合 ReActPlan-and-Solve 双范式,配备三层记忆体系混合检索 + 重排序 RAG 管道,以及 Prometheus / Langfuse 全链路可观测。

Java17 Spring Boot3.2.5 Spring AI1.1.4 LLMQwen3.5-9B-MLX-4bit Embeddingbge-m3 · 1024d Vectorpgvector · HNSW · cosine MemoryRedis TraceLangfuse · OTel
01 · Tech Stack

技术选型

每个组件都有明确动机:性能、可观测、可替换。无 Spring AI 之外的额外编排框架,避免抽象冗余。

Runtime
Java
17 (LTS)
Framework
Spring Boot
3.2.5
AI SDK
Spring AI
1.1.4 · OpenAI compat
Web
Spring MVC + SseEmitter
阻塞 IO · 中等并发
Vector DB
pgvector
HNSW · COSINE
RDBMS
PostgreSQL
JPA + FTS BM25
Cache / Memory
Redis
List + LRU eviction
Doc Parse
Apache Tika
2.9.2 · 多格式
Metrics
Micrometer + Prometheus
Counter / Timer / Summary
Tracing
OpenTelemetry → Langfuse
OTLP HTTP/Protobuf
Dashboards
Grafana
JVM + Business
AOP
Spring AOP
@Aspect · Tool tracing
Async
Reactor + Micrometer Context
ThreadLocal 跨线程传播
LLM (local)
oMLX · Qwen3.5-9B
MLX-4bit
Embedding (local)
bge-m3-mlx-fp16
1024 维
Packaging
Docker Compose
profiles: metrics / observe
02 · Architecture

系统架构

四层清晰:Controller → Service → Agent/RAG/Memory 协作 → Spring AI 抽象 LLM/Embedding/Tool。

┌──────────────────────────────────────────────────────────────────────────┐
│                            REST API Layer                                │
│   ChatController · RagController · TopicController · AiInteractionCtrl   │
│              SSE streaming via SseEmitter (Spring MVC)                   │
└────────────────────────────────┬─────────────────────────────────────────┘
                                 │
┌────────────────────────────────▼─────────────────────────────────────────┐
│                            Service Layer                                 │
│   ChatService · RagService · MemoryService · TopicQueryService           │
└────┬──────────────────────┬──────────────────────────┬──────────────────┘
     │                      │                          │
┌────▼────────────┐  ┌──────▼─────────────┐   ┌────────▼────────────────┐
│   Agent Core    │  │    RAG Pipeline      │   │     Memory System       │
│ ─────────────── │  │ ─────────────────  │   │ ─────────────────────── │
│ AgentOrch.      │  │ DocumentExtractor  │   │ Working  (Redis)        │
│ TaskPlanner     │  │ OverlapSplitter    │   │ Summary  (pgvector)     │
│ ToolRegistry    │  │ QueryRewriter+HyDE │   │ Long-term (reflection)  │
│ StepCollector   │  │ Hybrid Retrieval   │   │ UserProfileService      │
│  + AOP Aspect   │  │  Dense ⊕ BM25/RRF  │   │ Consolidator / Decay /  │
│ Tools: ×3       │  │ 2-stage Rerank     │   │ Eviction / Reflection   │
└────┬────────────┘  └──────┬─────────────┘   └────────┬────────────────┘
     │                      │                          │
     └──────────────────────┴──────────────────────────┘
                                 │
┌────────────────────────────────▼─────────────────────────────────────────┐
│                  Spring AI (1.1.4) · OpenAI-compatible                  │
│        ChatClient · EmbeddingModel · VectorStore · Function Tools        │
└──────────────────────────────┬───────────────────────────────────────────┘
                               │
                  ┌────────────▼─────────────┐
                  │  oMLX local · or Cloud   │
                  │  Qwen3.5-9B · bge-m3     │
                  └──────────────────────────┘
03 · Agent Paradigm

ReAct + Plan-and-Solve 混合范式

不是二选一。Plan 给骨架,ReAct 给灵活性:先一次性低温度规划,再用强制约束驱动 ReAct 严格执行。稳定与灵活的平衡点。

PARADIGM A

Plan-and-Solve

TaskPlanner · 独立 LLM 调用 · 结构化输出
  • 先全局规划:不依赖对话历史,低 temperature 拿整体视角
  • 使用 Spring AI BeanOutputConverter 强制结构化输出 List<PlanStep>
  • 每步声明 action / reason,可解析、可追溯
  • 失败时回退到纯 ReAct(PlanGenerationException → empty plan)
  • Metric:ai.planner.result{status=success|parse_error}
PARADIGM B

ReAct (Reasoning + Acting)

AgentOrchestrator · Spring AI 工具循环
  • 运行时灵活:Spring AI 内置 tool-calling 循环,逐步 Thought → Action → Observation
  • 3 个 Tool 自动注册:Calculator · Weather · KnowledgeSearch
  • 每次工具调用被 ToolExecutionAspect (AOP) 拦截 → StepCollector 记录
  • 硬上限 max-steps=10,超出抛 MaxStepsExceededException
  • RAG 工具内部去重:相同 rewritten query 不重复检索

· How they combine ·

1 · planTaskPlanner.plan()独立 LLM 调用生成 PlanStep[];附带 reasoningContent
2 · enforcebuildSystemPrompt()把 plan 注入 system prompt + 强制约束指令
3 · reactChatClient.toolNames()ReAct 循环在 plan 骨架下执行,逐步触发对应工具
4 · traceToolExecutionAspect每次工具调用通过 AOP 自动捕获为 AgentStep
5 · streamChatStreamEventplan_thinking → plan → thinking → step → token → done
T

设计权衡:为什么要混合?

纯 ReAct 在复杂任务下容易"跑题"或绕过工具直接回答;纯 Plan 牺牲了响应当前上下文的灵活性。 混合方案让 LLM 先想清楚再做——Plan 像编译期检查,ReAct 像运行时执行。强制约束指令杜绝模型"拿训练知识糊弄"的退化路径。

04 · Agent Design

Agent 实现细节

围绕 AgentOrchestrator 的 8 步生命周期。StepCollector 用 ThreadLocal 收集,AOP 切面零侵入。

┌─ AgentOrchestrator.chat() / streamChat() ─────────────────────────────────┐
│                                                                              │
│   1. StepCollector.init(maxSteps)         // reset thread-local state      │
│   2. TaskPlanner.plan()                   // separate low-temp LLM call     │
│   3. buildSystemPrompt(plan, sessionId, topicId)                              │
│        ├─ baseSystemPrompt                                                  │
│        ├─ UserProfileService.formatForSystemPrompt()   // 个性化注入      │
│        ├─ topicSection (若指定 topicId)                                       │
│        ├─ formatPlan(plan)                                                  │
│        ├─ formatPlanEnforcement(plan)                  // 强制工具调用约束     │
│        └─ maxSteps 提示                                                       │
│   4. buildHistory(sessionId)              // 从 MemoryService 拉取最近 20 条│
│   5. chatClient.prompt().toolNames(...).call() / .stream()                    │
│        ↳ Spring AI 自动管理 ReAct 循环                                         │
│        ↳ ToolExecutionAspect (AOP) 拦截每次 apply() → StepCollector       │
│   6. recordTokenUsage() · recordRagMetrics()    // Micrometer counters    │
│   7. memoryService.addMessage(user) · addMessage(assistant)                    │
│   8. StepCollector.clear()                // 防止 ThreadLocal 泄漏         │
│                                                                              │
└──────────────────────────────────────────────────────────────────────────────┘
Tool · Computation

CalculatorTool

递归下降解析器,支持 + − × ÷ 与括号。Sanitize 后只接受 [0-9+\-*/().] 字符。生产可换 exp4j。

Tool · External

WeatherTool

Mock 数据演示 Function Calling 模式(Beijing/Shanghai/Shenzhen/Chengdu)。生产替换为真实 API + Resilience4j 熔断。

Tool · Retrieval

KnowledgeSearchTool

触发完整 RAG 管道:QueryRewriter → HyDE → Hybrid → Rerank。基于 StepCollector 做请求内查询去重,节省 LLM/检索调用。

Reactor
10

max-steps

单次对话工具调用硬上限

RAG
3

max-calls-per-session

单次请求 RAG 调用上限

Stream
1200s

SSE timeout

单次流式请求最大等待

Planner
structured

BeanOutputConverter

JSON Schema 强制输出

05 · Memory System

三层记忆体系

从 Working → Summary → Long-term,事件驱动逐级蒸馏。配合衰减与淘汰,避免向量库无限膨胀。

Tier 1 · Hot

Working Memory

Redis · List + TTL

当前会话的原始对话流。新消息 rpush,超过上限 lpop 进入待蒸馏队列。Redis 不可用时自动降级到 ConcurrentHashMap fallback。

key
ai:session:{id}
max
20 messages
ttl
2 hours
fallback
in-memory map
Tier 2 · Warm

Summary Memory

pgvector · Document type=summary

每淘汰满 batch_size 条消息,触发 SummarizationRequestEvent → MemorySummarizer 异步蒸馏成段落,带 importance score 写入向量库供后续检索。

trigger
batch-size=3
flow
drain → event → async
store
VectorStore.add()
meta
importance · createdAt
Tier 3 · Cold

Long-term Reflection

pgvector · type=reflection

每累计 N 个 summary,触发 ReflectionRequestEvent → ReflectionWorker 跨会话归纳"用户的薄弱点 / 偏好"。配合 UserProfileService 反哺 system prompt。

trigger
reflection-threshold=3
episode-min
4
consumer
UserProfileService
inject
→ system prompt

· Background Workers ·

组件触发职责关键配置
MemorySummarizerSummarizationRequestEvent对话片段 → 摘要 + importance scorebatch-size=3
MemoryConsolidatorConsolidationRequestEvent写入 VectorStore,达阈值触发反思reflection-threshold=3
ReflectionWorkerReflectionRequestEvent归纳长期模式 → 写回 type=reflectionepisode-threshold=4
ImportanceDecayManagercron 0 30 3 * * ?30 天无访问 importance 减半half-life=30d · min=0.01
EvictionPolicyManagercron 0 0 3 * * ?低重要度 / 过期文档物理淘汰threshold=0.1 · max-age=2d
MemoryAccessUpdater每次检索命中更新 lastAccessedAt 时间戳
UserProfileService每次 chat 入口从 reflection 提取画像 → 注入 system prompt
06 · RAG Pipeline

检索增强:混合 + 重排

摄入与查询两条主链路。RetrievalRouter 启发式选择 DENSE / HYBRID / SPARSE,Rerank 支持启发式与 cross-encoder。

Ingestion
parseApache Tikamulti-format
splitOverlapTextSplitter500 / 50 tokens
embedbge-m31024 dim · fp16
storepgvectorHNSW · cosine
Query
rewriteQueryRewriterLLM normalize
expandHyDEhypothetical doc
routeRetrievalRouterheuristic
recallDense ⊕ BM25PG FTS sparse
fuseRRFreciprocal rank
rerankHeuristic / X-Enc2-stage
filtertop-Ksim ≥ 0.6

Retrieval 配置

chunk-size / overlap500 / 50
similarity-threshold0.6 bge-m3 区间 0.4–0.65
default-top-k5
hybrid-enabledtrue · BM25 + Dense
sparse FTS configenglish
rerank-enabled / typetrue · heuristic | cross-encoder
query-rewrite / hydetrue / true
max-calls-per-session3

RetrievalRouter 启发式

// 自动选择策略
if (strategy != AUTO)         return strategy;
if (hasMetadataFilters())     return DENSE;

// 短查询 / 引号 / 含数字 → keyword-like
boolean keywordLike =
    tokens.size() <= 3
 || query.contains("\"")
 || query.matches(".*\\d.*");

return keywordLike ? HYBRID : DENSE;

短查询(核心名词)走 BM25;自然语言长句走 Dense;带过滤器(如 topicId)始终用 Dense 以利用 metadata 过滤。

07 · Observability

可观测:双线 opt-in

Metrics 与 LLM Tracing 两条独立线,docker-compose overlay 按需启用,互不依赖。生产可分别下沉到 VictoriaMetrics / Langfuse 自托管。

Profile 矩阵

Profile容器UI
baseapp + postgres + redis
--profile metrics+ prometheus + grafana:3000
--profile observe+ langfuse-* + clickhouse + minio:3001
both全开:3000 + :3001

关键 Metric

ai.agent.chat.durationAgent 端到端延迟
ai.token.input / outputToken 成本追踪
ai.rag.calls_per_session每会话 RAG 调用分布
ai.rag.dedup.skipped请求内查询去重命中
ai.planner.result{status}规划成功 / 解析失败
agent.memory.redis.failureRedis 读写失败计数

内存限制

Docker Compose 不再设置 mem_limit / mem_reservation,Redis、Node 与 ClickHouse 也不再设置本地开发内存上限;各组件按宿主机 / Docker Desktop 配额自适应。

08 · SSE & ThreadLocal

跨线程上下文传播

SSE + Reactor + 自定义线程池叠加,ThreadLocal 在三种切换中都要稳。项目用 Micrometer Context Propagation + ExecutorService 装饰器双管齐下。

Pattern A

手动快照

提交线程 get() 抓快照,工作线程 finally 恢复原值。一次性任务用,频繁则失控。

Pattern B

ExecutorService 装饰器

SessionAwareExecutor 在 submit/invoke 入口统一 wrap,调用方无感知。覆盖自定义线程池。

Pattern C

Micrometer Accessor

注册 ThreadLocalAccessor + Hooks.enableAutomaticContextPropagation(),Reactor publishOn/subscribeOn 全自动传播。

τ

项目实践:ApplicationRunner 时序控制

ApplicationRunner 在所有 Bean 初始化完成、第一个真实请求之前的窗口注册 Accessor + 开启 Hook, 既覆盖后续所有 Reactor pipeline,又能享受 Spring 上下文。这是 "Servlet 任意线程 → Reactor 调度器线程" 的标准解法。

09 · API Surface

主要接口

REST + SSE 双形态。Agent 通过 sessionId 持续记忆,通过 topicId 限定知识检索域。

MethodPath说明
POST/api/v1/chat同步 Agent 对话(plan + react + memory + tools)
POST/api/v1/chat/streamSSE 流式对话:plan_thinking → plan → thinking → step → token → done
POST/api/v1/rag/ingest文档入库(文本 / 文件 via Tika)
GET/api/v1/rag/search向量检索(debug 用)
GET/actuator/health健康检查(show-details: always)
GET/actuator/prometheusPrometheus 指标抓取端点