RAG检索质量怎么评估?3个指标+LLM裁判实战

你的 RAG 知识库问答上线后,产品经理问"效果到底好不好",你只能回一句"感觉还行"。但"感觉"当不了指标------检索结果对不对、答案有没有编造,都得量化。这篇文章用 Spring AI 2.0 手写一套评估框架:先用召回率(Recall@K)、平均倒数排名(MRR)、命中率(Hit Rate)三个指标量化检索层,再用 LLM 当裁判给答案的忠实度打分。代码完整可运行,读完就能给自己的 RAG 系统建一套评测集。

一、这个问题到底是什么

RAG 系统上线前,必须有一个量化的评估手段,否则"效果好不好"永远是一笔糊涂账。

先说清楚 RAG 是什么。RAG(检索增强生成)是一套流程:用户问一个问题,系统先去知识库里检索几篇相关资料,再把资料拼进提示词,让大模型"对着资料作答"。它解决的核心痛点是大模型爱编造------你给它一份内部文档,它就能老老实实照着文档答,而不是凭空瞎编。

这套流程分成两个环节,每个环节都可能翻车,所以评估也必须拆成两层:

  • 检索层:用户问"怎么退款",系统有没有把《退款规则》这篇文档捞上来?如果捞上来的是《积分规则》,后面大模型再厉害也答不对。
  • 生成层:资料捞上来了,大模型有没有照着资料说?还是无视资料继续编?

大部分团队只盯着"最终答案对不对",但只看最终答案没法定位问题。答案错了,到底是检索没捞到对的东西,还是捞到了但大模型没用对?分不清,就没法针对性改进。所以正确的做法是:检索层用客观指标量化,生成层用 LLM 当裁判打分。

这篇文章要交付三样东西:

  1. 三个检索指标:Recall@K(该找的找回来多少)、MRR(正确的排在第几位)、Hit Rate(至少命中一个的查询占比)。
  2. 一个 LLM 裁判:让大模型自己评估"答案有没有忠于资料"(忠实度)。
  3. 一套完整可跑的代码:评测集 + 指标计算 + 检索评测 + LLM 裁判,全程只用内存向量库,不装任何外部数据库。

一个类比帮你记住:检索评估像批改"开卷考试",检查你有没有翻到正确的那一页;LLM 裁判像批改"有没有照着书抄对答案"。 两件事分开评,才能知道该修哪头。

二、底层原理到底怎么回事

要搞懂这三个指标,先得明白"评测集(golden set)"是什么。

评测集就是一份标准答案表。 每一行是一个测试问题(query),旁边标着"这个问题应该检索到哪几篇文档"(用文档的唯一 ID 表示)。比如:

问题 应该检索到的文档 ID
Java 里有哪些大模型开发框架? doc-1, doc-2
RAG 怎么减少大模型胡编乱造? doc-4

有了这份标准答案,检索结果对不对就能对账了:系统实际返回的文档 ID,和标准答案里的 ID 做交集。

指标一:Recall@K(召回率)

Recall 回答的问题是"该找的,找回来了多少"。它不看排名,只看"在不在前 K 个结果里"。

公式:Recall@K = 前K个结果里命中的相关文档数 / 该问题总共应有的相关文档数

举个例子:某问题应该有 2 篇相关文档(doc-1、doc-2),系统返回前 3 个结果是 doc-1, doc-5, doc-3。命中了 doc-1,没命中 doc-2,所以 Recall@3 = 1/2 = 0.5。

这个指标适合回答"漏检"问题------知识库里明明有答案,但没被捞出来,用户的体验就是"它不会"。

指标二:MRR(Mean Reciprocal Rank,平均倒数排名)

MRR 回答的问题是"第一个对的结果,排在第几位"。位置越靠前分越高,因为它关心用户第一眼看到的东西对不对。

公式:先算每个问题的"倒数排名"(第一个相关文档排第 1 位得 1 分,第 2 位得 1/2,第 3 位得 1/3,一个都没找到得 0 分),再对所有问题求平均。

举个例子:某问题第一个相关文档排在第 2 位,那么这一个问题的 RR(倒数排名)就是 1/2 = 0.5。把一堆问题的 RR 平均起来,就是 MRR。

