
1. 从传统RAG到Agentic RAG为什么需要“反思”如果你最近在折腾RAG检索增强生成大概率已经踩过不少坑了用户问“苹果公司的市值”系统却给你返回一堆关于水果“苹果”的营养价值文档或者用户输入一个拼写错误或模糊的查询比如“LangChian4j怎么用”检索器直接懵圈要么召回无关内容要么干脆空手而归。传统的RAG流程——检索Retrieve然后生成Generate——就像一个老实巴交的办事员你给什么指令他就按部就班地去资料库向量数据库里找最“像”的文档然后一股脑塞给大模型去生成答案。这个过程缺乏一个关键的环节对检索动作本身的“质检”与“反思”。这就是Agentic RAG或者说“具有代理能力的RAG”要解决的核心问题。它不再把RAG看作一个僵化的管道而是赋予其一个“智能体”Agent的视角。这个智能体具备感知-决策-行动-反思的循环能力。具体到检索环节“反思”能力意味着系统能够评估当前检索结果的质量判断其是否足够相关、完整甚至能识别出用户查询本身可能存在的问题如歧义、错误并据此动态调整策略比如触发重写查询、进行网络搜索补充或者直接承认知识库的不足。而CRAGCorrective Retrieval Augmented Generation纠错检索增强生成正是实现这种“反思”与“纠错”能力的一种前沿架构范式。它核心的思想是引入一个“检索评估器”在得到初步检索结果后不是直接送去生成而是先“踩一脚刹车”问问自己“我这次检索干得怎么样”如果评估认为结果不佳就会触发一个“纠正”动作这个动作可能就是重写查询、切换检索方法如从向量检索切到关键词检索、或引入外部知识源。LangChain4j作为一个强大的Java集成框架为我们实现这套复杂但精妙的流程提供了得心应手的工具。所以这次我们不谈空洞的概念直接进入实战。我将手把手带你利用LangChain4j构建一个具备CRAG思想的Agentic RAG系统。你会看到如何让RAG系统学会“三思而后行”从而显著提升回答的准确性和可靠性。2. 战场准备LangChain4j环境与核心组件搭建在开始构建复杂系统之前我们必须把战场打扫干净工具准备齐全。LangChain4j虽然API设计优雅但依赖管理不慎也会让人头疼。这里我基于一个真实的Spring Boot项目来搭建你可以直接复用。2.1 依赖管理与版本锁定首先pom.xml里的依赖声明是重中之重。LangChain4j模块众多我们只需要核心的几个。特别注意版本号不同版本间API可能有细微差别我强烈建议锁定一个稳定版本避免后续踩坑。properties langchain4j.version0.31.0/langchain4j.version !-- 使用一个稳定的版本 -- /properties dependencies !-- LangChain4j 核心 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version${langchain4j.version}/version /dependency !-- 用于连接OpenAI等大模型 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-open-ai/artifactId version${langchain4j.version}/version /dependency !-- 用于本地嵌入模型生成向量可选但推荐用于测试和降成本 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-embeddings-all-minilm-l6-v2/artifactId version${langchain4j.version}/version /dependency !-- 内存向量数据库方便快速原型验证 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-embeddings-store-filter/artifactId version${langchain4j.version}/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-embeddings-store-in-memory/artifactId version${langchain4j.version}/version /dependency !-- Spring Boot基础 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency /dependencies注意langchain4j-embeddings-all-minilm-l6-v2集成了一个轻量级的本地嵌入模型无需API调用即可生成向量对于快速验证和成本敏感的场景非常有用。生产环境可以考虑更强大的本地模型如通过langchain4j-embeddings-local-ai连接LocalAI或商用嵌入API。2.2 核心Bean配置模型、嵌入与存储接下来在Spring的配置类或主应用类中我们需要初始化几个核心Bean。这里我采用一个Configuration类来集中管理。import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.model.openai.OpenAiEmbeddingModel; import dev.langchain4j.store.embedding.EmbeddingStore; import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class LangChain4jConfig { // 你的OpenAI API Key务必通过环境变量或配置中心管理不要硬编码 private final String openAiApiKey System.getenv(OPENAI_API_KEY); /** * 配置对话模型用于最终的答案生成和Agent的决策逻辑 * 这里使用GPT-3.5 Turbo平衡性能与成本。 */ Bean public ChatLanguageModel chatLanguageModel() { return OpenAiChatModel.builder() .apiKey(openAiApiKey) .modelName(gpt-3.5-turbo) // 可根据需要换为 gpt-4 .temperature(0.2) // 较低的温度使输出更确定适合任务执行 .maxTokens(500) .build(); } /** * 配置嵌入模型用于将文本和查询转换为向量。 * 方案一使用OpenAI的嵌入模型效果好但有成本 */ Bean public EmbeddingModel openAiEmbeddingModel() { return OpenAiEmbeddingModel.builder() .apiKey(openAiApiKey) .modelName(text-embedding-3-small) // 性价比高的新模型 .build(); } /** * 方案二使用本地嵌入模型零成本适合原型和特定场景 * 取消下面的注释并注释掉上面的openAiEmbeddingModel Bean即可切换。 */ // Bean // public EmbeddingModel localEmbeddingModel() { // return new AllMiniLmL6V2EmbeddingModel(); // } /** * 配置向量存储。 * 这里使用内存存储重启后数据丢失。生产环境请换为Pinecone、Milvus、PGVector等持久化存储。 * 注意InMemoryEmbeddingStore本身不带检索功能需要配合EmbeddingStoreIngestor和EmbeddingStoreRetriever使用。 */ Bean public EmbeddingStoreTextSegment embeddingStore() { return new InMemoryEmbeddingStore(); } }这个配置类奠定了我们系统的基础。ChatLanguageModel是我们的“大脑”负责复杂的逻辑判断和内容生成EmbeddingModel是“翻译官”把文字世界映射到向量空间EmbeddingStore是“资料库”存放着我们处理好的知识片段。选择内存存储纯粹是为了演示简便在实际项目中这绝对是第一个需要替换的组件。比如对于专利检索、法律条文查询这类严肃场景向量数据库的持久化、可扩展性和检索性能至关重要。3. 实现CRAG核心检索评估与动态纠错流程现在进入最核心的部分如何让RAG系统拥有“反思”能力CRAG的论文提出了一种优雅的架构我们将其落地到LangChain4j中。整个流程可以分解为几个可管理的步骤。3.1 第一步构建知识库与基础检索器在系统能“反思”之前它得先会“检索”。我们首先需要有一个灌入了知识的数据源以及一个基础检索器。import dev.langchain4j.data.document.Document; import dev.langchain4j.data.document.loader.FileSystemDocumentLoader; import dev.langchain4j.data.document.splitter.DocumentSplitters; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.store.embedding.EmbeddingStoreIngestor; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.nio.file.Path; import java.nio.file.Paths; Component public class KnowledgeBaseLoader { private final EmbeddingModel embeddingModel; private final EmbeddingStoreTextSegment embeddingStore; public KnowledgeBaseLoader(EmbeddingModel embeddingModel, EmbeddingStoreTextSegment embeddingStore) { this.embeddingModel embeddingModel; this.embeddingStore embeddingStore; } /** * 项目启动时加载知识文档并存入向量库。 * 这里以加载一个Markdown文件为例。 */ PostConstruct public void initKnowledgeBase() throws Exception { // 1. 加载文档。可以从文件系统、URL、数据库等多种来源加载。 Path documentPath new ClassPathResource(knowledge/company_faq.md).getFile().toPath(); Document document FileSystemDocumentLoader.loadDocument(documentPath); // 2. 分割文档。这是关键步骤分割策略直接影响检索精度。 // 使用递归分割器按最大token数分割尽量保证语义完整性。 ListTextSegment segments DocumentSplitters.recursive(300, 30).split(document); // 3. 生成嵌入向量并存储。 EmbeddingStoreIngestor ingestor EmbeddingStoreIngestor.builder() .embeddingModel(embeddingModel) .embeddingStore(embeddingStore) .build(); ingestor.ingest(segments); System.out.println(知识库加载完成共存入 segments.size() 个文本片段。); } }有了知识库基础检索器就很简单了。LangChain4j提供了EmbeddingStoreRetriever它负责计算查询的向量并在向量库中寻找最相似的片段。Component public class BasicRetriever { private final EmbeddingModel embeddingModel; private final EmbeddingStoreTextSegment embeddingStore; public BasicRetriever(EmbeddingModel embeddingModel, EmbeddingStoreTextSegment embeddingStore) { this.embeddingModel embeddingModel; this.embeddingStore embeddingStore; } /** * 基础检索将查询向量化并从存储中召回最相关的N个片段。 * param query 用户查询 * param maxResults 最大返回结果数 * return 相关文本片段列表 */ public ListTextSegment retrieve(String query, int maxResults) { // 将查询文本转换为向量 Embedding queryEmbedding embeddingModel.embed(query).content(); // 在向量库中进行相似度搜索 ListEmbeddingMatchTextSegment matches embeddingStore.findRelevant(queryEmbedding, maxResults); // 提取匹配的文本片段 return matches.stream().map(EmbeddingMatch::embedded).collect(Collectors.toList()); } }至此一个传统RAG的检索部分就完成了。但它是“盲目的”无论查询是“苹果市值”还是“苹果好吃吗”它都会尽力去找最相似的向量。接下来我们要给它装上“质检员”。3.2 第二步设计检索评估器——系统的“质检员”检索评估器是CRAG的灵魂。它的任务是给本次检索的结果打分判断其是否可靠。论文中常用一个轻量级的大模型如GPT-3.5来完成这个任务。我们设计一个RetrievalEvaluator类。import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.output.structured.Description; import dev.langchain4j.service.SystemMessage; import dev.langchain4j.service.UserMessage; import lombok.Data; public interface RetrievalEvaluator { /** * 评估检索结果的相关性。 * param query 用户查询 * param retrievedContext 检索到的上下文拼接后的字符串 * return 评估结果包含分数和理由 */ EvaluationResult evaluate(String query, String retrievedContext); } Data class EvaluationResult { Description(相关性评分范围0-1越高表示越相关) private double relevanceScore; Description(评估理由解释为什么给出这个分数) private String reasoning; Description(建议的后续动作PROCEED直接生成, REWRITE重写查询, SUPPLEMENT补充检索) private String suggestedAction; }我们需要一个实现类利用大模型进行结构化输出。LangChain4j的SystemMessage和UserMessage注解配合结构化输出能让代码非常清晰。import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.output.structured.Description; import dev.langchain4j.service.AiServices; Component public class LLMRetrievalEvaluator implements RetrievalEvaluator { private final EvaluationAgent evaluationAgent; // 通过AiServices创建一个代理专门负责评估任务 interface EvaluationAgent { SystemMessage(你是一个检索质量评估专家。你的任务是根据用户查询和检索到的上下文判断上下文是否足够相关和完整以回答问题。请严格按格式输出。) EvaluationResult evaluateRelevance(UserMessage String query, UserMessage String context); } public LLMRetrievalEvaluator(ChatLanguageModel chatLanguageModel) { this.evaluationAgent AiServices.create(EvaluationAgent.class, chatLanguageModel); } Override public EvaluationResult evaluate(String query, String retrievedContext) { // 如果检索结果为空直接判定为不相关 if (retrievedContext null || retrievedContext.trim().isEmpty()) { EvaluationResult result new EvaluationResult(); result.setRelevanceScore(0.0); result.setReasoning(检索结果为空无法提供任何相关信息。); result.setSuggestedAction(REWRITE); // 建议重写查询或尝试其他检索方式 return result; } // 调用大模型进行评估 return evaluationAgent.evaluateRelevance(query, retrievedContext); } }这个评估器会接收用户查询和检索到的文本让大模型扮演专家输出一个结构化的评估结果。suggestedAction字段是关键它决定了流程的走向。这里我定义了三种动作PROCEED结果很好直接生成答案、REWRITE结果不太行需要优化查询、SUPPLEMENT结果部分相关需要补充信息比如调用网络搜索。3.3 第三步构建查询重写器与备用检索器如果评估器建议REWRITE我们就需要优化原始查询。同样我们可以用一个大模型代理来完成。Component public class QueryRewriter { interface RewriteAgent { SystemMessage(你是一个查询优化专家。用户提供了一个原始查询和不太相关的检索结果。请分析原因并生成一个更清晰、无歧义、更利于检索的新查询。只返回优化后的查询文本。) String rewriteQuery(UserMessage String originalQuery, UserMessage String poorContextSnippet); } private final RewriteAgent rewriteAgent; public QueryRewriter(ChatLanguageModel chatLanguageModel) { this.rewriteAgent AiServices.create(RewriteAgent.class, chatLanguageModel); } public String rewrite(String originalQuery, String poorContext) { return rewriteAgent.rewriteQuery(originalQuery, poorContext); } }对于SUPPLEMENT动作我们需要一个备用检索器。在真实场景中这可能是关键词检索如BM25当向量检索因表述方式不同而失效时传统的关键词匹配可能更有效。你可以集成Lucene或Elasticsearch。混合检索结合向量检索和关键词检索的分数。网络搜索当知识库信息不足时调用必应Bing或谷歌搜索API获取最新信息。注意实现网络搜索时务必使用合规的API接口并遵守相关服务条款。这里为了简化我演示一个“模拟”的备用检索器它假设在向量检索失败时尝试一个更宽泛的检索策略比如减少相似度阈值召回更多结果。Component public class FallbackRetriever { private final EmbeddingModel embeddingModel; private final EmbeddingStoreTextSegment embeddingStore; public FallbackRetriever(EmbeddingModel embeddingModel, EmbeddingStoreTextSegment embeddingStore) { this.embeddingModel embeddingModel; this.embeddingStore embeddingStore; } /** * 备用检索策略降低相似度阈值召回更多可能相关的结果。 */ public ListTextSegment fallbackRetrieve(String query, int maxResults) { Embedding queryEmbedding embeddingModel.embed(query).content(); // 注意InMemoryEmbeddingStore的findRelevant方法可能不支持直接设置阈值。 // 这里为了演示逻辑假设我们召回双倍数量然后在业务逻辑里处理。 // 实际使用支持阈值的数据库如Milvus、Pinecone时可以设置score_threshold。 ListEmbeddingMatchTextSegment matches embeddingStore.findRelevant(queryEmbedding, maxResults * 2); // 可以在这里根据匹配分数进行过滤模拟阈值效果 return matches.stream() .map(EmbeddingMatch::embedded) .collect(Collectors.toList()); } }3.4 第四步组装CRAG Agent——指挥整个流程现在所有零件都准备好了我们需要一个“指挥官”来串联整个流程。这就是我们的CRAGAgent。import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.List; import java.util.stream.Collectors; Slf4j Service public class CRAGAgent { private final BasicRetriever basicRetriever; private final RetrievalEvaluator retrievalEvaluator; private final QueryRewriter queryRewriter; private final FallbackRetriever fallbackRetriever; private final ChatLanguageModel chatModel; // 用于最终答案生成 // 构造函数注入所有依赖... public CRAGAgent(BasicRetriever basicRetriever, RetrievalEvaluator retrievalEvaluator, QueryRewriter queryRewriter, FallbackRetriever fallbackRetriever, ChatLanguageModel chatModel) { this.basicRetriever basicRetriever; this.retrievalEvaluator retrievalEvaluator; this.queryRewriter queryRewriter; this.fallbackRetriever fallbackRetriever; this.chatModel chatModel; } public String answer(String userQuery) { log.info(用户查询: {}, userQuery); String finalContext; ListTextSegment retrievedSegments; // --- 第一轮基础检索 --- retrievedSegments basicRetriever.retrieve(userQuery, 3); // 先尝试召回3个 String initialContext concatenateSegments(retrievedSegments); log.info(初始检索结果: {}, initialContext); // --- 第二轮评估与反思 --- EvaluationResult evaluation retrievalEvaluator.evaluate(userQuery, initialContext); log.info(检索评估: 分数{}, 建议动作{}, 理由{}, evaluation.getRelevanceScore(), evaluation.getSuggestedAction(), evaluation.getReasoning()); // --- 第三轮决策与纠正 --- switch (evaluation.getSuggestedAction()) { case PROCEED: finalContext initialContext; log.info(评估通过使用初始上下文。); break; case REWRITE: String rewrittenQuery queryRewriter.rewrite(userQuery, initialContext); log.info(查询被重写为: {}, rewrittenQuery); // 用重写后的查询重新检索 retrievedSegments basicRetriever.retrieve(rewrittenQuery, 3); finalContext concatenateSegments(retrievedSegments); log.info(重写后检索结果: {}, finalContext); // 可选对重写后的结果再次评估这里简化处理 break; case SUPPLEMENT: log.info(尝试补充检索...); ListTextSegment fallbackSegments fallbackRetriever.fallbackRetrieve(userQuery, 5); // 合并初始结果和补充结果 retrievedSegments.addAll(fallbackSegments); // 简单去重根据内容哈希 retrievedSegments retrievedSegments.stream() .distinct() .collect(Collectors.toList()); finalContext concatenateSegments(retrievedSegments); log.info(补充后合并上下文: {}, finalContext); break; default: log.warn(未知的建议动作使用初始上下文。); finalContext initialContext; } // --- 最终生成答案 --- // 构建最终提示词明确要求基于给定上下文回答 String prompt String.format( 请基于以下上下文信息回答用户的问题。如果上下文信息不足以回答问题请明确说明你不知道。 上下文信息 %s 用户问题%s 请给出准确、简洁的答案 , finalContext, userQuery); String answer chatModel.generate(prompt); log.info(最终答案: {}, answer); return answer; } private String concatenateSegments(ListTextSegment segments) { if (segments null || segments.isEmpty()) { return [未检索到相关信息]; } return segments.stream() .map(TextSegment::text) .collect(Collectors.joining(\n\n)); } }这个CRAGAgent的answer方法清晰地展示了整个“检索-评估-纠正”的闭环流程。它首先进行常规检索然后引入“质检员”评估器进行反思。根据反思结果系统会做出不同的决策直接通过、优化查询、或补充检索。这个动态决策过程正是Agentic RAG智能性的体现。4. 实战测试与效果对比看“反思”如何提升效果理论说再多不如跑个例子看看。我们假设知识库company_faq.md里包含以下内容Q: 公司的旗舰产品是什么 A: 我们的旗舰产品是“智能数据分析平台X1”专注于实时大数据可视化。 Q: 如何联系技术支持 A: 请发送邮件至 supportexample.com或拨打400-xxx-xxxx。 Q: 公司最近的融资情况如何 A: 我司于2023年完成了B轮融资由红杉资本领投。4.1 测试案例一处理模糊与错误查询用户查询“你们那个数据分析工具叫啥来着我忘了具体名字。”传统RAG流程向量检索可能会匹配到“智能数据分析平台X1”这个片段但也可能因为“工具”、“叫啥”等口语化表述导致相似度不高召回失败或召回其他不相关的内容如“技术支持”。我们的CRAG流程基础检索器召回相关片段假设召回了“旗舰产品”和“技术支持”两个片段。评估器分析“用户问数据分析工具的名字上下文提到了‘智能数据分析平台X1’高度相关。但同时也包含了不相关的技术支持信息。” 它可能给出一个较高的分数如0.85并建议PROCEED。系统直接使用包含产品名的上下文生成答案“我们的旗舰产品是‘智能数据分析平台X1’。”用户查询“蓝链4j怎么集成”一个明显的拼写错误“蓝链”应为“LangChain”传统RAG流程查询向量与知识库中任何片段的向量都不相似很可能返回空结果或极低分的无关结果导致大模型胡编乱造。我们的CRAG流程基础检索器召回的结果质量极差空或无关。评估器分析“检索结果与查询‘蓝链4j怎么集成’完全不相关。查询可能存在拼写错误或指代不明。” 给出低分如0.1建议REWRITE。查询重写器工作。它看到原始查询和糟糕的上下文可能会输出“LangChain4j 集成方法”。用重写后的查询重新检索这次很可能成功召回关于LangChain4j集成的知识如果知识库里有的话。生成正确答案。4.2 测试案例二处理知识库未覆盖的查询用户查询“公司CEO最近在公开场合发表了什么观点”传统RAG流程知识库中没有CEO相关言论检索结果为空或勉强匹配到“公司”、“融资”等词。大模型要么说“我不知道”更糟糕的情况下会基于“融资”这个微弱关联开始编造CEO的言论幻觉。我们的CRAG流程基础检索器召回的结果不相关比如只召回“融资情况”。评估器分析“用户询问CEO的公开观点但上下文中只提及了融资信息无法回答该问题。知识库可能缺乏此信息。” 给出低分建议SUPPLEMENT。这里是关键在我们的演示中FallbackRetriever只是扩大了召回范围。但在一个更完善的系统里SUPPLEMENT动作应该触发网络搜索。系统可以调用合规的搜索API例如必应搜索的企业级API获取关于该公司CEO的最新报道或演讲摘要。将内部检索结果和网络搜索结果融合形成最终上下文。生成一个基于真实网络信息的答案或者诚实地说明“根据内部资料未找到但根据公开报道...”。通过这两个案例你可以直观地感受到CRAG带来的提升。它通过一个简单的“评估-决策”循环极大地增强了RAG系统对复杂、模糊、错误查询的鲁棒性并提供了引入外部知识源的入口缓解了知识库覆盖不足的问题。5. 深入优化与生产级考量上面的实现是一个可运行的原型但要投入生产环境还有大量的优化工作需要做。这里分享几个我从实际项目中总结的关键点。5.1 评估器的优化成本、延迟与准确性平衡让大模型评估每一次检索虽然效果好但会增加成本和延迟。优化策略包括轻量化评估不是所有查询都需要评估。可以设置一个“快速过滤器”例如如果检索到的top1片段的向量相似度分数超过一个很高的阈值如0.95直接认为检索成功跳过评估。这能节省大量简单查询的LLM调用。小模型评估评估任务相对简单可以考虑使用更小、更快的模型如GPT-3.5 Turbo甚至专门微调的小模型来完成而不是使用昂贵的GPT-4。缓存评估结果对于相同或相似的查询可以缓存评估结果在一定时间内复用。5.2 纠错策略的扩展超越重写与补充我们只实现了REWRITE和SUPPLEMENT。更复杂的策略可以包括多路检索与融合同时使用向量检索和关键词检索BM25评估器可以决定更相信哪一路的结果或者将两路结果融合如加权平均。迭代式重写重写查询后可以再次评估如果还不理想可以基于新的结果进行第二轮重写形成一个迭代优化循环。子问题分解对于复杂查询如“对比产品X1和竞争对手Y2在价格和性能上的差异”评估器可以判断其复杂性并触发一个“规划”步骤将大问题分解成多个子问题分别检索再综合。5.3 与成熟向量数据库的集成InMemoryEmbeddingStore只是玩具。生产环境需要持久化与可扩展性使用Milvus、Pinecone、Weaviate或PGVector如果你用PostgreSQL。这些数据库支持海量向量数据的持久化存储和高效检索。高级检索功能它们支持过滤Filtering、混合搜索Hybrid Search结合向量和标量字段、分页等能更好地实现我们SUPPLEMENT策略中的复杂逻辑。性能专门的向量数据库针对大规模相似性搜索进行了优化。以集成Milvus为例你需要引入对应的LangChain4j模块并配置连接dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-store-embedding-milvus/artifactId version${langchain4j.version}/version /dependency配置Bean时使用MilvusEmbeddingStore替代InMemoryEmbeddingStore并配置主机、端口、集合名等参数。5.4 监控、评估与持续迭代一个上线的RAG系统不是一劳永逸的。必须建立监控和评估体系关键指标监控检索成功率、评估器调用比例重写/补充率、平均响应延迟、Token消耗成本。答案质量评估可以定期用一批标注好的测试问题QA对来跑系统计算答案的准确率Accuracy、忠实度Faithfulness答案是否严格基于上下文、相关性Relevance。LangChain4j也提供了一些评估工具链的集成。日志与溯源像我们代码中那样详细记录每一轮的查询、检索结果、评估分数、决策动作和最终答案。这对于排查bad case、理解系统瓶颈至关重要。当用户反馈答案不对时你能快速定位是检索失败了还是评估器误判了或者是大模型生成时产生了幻觉。构建一个真正健壮、智能的Agentic RAG系统是一个将精妙算法思想与扎实工程实践相结合的过程。LangChain4j提供了优秀的抽象和模块化设计让我们能专注于业务逻辑的编排。从基础的RAG管道到引入检索评估的CRAG再到未来可能集成更多工具调用Tool Use和复杂规划Planning的智能体这条路充满了挑战但每一步的进化都让我们的系统离“真正理解用户意图”更近一步。