找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2994

积分

0

好友

404

主题
发表于 2 小时前 | 查看: 3| 回复: 0

最近,我基于现有的知识库文档,搭建了一套智能问答系统。最初的动机很直接:能不能让已有的文档,以“问答”这种更便捷的方式被使用起来?

在实现过程中,既有收获,也踩了不少坑。有些环节效果不错,有些地方则还有很大的优化空间。这篇文章算是对整个实现过程的一次复盘梳理,如果你也在探索RAG相关的应用,或许能带来一些参考。

🧩 整体思路:一个简单的 RAG 流程拆解

整个系统的核心是使用 Spring AI Alibaba ReactAgent 进行智能体(Agent)的流程编排,向量检索部分则选择了 Qdrant

从流程上看,可以清晰地拆解为四个主要阶段:

文档处理 → 索引增强 → 检索 → 问答生成

对应到具体实现,分别是:

  • 文档处理:语雀 / Markdown → 清洗 → 切块
  • 索引增强:提取标题、关键词、摘要
  • 检索:向量检索 + BM25 + 融合排序
  • 问答:Query Rewrite + Agent 多次检索 + 生成答案

这几个环节串联起来,就构成了一条相对完整的 RAG 链路。

基于语雀/Markdown文档的RAG问答系统流程图

📌 第一步:先解决“文档从哪里来”

我的知识源主要来自两类:

  • 语雀知识库
  • 本地 Markdown 文档

这里有一个关键处理:不仅要提取文档正文,还要把目录结构一并保留下来。因为在后续的切块步骤中,目录信息是天然的、有价值的上下文。

⚙️ 第二步:文档清洗(基础但必要)

获取文档后,并非直接进行向量化,而是先做一轮清洗:

  • 移除无关的图片
  • 清理注释等杂质
  • 保留代码块
  • 统一转换为纯净的 Markdown 文本

这一步逻辑不复杂,但如果不做,后续环节的问题会被放大。

✂️ 第三步:切块(Chunking 比想象中更重要)

RAG 中一个容易被轻视的环节是:文档究竟该怎么切?

很多常见的做法是:

  • 按固定 Token 数量切分
  • 或简单按段落切分

但这很容易导致一个核心问题:语义上下文被生硬切断

我的策略是:结合目录结构进行分级字数控制

大致规则如下:

  • 内容不长 → 整体作为一个块
  • 内容较长 → 按一级标题切分
  • 仍然很长 → 按二级标题切分
  • 过长 → 再按段落切分

核心逻辑代码示例如下:

List<MarkdownUtils.ChunkResult> chunks = MarkdownUtils.splitWithContext(content, docContext);

for (MarkdownUtils.ChunkResult chunk : chunks) {
    FeaturePoint point = extractFromChunk(chunk);
}

我特别在意一点:每个 chunk 都必须拥有一个“能够清晰承接上下文的标题”。这并非为了美观,而是为了明确表达:

  • 这个片段来自哪篇文档?
  • 它属于哪个章节?
  • 当前具体在讲述什么内容?

可以这样理解:每个 chunk 都应该是一个 “最小语义单元”,而非一段随机的文本片段。

Markdown文档结构化切块示意图

🧠 第四步:索引增强(不是只存正文)

完成切块后,并非直接将原始内容存入向量库,而是增加了一步 “信息补充”:提取关键词和摘要。

这里我经历了一个小调整:最初尝试使用 Agent 来提取关键词,但实践后发现,将其改为基于规则的代码实现更为合适。原因很实际:

  • 关键词提取更偏向规则型任务
  • 代码实现更稳定、可控
  • 后续调整和优化的成本也更低

至于摘要生成,若有更高要求,仍可考虑用大模型辅助。

最终,这些增强信息会与正文一同存储:

QaPair pair = QaPair.builder()
        .featureTitle(point.title())
        .featureKeyword(point.keywords())
        .featureSummary(point.summary())
        .featureContent(point.content())
        .build();

一个直观的体会是:并非所有环节都适合使用 Agent。在规则明确的地方,传统的工程逻辑往往更高效可靠。

🚀 第五步:向量化(拼的不只是内容)

在进行向量化时,我没有仅仅对正文做 Embedding,而是将多种信息拼接起来:
标题 + 关键词 + 摘要 + 正文

然后再写入 Qdrant 向量数据库。

