Solon v4.1.1

rag - RAG 做法全景与选型指南

</> markdown
2026年10月11日 上午8:12:03

本文是本系列的进阶补充。前几篇讲清了 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)

几条实战心法:

  1. 先把检索质量做扎实,再上花活。多数 RAG 效果差,问题出在检索而不是生成:切块(SplitterPipeline)→ 召回条件(QueryCondition)→ 重排(RerankingModel),这三步的收益远大于架构升级。
  2. 元数据从入库那一刻就带上。new Document(content, metadata)(或 .metadata("file", ...) 链式补写)+ 检索时的 filterExpression(...),是成本最低的精度增强。
  3. 被动流水线与主动 Agent 不冲突。可以在系统里并存:高频简单问答走 Advanced 流水线(快、便宜),疑难工单/研究任务路由给 ReActAgent(准、贵)。
  4. 别迷信流派。选型的依据是问题形态与成本预算,不是论文的新旧。

附:各流派在 solon-ai 中的组件速查

流派核心组件(所在模块)关键 API
Naive RAGsolon-ai(core)InMemoryRepository、repository.search()、ChatMessage.ofUserAugment()
Advanced RAGsolon-ai(core)SplitterPipeline、SemanticSplitter、QueryCondition、RerankingModel.rerank()、SimilarityUtil.refilter()
Modular RAGsolon-ai(core)+ loaders/repositorys 插件DocumentLoader / DocumentSplitter / Repository / RepositoryStorable 自由组合
混合检索solon-ai-repo-elasticsearchSearchType.HYBRID、HybridSearchParams.of(vectorWeight)、MetadataField
结构化 RAG任意数据访问组件查询结果(可 toString 的任意集合)+ ChatMessage.ofUserAugment()
Self/CRAG/Adaptivesolon-ai(core)+ search 插件相似度阈值 + WebSearchRepository 兜底 + 复杂度路由
Agentic RAGsolon-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 均以当前源码为准核对。