欢迎回来
登录你的知识库账户
忘记密码?
还没有账户?立即注册
创建账户
注册你的专属知识库
已有账户?去登录
找回密码
输入注册邮箱获取验证码
返回登录
请输入图片中的验证码以继续注册
加载中...
取消
新建收藏
手动添加你喜欢的内容
取消
编辑头像与昵称
上传新头像或修改你的显示昵称
支持 JPG/PNG,最大 2MB
取消

问题反馈

notebasewww.notebase.cn
控制台
内容库
动态
管理
账户
U
用户
--
在线
v0.8.7 · 知识库
笔记
KnowledgeBase
网络无边,知识有迹。
0笔记
0工具
30推荐

分类导航

按主题直达

编辑精选

站内用户贡献 · 真实笔记

最新收录

每日更新
继续浏览全部内容 →
>
笔记
0
加载中...
工具
0
此页用于记录用户反馈问题后的每一次改进
笔记用法

“写笔记”支持四种格式——Word 文档、Excel 表格、Markdown、纯文本,起稿或二次编辑时都能随时切换,同一篇笔记想用哪种形态来记,都由你说了算。

md、txt、csv、json 这类纯文本则原样载入,不做多余加工。拿一张现成的表倒进来、改几笔、再导出去,等于白用一台免费的格式转换器。

要带走就在右上角点“下载”,可导出 PDF、Word、Markdown、Excel、TXT 等格式;列表卡片“⋯”菜单里,也有同样的下载入口。

工具用法

在“工具”页点“+ 上传工具”即可发布:填好名称与链接,再用 Markdown 把使用方法写清楚——能解决什么问题、怎么装、怎么用,比堆介绍实在。

要分发安装包就一并上传压缩包(ZIP、RAR、7Z、TAR.GZ,最大 35MB),别人在详情页一键下载;只放链接不带附件也可以。

工具按大家的收藏热度排序,好用的自然会被顶上来。发布后可在详情页或卡片菜单里编辑、下架。

隐藏笔记

写笔记时勾上“隐藏”,这篇就只存在于你自己的账号里:不进列表、不进搜索、不上首页精选,也不会出现在任何公开的页面,链接发给别人同样打不开。

适合放密码、草稿、日记这类只给自己看的内容;想公开,去“发布”打开它,把“隐藏”的勾去掉再保存,之后编辑会默认保持原状态,不会悄悄变回公开。

数据安全

你的内容会同时保存在多个副本上,系统定期做备份与完整性校验,再配合异地容灾机制:就算某台机器出问题,数据也不会丢,可以长期放心存放;特别重要的资料,仍建议你另外再留一份备份。

技术

全站跑在容器化、模块化的现代架构上,更新、部署、回滚都很快,扩展性和稳定性都按长期运营的标准来设计(Built for reliability, designed to scale)。

理念

这个网站最早只是一个人的笔记仓库,后来慢慢长成现在的知识中枢。设计上很克制——没有广告、没有追踪、没有推荐算法,只是干干净净地存放一些东西;既然做好了,就公开出来,万一有人用得上呢。

原则

不做大而全,不做平台梦,保持简单、保持克制、保持好奇。所有内容都由用户贡献、由用户维护:不会突然冒出付费墙,不会在角落塞广告位,也不会把你的数据卖给第三方。

更多

产品会持续迭代,站内日志页记录着每一次改动,改了什么都有迹可循;想了解这个站是怎么一步步走到今天的,翻翻日志就能看到来龙去脉。

举报

如果在这里看到涉嫌违规的内容,点对应卡片右侧的“举报”按钮就能提交,我们会尽快核实处理;也谢谢你花一点时间,一起把这里维护干净。

趋势
// 点击导航加载发现
归档
// 归档为空
最近浏览
// 暂无浏览记录
发布
// 加载中...
用户发布
// 加载中...
用户管理
// 加载中...
访问统计
// 加载中...
内容审核
// 加载中...
个人信息
// 加载中...
返回首页