这个指标对"答案拼接场景"特别重要------RAG 里排在最前面的文档往往权重最高,如果对的文档总被挤到第 5、第 6 位,生成质量就上不去。

指标三:Hit Rate@K(命中率)

Hit Rate 回答的问题是"前 K 个结果里,至少捞到一个对的没有"。它是最宽松的指标------不看捞回来几个、不看排名,只要有就行。

公式:Hit Rate@K = 至少命中一个相关文档的问题数 / 总问题数

举例:10 个问题里,有 8 个问题的前 3 个结果里至少有一个相关文档,那 Hit Rate@3 = 80%。

这三个指标是从三个角度给检索打分,没有任何一个能单独说明问题,必须一起看:Hit Rate 高但 MRR 低,说明"能捞到但排得靠后";Recall 低说明"漏检严重"。

接下来是 LLM 裁判(LLM-as-Judge)的原理。

生成层的质量没法用上面那种"对 ID"的方式量化,因为"答案好不好"是语义判断,得靠人来读。但人工评测太贵、太慢,于是就有了"让大模型当裁判":把问题、检索到的资料、系统生成的答案三段一起喂给大模型,让它打分。

裁判的核心是忠实度(Faithfulness)------答案里说的每一句话,是不是都能在"资料"里找到依据。如果答案是"根据资料,退款需要 3 个工作日",而资料里写的是"退款 7 个工作日",那这个答案就是编造,忠实度低。

为什么用忠实度而不是"答案对不对"?因为"对不对"需要你有一份标准正确答案,成本极高;而"忠实度"只需要有"检索到的资料"(系统本来就有),裁判只需要比对"答案 vs 资料"即可,可以全自动跑。另外一个常用维度是相关性(Relevance)------答案有没有正面回答用户的问题,这个也能量化,后面会提。

一句话总结原理:检索层用"对 ID"的数学指标量化,生成层用"大模型读答案"来量化,两层分开,才能精准定位 RAG 的短板。

三、实战:手把手写代码

下面用 Spring Boot 4.1.1 + Spring AI 2.0.1(均为当前最新 GA 版)+ Java 21。全程用内存向量库 SimpleVectorStore,不依赖 Redis、Milvus 等外部服务,你只要填一个 OpenAI API Key 就能跑通。

3.1 依赖:pom.xml

关键点:用 spring-ai-bom 统一管理 Spring AI 版本,避免各子模块版本对不齐;starter 用 spring-ai-starter-model-openai,它一次性带来聊天模型(用于 LLM 裁判)和 Embedding 模型(用于向量化)。

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>4.1.1</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>rag-eval</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>rag-eval</name>
    <description>RAG 检索质量评估 + LLM 裁判实战</description>

    <properties>
        <java.version>21</java.version>
        <spring-ai.version>2.0.1</spring-ai.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!-- Spring AI 2.0 的 OpenAI starter:同时提供 ChatModel 和 EmbeddingModel -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-openai</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3.2 配置:application.yml

配置 OpenAI 的 API Key、聊天模型(当裁判)和 Embedding 模型(做向量化)。

yaml 复制代码
spring:
  application:
    name: rag-eval
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4o-mini
      embedding:
        options:
          model: text-embedding-3-small

3.3 示例一:指标计算器(纯 Java,不依赖框架)

先写一个纯 Java 的指标计算类。它只吃两样东西:系统实际返回的文档 ID 列表,和标准答案里的相关 ID 集合。把指标计算独立出来,方便你写单元测试、到处复用,也方便理解每个公式到底在算什么。

java 复制代码
package com.example.rageval;

import java.util.List;
import java.util.Set;

/**
 * RAG 检索质量指标计算器。
 * 输入:检索返回的文档 ID 列表 + 标准答案(该问题应有的相关文档 ID 集合)。
 * 输出:Recall@K、倒数排名 RR、命中率 Hit@K。
 */
public final class RagMetrics {

    private RagMetrics() {
    }

    /**
     * 计算 Recall@K(召回率)。
     * 含义:前 K 个结果里,命中了多少个"应该命中的文档",除以应该命中的文档总数。
     * 值域 0~1,越接近 1 说明"该找的都找回来了",漏检越少。
     */
    public static double recallAtK(List<String> retrievedIds, Set<String> relevantIds, int k) {
        if (relevantIds.isEmpty()) {
            return 0.0;
        }
        int limit = Math.min(k, retrievedIds.size());
        Set<String> topK = Set.copyOf(retrievedIds.subList(0, limit));
        long hitCount = topK.stream().filter(relevantIds::contains).count();
        return (double) hitCount / relevantIds.size();
    }

