LangChain 效果评测:没有评测,优化就是在瞎调

个人主页: > for_ever_love__ <(欢迎各位大佬莅临😊)
其他栏目: > 大模型开发从0到1 <
其他栏目: > iOS项目总结大全 <
其他栏目: > 我想学python了 <
其他栏目: > iOS UI <

文章目录

LangChain 效果评测:没有评测,优化就是在瞎调

改了切分参数,感觉"好像好了一点";

换了 embedding 模型,觉得"似乎更准了"。

这些"感觉"是不可靠的,而且每一次改动都可能悄悄让另一类问题变差。

这篇讲怎么用最小的成本搭一套能用的评测:

从 20 条测试集开始,到能放进 CI 的回归测试。

一、为什么要先做评测

一个真实的场景:你发现"召回不全",把 chunk_size 从 500 降到 300。

召回率确实上去了,但每个 chunk 太短缺上下文,

生成质量反而下降 ------而你只测了召回率,没测最终答案质量,

于是高高兴兴上线了。

RAG 各环节是互相牵制的:

改动 可能变好 可能变差
chunk_size ↓ 召回更聚焦 上下文缺失,答案不完整
chunk_overlap ↑ 边界信息完整 冗余,重复召回
top_k ↑ 召回率↑ 噪声↑,成本↑
加 rerank 排序更准 延迟↑,成本↑

没有量化指标,你根本不知道自己是在优化还是在拆东墙补西墙。

二、评测什么:三层指标

层次 指标 回答的问题
检索层 召回率、MRR、NDCG "相关的文档找出来了吗、排得对吗"
生成层 忠实度、相关性 "答案是否基于上下文、是否答对"
系统层 延迟、成本、拒答率 "能不能上线"

三层都要看。 只看生成层,你不知道是检索还是生成的锅;

只看检索层,可能出现"检索全对但答案很烂"。

三、从 20 条测试集开始

不要一上来就想搞几百条,20~30 条就能开始,而且质量比数量重要。

python 复制代码
test_set = [
    {
        "question": "二线城市住宿标准是多少?",
        "ground_truth": "450 元",
        "relevant_chunk_ids": ["handbook.md#c17"],
    },
    {
        "question": "报销需要几个工作日?",
        "ground_truth": "审核 5 个工作日,到账 3 个工作日",
        "relevant_chunk_ids": ["handbook.md#c42"],
    },
    # ... 凑够 20~30 条
]

建测试集的三条原则:

  1. 覆盖典型场景 ------别全挑简单的。要包含:
    能直接答的、需要跨 chunk 综合的、库里根本没有的(测拒答);
  2. ground_truth 要人工写 ------别用 LLM 生成答案当标准答案,
    否则你在用模型评测模型,测出来的分数没有意义;
  3. 标注 relevant_chunk_ids ------否则只能测生成层,
    没法定位是检索的锅还是生成的锅。

四、检索层评测

python 复制代码
def eval_retrieval(retriever, test_set, k=5):
    recall_sum, mrr_sum = 0.0, 0.0

    for case in test_set:
        got_ids = [d.metadata["chunk_id"] for d in retriever.invoke(case["question"])[:k]]
        expect = set(case["relevant_chunk_ids"])

        # 召回率:相关的召回了多少
        hit = len(expect & set(got_ids))
        recall_sum += hit / len(expect) if expect else 0

        # MRR:第一个相关结果排在第几位
        for rank, cid in enumerate(got_ids, 1):
            if cid in expect:
                mrr_sum += 1 / rank
                break

    n = len(test_set)
    return {"recall@k": recall_sum / n, "mrr": mrr_sum / n}

两个指标的含义:

  • Recall@k :正确答案在 top-k 里的比例。
    这个数字决定重排有没有意义 ------如果 top-10 召回率只有 50%,
    重排再强也只能救回一半;
  • MRR(Mean Reciprocal Rank) :第一个正确结果排名的倒数均值。
    平均排第 1 → 1.0,平均排第 2 → 0.5。这个数字反映"排得准不准"。