RAG应用性能测试:完整工程指南

2026/7/6人工智能

写在前面

如果你跟我一样是从JMeter或者k6背景过来的,第一次接触RAG(检索增强生成)端点时,第一反应肯定是直接扔一个负载测试上去,看看响应时间怎么样。这没错,但只做对了一半。

一个RAG应用完全可以给你一个又快又自信但完全错误的答案——普通的负载测试永远不会告诉你这个。你需要两个测试维度,不是一个:性能和质量。

这篇笔记会用一个贯穿始终的例子来展开:一个文档助手,回答"如何在非GUI模式下运行JMeter?"这个问题,基于一个小型的知识库。

为什么RAG打破了传统负载测试的假设

传统API返回完整响应,你测量往返时间就完事了。但RAG端点在回答之前干了两件昂贵的事:

  1. 检索上下文 - 从向量数据库或搜索索引中拉取相关文档
  2. 逐token生成响应 - 流式输出结果

第二步特别重要。单个请求可能流式输出几百个token,持续好几秒。所以"请求持续时间"这个单一数字掩盖了两个完全不同的问题:

  • 模型开始回答花了多久(启动延迟)
  • 模型生成起来后速度如何(生成速度)

一个启动慢但生成快的系统,在聊天UI里会让用户盯着空白屏幕发呆。一个启动快但生成慢的系统,问个简单问题还行,但要总结长文档就很痛苦。把这两个数字平均一下,什么都看不出来。

两个测试维度:性能和质量

我把RAG测试看作两个独立的关卡,只不过它们跑在同一个端点上。

性能回答的是:它有多快?负载下能不能扛住?这是k6的活儿,跟其他API负载测试一样,只是加上了LLM特有的指标。

质量回答的是:答案真的基于检索到的内容吗?还是模型自己瞎编的?这就是DeepEval上场的地方,用LLM作为裁判,对每次响应的忠实度和相关性打分。

单独任何一个关卡都不能告诉你完整的故事。一个快但胡言乱语的RAG应用比一个慢但准确的要糟糕得多;一个完全基于事实但需要8秒才能响应的应用,不管多准确都会流失用户。

真正重要的指标

性能指标

指标 含义
TTFT(首token延迟) 用户盯着空白屏幕多久才看到第一个字
ITL(token间延迟) 生成开始后,token流输出的流畅度
Tokens/sec(每秒token数) 生成速度,对长篇回答尤其重要
p95 / p99 延迟 尾部体验,不是平均值

TTFT是整个系统里最影响用户体验的数字,也是传统负载测试工具最难单独测量的指标——因为它们是为原子化的请求/响应周期设计的,不是为流式输出设计的。

质量指标

指标 含义
Faithfulness(忠实度) 答案是否基于检索到的上下文,还是模型自己编的
Answer Relevancy(答案相关性) 答案是否真正回答了问题,还是听起来像那么回事
Context Precision(上下文精确度) 检索是否返回了正确的文档片段,且排序合理
Context Recall(上下文召回率) 检索是否遗漏了答案需要的任何内容

这四个指标承载了RAG评估中大部分诊断价值。忠实度和答案相关性属于生成侧;上下文精确度和召回率属于检索侧。

当忠实度低但上下文召回率高时,说明检索器干得不错,但模型没理它——这是提示词的问题,不是检索的问题。在调错组件之前,搞清楚这个区别很有价值。

用DeepEval检测幻觉

我选择DeepEval而不是RAGAS,主要是因为DeepEval把评估当作pytest测试用例来处理,带有通过/失败阈值——这正好是CI/CD关卡需要的形态。它还接受任何LLM作为裁判模型,所以不绑定某个厂商,尽管我们的示例应用用了Gemini。

下面是一个测试用例的样子,针对我们的JMeter文档助手示例:

from deepeval.metrics import FaithfulnessMetric, AnswerRelevancyMetric
from deepeval.test_case import LLMTestCase
from deepeval.models import GeminiModel