    /**
     * 计算单个问题的倒数排名 RR(Reciprocal Rank)。
     * 含义:第一个相关文档排在第几位。第 1 位得 1 分,第 2 位得 1/2,第 3 位得 1/3,没找到得 0。
     * MRR 就是对一堆问题的 RR 求平均。
     */
    public static double reciprocalRank(List<String> retrievedIds, Set<String> relevantIds) {
        for (int i = 0; i < retrievedIds.size(); i++) {
            if (relevantIds.contains(retrievedIds.get(i))) {
                return 1.0 / (i + 1);
            }
        }
        return 0.0;
    }

    /**
     * 计算 Hit Rate@K(命中率,单条)。
     * 含义:前 K 个结果里,是否至少命中一个相关文档。
     * 汇总时就是"命中的问题数 / 总问题数"。
     */
    public static boolean hitAtK(List<String> retrievedIds, Set<String> relevantIds, int k) {
        int limit = Math.min(k, retrievedIds.size());
        Set<String> topK = Set.copyOf(retrievedIds.subList(0, limit));
        return topK.stream().anyMatch(relevantIds::contains);
    }
}

这段代码的关键行为解释:三个方法都只在"前 K 个结果"这个窗口内计算,窗口大小由 Math.min(k, retrievedIds.size()) 保证不会越界。Set.copyOf 会把前 K 个 ID 去重放进一个集合,这样"命中判断"就是一次集合查找,代码短、也够快------评测集通常就几十上百条,性能不是瓶颈。

3.4 示例二:检索评测服务(用内存向量库跑指标)

这个服务干三件事:把测试知识库灌进 SimpleVectorStore、定义评测集、跑检索并算出三个指标。注意一个细节:每篇文档的 metadata 里都带一个 doc_id ,评测时用这个字段对账,而不是用向量库自己生成的内部 ID------因为内部 ID 你控制不了,换个库、重启一次就可能变,而 doc_id 是你自己定的、稳定可控。

java 复制代码
package com.example.rageval;

import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 检索层评测服务:灌数据 -> 定义评测集 -> 跑检索 -> 算指标。
 */
@Service
public class RetrievalEvaluator {

    private final VectorStore vectorStore;

    public RetrievalEvaluator(EmbeddingModel embeddingModel) {
        // 用内存向量库,无需安装任何外部数据库,开箱即跑
        this.vectorStore = SimpleVectorStore.builder(embeddingModel).build();
        ingest();
    }

    /**
     * 灌入测试知识库。每篇文档用 metadata 里的 doc_id 作为唯一标识。
     */
    private void ingest() {
        List<Document> docs = List.of(
                new Document("Spring AI 是 Spring 官方推出的 AI 应用开发框架,提供 ChatClient、EmbeddingModel、VectorStore 等核心组件,帮助 Java 开发者快速集成大模型。",
                        Map.of("doc_id", "doc-1")),
                new Document("LangChain4j 是 Java 生态的 LLM 应用框架,提供对话、RAG、Agent 等能力,与 Spring Boot 集成良好。",
                        Map.of("doc_id", "doc-2")),
                new Document("向量检索的核心是把文本转成 Embedding 向量,再用余弦相似度计算语义距离,距离越近表示含义越接近。",
                        Map.of("doc_id", "doc-3")),
                new Document("RAG(检索增强生成)先检索相关资料,再把资料拼进提示词让大模型作答,能有效减少大模型胡编乱造。",
                        Map.of("doc_id", "doc-4")),
                new Document("Redis 是一个高性能的内存数据库,常用于缓存、分布式锁、消息队列等场景。",
                        Map.of("doc_id", "doc-5"))
        );
        vectorStore.add(docs);
    }