为什么要这样做?因为在企业级应用场景中,许多关键信息(如菜单名、字段名、功能名)恰恰蕴含在标题和关键词里。如果只对正文进行向量化,这些重要线索反而容易丢失。

简单来说,目标不是存储“内容本身”,而是存储 “更容易被检索到的内容表达”

🔍 第六步:检索(向量 + BM25 的混合策略)

在检索层,我没有只依赖向量检索。纯向量检索在实际应用中可能存在一些问题:

  • 对精确术语不敏感
  • 有时会召回语义相关但内容并不精准的结果

因此,我实现了一层混合检索:

  • 向量检索:负责语义层面的宽泛召回
  • BM25:负责关键词的精确命中
  • RRF (Reciprocal Rank Fusion):对多路检索结果进行融合与重排序

核心逻辑类似这样:

List<Document> vectorResults = performVectorSearch(query, vectorTopK, similarityThreshold);
List<Document> keywordResults = performKeywordSearch(query, bm25TopK);

List<Document> mergedResults = mergeWithRRF(vectorResults, keywordResults, finalTopK);

经过这样处理,在我的测试场景中,检索命中效果变得更加稳定,尤其对于包含“精确名称”类的问题,表现更好。

🔄 第七步:Query Rewrite(多轮对话的关键)

为了支持流畅的多轮对话,我增加了一层 Query Rewrite(查询改写)。用户的当前问题不会直接用于检索,而是会结合最近几轮的对话历史,被改写为一个更完整、更独立的问题。

例如,用户可能连续提问:

  • “这个怎么配?”
  • “为什么不行?”

如果直接将“为什么不行?”拿去检索,基本无法命中任何内容。经过 Query Rewrite 后,这个问题可能会被补充上下文,改写成“为什么在配置 X 功能时会出现 Y 错误?”,然后再进行检索。

本质上,这一步是在做 Query Reformulation(查询重构),用以解决多轮对话中普遍存在的上下文缺失问题。

💬 第八步:让 Agent 参与“检索过程”

最后才进入问答生成阶段。这里我没有采用简单的“检索一次,直接生成答案”的模式,而是设计为:让 ReactAgent 具备多次调用检索工具的能力

例如,Agent 可以:

  • 根据初步结果,变换关键词再次检索
  • 进行多轮、递进式的检索以收集更全面的信息
  • 最后再组织和生成最终答案

核心的 Agent 构建方式如下:

return ReactAgent.builder()
        .name("QA-Chat-Agent")
        .model(chatModel)
        .methodTools(vectorSearchTool)
        .build();

这种方式更接近于人类“查阅资料”的过程,而非简单地“直接编造答案”。

📊 一些实践中的体会(仅供参考)

这部分不算正式结论,只是我在这次实现过程中的几点感受:

  • 文档质量是天花板:原始文档的质量,直接决定了最终问答效果的上限。
  • 切块方式影响巨大:不同的切块策略,对检索召回率的影响非常显著。
  • 标题是检索的一部分:标题不仅仅是装饰,它本身是重要的检索信号。
  • Query Rewrite 很有必要:对于多轮对话场景,查询改写几乎是必选项。
  • Agent 分工要清晰:并非 Agent 用得越多越好,明确其职责边界更重要。

如果用一句话总结:在 RAG 系统中,模型能力固然重要,但数据和检索环节的设计往往更为关键。

🌐 开源 & 体验

我将这套实现进行了整理,并开源了一个版本:

项目 README 包含了完整的流程说明,涵盖:

  • 文档处理与切块策略
  • 向量化与 Qdrant 存储
  • 混合检索实现
  • Query Rewrite 逻辑
  • Agent 调用链路

如果你正在开发类似的 RAG 应用,欢迎参考或基于此进行改造。

RAG问答系统GitHub仓库截图

同时,我也部署了一个在线体验环境:

AI智能助手在线问答界面截图

🧾 最后

这套系统仍在持续迭代和打磨中。后续可能优化的方向包括 Prompt 的精细调整、知识库的持续治理等。

如果你也在探索 RAG 或 Agent 的落地,欢迎在技术社区进行交流,例如 云栈社区 就聚集了许多关注此类实践的开发者。我也仍在不断学习和实践中。

扩展阅读:




上一篇:过度思考的本质,是安全感匮乏导致的持续内耗
下一篇:实时语音与生成式UI:AI Agent迈向OS级系统助手的核心技术解析
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-4-17 04:04 , Processed in 0.632916 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表