首页 / 文章

生成式人工智能与智能体的评估:CI环境中的单轮测试与多轮测试

比较基于轨迹的单轮评估与由模拟器驱动的多轮评估,涵盖黄金标准、大语言模型评分员、智能体指标、持续集成关卡以及七种工作实践。

发布者:REACTAPP.TOP阅读约 8 分钟1800 词

来源: End-to-End Evals for Gen AI and Agentic AI: A Practitioner’s Guide to Single-Turn and Multi-Turn Evaluation

“我的提示词能用”并非工程标准。传统的单元测试结果要么通过要么失败,具有确定性,而生成式系统则会产生开放性、概率性的答案。RAG流程可能大部分正确,某个智能体完成任务的方式在纸面上看似成功,但实际上却存在风险。正因如此,与凭直觉或满屏“我觉得没问题”的聊天记录相比,评估机制如今已成为生成式AI和智能体系统严肃的机器学习工程基础。下文将介绍需要测量的内容、单轮评估与多轮评估的差异、各自的优缺点,以及适用于CI流程的Python实现方式。

为何评估不可或缺

大语言模型应用的三个特性使其评估成为必要:

  1. 非确定性。相同的提示在不同运行、模型版本或温度值下可能会产生不同的结果。
  2. 智能体中的累积故障。普通聊天机器人只有一次出错的机会,而智能体则需要依次执行10到50次模型调用、工具调用和决策。每一步的轻微错误都会逐渐累积,最终导致频繁的整体故障。
  3. 隐性性能下降。提示的微调、模型的升级或向量存储的重新索引都可能降低质量,却不会引发任何异常提示。

评估机制能将“感觉更好”这样的主观评价转化为可随时间追踪的数值,用于控制部署时机并比较不同版本的性能。

两种评估形式

几乎所有的评估框架,包括DeepEval、Ragas、LangSmith、Braintrust和OpenAI Evals,都采用以下两种模式中的一种。

单次遍历、基于轨迹

流程很简单:获取每个样本,调用应用程序一次,然后对轨迹进行评分。

Open a test run with trace-level metrics
  for each golden:
    yield golden
    call app with golden.input
    capture trace
    build a test case from the trace
    score test case with metrics
  upload test run (traces + scores)

黄金样本是一种带有标注的示例:包括输入内容、模型应生成的答案,以及通常所需的上下文或工具调用信息。运行轨迹则记录了单次运行的完整过程,涵盖检索了哪些片段、调用了哪些工具、其中的推理逻辑、延迟时间与token数量,而不仅仅是最终输出文本。通过评估这些轨迹而非仅答案本身,就能精准定位出故障所在环节——是信息检索、内容生成还是中途的某个工具调用出了问题?

多轮、基于模拟器的模式

这对智能体和聊天助手而言是更困难且更具现实意义的测试场景。

def simulate(conversational goldens)
  for each golden:
    loop until expected outcome OR max turns:
      simulator model generates a user turn
      ask app for next reply
      capture assistant turn
    build a ConversationalTestCase from the full transcript
  evaluate(test cases, metrics)   # metrics run concurrently
    -> pass/fail + reason per metric

这里没有固定的输入输出对。一个模拟模型会扮演具有特定目标的用户,比如取消订阅或为某笔订单申请退款,并像真人一样根据助手的回复调整自己的消息内容。该系统的评价标准是整个交互过程:它是否始终围绕任务展开?在多轮对话中是否正确使用了工具?最终是否实现了目标?又是否在三轮对话后就编造出了政策细节?

智能体、多步骤助手以及任何具备记忆或状态功能的系统都需要这种评估方式。单轮评估无法发现智能体在第四轮出现自相矛盾的情况,也无法检测到其在循环中多次调用同一工具的行为。

选择评估指标

评估指标可分为四类,将它们结合起来使用更为明智,因为没有任何一种指标能够完整反映实际情况。

一个实用的心智模型:单轮指标衡量的是瞬时状态,而智能体指标衡量的是发展轨迹。

以大语言模型作为评判者

目前大多数无需参考数据的指标都是通过“以大语言模型作为评判者”的方式来工作的。一个强大的模型——通常是与被测试模型不同且能力更强的模型——会根据评分标准对输出结果进行打分,并解释其评分依据。

from dataclasses import dataclass