    /**
     * 评测集(golden set):每个问题对应"应该被检索到的文档 ID 集合"。
     * 真实项目里这个集合至少要 50~100 条才有统计意义,这里为了演示放 4 条。
     */
    public List<TestCase> goldenSet() {
        return List.of(
                new TestCase("Java 里有哪些大模型开发框架?", Set.of("doc-1", "doc-2")),
                new TestCase("RAG 是怎么减少大模型胡编乱造的?", Set.of("doc-4")),
                new TestCase("什么是向量检索?", Set.of("doc-3")),
                new TestCase("Redis 能用来干什么?", Set.of("doc-5"))
        );
    }

    /**
     * 跑一遍完整评测,返回每个问题的明细 + 三个汇总指标。
     */
    public EvalResult evaluate(int topK) {
        List<TestCase> cases = goldenSet();
        List<CaseResult> caseResults = new ArrayList<>();
        double recallSum = 0.0;
        double rrSum = 0.0;
        int hitCount = 0;

        for (TestCase tc : cases) {
            List<Document> hits = vectorStore.similaritySearch(
                    SearchRequest.builder().query(tc.query()).topK(topK).build());

            // 把检索结果转成我们自己定义的 doc_id 列表,才能和标准答案对账
            List<String> retrievedIds = hits.stream()
                    .map(d -> (String) d.getMetadata().get("doc_id"))
                    .toList();

            double recall = RagMetrics.recallAtK(retrievedIds, tc.relevantIds(), topK);
            double rr = RagMetrics.reciprocalRank(retrievedIds, tc.relevantIds());
            boolean hit = RagMetrics.hitAtK(retrievedIds, tc.relevantIds(), topK);

            recallSum += recall;
            rrSum += rr;
            if (hit) {
                hitCount++;
            }

            caseResults.add(new CaseResult(tc.query(), retrievedIds, recall, rr, hit));
        }

        int n = cases.size();
        return new EvalResult(
                caseResults,
                recallSum / n,   // 平均 Recall@K
                rrSum / n,       // MRR
                (double) hitCount / n  // Hit Rate@K
        );
    }

    // ---- 内部数据结构(record) ----

    public record TestCase(String query, Set<String> relevantIds) {
    }

    public record CaseResult(String query, List<String> retrievedDocIds,
                             double recall, double reciprocalRank, boolean hit) {
    }

    public record EvalResult(List<CaseResult> cases,
                             double meanRecall, double mrr, double hitRate) {
    }
}

这段代码的关键行为解释:SimpleVectorStore.builder(embeddingModel).build() 构造一个内存向量库,add(docs) 时它内部会调用 Embedding 模型把每篇文档向量化再存起来。similaritySearch 则把查询向量化后做余弦相似度排序,返回按相似度降序的文档列表。因为 SimpleVectorStore 默认就是余弦相似度、默认返回的就是从高到低排序,所以我们直接取前 topK 个即可,符合"靠前的更相关"这个评测前提。

3.5 示例三:LLM 裁判评估答案忠实度

生成层的评估用 ChatClient 实现。核心思路:把"问题 + 检索到的资料 + 系统答案"三段拼进提示词,让大模型只输出一个 0~1 的小数当分数。温度要低(这里用默认的模型配置即可,生产建议设 0),prompt 要严格限定"只输出数字"。

java 复制代码
package com.example.rageval;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;

/**
 * LLM 裁判:用大模型评估答案质量(忠实度 Faithfulness)。
 */
@Service
public class LLMJudgeService {

    private final ChatClient chatClient;

    public LLMJudgeService(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder.build();
    }

    /**
     * 评估忠实度:答案是否完全基于"资料",有没有编造资料里不存在的内容。
     * 返回 0~1 的分数:1.0 = 完全忠实,0.0 = 严重编造。
     */
    public double judgeFaithfulness(String question, String context, String answer) {
        String prompt = """
                你是一个严格的评测裁判。请判断下面的"答案"是否完全基于"资料"得出,有没有编造资料里不存在的内容。

                问题:%s

                资料:
                %s

                答案:
                %s

                请只输出一个 0 到 1 之间的小数,表示忠实度分数:
                - 1.0 表示答案完全基于资料,没有任何编造
                - 0.0 表示答案与资料完全无关或严重编造
                - 中间值表示部分基于资料

                只输出数字,不要输出任何其他文字。
                """.formatted(question, context, answer);

        String content = chatClient.prompt()
                .user(prompt)
                .call()
                .content();

        try {
            return Double.parseDouble(content.trim());
        } catch (NumberFormatException e) {
            // 模型偶尔不听话,返回原始输出便于排查,分数记 0
            System.err.println("LLM 裁判输出无法解析为数字:" + content);
            return 0.0;
        }
    }
}