judge_model = GeminiModel(
    model="gemini-3.5-flash",
    api_key=os.getenv("GEMINI_API_KEY"),
)

faithfulness_metric = FaithfulnessMetric(threshold=0.75, model=judge_model)
answer_relevancy_metric = AnswerRelevancyMetric(threshold=0.8, model=judge_model)

def test_jmeter_non_gui_mode_answer():
    question = "How do I run JMeter in non-GUI mode?"
    result = query_rag_app(question)
    
    test_case = LLMTestCase(
        input=question,
        actual_output=result["answer"],
        retrieval_context=result["retrieved_chunks"],
    )

    for metric in [faithfulness_metric, answer_relevancy_metric]:
        metric.measure(test_case)
        status = "PASS" if metric.success else "FAIL"
        print(f"[{status}] {metric.__class__.__name__}: {metric.score:.3f}")

    failed = [m for m in [faithfulness_metric, answer_relevancy_metric] if not m.success]
    if failed:
        names = ", ".join(m.__class__.__name__ for m in failed)
        raise AssertionError(f"Metrics below threshold: {names}")

用 pytest 运行这个测试,它要么通过要么失败,跟其他任何测试一样。这就是关键——它把一个模糊的"AI说得对不对"的问题,变成了一个二进制的CI/CD信号。

测试套件还包括重试逻辑,用于处理Gemini API的临时503错误,自动重试最多3次,带指数退避。DeepEval同时生成JUnit XML和HTML报告,可以轻松接入任何理解pytest输出的CI系统。

用k6做负载测试(以及为什么你暂时测不了TTFT)

这里有个让人沮丧的地方:如果你来这里是想找到干净利落的TTFT测量方案,那我要告诉你——k6的SSE扩展(xk6-sse)跟k6 v2不兼容。它只支持 go.k6.io/k6 v1,在它更新之前,你只能在k6 v2的改进架构和正确测量流式指标之间二选一。

所以配套的仓库做了务实的选择:测试 /chat/complete 端点而不是 /chat 流式端点,用k6内置的 http 模块。不需要自定义二进制文件,不需要扩展,就是标准k6。

代价是你失去了真正的TTFT测量,因为 /chat/complete 会等完整响应返回。你得到的是端到端延迟,这仍然有用——它能告诉你系统是否慢,只是不能告诉你为什么慢。

测试长这样:

import http from 'k6/http';
import { Trend, Counter } from 'k6/metrics';
import { check } from 'k6';

const totalDuration = new Trend('total_duration_ms', true);
const tokensPerSecond = new Trend('tokens_per_second');

const BASE_URL = __ENV.RAG_APP_URL || 'http://localhost:8080';

export const options = {
    scenarios: {
        rag_chat: {
            executor: 'ramping-vus',
            stages: [
                { duration: '30s', target: 10 },
                { duration: '1m', target: 10 },
                { duration: '30s', target: 0 },
            ],
        },
    },
    thresholds: {
        'total_duration_ms': ['p(95)<5000'],
        'tokens_per_second': ['avg>10'],
    },
};

export default function () {
    const payload = JSON.stringify({
        question: "How do I run JMeter in non-GUI mode?",
    });

    const params = {
        headers: { 'Content-Type': 'application/json' },
    };

    const res = http.post(`${BASE_URL}/chat/complete`, payload, params);

    check(res, {
        'status is 200': (r) => r.status === 200,
    });

    if (res.status === 200) {
        const body = JSON.parse(res.body);
        totalDuration.add(res.timings.duration);
        
        // 估算每秒token数
        const tokenCount = body.answer.split(' ').length;
        const durationSec = res.timings.duration / 1000;
        tokensPerSecond.add(tokenCount / durationSec);
    }
}

注意阈值设置:total_duration_ms 的p95要小于5000ms,tokens_per_second 的平均值要大于10。这些值需要根据你的实际情况调整,但至少给了你一个硬性门槛。

把两件事串起来:CI/CD流水线

