dawn-ai

Issues

RAG

RAG 召回率偏低

  • “xxx” 这种很短的人名 query,对 embedding 模型来说语义信息太少
  • 当前 /rag/search 没有关键词精确匹配兜底
  • 阈值 0.7 又偏高
  • 结果就是:明明文本里有“xxx”,但向量相似度没过线,最终返回 0

如果你要这个搜索更符合直觉,最有效的是这三种改法:

  1. 把 similarity-threshold 从 0.7 下调到 0.4 到 0.55,先恢复基础召回。
  2. 给 /rag/search 增加关键词兜底:向量结果为 0 时,再做一次 content 的精确匹配或 LIKE 检索。
  3. 对短 query,尤其是 2 到 6 个字的人名、地名、术语,走混合检索而不是只走向量。

整改方案

P0

  • [x] 实现 overlap-text-splitter:由于 springai 自带的 textsplitter 不支持设置 chunk overlap size,需要自实现。
  • [ ] [线程池定制]为 LLM API 调用和 Rerank 模型调用配置专属的“重度 I/O 密集型” ThreadPoolExecutor
    • 基于公式 CPU核数 × (1 + WaitTime/ComputeTime) 计算合理的 corePoolSize(预期在几十到上百之间)。
    • 设定合理的 BlockingQueue 大小,防止内存打满。
    • 编写自定义的 RejectedExecutionHandler(拒绝策略),确保在线程池打满时,主业务不会崩溃,且能记录报警日志。

P1

  • [ ] [多路混合召回]
    • [x] 引入 ElasticSearch (或同类方案) 提供 BM25 稀疏向量检索,结合向量库,实现 Dense + Sparse 双路并发召回。
    • [ ] [并发重构] 使用 CompletableFuture.supplyAsync() 将 BM25 检索和向量检索改为并发执行,使用 allOf().join() 统一收集结果,将串行耗时压缩为最大单路耗时。
    • [ ] [超时熔断设计] 利用 Java 9+ 的 .completeOnTimeout() API 为外部 Rerank 模型调用加上严格的 SLA 限制(例如 800ms)。
    • [ ] [降级链路实现] 使用 .exceptionally() 或者 resiliency 库编写优雅的 Fallback 逻辑。当 Rerank 失败或 LLM API 阻滞时,能够自动返回未重排的原始召回结果或降级话术。

P2

  • [ ] [trunk重构] 废弃按字数切分的策略。开发基于 AST(抽象语法树)或 Markdown 结构的 语义级分块 (Semantic Chunking) 工具。
  • [ ] [数据模型优化] 在向量数据库(如 Milvus/Qdrant)中实现 “父子文档 (Parent-Child)” 关联映射(类似二级索引回表)。存储子 Chunk 的 Embedding,但保留指向完整父段落的 ID。
  • [x] [重排序] 在业务层接入 BGE-Reranker 等重排序模型,对多路召回的 Top 20 结果进行深度交叉评分,提取真正的 Top 5。

SSE

Issues

多线程之间的上下文传递问题:streaming 模式下,threadlocal 跨线程失效, 导致 NPE.

❗ IMPORTANT
StepCollector 中的 MAX_STEPS 变量没有设置默认值
chatStreamExecutor 线程 A
  └─ StepCollector.init(maxSteps)      MAX_STEPS 写入线程 A  ThreadLocal
  └─ chatClient.stream()...blockLast()  阻塞线程 A
        └─ Reactor worker 线程 B (WebClient 回调)
              └─ KnowledgeSearchTool.apply()
                    └─ ToolExecutionAspect
                          └─ StepCollector.getAndIncreaseStepNumber()
                                └─ MAX_STEPS.get()  null  线程 B 从未 init()
                                └─ next > null  NullPointerException 💥

Memory

当前方案: redis (会话级别的上下文短期记忆) + 向量库(长期记忆)

1.Summary Buffer(摘要缓冲)

  • 对话达到阈值时,触发低成本模型将旧对话浓缩为摘要
  • 替换原有完整对话,减少 Token 消耗

2. 记忆固化 (Consolidation)

  • 用户离线或 Task 结束后,后台任务盘点短期记忆
  • 提取关键信息 → Embedding → 持久化到向量数据库
  • 清空/截断 Redis 短期记忆