用法:先看 top-10 的 Recall,再看 top-3 的 MRR。

前者低说明召回有问题(改切分/embedding),后者低说明排序有问题(上 rerank)。

五、生成层评测

生成层没有标准答案可比(答案是一段话),主流做法是用 LLM 当裁判。

5.1 自己实现一个 LLM 裁判

python 复制代码
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

judge = ChatOpenAI(model="gpt-4o-mini", temperature=0)

FAITHFULNESS = ChatPromptTemplate.from_template("""
判断【回答】中的内容是否全部能在【上下文】中找到依据。

评分标准:
2 = 全部内容都有依据,没有编造
1 = 大部分有依据,但有少量推断或补充
0 = 包含明显编造的内容,或上下文里根本没有答案却给出了答案

只输出一个数字(0/1/2),不要解释。

【上下文】
{context}

【回答】
{answer}
""")

def eval_faithfulness(chain, test_set):
    scores = []
    for case in test_set:
        # 需要同时拿到答案和它用的上下文
        out = chain_with_source.invoke(case["question"])
        s = (FAITHFULNESS | judge | StrOutputParser()).invoke({
            "context": format_docs(out["context"]),
            "answer": out["answer"],
        })
        scores.append(int(s.strip()))
    return sum(scores) / len(scores) / 2     # 归一化到 0~1

忠实度(Faithfulness)是最重要的一个指标 ------

它直接反映"模型有没有编"。RAG 最危险的失效就是一本正经地胡说。

5.2 拒答率:别忘了测"不该答的"

python 复制代码
UNANSWERABLE = [
    {"question": "公司明年的上市计划是什么?", "expect": "拒答"},
    {"question": "CEO 的私人手机号是多少?", "expect": "拒答"},
]

def eval_refusal(chain):
    ok = 0
    for case in UNANSWERABLE:
        ans = chain.invoke(case["question"])
        if "不知道" in ans or "没有找到" in ans or "无法回答" in ans:
            ok += 1
        else:
            print("未拒答:", case["question"], "->", ans[:60])
    return ok / len(UNANSWERABLE)

这个指标比忠实度更容易被忽略,但上线后用户投诉往往来自这里。

测试集里一定要有 3~5 条"库里没有"的问题。

5.3 用 RAGAs

不想自己写裁判,可以用现成的库:

bash 复制代码
pip install ragas
python 复制代码
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision
from datasets import Dataset

data = Dataset.from_list([
    {"question": q, "answer": a, "contexts": ctxs, "ground_truth": gt}
    for q, a, ctxs, gt in results
])

score = evaluate(data, metrics=[faithfulness, answer_relevancy, context_precision])
print(score)

RAGAs 提供四个指标:

faithfulness(忠实度)、answer_relevancy(答案相关性)、
context_precision(上下文精确率)、context_recall(上下文召回率)
。

优点是开箱即用,缺点是它需要调用 LLM(有成本),

而且对中文的支持取决于你配的 judge 模型 ------

用 GPT-4 系列判中文效果尚可,用小模型判中文会明显不准。

六、系统层指标

python 复制代码
import time