JUDGE_PROMPT = """
You are evaluating an AI assistant's response for FAITHFULNESS.
A response is faithful if every factual claim it makes is supported
by the provided context. It is unfaithful if it adds unsupported
claims, contradicts the context, or fabricates details.
Context:
{context}
Response:
{response}
Return JSON: {{"score": <0.0-1.0>, "reason": "<short explanation>"}}
"""

@dataclass
class TestCase:
    input: str
    actual_output: str
    retrieval_context: list[str]

def faithfulness_metric(test_case: TestCase, judge_llm) -> dict:
    prompt = JUDGE_PROMPT.format(
        context="\n".join(test_case.retrieval_context),
        response=test_case.actual_output,
    )
    result = judge_llm.generate(prompt, response_format="json")
    return {"score": result["score"], "reason": result["reason"], "passed": result["score"] >= 0.7}

评分者也存在一些问题:

  • 自我偏好偏差。模型往往会更宽容地评价自己的输出或自己风格下的输出。尽可能使用其他模型系列的评分者。
  • 提示词敏感性。评分标准措辞的微小变化都会影响分数。应像管理代码一样对评分提示词进行版本控制。
  • 成本与延迟。使用评分者也需要花费资金。在预算有限的情况下,先测试部分样本,不必对所有内容进行评分。
  • 长度与冗长性偏差。无论答案是否正确,评分者都倾向于给更长、更自信的回答更高的分。应定期用少量人工标注的数据集进行校准。

实际应用中的单轮评分

一个理想的黄金数据集甚至可以只是一份包含输入和预期输出的字典列表。

goldens = [
    {
        "input": "What's our refund policy for digital products?",
        "expected_output": "Digital products are refundable within 14 days if unused.",
        "expected_tools": [],
    },
    {
        "input": "Cancel order #48213",
        "expected_output": None,  # open-ended, judged not matched
        "expected_tools": ["lookup_order", "cancel_order"],
    },
]

运行器会按照上述流程操作:启动一次测试运行,遍历每个黄金用例,调用应用程序,捕获跟踪信息,进行评分,最后上传结果。

from deepeval import evaluate
from deepeval.metrics import FaithfulnessMetric, AnswerRelevancyMetric
from deepeval.test_case import LLMTestCase

def run_eval_suite(goldens, app_callback, metrics):
    test_cases = []
    for golden in goldens:
        trace = app_callback(golden["input"])   # your traced LLM/RAG app
        test_case = LLMTestCase(
            input=golden["input"],
            actual_output=trace.final_output,
            retrieval_context=trace.retrieved_chunks,
            expected_output=golden.get("expected_output"),
        )
        test_cases.append(test_case)
    results = evaluate(test_cases, metrics)  # runs metrics concurrently
    return results
metrics = [FaithfulnessMetric(threshold=0.7), AnswerRelevancyMetric(threshold=0.7)]
results = run_eval_suite(goldens, my_rag_app, metrics)

断言将分数转换为阈值。

def assert_eval_gate(results, min_pass_rate=0.9):
    passed = sum(1 for r in results if r.success)
    pass_rate = passed / len(results)
    assert pass_rate >= min_pass_rate, f"Eval pass rate {pass_rate:.2%}
    below gate {min_pass_rate:.0%}"

像集成 pytest 那样将其接入 PR 流水线。如果提示词或检索方式的变更导致准确性低于阈值,就应阻止合并,绝不能让有问题的版本悄悄发布。

实际应用中的多轮对话

模拟器接收的并非固定输入,而是用户资料和目标,以此来引导对话进程。

class ConversationSimulator:
    def __init__(self, simulator_model, max_turns=8):
        self.simulator_model = simulator_model
        self.max_turns = max_turns

    def simulate(self, golden, chatbot_callback):
        transcript = []
        user_turn = self.simulator_model.generate_opening(golden["scenario"])
        for _ in range(self.max_turns):
            transcript.append({"role": "user", "content": user_turn})
            assistant_turn = chatbot_callback(transcript)
            transcript.append({"role": "assistant", "content": assistant_turn})
            if self.simulator_model.is_goal_met(golden["expected_outcome"], transcript):
                break
            user_turn = self.simulator_model.generate_next_turn(transcript, golden)
        return transcript

随后会使用代理度量标准对生成的对话进行评分。