这段代码的关键行为解释:Double.parseDouble(content.trim()) 前先 trim() 去掉模型可能多输出的换行和空格。catch 兜底是必须的------大模型即便被要求"只输出数字",偶尔也会补一句"好的,0.8",这时直接解析会抛异常。这里记 0 分并打印原始输出,方便你回头查是哪条 prompt 把模型带偏了。

3.6 入口类:把评测跑起来

最后写启动类,在 CommandLineRunner 里依次跑检索评测和 LLM 裁判,把结果打印到控制台。

java 复制代码
package com.example.rageval;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class RagEvalApplication {

    public static void main(String[] args) {
        SpringApplication.run(RagEvalApplication.class, args);
    }

    @Bean
    CommandLineRunner runEval(RetrievalEvaluator evaluator, LLMJudgeService judge) {
        return args -> {
            System.out.println("========== 检索质量评测(topK=3) ==========");
            RetrievalEvaluator.EvalResult result = evaluator.evaluate(3);
            for (RetrievalEvaluator.CaseResult c : result.cases()) {
                System.out.printf("问题:%s%n", c.query());
                System.out.printf("  检索到:%s%n", c.retrievedDocIds());
                System.out.printf("  Recall@3=%.2f  RR=%.2f  命中=%s%n",
                        c.recall(), c.reciprocalRank(), c.hit());
            }
            System.out.printf("汇总:平均 Recall@3=%.2f | MRR=%.2f | HitRate@3=%.2f%n",
                    result.meanRecall(), result.mrr(), result.hitRate());

            System.out.println();
            System.out.println("========== LLM 裁判评估(忠实度) ==========");
            double faithfulness = judge.judgeFaithfulness(
                    "RAG 是怎么减少大模型胡编乱造的?",
                    "RAG(检索增强生成)先检索相关资料,再把资料拼进提示词让大模型作答,能有效减少大模型胡编乱造。",
                    "RAG 会先检索资料再让大模型作答,所以能减少大模型凭空编造。");
            System.out.printf("答案忠实度分数:%.2f(满分 1.0)%n", faithfulness);
        };
    }
}

跑起来的命令:

bash 复制代码
export OPENAI_API_KEY=你的密钥
mvn spring-boot:run

你会在控制台看到每个问题的检索明细和三个汇总指标,最后是 LLM 裁判给答案打的忠实度分数。

四、踩坑经验和最佳实践

这一节是我在实际做 RAG 评估时踩过的坑,照着做能少走很多弯路。

坑一:评测集和开发数据混在一起,指标虚高。 如果你拿"已经调优过的那批文档"当评测集,指标当然好看,但那叫自欺欺人。正确做法是评测集要独立、要覆盖真实用户会问的问题,最好从线上日志里抽样真实 query,再人工标注标准答案。

坑二:靠向量库内部 ID 对账,结果不稳定。 向量库给你分配的 ID(比如 UUID)会随存储重启、重新导入而变化,评测结果就没法复现。所以一定要像本文这样,在 metadata 里放一个自己定义的 doc_id,用它对账。

坑三:topK 不固定,指标不可比。 Recall@3、Recall@5、Recall@10 是完全不同的三个数。两次评测如果 topK 都不一样,比出来的"进步"就是假的。每次评测前先定死 topK,并且和你线上真实取用的 topK 保持一致。

坑四:LLM 裁判不稳定,同一份答案两次打分可能不同。 大模型有随机性。缓解办法:把 temperature 设成 0,固定裁判用的 prompt,同一个问题跑 3 次取平均。如果分数波动仍然很大,说明这个裁判 prompt 本身写得不够明确,先修 prompt 再谈分数。

坑五:只看总分,不看明细。 汇总指标是平均值,会把问题藏起来。评测结果一定要保留每个问题的明细(本文的 CaseResult 就是干这个的),否则你只知道"MRR 降了",却不知道是哪类 query 掉下去了。

