资讯动态

FEL 框架检索增强生成(RAG)实战:基于 FEL Flow 快速搭建 Native RAG 应用

发布时间:2026/10/14 8:23:19 来源:尧图企业网站定制
人工智能大模型AI 应用后端AI AgentRAG流程编排微服务【免费下载链接】fit-frameworkFIT: 企业级AI开发框架提供多语言函数引擎FIT、流式编排引擎WaterFlow及Java生态的LangChain替代方案FEL。原生/Spring双模运行支持插件热插拔与智能聚散部署无缝统一大模型与业务系统。项目地址https://gitcode.com/ModelEngine/fit-framework点击查看免费下载导读本篇文章讲解 ModelEngine 的 FELFit Engine for LLMJava 生态的 LangChain 替代方案中检索增强生成RAG能力的完整使用路径从 RAG 解决大模型幻觉与知识过时问题的原理出发介绍 FEL 抽象出的数据入库与检索生成两类核心算子并基于 FEL Flow 编排一个可运行的 Native RAG 示例语料入库、查询改写、向量检索、上下文合成、答案生成。读完本文你将掌握在 fit-framework 仓库中从零搭建一个带多轮历史对话能力、可检索私有知识库的 RAG Web 应用的具体方法并能结合源码理解其底层实现。为什么需要 RAG大模型的边界与检索增强大型语言模型LLM火爆之后基于其构建的应用与日俱增。然而在垂直应用领域中大模型的实际表现往往不如预期。其根本原因在于大模型的知识以参数形式存储在模型内部模型无法了解训练集之外的知识。因此如何构建一个具备垂直领域知识的大模型应用成为业界难题。检索增强生成Retrieval-Augmented GenerationRAG将检索能力集成到 LLM 文本生成过程中它结合了检索系统从大型语料库中获取相关文档片段和 LLM使用这些片段中的信息生成答案。引入 RAG 主要有两大价值缓解幻觉问题在知识密集型任务下即使强如 GPT4 的模型也会编造答案给用户带来信任危机。通过引入外部知识可以限制模型在 Reference 范围内生成答案从而有效缓解幻觉。回答实事、与时俱进大模型语料的收集、预处理和训练是一个非实时流程通常极其耗费时间。RAG 可以帮助模型有效更新语料回答训练截止日期之后发生的事实性问题。在 fit-framework 仓库中标准 RAG 架构图 直观展示了这一经典的“索引-检索-生成”链路从传统 RAG 到模块化 RAGFEL 的算子抽象随着 RAG 技术的进一步发展新的技术突破了传统的“检索 - 生成”框架催生了**模块化 RAGModular RAG**的概念。在结构上它更加自由和灵活引入了更多的具体功能模块例如查询搜索引擎、融合多个回答在技术上它将检索与微调、强化学习等技术融合在流程上它允许对 RAG 模块之间进行设计和编排出现了多种 RAG 模式。FEL 正是沿着模块化 RAG 的思路将 RAG 的各个阶段抽象为一组可组合的算子Operator。这些算子均继承自统一的PatternI, O函数式接口用户通过灵活组合各个算子即可快速搭建属于自己业务的 RAG 应用。数据入库阶段的算子针对数据入库阶段FEL 抽象出以下三个算子源码位于framework/fel/java/fel-core/src/main/java/modelengine/fel/core/pattern/目录FunctionalInterface public interface SourceI extends PatternI, ListDocument { /** * 根据输入参数加载文档。 * * param input 表示输入参数的 {link I}。 * return 表示加载文档列表的 {link List}{code }{link Document}{code }。 */ ListDocument load(I input); } FunctionalInterface public interface SplitterD extends PatternD, ListD { /** * 将输入数据进入切分。 * * param data 表示输入数据的 {link D}。 * return 表示切分完毕数据列表的 {link List}{code }{link D}{code }。 */ ListD split(D data); } public interface StoreD extends PatternD, Void { /** * 添加数据到存储中。 * * param data 表示待添加数据的 {link D}。 */ void persistent(D data); }SourceI负责从外部加载文档返回文档列表例如从 JSON 文件、文本文件或网络地址加载SplitterD负责将文档切分为适合向量化的片段StoreD负责将文档持久化到存储如向量数据库中。检索与生成阶段的算子针对检索阶段FEL 抽象出以下三个算子public interface RetrieverI, O extends Measurable extends PatternI, ListO { /** * 根据用户输入进行检索。 * * param query 表示用户输入的 {link I}。 * return 返回可量化数据的 {link List}{code }{link O}{code }。 */ ListO retrieve(I query); } public interface PostProcessorD extends PatternD, D { /** * 对检索结果进行后处理。 * * param data 表示输入数据的 {link D}。 * return 表示处理后数据的 {link D}。 */ D process(D data); } public interface SynthesizerI extends PatternI, Content { /** * 将检索的数据进行合成。 * * param input 表示检索数据的 {link I}。 * return 表示合成结果的 {link Content}。 */ Content synthesize(I input); }RetrieverI, O extends Measurable根据用户查询进行检索返回可量化带相关度分数的结果列表PostProcessorD对检索结果进行后处理例如过滤、去重或重排序FEL 提供了 RerankDocumentProcessor可基于RerankModel对检索文档重新排序SynthesizerI将检索到的多个文档片段合成为一个统一的Content作为后续提示词的上下文。说明以上六个算子在仓库fel-core模块中均有对应接口定义Source、Splitter、Store、Retriever、PostProcessor、Synthesizer同时fel-core还提供了DocumentPostProcessor、DocumentSynthesizer等面向文档检索结果的专用接口见 DocumentSynthesizer。快速搭建一个 Native RAG 应用接下来演示如何基于 FEL Flow 快速搭建一个 Native RAG 应用。完整的可运行工程位于仓库 examples/fel-example/05-retrieval下文步骤与仓库代码一一对应。第一步引入 Maven 依赖在项目pom.xml中加入以下依赖完整配置可参考 05-retrieval/pom.xmldependencies dependency groupIdorg.fitframework/groupId artifactIdfit-starter/artifactId version${fit.version}/version /dependency dependency groupIdorg.fitframework/groupId artifactIdfit-plugins-starter-web/artifactId version${fit.version}/version /dependency dependency groupIdorg.fitframework.plugin/groupId artifactIdfit-http-client-okhttp/artifactId version${fit.version}/version /dependency dependency groupIdorg.fitframework.fel/groupId artifactIdfel-core/artifactId version${fel.version}/version /dependency dependency groupIdorg.fitframework.fel/groupId artifactIdfel-flow/artifactId version${fel.version}/version /dependency dependency groupIdorg.fitframework.fel/groupId artifactIdfel-model-openai-plugin/artifactId version${fel.version}/version /dependency /dependencies各依赖的作用fit-starter/fit-plugins-starter-webFIT 框架基础启动器与 Web 能力提供Component、RequestMapping等注解与 HTTP 服务fit-http-client-okhttp基于 OkHttp 的 HTTP 客户端用于访问 OpenAI 兼容的大模型服务fel-coreFEL 核心库提供模型、记忆、文档、向量存储、算子等基础能力fel-flowFEL Flow 流程编排引擎用于以响应式流程的方式组装 RAG 各环节fel-model-openai-pluginOpenAI 兼容的模型接入插件提供ChatModel与EmbedModel的默认实现。第二步配置模型参数在application.yml配置文件中加入以下配置完整配置见 05-retrieval/src/main/resources/application.ymlfel: openai: api-base: ${api-base} api-key: ${your-api-key} example: model: chat: ${model-name} embed: ${model-name}各配置项说明fel.openai.api-baseOpenAI 兼容服务的接口地址占位符${api-base}需要在启动时通过环境变量或启动参数注入fel.openai.api-key调用模型服务的密钥占位符${your-api-key}example.model.chat对话模型名称用于文本生成占位符${model-name}example.model.embed向量嵌入模型名称用于文档与查询的向量化。对话模型与嵌入模型可以相同也可以分别指定。第三步编写控制器代码创建一个继承 FIT 组件的控制器类完整源码见 RetrievalExampleController.java。其整体结构如下Component RequestMapping(/ai/example) public class RetrievalExampleController { private static final String REWRITE_PROMPT 作为一个向量检索助手你的任务是结合历史记录为”原问题“生成”检索词“ 生成的问题要求指向对象清晰明确并与“原问题语言相同。\n\n 历史记录\n---\n DEFAULT_HISTORY_KEY ---\n原问题{{query}}\n检索词; private static final String CHAT_PROMPT Answer the question based on the context below. If the question cannot be answered using the information provided answer with \I dont know\.\n\n Context: {{context}}\n\n Question: {{query}}\n\n Answer: ; private final AiProcessFlowString, ChatMessage ragFlow; private final Memory memory new CacheMemory(); public RetrievalExampleController(ChatModel chatModel, EmbedModel embedModel, ObjectSerializer serializer, Value(${example.model.chat}) String chatModelName, Value(${example.model.embed}) String embedModelName) { DocumentEmbedModel documentEmbedModel new DefaultDocumentEmbedModel(embedModel, EmbedOption.custom().model(embedModelName).build()); VectorStore vectorStore new MemoryVectorStore(documentEmbedModel); ChatFlowModel chatFlowModel new ChatFlowModel(chatModel, ChatOption.custom().model(chatModelName).stream(false).build()); AiProcessFlowTip, Content retrieveFlow AiFlows.Tipcreate() .runnableParallel(history(), passThrough()) .conditions() .match(tip - !tip.freeze().get(DEFAULT_HISTORY_KEY).text().isEmpty(), node - node.prompt(Prompts.human(REWRITE_PROMPT)) .generate(chatFlowModel) .map(ChatMessage::text)) .others(node - node.map(tip - tip.freeze().get(query).text())) .retrieve(new DefaultVectorRetriever(vectorStore, SearchOption.custom().topK(1).build())) .synthesize(docs - Content.from(docs.stream().map(Document::text).collect(Collectors.joining(\n\n)))) .close(); AiProcessFlowFile, ListDocument indexFlow AiFlows.Filecreate() .load(new JsonFileSource(serializer, StringTemplate.create({{question}}: {{answer}}))) .index(vectorStore) .close(); File file FileUtils.file(this.getClass().getClassLoader().getResource(data.json)); notNull(file, The data cannot be null.); indexFlow.converse().offer(file); this.ragFlow AiFlows.Stringcreate() .map(query - Tip.from(query, query)) .runnableParallel(value(context, retrieveFlow), passThrough()) .prompt(Prompts.history(), Prompts.human(CHAT_PROMPT)) .generate(chatFlowModel) .close(); } /** * 聊天接口。 * * param query 表示用户输入查询的 {link String}。 * return 表示聊天模型生成的回复的 {link ChatMessage}。 */ GetMapping(/chat) public ChatMessage chat(RequestParam(query) String query) { ChatMessage aiMessage this.ragFlow.converse().offer(query).await(); this.memory.add(new HumanMessage(query)); this.memory.add(aiMessage); return aiMessage; } }说明仓库中的示例代码在构造器注入处使用了Fit(alias json) ObjectSerializer serializer注入 JSON 序列化器并使用indexFlow.converse().doOnError(...).doOnFinally(...).offer(file)异步构建索引其余结构与上文一致。data.json在示例中通过getData()方法动态生成临时文件语料为“黑神话悟空”相关的问答对。该控制器在构造阶段做了三件事初始化模型与向量存储、构建语料入库流程indexFlow并立即执行索引构建、构建知识检索流程retrieveFlow与RAG 主流程ragFlow。下面逐一拆解。语料入库流程从 JSON 文件到向量数据库首先创建文档嵌入模型与向量存储然后定义入库流程DocumentEmbedModel documentEmbedModel new DefaultDocumentEmbedModel(embedModel, EmbedOption.custom().model(embedModelName).build()); AiProcessFlowFile, ListDocument indexFlow AiFlows.Filecreate() .load(new JsonFileSource(serializer, StringTemplate.create({{question}}: {{answer}}))) .index(vectorStore) .close();该流程接收一个 JSON 文件解析后生成文档并存入向量数据库流程背后的具体实现均有源码可查JsonFileSource源码是Source算子的实现它使用ObjectSerializer将 JSON 文件内容反序列化为对象列表再依据StringTemplate这里是{{question}}: {{answer}}中的占位符从每个 JSON 对象取值并渲染为文档文本。若某个占位符对应的 key 缺失会抛出The key {0} is missing异常同时可通过JsonMetadataExtractor提取文档元数据默认返回空 Map。DefaultDocumentEmbedModel源码将EmbedModel包装为面向文档的嵌入模型对文档列表批量调用service.generate(inputs, embedOption)生成向量对单个字符串调用generate(input, embedOption)生成向量。MemoryVectorStore源码是VectorStore的内存简易实现其类注释明确标注“不要在生产环境中使用”生产环境应替换为真正的向量数据库。其persistent(ListDocument)方法先对全部文档批量生成嵌入向量再将“文档 向量”以DocumentWithEmbedding形式存入ConcurrentHashMap文档若无 ID 会自动生成 UUID。知识检索流程查询改写与向量检索检索流程接收一个Tip对象首先根据问题和历史记录对原问题进行重写指代消解再从向量数据库中检索出相关知识并返回AiProcessFlowTip, Content retrieveFlow AiFlows.Tipcreate() .runnableParallel(history(), passThrough()) .conditions() .match(tip - !tip.freeze().get(DEFAULT_HISTORY_KEY).text().isEmpty(), node - node.prompt(Prompts.human(REWRITE_PROMPT)) .generate(chatFlowModel) .map(ChatMessage::text)) .others(node - node.map(tip - tip.freeze().get(query).text())) .retrieve(new DefaultVectorRetriever(vectorStore, SearchOption.custom().topK(1).build())) .synthesize(docs - Content.from(docs.stream().map(Document::text).collect(Collectors.joining(\n\n)))) .close();关键节点解析runnableParallel(history(), passThrough())并行分支。history()从当前AiFlowSession的内部状态中读取历史记忆键为DEFAULT_HISTORY_KEY history实现见 SyncTipper.javapassThrough()将输入Tip原样透传。conditions().match(...)条件分支。若历史记录非空则用REWRITE_PROMPT模板让 LLM 结合历史记录将原问题重写为指向清晰、语言一致的“检索词”否则直接透传原始query。这就是实现多轮对话“指代消解”的关键。retrieve(new DefaultVectorRetriever(...))执行向量检索。DefaultVectorRetriever 是RetrieverString, MeasurableDocument的实现其retrieve(query)直接委托给VectorStore.search(query, searchOption)。synthesize(...)将检索到的文档片段文本用\n\n拼接为统一的Content作为最终提示词的上下文。SearchOption.custom().topK(1)表示只返回相似度最高的 1 条文档topK为检索返回数量还可配置filter标量过滤器定义见 SearchOption.java。RAG 主流程检索 → 组装提示词 → 生成答案主流程首先进行知识检索然后根据检索到的知识组装提示词最后生成答案AiProcessFlowString, ChatMessage ragFlow AiFlows.Stringcreate() .map(query - Tip.from(query, query)) .runnableParallel(value(context, retrieveFlow), passThrough()) .prompt(Prompts.history(), Prompts.human(CHAT_PROMPT)) .generate(chatFlowModel) .close();执行链路说明map(query - Tip.from(query, query))将用户输入字符串包装为Tip。Tip源码本质是一个MapString, Content参数容器为MessageTemplate提供模板数据freeze()后返回只读视图runnableParallel(value(context, retrieveFlow), passThrough())将上一步的retrieveFlow作为并行分支以context键注入Tip同时透传原数据——即“先生成上下文再继续往下走”prompt(Prompts.history(), Prompts.human(CHAT_PROMPT))将历史记录与CHAT_PROMPT模板组装成完整的提示词。CHAT_PROMPT要求模型“仅基于 Context 回答无法回答时回复 I dont know”这正是用外部知识约束模型、缓解幻觉的手段generate(chatFlowModel)调用ChatFlowModel构造时指定stream(false)即非流式输出生成最终答案。接口chat()在返回回答的同时将HumanMessage(query)和 AI 回答写入CacheMemory为下一轮对话提供历史。运行验证启动应用默认端口 8080后在浏览器输入http://localhost:8080/ai/example/chat?query请介绍一下黑神话悟空返回如下响应{ content: 《黑神话悟空》是一款基于《西游记》故事之后的故事情节的游戏。在故事中孙悟空成佛后不愿受到拘束辞去佛位欲回到花果山逍遥快活但遭到天庭猜忌。二郎神、四大天王、巨灵神率领天兵天将再次杀奔花果山。在与二郎神的激战中孙悟空被重新出现的紧箍咒所困最终不敌二郎神肉身残躯化为巨石魂魄分为六件根器名为六根隐藏于世间。多年后玩家扮演的花果山猿猴“天命人”踏上了寻找遗失根器、解救和复活孙悟空的旅程。, toolCalls: [] }再输入注意此处使用了指代“这个游戏”用于验证多轮历史http://localhost:8080/ai/example/chat?query这个游戏有哪些玩法返回如下响应{ content: 这个游戏的玩法包括\n\n1. 控制名为“天命人”的花果山猿猴使用如意金箍棒作为主要武器进行战斗。\n\n2. 战斗系统包含三种主要棍法劈棍、戳棍和立棍为玩家提供多样化的战斗选择。\n\n3. 在战斗中玩家需要管理资源通过计量表积累“专注点”用于连招或蓄力重击。\n\n4. 冲刺、闪避和攻击会消耗气力值。\n\n5. 除了物理战斗玩家还可以使用“奇术”、“身法”、“毫毛”和“变化”等四类法术这些法术有冷却时间并消耗法力值。\n\n6. 各类法术按技能细分例如“定身”奇术可以冻结敌人“聚形散气”身法可留下假身以便散气遁走聚形时再行突袭“身外身法”的毫毛法术能召唤分身协助战斗变化法术允许主角变形为不同的生物拥有独特的招式和生命值。\n\n7. 玩家可以通过击败强大的敌人获得“精魄”技能这些技能类似于一次性的变身例如“幽魂”精魄可以进行头锤攻击。\n\n8. 游戏的进程大多是线性的但也有宽阔的区域供玩家探索。\n\n9. 玩家会遇到各种妖怪头目作为敌人。\n\n10. 玩家的检查点是世界各地的神龛土地庙。\n\n11. 游戏地图会随游戏进展而变化。, toolCalls: [] }可以看到应用可以根据历史记录对问题进行指代消除并且根据知识库语料正确回答了黑神话悟空的相关问题——即使模型本身未必具备该游戏的最新知识答案依然被约束在检索到的语料范围内。深入理解向量检索的底层实现以MemoryVectorStore为例检索的核心逻辑源码如下Override public ListMeasurableDocument search(String query, SearchOption option) { ListFloat queryEmbedding this.embedModel.embed(query).embedding(); return this.cache.values() .stream() .map(d - new MeasurableDocument(d, MathUtils.cosineSimilarity(queryEmbedding, d.getEmbedding()))) .sorted(Comparator.comparingDouble(MeasurableDocument::score).reversed()) .limit(option.topK()) .collect(Collectors.toList()); }检索过程可以概括为三步使用DocumentEmbedModel.embed(String)将用户查询编码为查询向量遍历库中所有文档利用MathUtils.cosineSimilarity计算查询向量与每个文档向量的余弦相似度并包装为带score的MeasurableDocument按相似度降序排序截取前topK条作为检索结果返回。从该实现可以看出检索质量的根基在于嵌入模型与语料切分质量topK则控制送入 LLM 的上下文规模。若希望进一步提升检索精度可以在retrieve与synthesize之间插入PostProcessor如 RerankDocumentProcessor对初步结果做重排序或使用SearchOption.filter做标量字段过滤。此外MemoryVectorStore仅适合演示与原型验证源码注释明确提示不要在生产环境使用生产环境建议替换为支持持久化与近似最近邻检索的向量数据库并通过Store算子接入 FEL 的VectorStore接口。小结本文完整演示了基于 FEL 与 FEL Flow 搭建 Native RAG 应用的全过程从模块化 RAG 的六个核心算子抽象Source、Splitter、Store、Retriever、PostProcessor、Synthesizer到依赖与配置再到语料入库、查询改写、向量检索、上下文合成、答案生成三个流程的编排最后以真实请求验证了多轮对话下的指代消解与知识库问答能力。你可以基于 examples/fel-example/05-retrieval 完整示例直接运行与改造将其中的MemoryVectorStore、JsonFileSource等组件替换为面向业务的实现即可快速落地垂直领域的知识问答应用。赞分享人工智能大模型AI 应用后端AI AgentRAG流程编排微服务【免费下载链接】fit-frameworkFIT: 企业级AI开发框架提供多语言函数引擎FIT、流式编排引擎WaterFlow及Java生态的LangChain替代方案FEL。原生/Spring双模运行支持插件热插拔与智能聚散部署无缝统一大模型与业务系统。项目地址https://gitcode.com/ModelEngine/fit-framework点击查看免费下载相关推荐如何永久保存微信聊天记录WeChatMsg一站式数据管理解决方案如何永久保存微信聊天记录WeChatMsg一站式数据管理解决方案 你是否曾担心珍贵的微信聊天记录会因手机丢失而永远消失那些与亲友的温馨对话、重要的工作沟通、n8n RAG 实战指南基于 LangChain 原语搭建检索增强生成工作流n8n RAG 实战指南基于 LangChain 原语搭建检索增强生成工作流 RAGRetrieval Augmented Generation检索增强生AI 技能AI 插件工作流自动化流程编排OpenViking RAG Benchmark 实战从零搭建 RAG 检索增强生成评估流水线OpenViking RAG Benchmark 实战从零搭建 RAG 检索增强生成评估流水线 OpenViking 仓库在 benchmark/RAG/ 下人工智能AI AgentAgent 记忆RAG后端数据库上一篇Arduino ESP32 核心包 15 分钟安装指南开发环境配置到烧录第一个程序的完整流程下一篇Zephyr 显示盾板实战为 esp32c3_042_oled 启用 SH1106 72x40 OLEDabrobot_sh1106_72x40 完全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