rag - RAG 做法全景与选型指南
本文是本系列的进阶补充。前几篇讲清了 solon-ai RAG 的积木块(嵌入模型、重排模型、文档加载分割、知识库、查询条件、Agent RAG);这一篇换个视角——把业界主流的 RAG 做法(Naive / Advanced / Modular、混合检索、GraphRAG、Self-RAG / CRAG / Adaptive RAG、Agentic RAG……)一次性摊开,并逐一对应到 solon-ai 的落法。看完你应该能回答一个问题:我的业务场景,该用哪种做法、配哪几个 solon-ai 组件。
1、先给一张全景图
把检索什么时候发生、由谁决定,作为主线,RAG 的流派大致是这么演进的:
Naive RAG ──> Advanced RAG ──> Modular RAG ──> Agentic RAG
(检索一次) (检索前后加工) (模块自由编排) (模型自己决定检索)
│ │ │
└─────────────┴──────> 控制型分支:Self-RAG / CRAG / Adaptive RAG
(对检索结果自我评估与纠错)
横向叠加能力:混合检索、元数据过滤、重排、语义分割、多知识库、图/结构化检索……
用一张表先建立直觉(怎么选看第 8 节):
| 做法 | 检索时机 | 由谁决定检索 | solon-ai 里的落法 |
|---|---|---|---|
| Naive RAG | 提问前固定一次 | 代码 | repository.search() + ChatMessage.ofUserAugment() |
| Advanced RAG | 提问前固定一次(前后加工) | 代码 | SplitterPipeline + QueryCondition + RerankingModel |
| 混合检索 | 提问前固定一次 | 代码 | SearchType.HYBRID + HybridSearchParams |
| 多知识库/结构化检索 | 提问前(可路由) | 代码或模型 | 多个 Repository 按条件选择;数据库查询结果直接增强 |
| Agentic RAG | 推理中,按需多次 | 模型 | RepositoryTool 挂到 ChatModel / ReActAgent |
| 控制型(Self/CRAG/Adaptive) | 提问前,但带评估与纠错 | 代码 + 模型信号 | 相似度阈值重过滤 + 联网搜索兜底 + 复杂度路由 |
下面逐个展开,每种做法都配可运行的 solon-ai 配置化示例。
2、Naive RAG:最朴素的"检索→增强→生成"
业界说的 Naive RAG,就是一条线性流水线:切块 → 向量化入库 → 查询时取 top-k → 拼进提示语 → 生成。这也是 solon-ai 官方系列文章一以贯之的基础用法(概念篇里的标准四步)。
特点
- 实现简单,几行代码就能跑通,适合原型验证
- 短板:切块粗糙导致上下文断裂、检索结果不准时没有纠偏手段,关键信息容易"迷失在中间"(lost in the middle)
solon-ai 落法(对应系列四、五、七篇)
先备好两个模型与知识库(后文所有示例都复用这套底座):
# app.yml
solon.ai.chat:
demo:
apiUrl: "https://ai.moark.com/v1/chat/completions" # 使用完整地址时不需要填写 standard
model: "deepseek-flash"
solon.ai.embed:
demo:
apiUrl: "https://ai.moark.com/v1/embeddings"
model: "bge-m3"
@Configuration
public class RagConfig {
@Bean
public ChatModel chatModel(@Inject("${solon.ai.chat.demo}") ChatConfig config) {
return ChatModel.of(config).build();
}
@Bean
public EmbeddingModel embeddingModel(@Inject("${solon.ai.embed.demo}") EmbeddingConfig config) {
return EmbeddingModel.of(config).build();
}
@Bean
public InMemoryRepository repository(EmbeddingModel embeddingModel) {
return new InMemoryRepository(embeddingModel); //内存知识库;生产建议换 Milvus/ES/Qdrant 等
}
}
入库 + 检索 + 增强,三步就是 Naive RAG 的全部:
@Component
public class NaiveRagService {
@Inject InMemoryRepository repository;
@Inject ChatModel chatModel;
//入库:加载 -> 分割 -> 存储
public void ingest(String file) throws IOException {
List<Document> documents = new SplitterPipeline()
.next(new TokenSizeTextSplitter(500)) //按 token 上限切块(默认 500)
.split(new TextLoader(new File(file)).load());
repository.save(documents);
}
//查询:检索 -> 增强 -> 生成
public String ask(String message) throws IOException {
List<Document> context = repository.search(message); //1.检索
ChatMessage prompt = ChatMessage.ofUserAugment(message, context); //2.消息增强(内部为字符串格式化)
return chatModel.prompt(prompt).call().getMessage().getContent(); //3.生成
}
}
3、Advanced RAG:在检索的"前、中、后"做优化
Naive 的效果不够,就在流水线的三个环节上加工,这是生产环境最常见的基线形态。
检索前(切块与查询)
- 语义分割:
SemanticSplitter(v3.10.1+)借助嵌入模型在语义边界切分,块内主题更聚焦 - 分割管道:
SplitterPipeline把多个分割器串起来用(比如先按段落正则、再按 token 兜底)
List<Document> documents = new SplitterPipeline()
.next(new RegexTextSplitter("\n\n")) //先按段落粗切
.next(new SemanticSplitter(embeddingModel)) //再按语义边界细切
.next(new TokenSizeTextSplitter(500)) //最后按 token 兜底,确保不超限
.split(loader.load());
检索中(条件控制)
QueryCondition 是检索的"遥控器"(对应系列第六篇):
List<Document> context = repository.search(new QueryCondition(message)
.limit(8) //多召回一些,给重排留余量
.similarityThreshold(0.5) //相似度阈值(默认 0.4)
.filterExpression("category == 'manual' AND year >= 2025") //元数据过滤(SnEL 语法)
);
检索后(重排)
粗召回 8 条 → 重排模型精排 → 取头部几条,几乎是最划算的一步优化:
solon.ai.rerank:
demo:
apiUrl: "https://api.moark.com/v1/rerank"
apiKey: "......"
standard: "giteeai"
model: "bge-reranker-v2-m3"
@Configuration
public class RerankConfig {
@Bean
public RerankingModel rerankingModel(@Inject("${solon.ai.rerank.demo}") RerankingConfig config) {
return RerankingModel.of(config).build();
}
}
public String ask(String message) throws IOException {
List<Document> context = repository.search(new QueryCondition(message).limit(8)); //1.粗召回
context = rerankingModel.rerank(message, context); //2.精排
context = SimilarityUtil.refilter(context.stream(), 4); //3.按评分截取头部(可选)
ChatMessage prompt = ChatMessage.ofUserAugment(message, context);
return chatModel.prompt(prompt).call().getMessage().getContent();
}
特点:召回质量显著提升,仍是固定流水线,成本与延迟可控。多数知识库问答做到这一档就够了。
4、Modular RAG:模块自由编排
把 RAG 拆成可插拔的模块(加载、分割、存储、检索、重排、增强),按业务自由编排成不同形态。solon-ai 的接口设计天然就是这个思路:
DocumentLoader/DocumentSplitter/Repository/RepositoryStorable全部是小接口,内置适配 + 自定义扩展;- "常规检索"(Rdb、Es、MongoDb 的普通查询)和"矢量相关性检索"都能包成知识库,统一走
search(QueryCondition)一个入口。
一个体现"编排自由"的例子——把联网搜索包成只读知识库,混入本地流水线:
//把"联网搜索"当成一个动态知识库用(仍是被动检索的编排形态)
@Bean
public BochaWebSearchRepository webRepository(EmbeddingModel embeddingModel,
@Inject("${solon.ai.repo.demo}") AiConfig config) {
return new BochaWebSearchRepository(embeddingModel, config); //solon-ai-search-bocha
}
public String askWithWeb(String message) throws IOException {
//联网搜 10 条 -> 相似度过滤取 4 条(太多,费 tokens)
List<Document> context = webRepository.search(new QueryCondition(message).limit(10));
context = SimilarityUtil.refilter(context.stream(), 4);
return chatModel.prompt(ChatMessage.ofUserAugment(message, context))
.call().getMessage().getContent();
}
solon-ai 提供 Bocha、Tavily、Baidu 三家联网搜索知识库适配(
solon-ai-search-*插件),详见系列第十篇《示例:联网搜索知识库》。
特点:灵活性最高,可按场景定制流水线;代价是设计与维护复杂度。小团队建议先停留在 Advanced 档,别为了编排而编排。
5、混合检索与多路召回(Hybrid Search)
纯向量检索语义泛化好,但专有名词、型号、接口名这类精确匹配弱;BM25/全文检索正好相反。混合检索就是两路互补,融合打分。
solon-ai 在 Elasticsearch 适配(v3.3+)里内置了三种搜索类型:SearchType.FULL_TEXT、SearchType.VECTOR、SearchType.HYBRID:
//构建带元数据字段的 ES 知识库
repository = ElasticsearchRepository.builder(embeddingModel, client)
.addMetadataField(MetadataField.keyword("category"))
.addMetadataField(MetadataField.numeric("year"))
.build();
//混合检索:全文 + 向量,权重各占一半
List<Document> results = repository.search(new QueryCondition("solon framework")
.searchType(SearchType.HYBRID)
.hybridSearchParams(HybridSearchParams.of(0.7)) //向量权重 0.7,全文权重自动补到 0.3
.limit(8));
如果向量库不是 ES(Milvus / PgVector / Redis…),SearchType.HYBRID 用不上——但思路可以照搬:向量库走语义这一路,另建一个纯全文索引的 ElasticsearchRepository 走关键词这一路,手动双路召回后用重排模型融合。因为 FULL_TEXT 只对 content 做 match 查询,全程不碰 embedding 字段,所以这个全文索引可以建得很轻(不占向量库内存),ES 索引里还能同时做存量的业务全文检索:
//① 语义这一路:向量库任选(PgVector / Milvus / Redis…)
Repository vectorRepo = PgVectorRepository.builder(embeddingModel, dataSource).tableName("kb_chunks").build();
//② 关键词这一路:ES 索引只做全文(FULL_TEXT 不碰 embedding,索引可以建得很轻)
Repository keywordRepo = ElasticsearchRepository.builder(embeddingModel, client)
.indexName("kb_fulltext")
.build();
List<Document> vectorHits = vectorRepo.search(new QueryCondition(query).limit(8));
List<Document> keywordHits = keywordRepo.search(new QueryCondition(query)
.searchType(SearchType.FULL_TEXT) //纯 BM25/match 检索,不做向量
.limit(8));
//③ 两路合并去重(按 ID),交给重排模型统一精排——重排模型天然就是融合器
List<Document> merged = Stream.concat(vectorHits.stream(), keywordHits.stream())
.collect(Collectors.toMap(Document::getId, d -> d, (a, b) -> a)) //按 ID 去重,重复命中优先保留向量路结果
.values().stream().collect(Collectors.toList());
List<Document> reranked = rerankingModel.rerank(query, merged);
入库时给两个库各写一份(vectorRepo.save(docs); keywordRepo.save(docs);)即可,ID 一致方便去重与追溯。
若连重排模型都不想接,退化方案是对两路结果按排名做 RRF(Reciprocal Rank Fusion) 融合——rank 固定权重 1 / (k + rank),k 取 60:score = Σ 1/(60 + rank_i),同名文档跨两路累加。实现只要十来行,不再展开。
连 ES 全文索引都不想维护的场景,"多路召回 + 重排"就是最后的平替:InMemoryRepository / 向量库一路、WebSearchRepository / 关键词库另一路,结果合并后交给 RerankingModel.rerank() 统一精排即可。
特点:互补两路短板,工业界标配;代价是要维护两套索引或一次多路调用。
6、结构化 RAG:Text2SQL 的"solon-ai 打法"
数据在数据库里、要的是精确数字("上季度华东区销售额"),向量检索是错误工具——正确做法是让模型把问题转成 SQL,查询结果作为上下文回填。
solon-ai 的打法(对应系列第一篇"常规检索"):查询结果只要能转成字符串,就能用同一个入口增强消息:
//用普通数据库查询结果做"常规检索"增强
List<Map<String, Object>> rows = db.sql("select region, amount from sales where quarter = ?", q).getMapList();
ChatMessage prompt = ChatMessage.ofUserAugment(message, rows); //上下文只要能转为字符串就行
ChatResponse resp = chatModel.prompt(prompt).call();
更进一步,把 SQL 生成交给模型自己:注册一个"销售查询"工具(@ToolMapping),LLM 按需调用——这其实就是下一节 Agentic RAG 的思路,说明流派之间边界本来是模糊的。
特点:结果精确、天然支持聚合统计;依赖 schema 质量与生成查询的校验(只读账号、白名单表是底线)。
7、控制型 RAG:Self-RAG / CRAG / Adaptive RAG
这一族的做法不改变"被动检索"的骨架,而是给流水线加上评估与纠错:
- Self-RAG(自反思):模型自己决定要不要检索,并自评回答有没有依据。学术版需要微调模型;工程实践里常用"相似度阈值"近似——检索结果都不达标,就明确告知模型"无可靠上下文,请说明或追问"。
- CRAG(纠错):检索质量差就触发纠错——改写关键词重搜,或转向联网搜索兜底。
- Adaptive RAG(自适应):先判断问题复杂度——简单题直接答、中等题单轮检索、复杂题多轮检索,按需分配算力。
solon-ai 落法(三者可以组合在同一段业务代码里):
public String askGuarded(String message) throws IOException {
List<Document> context = repository.search(new QueryCondition(message).limit(8));
//① Self-RAG 的工程近似:用相似度阈值评估检索质量
if (context.isEmpty()
|| context.stream().noneMatch(d -> d.getScore() >= 0.55)) {
//② CRAG 兜底:本地知识不够,转向联网搜索知识库重查
context = webRepository.search(new QueryCondition(message).limit(5));
}
ChatMessage prompt = ChatMessage.ofUserAugment(message, context);
//③ Adaptive RAG 的分级思路:问句太长(复杂),走流式慢慢答;简单问句一次 call
if (message.length() > 100) {
chatModel.prompt(prompt).stream() //流式:Flux<ChatEvent>,订阅后开始执行
.subscribe(e -> { /* 处理增量事件 */ });
return "(已进入流式应答)";
}
return chatModel.prompt(prompt).call().getMessage().getContent();
}
上例里的复杂度判断(
length > 100)只是示意。工程上更稳的做法是用一次轻量模型调用做意图分级,再路由到不同档位;或干脆交给下一节的 ReActAgent 让模型自决。
特点:对"检索到垃圾"的鲁棒性最好,成本可控;分级策略需要根据业务调参。
8、Agentic RAG:从被动到主动
前面所有做法有个共同点:检索发生在提问之前,由代码决定。Agentic RAG 把检索行为从"预处理阶段"移入"模型推理阶段"——模型自己决定要不要查、查什么、查几轮、结果不好要不要换个关键词再查。solon-ai 提供了现成的三级跳:
阶段 A:把知识库变成工具
RepositoryTool(v3.10.1+)把任意 Repository 封装成 LLM 可调用的工具:
RepositoryTool repositoryTool = new RepositoryTool(repository, rerankingModel);
ChatModel agent = ChatModel.of(apiUrl)
.apiKey(apiKey).model(model)
.defaultToolAdd(repositoryTool) //挂载知识库工具
.build();
agent.prompt("刘德华今年有几场演唱会?").call(); //LLM 自行决定是否调用、怎么调用
工具内部已做多关键词批量检索(最多 5 个词,每个词独立检索后汇总)、结果格式化(标题/相关度/内容/引用地址)与长度截断保护(单条内容超 2000 字符截断)。
阶段 B:升级 ReActAgent,支持多轮推理校准
复杂任务需要"思考-行动-观察"循环时,用 solon-ai-agent 的 ReActAgent:
ReActAgent agent = ReActAgent.of(chatModel)
.name("知识助理").role("检索增强问答")
.defaultToolAdd(repositoryTool)
.build();
agent.prompt("对比一下 A、B 两个产品在 2025 年的价格策略").call();
模型会自己拆问题、多轮检索、交叉比对——这正是 Adaptive/Iterative RAG 在 Agent 形态下的自然实现。
阶段 C:混合工具链
知识库工具 + 联网搜索 + 网页抓取 + 代码搜索组合挂载,Agent 获得跨数据源的"感官":
ReActAgent agent = ReActAgent.of(chatModel)
.defaultToolAdd(repositoryTool) //私有知识库
.defaultTalentAdd(new WebsearchTalent()) //全网实时信息(solon-ai-talent-web)
.defaultTalentAdd(new WebfetchTalent()) //指定 URL 内容抓取
.build();
Talents 是 solon-ai 的"才能"组件体系(每个 Talent 可携带一组工具),
WebsearchTalent/WebfetchTalent来自solon-ai-talent-web插件(v3.9.6+);若只想要单个工具,也可以从 Talent 里取出工具后用defaultToolAdd挂载。
特点:上限最高,能处理复杂、模糊、多跳的问题;代价是延迟高、token 消耗大、行为不易复现。适合深度研究型任务,不适合高并发的简单问答。
9、选型建议:一张决策路径
问题形态?
├─ 要精确数字/统计,数据在数据库 ──────────────> 结构化 RAG(查询结果直接增强)
├─ 全局性总结、多跳关系("这批文档的共同主题") ──> GraphRAG 思路(solon-ai 暂无内置适配,
│ 可用 ReActAgent 多轮检索 + LLM 归纳近似)
├─ 普通知识库问答
│ ├─ 第一档:Naive(search + ofUserAugment)——先跑通
│ ├─ 第二档:+ 语义分割 / QueryCondition / Rerank —— 上生产的主流基线
│ ├─ 第三档:+ 混合检索或多路召回 —— 专有名词、型号类查询多时
│ └─ + 相似度阈值守卫 / 联网兜底 / 分级路由 —— 对幻觉敏感、鲁棒性要求高时
└─ 复杂研究型、多数据源、开放式任务 ────────────> Agentic RAG(RepositoryTool + ReActAgent)
几条实战心法:
- 先把检索质量做扎实,再上花活。多数 RAG 效果差,问题出在检索而不是生成:切块(
SplitterPipeline)→ 召回条件(QueryCondition)→ 重排(RerankingModel),这三步的收益远大于架构升级。 - 元数据从入库那一刻就带上。
new Document(content, metadata)(或.metadata("file", ...)链式补写)+ 检索时的filterExpression(...),是成本最低的精度增强。 - 被动流水线与主动 Agent 不冲突。可以在系统里并存:高频简单问答走 Advanced 流水线(快、便宜),疑难工单/研究任务路由给 ReActAgent(准、贵)。
- 别迷信流派。选型的依据是问题形态与成本预算,不是论文的新旧。
附:各流派在 solon-ai 中的组件速查
| 流派 | 核心组件(所在模块) | 关键 API |
|---|---|---|
| Naive RAG | solon-ai(core) | InMemoryRepository、repository.search()、ChatMessage.ofUserAugment() |
| Advanced RAG | solon-ai(core) | SplitterPipeline、SemanticSplitter、QueryCondition、RerankingModel.rerank()、SimilarityUtil.refilter() |
| Modular RAG | solon-ai(core)+ loaders/repositorys 插件 | DocumentLoader / DocumentSplitter / Repository / RepositoryStorable 自由组合 |
| 混合检索 | solon-ai-repo-elasticsearch | SearchType.HYBRID、HybridSearchParams.of(vectorWeight)、MetadataField |
| 结构化 RAG | 任意数据访问组件 | 查询结果(可 toString 的任意集合)+ ChatMessage.ofUserAugment() |
| Self/CRAG/Adaptive | solon-ai(core)+ search 插件 | 相似度阈值 + WebSearchRepository 兜底 + 复杂度路由 |
| Agentic RAG | solon-ai(core)+ solon-ai-agent(+ solon-ai-talent-web) | RepositoryTool、ChatModel.defaultToolAdd()、ReActAgent.of()、defaultTalentAdd() |
版本说明:本文示例基于 solon-ai v3.10.1+(
RepositoryTool、SemanticSplitter均自此版本引入;混合检索自 v3.3 起)。联网搜索知识库适配在solon-ai-rag-searchs仓库下(bocha / tavily / baidu);Agent 的联网工具(WebsearchTalent/WebfetchTalent)在solon-ai-talent-web插件。文中 API 均以当前源码为准核对。