最佳实践清单:

  • 评测集至少 50~100 条,才有统计意义;4 条只够演示。
  • 检索层三个指标(Recall@K、MRR、Hit Rate)一起看,不单独下结论。
  • 生成层至少评两个维度:忠实度(答案有没有编造)+ 相关性(答案有没有答非所问)。
  • 换了 Embedding 模型、改了分块策略、换了向量库,都要重新跑一遍评测集。
  • 把评测做成 CI 的一环,每次改检索逻辑自动出指标,防止"改好了 A 指标、改坏了 B 指标"。

五、性能对比和技术选型

评估方案本身也有选型问题,这里给几个实用结论。

检索层:客观指标优先,人工只做兜底。 Recall@K、MRR、Hit Rate 都是纯数学计算,零成本、可复现、毫秒级出结果,应该作为主力。人工评测只适合在"语义等价但 ID 不同"的边缘 case 上做抽查------比如两篇文档内容几乎一样,系统捞到了 A,标准答案写的是 B,指标会误判为漏检,这种才需要人看一眼。

生成层:LLM 裁判优先,人工抽检。 用大模型评估答案忠实度,成本远低于人工,且能全自动覆盖大批量答案。但 LLM 裁判本身有随机性和偏见(比如偏爱更长、更流畅的答案),所以建议每月人工抽检 5%~10% 的样本,校准 LLM 裁判的分数是否靠谱。

别用 BLEU/ROUGE 这类机器翻译指标评 RAG。 BLEU/ROUGE 是算"字面重合度"的,而 RAG 答案换一种说法表达同一个意思,字面可能完全对不上,用它们评会严重低估真实质量。语义层面的忠实度、相关性,交给 LLM 裁判更合适。

选型小结: 检索层用三指标(零成本、可复现),生成层用 LLM 裁判(低成本、可扩展),人工只在两头做校准和兜底。这是当前性价比最高的组合,也是业界主流做法。

六、总结

RAG 系统"效果好不好",不能靠感觉,必须靠量化评估。评估拆成两层:检索层用数学指标,生成层用 LLM 裁判,两层分开才能精准定位问题出在"没捞到"还是"没用对"。

检索层三个指标各有分工:Recall@K 看"该找的找回来多少",MRR 看"对的排在第几位",Hit Rate 看"至少捞到一个没有"。三个一起看,漏检、排序、空手而归三种毛病都能暴露出来。生成层用 LLM 当裁判评忠实度------答案有没有照着资料说、有没有编造,这是防止大模型"一本正经地胡说"的最后一道闸。

几个关键动作再强调一遍:评测集要独立、要够大(50 条以上);对账用自己定义的 doc_id,别用向量库内部 ID;topK 每次固定;LLM 裁判要低温、固定 prompt、必要时跑多次取平均。

全文代码基于 Spring Boot 4.1.1 + Spring AI 2.0.1 + Java 21,只用内存向量库,填一个 OpenAI API Key 就能跑通复现。把这套评估框架接进你的 RAG 项目,下次产品再问"效果怎么样",你可以直接甩三个数字加一个裁判分数过去,而不是一句"感觉还行"。

相关推荐
ShuangNaoPai1 小时前
失效检测与原型匹配:基于双脑派于华的高尔夫案例,重构AI内容生成策略
人工智能·架构·产品运营·prompt
卢总霸气1 小时前
上海交大机动学院学位论文参考文献Zotero CSL 样式-2026
java
西柚不烦恼1 小时前
Windows 本地部署私有 AI 全攻略:纯 CPU 环境(Ollama + Open WebUI)
人工智能·windows
FLJwu1 小时前
运动相机采购实战|第四篇(终章):一张清单走完选型全流程,把风险拦在源头
c语言·开发语言·数码相机·运动相机·oem 量产
收纳过往1 小时前
数据库加索引的时机:慢查询日志说话
android·开发语言·数据库·python·低代码·rxjava
段一凡-华北理工大学1 小时前
高炉能源系统智能化:热风炉、煤气与极致能效~系列文章07:高炉煤气:产生规律与全厂用户平衡
java·前端·能源
咖啡八杯1 小时前
Redis 工具封装:RedisCache 对 Operations 体系的二次封装
java·数据库·redis·缓存·框架·springboot·若依
2601_962885721 小时前
让 AI Agent 自动收盘复盘:定时取全市场数据 + 生成复盘
人工智能
wno7041 小时前
Springboot国际化信息(i18n)解析
java·spring boot·后端