3. 遗忘机制 (Decay / Eviction)

  • 向量检索时引入 Rerank 公式:
    最终相关性 = 向量相似度 * a + 时间衰减 * b + 重要性评分 * c

  • 越近、越重要的记忆优先召回

Harness Engineering

Tools

  • [ ] 集成更多的 tools,参考 java-agent、openharness、claude-code 项目

Propmt Engineering

  • Output Structure:限制LLM 的输出结果符合结构化的格式,适应非概率型的业务场景

ReAct

  • max-steps: 单次对话 tool 最多调用次数,避免 LLM <-> AGENT 陷入死循环
  • plan-and-resovle:
  • llm params optimization:
    • temperature
    • ...

RAG

  • [x] Query 重写
  • [x] Agentic RAG (LLM 驱动), Max-rag-calls (每次请求,RAG 工具最多调用次数)
  • [x] Embedding、Chunk、Overlap

提升召回率

  • [x] sparse + dense retrieval, 多路归并
  • [x] rerank
  • [x] evaluation
  • [ ] HYDE - Hypothetical Document Embeddings

向量检索算法

  • IVF (Inverted File System) - 缩小搜索范围
  • PQ (Product Quantization) - 压缩向量体积
  • HNSW (Hierarchical navigable small world) - 分层可导航小世界算法
  • HNSW_PQ / HNSW_SQ
  • DiskANN (Vamana 图)

向量数据库

📝 NOTE
当前使用的 Posgress 提供的 PgVector
  • Postgresql 插件 - PGVector
  • Faiss
  • Milvus
  • Qdrant
  • Weaviate

Memory Management

  • 核心记忆 - 用户画像、核心指令等信息,每次都会携带在 System Prompt 里
  • 短期记忆 - 对话历史 > - 存储内容:当前 Session(会话)中最近发生的 N 轮对话历史。 > - 管理策略与痛点: 随着对话进行,Token 会迅速逼近大模型的上限(同时导致 KV Cache 暴增,拖慢生成速度)。必须引入截断与压缩机制: > - Sliding Window (滑动窗口):最粗暴的方法,只保留最近的 N 条消息(如 LangChain4j 中的 MessageWindowChatMemory)。 > - Token Bounding (Token 限制):实时计算历史记录的 Token 数,超过阈值(如 4000 tokens)就丢弃最老的记录。 > - Summary Buffer (摘要缓冲):当对话达到一定长度时,触发一个后台的小模型或低成本 API(如 Gemini Flash),将旧的对话“浓缩”成一段简短的摘要(Summary),然后替换掉原有的完整对话。 > - 技术选型:通常存在后端内存中,或者为了分布式无状态化,存储在 Redis 中(如 RedisChatMemoryStore)。
  • 长期记忆 - 向量数据库
  • 记忆流转与管理
    • 记忆固化 (Consolidation) 类似于人类的“睡眠”。当用户离线或当前 Task 结束后,后台调度任务(如使用 Spring 的 @Async 或定时任务)会对短期记忆进行盘点。提取关键信息、生成摘要、调用 Embedding API,然后持久化到长期记忆(向量/图数据库)中,最后清空或截断 Redis 中的短期记忆。
    • 记忆反思 (Reflection) 借鉴斯坦福 Generative Agents 论文:当 Agent 积累了足够多的散碎情景记忆后,系统会主动触发 LLM 进行“高维思考”。
      • 底层数据:“用户昨天问了多线程,今天问了 JUC,刚才问了线程池”。
      • 反思生成:“用户是一个关注高并发和底层原理的后端开发”。
      • 这个反思结果会被提升(Promote)到核心工作记忆或作为高权重的长期记忆。
    • 遗忘机制 (Decay / Eviction) 并不是所有检索出来的长期记忆都有价值。在进行向量检索时,工业界通常不仅看“相似度得分”,还会结合以下公式进行 Rerank(重排序):
      • 最终相关性 = 向量相似度权重 * a + 时间衰减权重 * b + 重要性评分权重 * c
      • 这意味着:越近发生的事、以及被 LLM 判定为越“重要”的事(比如用户的医疗过敏史 vs 用户昨天中午吃了什么),越容易被回忆起来。

Reflection

Agent 评估系统