现在我们有两条测试轨道了:

  1. 质量测试 - 用DeepEval + pytest,检查忠实度和相关性
  2. 性能测试 - 用k6,检查端到端延迟和吞吐量

把它们放进GitHub Actions流水线,这样每次PR都能自动跑:

name: RAG Performance & Quality Tests

on:
  pull_request:
    branches: [main]

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      - name: Install dependencies
        run: |
          pip install deepeval pytest
          pip install -r requirements.txt
      - name: Run quality tests
        env:
          GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
          RAG_APP_URL: ${{ secrets.RAG_APP_URL }}
        run: |
          pytest tests/test_quality.py --junitxml=quality-results.xml
      - name: Upload quality results
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: quality-results
          path: quality-results.xml

  performance:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Install k6
        run: |
          sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
          echo "deb https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
          sudo apt-get update
          sudo apt-get install k6
      - name: Run performance tests
        env:
          RAG_APP_URL: ${{ secrets.RAG_APP_URL }}
        run: |
          k6 run tests/load-test.js --out json=performance-results.json
      - name: Upload performance results
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: performance-results
          path: performance-results.json

这里的关键点是两个job是并行跑的,互不依赖。性能测试不需要等质量测试完成,反之亦然。这样整个流水线跑得最快。

实际运行中的注意事项

1. 测试数据集的选择

不要只用一个问题去测。你需要一个覆盖常见场景的测试集:

  • 简单问题 - 可以直接从检索到的文档中找到答案
  • 复杂问题 - 需要综合多个文档片段才能回答
  • 边缘问题 - 知识库中只有部分相关或不相关的信息
  • 无答案问题 - 知识库中完全没有相关信息

每个问题都应该有预期的忠实度阈值和相关性阈值。对于无答案问题,好的RAG系统应该返回"我不知道"而不是瞎编。

2. 裁判模型的选择

DeepEval使用LLM作为裁判,但裁判模型本身也会犯错。建议:

  • 使用能力更强的模型作为裁判(比如Gemini 3.5 Flash或GPT-4)
  • 不要用同一个模型既做应用又做裁判,否则会引入系统性偏差
  • 如果预算允许,对裁判结果做人工抽样验证

3. 阈值设置的艺术

阈值不是拍脑袋定的。建议先跑一轮基线测试,然后根据业务需求调整:

  • 忠实度0.75 - 这意味着允许25%的"自由发挥",对于创意写作可能可以,对于事实性问答太宽松了
  • 相关性0.8 - 20%的"跑题"空间,对于客服场景可能太高了
  • p95延迟<5000ms - 5秒对于对话式应用来说已经偏长了,理想情况下应该在2-3秒内

实际项目中,这些阈值需要跟业务方一起确定,并且随着系统迭代不断调整。

4. 失败处理

质量测试中的AssertionError会导致pytest返回非零退出码,CI流水线就会标记为失败。这意味着:

  • 如果模型开始胡言乱语(忠实度低),PR会被阻止合并
  • 如果模型答非所问(相关性低),PR也会被阻止
  • 如果检索器坏了(上下文召回率低),同样会被阻止

这就是你想要的——在代码进入生产环境之前,抓住所有可能让用户失望的问题。

总结一下

RAG应用的性能测试不能只靠传统的负载测试工具。你需要:

  1. 性能测试 - 用k6测量端到端延迟、吞吐量,未来如果能测TTFT就更好了
  2. 质量测试 - 用DeepEval测量忠实度、相关性、上下文精确度和召回率
  3. CI/CD集成 - 把两者都放进流水线,让每次PR都自动验证

两条轨道都通过了,才能说这个RAG应用"准备好上线了"。少任何一条,你都是在赌——赌模型不会突然发疯,赌用户不会因为慢而离开。

别赌,测。

编写使用方法
Markdown 格式 · Ctrl+Enter 确定
新建笔记
预览
数据表格
点击单元格编辑 · Tab 移动
A1fx
Sheet1
BIH1H2≡🔗</>
隐私提醒

取消
编辑工具
取消