第 29 期 | 测试图:我们该怎么自动化验收智能体的表现?

更新于 2026/4/17

🎯 本期学习目标

各位未来的顶级 AI 架构师们,欢迎回到我们的《LangGraph 多智能体专家课》。我是你们的导师。

咱们的「AI 万能内容创作机构 (AI Content Agency)」项目已经推进到了第 29 期。回顾前 28 期,我们从零开始,手搓了运筹帷幄的 Planner、掘地三尺的 Researcher、妙笔生花的 Writer,以及吹毛求疵的 Editor。整个 LangGraph 的流转如丝般顺滑,你看着终端里不断跳动的绿字,是不是觉得大功告成,准备打包上线,甚至已经开始幻想升职加薪了?

醒醒!

在传统软件工程中,写完代码不写测试,那叫“耍流氓”;而在大模型应用开发中,写完多智能体不写自动化评估,那叫**“埋地雷”**。大模型的输出具有非确定性(Non-deterministic),你昨天测着好好的,今天可能就给你生成一堆毫无逻辑的幻觉。你怎么证明你的 Researcher 真的找对了资料?你怎么保证你的 Writer 没有胡编乱造?

拍脑袋?凭感觉?肉眼看?这叫玄学,不叫工程。

今天,我们就来解决这个终极问题:如何对 LangGraph 多智能体工作流进行自动化验收?

通过本期学习,你将斩获以下核心技能:

  1. 思维破局:理解传统单元测试(assert a == b)在 Agent 时代的失效,掌握“LLM as a Judge”的核心理念。
  2. 金标准构建:学会在 AI Content Agency 项目中定义和构建“测试金标准(Golden Dataset)”。
  3. 引入 RAGAS 评估体系:熟练使用 RAGAS 框架,针对 Researcher 节点计算“准召率(Context Precision/Recall)”,针对 Writer 节点计算“忠实度与相关性(Faithfulness/Answer Relevance)”。
  4. 自动化测试流水线:编写脚本,将 LangGraph 运行与 RAGAS 评估无缝结合,实现一键自动化验收。

📖 原理解析

为什么不能用传统的单元测试?

在传统代码里,测试一个加法函数很简单:assert add(1, 1) == 2。 但在我们的 Agency 里,用户输入:“写一篇关于量子计算在金融领域应用的文章”。 Writer 生成的文章每次都不一样,字数不一样,句式不一样。你无法用 == 来断言。

因此,我们需要引入 Eval(评估) 的概念。业界目前最成熟的思路之一是利用 RAGAS (Retrieval Augmented Generation Assessment) 框架。虽然它名字里带 RAG,但它的核心指标完美契合我们的多智能体架构。

RAGAS 核心指标与 Agency 角色的映射

在我们的 AI Content Agency 中,我们其实可以把整个过程看作一个高级的 RAG 变体:

  1. Researcher 节点 = 检索器 (Retriever)。它负责去网上或本地知识库找资料。
  2. Writer/Editor 节点 = 生成器 (Generator)。它负责基于资料写文章并润色。

对应的,我们需要验收以下四个核心维度:

  • 维度 1:Context Precision (上下文精确率) - 考核 Researcher
    • 大白话:Researcher 找来的资料,是不是都是有用的?有没有塞一堆垃圾信息进来?
  • 维度 2:Context Recall (上下文召回率) - 考核 Researcher
    • 大白话:要回答用户的问题,必须的关键信息,Researcher 找全了吗?有没有漏掉核心知识点?
  • 维度 3:Faithfulness (忠实度) - 考核 Writer
    • 大白话:Writer 写的文章,是不是完全基于 Researcher 找来的资料?有没有自己瞎编乱造(幻觉)?
  • 维度 4:Answer Relevance (回答相关性) - 考核 Editor/最终输出
    • 大白话:最终交稿的文章,有没有真正解答 Planner 最初设定的目标和用户的需求?有没有答非所问?

自动化验收工作流 (Mermaid 图解)

为了实现自动化,我们需要构建一个测试脚本,它的运行逻辑如下:

sequenceDiagram
    participant TestScript as 自动化测试脚本
    participant Dataset as 金标准数据集 (Golden Dataset)
    participant LangGraph as AI Content Agency (Graph)
    participant RAGAS as RAGAS 评估引擎
    participant Report as 测试报告

    TestScript->>Dataset: 1. 加载测试用例 (Question & Ground Truth)
    loop 遍历每一个测试用例
        TestScript->>LangGraph: 2. 传入 Question 触发工作流
        activate LangGraph
        LangGraph-->>LangGraph: Planner 规划
        LangGraph-->>LangGraph: Researcher 检索 (产生 Contexts)
        LangGraph-->>LangGraph: Writer/Editor 生成 (产生 Answer)
        LangGraph-->>TestScript: 3. 返回最终 State (包含 Contexts 和 Answer)
        deactivate LangGraph
        TestScript->>TestScript: 4. 组装评估数据行
    end
    
    TestScript->>RAGAS: 5. 提交完整数据集进行批量评估 (LLM as a Judge)
    activate RAGAS
    RAGAS-->>RAGAS: 计算 Precision, Recall, Faithfulness, Relevance
    RAGAS-->>TestScript: 6. 返回评分矩阵
    deactivate RAGAS
    TestScript->>Report: 7. 生成并输出验收报告

看明白了吗?我们的测试脚本相当于一个“无情的监工”,拿着一本标准答案(金标准),不停地给 Agency 派发任务,然后把 Agency 产生的中间结果(Contexts)和最终结果(Answer)打包,丢给另一个更高级的 LLM(RAGAS 裁判)去打分。


💻 实战代码演练

接下来,我们用 Python 落地这个测试流水线。为了让大家能直接跑通这段代码,我用一个精简版的 Mock 函数代替了咱们前 28 期的庞大 Graph 代码,但接口和 State 结构是完全一致的。

准备工作

你需要安装以下依赖:

pip install ragas langchain-openai datasets langgraph

核心测试脚本 agency_evaluator.py

import os
from datasets import Dataset
from ragas import evaluate
from ragas.metrics import (
    faithfulness,
    answer_relevancy,
    context_precision,
    context_recall,
)
from langchain_openai import ChatOpenAI
from langchain_openai import OpenAIEmbeddings

# ==========================================
# 0. 环境变量配置 (请确保配置了你的 API KEY)
# ==========================================
os.environ["OPENAI_API_KEY"] = "sk-your-openai-api-key"

# ==========================================
# 1. 定义金标准数据集 (Golden Dataset)
# ==========================================
# 在实际工程中,这通常是一个手工打磨的 JSON/CSV 文件
# 包含了代表各种业务场景的极限测试用例
golden_dataset = [
    {
        "question": "请写一篇短文,介绍 LangGraph 的核心状态管理机制。",
        # ground_truth 是专家写的标准答案核心要点,用于计算 Recall
        "ground_truth": "LangGraph 通过 StateGraph 管理状态。它要求定义一个 TypedDict 作为 State。每个节点接收当前 State,并返回要更新的 State 字段。状态更新可以是覆盖,也可以是追加(通过 Annotated 和 operator)。"
    },
    {
        "question": "什么是大模型的幻觉?在我们的 Agency 项目中如何避免?",
        "ground_truth": "大模型幻觉指模型生成看似合理但实际错误或无根据的信息。在 Agency 项目中,通过引入 Researcher 节点进行 RAG 检索,并要求 Writer 节点严格基于检索到的 Context 进行创作,同时使用 Editor 节点进行事实核查来避免。"
    }
]