from deepeval.test_case import ConversationalTestCase, Turn
from deepeval.metrics import (
    TaskCompletionMetric,
    ToolCorrectnessMetric,
    ConversationCompletenessMetric,
)

def build_conversational_test_case(transcript, tools_called):
    turns = [Turn(role=t["role"], content=t["content"]) for t in transcript]
    return ConversationalTestCase(turns=turns, tools_called=tools_called)
    metrics = [
    TaskCompletionMetric(threshold=0.8),          # did it accomplish the user's goal?
    ToolCorrectnessMetric(threshold=1.0),          # right tools, right args, right order
    ConversationCompletenessMetric(threshold=0.7), # did it address everything raised?
    ]
    test_case = build_conversational_test_case(transcript, tools_called=agent_trace.tool_calls)
    result = evaluate([test_case], metrics)

典型的智能体级检查包括:

  • 工具调用正确性:使用正确的工具,以正确的参数顺序进行调用,而不仅仅是“调用了某个工具”。
  • 步骤效率与循环检测:反复调用同一工具却毫无进展。
  • 角色与规则遵循:在整个对话过程中始终遵守既定的角色定位和约束,而不仅限于开场环节。
  • 错误恢复:在工具调用失败后进行合理的重试,而非编造结果。
  • 上下文延续:记住用户三轮之前所说的话。

权衡利弊

单轮对话 多轮对话
成本 每个黄金轮次仅一次调用,成本低且速度快
每次运行都是一次多轮对话,涉及多次交互与各项指标 数据集 利用日志或合成数据生成的庞大标准集较为容易获取 模拟器能够处理那些没人会手动编写的极端情况与恶意用户行为 信号特征 能清晰区分检索错误与生成错误,几乎具有确定性,便于用于持续集成阶段的筛选 能够捕捉导致实际问题的累积性故障,并对包括工具、编排机制、系统状态及约束规则在内的整个系统进行测试 弱点 无法检测到跨轮次出现的偏差、矛盾及记忆丢失问题,还容易导致对标准集的过拟合 由于模拟器加入了随机性,方差较大;故障可能从第2轮开始却要等到第6轮才显现,因此调试更为困难;此外长文本也会给评估带来困扰

由于多轮输出结果存在噪声,需通过统计方法对其进行分析。运行该套程序N次,关注数据分布而非单一得分。

经验证有效的做法

  1. 从30到100个干净、优质的样本开始。 即使只有1,000个样本,若其中混有噪声,其效果也远不如50个整理有序的样本,因为错误的标签会干扰有效信息。
  2. 在RAG系统中分别评估检索和生成能力。 准确性与上下文关联度反映的是不同问题,将二者混为一谈会掩盖真正的缺陷。
  3. 使用更强大的独立模型进行判断,并定期将其得分与人工评估结果对比。即便每月仅检查20到30个样本,也能发现评估标准的偏差。
  • 追踪所有环节。评分应基于捕获的轨迹数据(获取的文档、工具调用记录、中间处理步骤),而不仅仅是最终输出文本。正因如此,捕获轨迹的步骤必须先于测试用例的构建。
  • 在部署前模拟各种场景。在产品上线之前,就要让系统与真实的用户角色及对抗性需求进行交互测试,比如愤怒的用户、模糊的请求或政策上的边缘情况,而非在问题发生之后才进行处理。
  • 将评估结果视为动态资产。每次修改提示词、升级模型或重建索引后,都应重新运行完整的测试套件,并在持续集成流程中设置通过率阈值,就像设置代码覆盖率阈值一样。
  • 关注随时间变化的分布情况。单次评估结果仅属于样本数据,并非真实标准。应重复执行关键测试套件并跟踪其变化趋势。
  • 核心要点

    生成式 AI 的评估最初只是附注,不过是演示之前有人运行的一个笔记本单元格而已。而代理系统早已超越了这种形式。一旦软件能够多次行动、调用工具并在不同轮次之间保持状态,评估就不再是质量检测,而是了解系统实际行为方式的核心手段。

    基于轨迹的单轮评估能够快速、低成本地检查输出质量。由模拟器驱动的多轮评估则能提供关于代理在整个交互过程中是否按预期运行的可靠判断,对于大多数实际应用的代理而言,这种判断最为重要。应同时构建这两种评估方式并实现自动化,但不可盲目信任任何一种,还需由人工对评估结果进行审核。