def eval_system(chain, test_set):
    latencies = []
    for case in test_set:
        t0 = time.time()
        chain.invoke(case["question"])
        latencies.append(time.time() - t0)
    latencies.sort()
    return {
        "p50": latencies[len(latencies) // 2],
        "p95": latencies[int(len(latencies) * 0.95)],
    }

看 p95 而不是平均值 ------用户投诉来自那些慢的 5%,

平均值会把它们掩盖掉。

成本就靠前面讲的 CostTracker 回调统计,

目标是算出"单次问答平均花多少钱"。

七、放进 CI:防止改崩

评测最大的价值不是"知道自己现在多少分",

而是每次改动后能立刻发现退步。

python 复制代码
# eval_run.py
BASELINE = {
    "recall@10": 0.85,
    "mrr@3": 0.62,
    "faithfulness": 0.90,
    "refusal_rate": 1.00,
    "p95_latency": 8.0,
}

TOLERANCE = 0.05   # 允许 5% 的波动

def main():
    current = run_all_metrics()
    failed = []
    for k, base in BASELINE.items():
        cur = current[k]
        if k == "p95_latency":
            ok = cur <= base * (1 + TOLERANCE)
        else:
            ok = cur >= base * (1 - TOLERANCE)
        print(f"{k}: {cur:.3f} (基线 {base}) {'✅' if ok else '❌'}")
        if not ok:
            failed.append(k)
    if failed:
        raise SystemExit(f"评测未通过: {failed}")

留 5% 的容差很重要 ------LLM 输出有随机性,

同一份代码跑两次分数也会有小幅波动,不留容差会天天误报。

放进 GitHub Actions:

yaml 复制代码
- name: Run RAG eval
  run: python eval_run.py

改动 PR 时自动跑一遍,退步就卡住。

八、常见误区

误区 问题 正确做法
用 LLM 生成标准答案 用模型评模型,分数虚高 人工写 ground_truth
测试集全是简单题 上线才发现复杂场景崩 覆盖难例和拒答题
只看总分 掩盖了局部退步 分层看 + 逐条看失败case
跑一次就下结论 LLM 有随机性 跑 2~3 次取平均
不留容差 CI 天天误报,最后被关掉 留 5%
只测检索 检索对了但答案很烂 三层都测

九、小结

  • 没有评测的优化就是瞎调,RAG 各环节互相牵制,改一处会影响另一处;
  • 三层指标:检索层(Recall@k、MRR)、生成层(忠实度、拒答率)、
    系统层(p95 延迟、成本)
    ;
  • 测试集 20~30 条就够开始 ,但必须人工写答案 、
    必须包含拒答类问题 、必须标注相关 chunk;
  • Recall 低 → 改切分/embedding;MRR 低 → 上 rerank;
  • 忠实度是生成层最重要的指标,拒答率最容易被忽略但最招投诉;
  • 放进 CI 并留 5% 容差,让"改崩了"在合并前被发现。

到这里,LangChain 从入门(LCEL、Model I/O)到 RAG(加载、切分、向量化、

检索、进阶)再到 Agent(工具调用、结构化输出、多 Agent、State、持久化)

最后是部署与评测,30 篇的完整链路就走完了。

相关推荐
得物技术1 小时前
动态规则的 Agent 实践:用确定性工程驾驭 LLM 的资产智能验收
人工智能·后端·llm
投资者内参1 小时前
高通CEO安蒙重磅发声:有AI厂商要求2028年在手机上跑千亿参数大模型,AI竞争从模型向终端延伸,手机换机潮将至,最看好智能眼镜
人工智能
水圈模拟1 小时前
从"数字花瓶"到"数字实验场"
人工智能·架构
狄龙1 小时前
UMI 式数据工作流:仿真采集 → 数据处理 → 训练使用(五)
人工智能
Solara1 小时前
库连续 35 天只写自己的日记:第 36 天的外采,进得了索引,进不了版本库
人工智能·程序员·ai编程
码农小旋风1 小时前
AI模型训练技术详解
人工智能
vivo互联网技术1 小时前
SSR-Merge:把 LoRA 合并从"参数算术"变成"信号路由" | ICML 2026
人工智能·算法·计算机视觉
Martina_03211 小时前
一张地图怎么生成可编辑3D地形?用6步检查高度图、道路约束与Mesh导出
图像处理·人工智能·平面·3d·ai·自然语言处理·游戏策划
武天旭1 小时前
AI安全--AI大模型的鲁棒性不足风险
人工智能