# ==========================================
# 2. 模拟我们前 28 期写好的 LangGraph Agency
# ==========================================
# 这里我们用一个 Mock 函数代替真实的 Graph 运行,
# 但请注意:它的输入输出结构与真实的 LangGraph State 完全一致!
def run_agency_graph(question: str) -> dict:
    """
    模拟 AI Content Agency 的执行过程
    真实场景下这里应该是: return app.invoke({"question": question})
    """
    print(f"🤖 [Agency Graph] 正在处理任务: {question}")
    
    # 模拟 Researcher 找到的上下文资料 (Contexts)
    if "LangGraph" in question:
        contexts = [
            "LangGraph 是 LangChain 推出的多智能体框架。",
            "在 LangGraph 中,StateGraph 是核心,通过 TypedDict 定义状态结构。",
            "节点函数接收 state 并返回状态的增量更新。"
        ]
        # 模拟 Writer/Editor 最终生成的文章 (Answer)
        answer = "LangGraph 的核心状态管理机制依赖于 StateGraph。开发者需要首先定义一个基于 TypedDict 的 State 结构。在图的运行过程中,每一个节点(Node)都会接收到当前全局的 State,执行自身的逻辑后,返回一个字典,用于更新全局 State。这种机制确保了多智能体之间能够稳定地共享和传递信息。"
    else:
        contexts = [
            "大模型幻觉是指模型在一本正经地胡说八道。",
            "通过检索增强生成(RAG)可以有效降低幻觉,因为模型有了外部知识参考。"
        ]
        answer = "大模型的幻觉是指模型生成了虚假的信息。在我们的 Agency 项目中,我们主要通过 RAG 技术来避免。Researcher 会先去寻找资料,然后 Writer 只根据资料写文章,这样就不会胡编乱造了。"
        
    return {
        "question": question,
        "contexts": contexts, # Researcher 的产出
        "answer": answer      # Editor 的最终产出
    }

# ==========================================
# 3. 自动化测试与评估流水线
# ==========================================
def run_evaluation_pipeline():
    print("🚀 开始执行 Agency 自动化验收测试...\n")
    
    evaluation_data = {
        "question": [],
        "answer": [],
        "contexts": [],
        "ground_truth": []
    }
    
    # Step 1: 遍历金标准,跑图收集数据
    for item in golden_dataset:
        question = item["question"]
        ground_truth = item["ground_truth"]
        
        # 跑图!获取 Agency 的真实表现
        final_state = run_agency_graph(question)
        
        # 组装 RAGAS 需要的数据格式
        evaluation_data["question"].append(question)
        evaluation_data["answer"].append(final_state["answer"])
        evaluation_data["contexts"].append(final_state["contexts"])
        evaluation_data["ground_truth"].append(ground_truth)
        
    # Step 2: 转换为 HuggingFace Dataset 格式
    dataset = Dataset.from_dict(evaluation_data)
    print("\n📊 数据收集完毕,开始调用 RAGAS 进行大模型打分 (LLM as a Judge)...")
    
    # Step 3: 配置评委大模型 (这里为了稳定,评委通常用 GPT-4o 级别)
    judge_llm = ChatOpenAI(model="gpt-4o")
    judge_embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
    
    # Step 4: 执行评估
    # 我们关注:忠实度(Writer)、回答相关性(Editor)、上下文精确率(Researcher)、上下文召回率(Researcher)
    result = evaluate(
        dataset=dataset,
        metrics=[
            faithfulness,
            answer_relevancy,
            context_precision,
            context_recall,
        ],
        llm=judge_llm,
        embeddings=judge_embeddings
    )
    
    # Step 5: 输出验收报告
    print("\n✅ 测试验收完成!最终评估报告如下:")
    print("=" * 50)
    # result 其实是一个字典,包含了各项指标的平均分
    for metric_name, score in result.items():
        print(f"🎯 {metric_name.capitalize().ljust(20)}: {score:.4f}")
    print("=" * 50)
    
    # 设定及格线断言 (这就是我们的 CI/CD 拦截逻辑)
    # 如果 Writer 写的文章幻觉太多(忠实度 < 0.8),测试直接挂掉!
    assert result["faithfulness"] >= 0.8, "❌ 测试失败:Writer 节点的忠实度低于 0.8,存在严重幻觉风险!"
    assert result["context_recall"] >= 0.7, "❌ 测试失败:Researcher 节点召回率不足,遗漏了关键信息!"
    
    print("🎉 恭喜!Agency 工作流通过了所有自动化验收指标!可以合入主分支!")

if __name__ == "__main__":
    run_evaluation_pipeline()

代码运行解析

当你运行这段代码时,你会看到它首先调用 run_agency_graph 获取了模拟的图状态。然后,RAGAS 会在后台使用 GPT-4o 作为评委,分析你传入的 contextsanswerground_truth。 最后输出的结果可能类似这样:

🎯 Faithfulness        : 0.9500  (说明 Writer 很乖,没有脱离上下文瞎编)
🎯 Answer_relevancy    : 0.9200  (说明 Editor 把关很好,紧扣用户问题)
🎯 Context_precision   : 0.8800  (说明 Researcher 找的资料信噪比很高)
🎯 Context_recall      : 0.8500  (说明 Researcher 基本找全了需要的知识点)

有了这个脚本,你以后每次修改了 Researcher 的 Prompt,或者调整了 Writer 的大模型参数,只需要跑一遍脚本。如果分数暴跌,你就知道你把代码改坏了。


坑与避坑指南

作为你们的导师,我见过太多团队在引入大模型自动化测试时踩进同一个泥坑里。以下是高阶视角的排错经验,价值千金,建议背诵:

1. 评委的“偏见”陷阱 (LLM Judge Bias)

坑: 你用 GPT-3.5 去生成文章,然后用 GPT-3.5 去当评委打分。你会发现分数出奇的高!因为同一个模型倾向于喜欢自己生成的文本风格。 避坑: 评委模型必须比生成模型高一个段位,或者至少是不同家族的。如果你的 Agency 用的是 Claude 3.5 Sonnet 生成,评估时尽量用 GPT-4o 作为 Judge。保持裁判的独立性和权威性。

2. 暴力塞 Context 导致的超时与破产

坑: 你的 Researcher 节点太猛了,一次性检索了 20 个网页,返回了 5 万字的 Context。你把这 5 万字连同问题一起塞进 RAGAS 评估库。结果?要么评估超时失败,要么月底看到 OpenAI 账单时心脏骤停。 避坑: 在将 State 传入测试流之前,做一个拦截器。限制 contexts 的 token 数量(例如截断前 5000 tokens)。评估的目的是看“核心链路”通不通,而不是在这个环节测试长文本极限。

3. 测试结果的“薛定谔状态” (Flaky Tests)

坑: 同样的测试代码,昨天跑 faithfulness 是 0.85(通过),今天跑变成了 0.78(失败),CI/CD 动不动就报警,团队对测试失去信任。大模型是非确定性的! 避坑:

  • 必须将评委模型(Judge LLM)的 temperature 设置为 0
  • 不要纠结于单次运行的绝对分数。在 CI/CD 中,金标准数据集至少要包含 20-50 个用例,取平均分进行断言(如代码中的 result["faithfulness"] >= 0.8)。大样本能有效平滑单次生成的方差。

4. 别把“测试”当“监控”

坑: 有些同学觉得 RAGAS 太好用了,把它部署到了生产环境,对用户的每一次线上请求都做一次 RAGAS 评估。结果系统延迟增加了 10 秒,用户全跑光了。 避坑: 记住本期的标题叫“自动化验收”。RAGAS 是离线测试工具,是在你发布新版本前跑的。线上的实时监控(我们下一期要讲的内容)需要更轻量级的方案(如 LangSmith 追踪),绝不能在线上用 LLM 实时打分阻塞主流程。


📝 本期小结

今天,我们为我们的「AI 万能内容创作机构」装上了最后一道防线——基于 RAGAS 的自动化验收流水线

我们打破了传统单元测试的思维局限,引入了“LLM as a Judge”的评估范式。通过提取 LangGraph 运行过程中的核心状态(contextsanswer),我们对 Researcher 的准召率(Precision/Recall)和 Writer/Editor 的生成质量(Faithfulness/Relevancy)进行了量化打分。

你现在不仅是一个会写 Prompt 和连线 Graph 的开发,你已经具备了高阶 AI 架构师的质控思维。你的代码终于不再是玄学,而是有数据支撑的工程结晶。

剧透预警: 图写完了,测试也跑通了。下一期,也就是我们整个系列课的大结局(第 30 期)!我们将拔剑出鞘,把这个庞大的 Agency 部署到生产环境,并接入 LangSmith 进行全链路的实时监控与人工介入(Human-in-the-loop)终极实战。

各位,把今天的测试脚本跑通,我们巅峰见!下课!