Skip to main content
智能体应用允许大型语言模型自行决定解决问题的下一步。这种灵活性非常强大,但模型的黑盒特性使得预测对智能体某一部分的调整会如何影响其他部分变得困难。为了构建可用于生产的智能体,彻底的测试是必不可少的。 有几种方法可以测试您的智能体:
  • 单元测试 通过使用内存中的模拟来单独锻炼您智能体的小而确定性的部分,以便您可以快速且确定性地断言确切的行为。
  • 集成测试 通过使用真实的网络调用来测试智能体,以确认组件协同工作、凭据和模式匹配,以及延迟可接受。
智能体应用往往更倾向于集成,因为它们将多个组件串联起来,并且必须处理由于大型语言模型非确定性特性导致的不可靠性。

单元测试

模拟聊天模型

对于不需要API调用的逻辑,您可以使用内存中的存根来模拟响应。 LangChain提供了GenericFakeChatModel用于模拟文本响应。它接受一个响应迭代器(AIMessages或字符串),并在每次调用时返回一个响应。它支持常规和流式使用。
from langchain_core.language_models.fake_chat_models import GenericFakeChatModel

model = GenericFakeChatModel(messages=iter([
    AIMessage(content="", tool_calls=[ToolCall(name="foo", args={"bar": "baz"}, id="call_1")]),
    "bar"
]))

model.invoke("hello")
# AIMessage(content='', ..., tool_calls=[{'name': 'foo', 'args': {'bar': 'baz'}, 'id': 'call_1', 'type': 'tool_call'}])
如果我们再次调用该模型,它将返回迭代器中的下一个项目:
model.invoke("hello, again!")
# AIMessage(content='bar', ...)

InMemorySaver Checkpointer

为了在测试期间启用持久性,您可以使用 InMemorySaver 检查点器。这允许您模拟多次回合来测试状态相关行为:
from langgraph.checkpoint.memory import InMemorySaver

agent = create_agent(
    model,
    tools=[],
    checkpointer=InMemorySaver()
)

# First invocation
agent.invoke(HumanMessage(content="I live in Sydney, Australia."))

# Second invocation: the first message is persisted (Sydney location), so the model returns GMT+10 time
agent.invoke(HumanMessage(content="What's my local time?"))

集成测试

许多智能体行为只有在使用真实的LLM时才会出现,例如智能体决定调用哪个工具、如何格式化响应,或者提示修改是否会影响整个执行轨迹。LangChain的agentevals包提供了专门为测试智能体轨迹与实时模型而设计的评估器。 AgentEvals 允许您通过执行 轨迹匹配 或使用 LLM 判定器,轻松评估您的智能体(包括工具调用的确切消息序列)的轨迹。

Trajectory match

为给定输入硬编码一个参考轨迹,并通过逐步比较来验证运行。非常适合测试定义良好的工作流程,其中您知道预期的行为。当您对应该调用哪些工具以及调用顺序有具体期望时使用。这种方法是确定性的、快速的且成本效益高,因为它不需要额外的LLM调用。

LLM-as-judge

使用大型语言模型(LLM)对智能体的执行轨迹进行定性验证。 “judge” LLM根据提示准则(可以包括参考轨迹)审查智能体的决策。更灵活,可以评估细微方面,如效率和适宜性,但需要调用LLM,且确定性较低。当您想评估智能体轨迹的整体质量和合理性,而不需要严格的工具调用或排序要求时使用。

安装 AgentEvals

pip install agentevals
或者,直接克隆 AgentEvals 仓库

轨迹匹配评估器

AgentEvals 提供了 create_trajectory_match_evaluator 函数,用于将您的智能体轨迹与参考轨迹进行匹配。有四种模式可供选择:
模式描述应用场景
strict按相同顺序匹配消息和工具调用测试特定序列(例如,在授权前查找策略)
unordered允许任何顺序的工具调用验证信息检索时顺序不重要
subset智能体只能调用参考工具(不允许额外工具)确保智能体不超过预期范围
superset智能体至少调用参考工具(允许额外工具)验证是否采取了所需的最小行动
strict 模式确保轨迹包含相同顺序的相同工具调用的相同消息,尽管它允许消息内容存在差异。这在需要强制执行特定操作顺序时很有用,例如在授权操作之前要求查找策略。
from langchain.agents import create_agent
from langchain.tools import tool
from langchain.messages import HumanMessage, AIMessage, ToolMessage
from agentevals.trajectory.match import create_trajectory_match_evaluator


@tool
def get_weather(city: str):
    """Get weather information for a city."""
    return f"It's 75 degrees and sunny in {city}."

agent = create_agent("openai:gpt-4o", tools=[get_weather])

evaluator = create_trajectory_match_evaluator(  
    trajectory_match_mode="strict",  
)  

def test_weather_tool_called_strict():
    result = agent.invoke({
        "messages": [HumanMessage(content="What's the weather in San Francisco?")]
    })

    reference_trajectory = [
        HumanMessage(content="What's the weather in San Francisco?"),
        AIMessage(content="", tool_calls=[
            {"id": "call_1", "name": "get_weather", "args": {"city": "San Francisco"}}
        ]),
        ToolMessage(content="It's 75 degrees and sunny in San Francisco.", tool_call_id="call_1"),
        AIMessage(content="The weather in San Francisco is 75 degrees and sunny."),
    ]

    evaluation = evaluator(
        outputs=result["messages"],
        reference_outputs=reference_trajectory
    )
    # {
    #     'key': 'trajectory_strict_match',
    #     'score': True,
    #     'comment': None,
    # }
    assert evaluation["score"] is True
unordered 模式允许以任何顺序调用相同的工具,这在您想验证是否检索到特定信息但不在乎顺序时很有帮助。例如,一个智能体可能需要检查一个城市的天气和事件,但顺序并不重要。
from langchain.agents import create_agent
from langchain.tools import tool
from langchain.messages import HumanMessage, AIMessage, ToolMessage
from agentevals.trajectory.match import create_trajectory_match_evaluator


@tool
def get_weather(city: str):
    """Get weather information for a city."""
    return f"It's 75 degrees and sunny in {city}."

@tool
def get_events(city: str):
    """Get events happening in a city."""
    return f"Concert at the park in {city} tonight."

agent = create_agent("openai:gpt-4o", tools=[get_weather, get_events])

evaluator = create_trajectory_match_evaluator(  
    trajectory_match_mode="unordered",  
)  

def test_multiple_tools_any_order():
    result = agent.invoke({
        "messages": [HumanMessage(content="What's happening in SF today?")]
    })

    # Reference shows tools called in different order than actual execution
    reference_trajectory = [
        HumanMessage(content="What's happening in SF today?"),
        AIMessage(content="", tool_calls=[
            {"id": "call_1", "name": "get_events", "args": {"city": "SF"}},
            {"id": "call_2", "name": "get_weather", "args": {"city": "SF"}},
        ]),
        ToolMessage(content="Concert at the park in SF tonight.", tool_call_id="call_1"),
        ToolMessage(content="It's 75 degrees and sunny in SF.", tool_call_id="call_2"),
        AIMessage(content="Today in SF: 75 degrees and sunny with a concert at the park tonight."),
    ]

    evaluation = evaluator(
        outputs=result["messages"],
        reference_outputs=reference_trajectory,
    )
    # {
    #     'key': 'trajectory_unordered_match',
    #     'score': True,
    # }
    assert evaluation["score"] is True
supersetsubset 模式匹配部分轨迹。superset 模式验证智能体至少调用了参考轨迹中的工具,允许调用更多工具。subset 模式确保智能体没有调用参考轨迹之外的任何工具。
from langchain.agents import create_agent
from langchain.tools import tool
from langchain.messages import HumanMessage, AIMessage, ToolMessage
from agentevals.trajectory.match import create_trajectory_match_evaluator


@tool
def get_weather(city: str):
    """Get weather information for a city."""
    return f"It's 75 degrees and sunny in {city}."

@tool
def get_detailed_forecast(city: str):
    """Get detailed weather forecast for a city."""
    return f"Detailed forecast for {city}: sunny all week."

agent = create_agent("openai:gpt-4o", tools=[get_weather, get_detailed_forecast])

evaluator = create_trajectory_match_evaluator(  
    trajectory_match_mode="superset",  
)  

def test_agent_calls_required_tools_plus_extra():
    result = agent.invoke({
        "messages": [HumanMessage(content="What's the weather in Boston?")]
    })

    # Reference only requires get_weather, but agent may call additional tools
    reference_trajectory = [
        HumanMessage(content="What's the weather in Boston?"),
        AIMessage(content="", tool_calls=[
            {"id": "call_1", "name": "get_weather", "args": {"city": "Boston"}},
        ]),
        ToolMessage(content="It's 75 degrees and sunny in Boston.", tool_call_id="call_1"),
        AIMessage(content="The weather in Boston is 75 degrees and sunny."),
    ]

    evaluation = evaluator(
        outputs=result["messages"],
        reference_outputs=reference_trajectory,
    )
    # {
    #     'key': 'trajectory_superset_match',
    #     'score': True,
    #     'comment': None,
    # }
    assert evaluation["score"] is True
您还可以设置 tool_args_match_mode 属性和/或 tool_args_match_overrides 来自定义评估器如何考虑实际轨迹中工具调用与参考之间的相等性。默认情况下,只有对同一工具具有相同参数的工具调用才会被视为相等。有关更多详细信息,请访问 仓库

LLM-as-Judge 评估器

您还可以使用大型语言模型(LLM)通过 create_trajectory_llm_as_judge 函数评估智能体的执行路径。与轨迹匹配评估器不同,它不需要参考轨迹,但如果有的话,也可以提供。
from langchain.agents import create_agent
from langchain.tools import tool
from langchain.messages import HumanMessage, AIMessage, ToolMessage
from agentevals.trajectory.llm import create_trajectory_llm_as_judge, TRAJECTORY_ACCURACY_PROMPT


@tool
def get_weather(city: str):
    """Get weather information for a city."""
    return f"It's 75 degrees and sunny in {city}."

agent = create_agent("openai:gpt-4o", tools=[get_weather])

evaluator = create_trajectory_llm_as_judge(  
    model="openai:o3-mini",  
    prompt=TRAJECTORY_ACCURACY_PROMPT,  
)  

def test_trajectory_quality():
    result = agent.invoke({
        "messages": [HumanMessage(content="What's the weather in Seattle?")]
    })

    evaluation = evaluator(
        outputs=result["messages"],
    )
    # {
    #     'key': 'trajectory_accuracy',
    #     'score': True,
    #     'comment': 'The provided agent trajectory is reasonable...'
    # }
    assert evaluation["score"] is True
如果您有一个参考轨迹,您可以在提示中添加一个额外的变量并传入参考轨迹。下面,我们使用预构建的 TRAJECTORY_ACCURACY_PROMPT_WITH_REFERENCE 提示并配置 reference_outputs 变量:
evaluator = create_trajectory_llm_as_judge(
    model="openai:o3-mini",
    prompt=TRAJECTORY_ACCURACY_PROMPT_WITH_REFERENCE,
)
evaluation = judge_with_reference(
    outputs=result["messages"],
    reference_outputs=reference_trajectory,
)
为了更灵活地配置LLM评估轨迹的方式,请访问仓库

异步支持

所有 agentevals 评估器都支持 Python asyncio。对于使用工厂函数的评估器,通过在函数名后添加 async,可以获得异步版本,即在 create_ 后面添加 async
from agentevals.trajectory.llm import create_async_trajectory_llm_as_judge, TRAJECTORY_ACCURACY_PROMPT
from agentevals.trajectory.match import create_async_trajectory_match_evaluator

async_judge = create_async_trajectory_llm_as_judge(
    model="openai:o3-mini",
    prompt=TRAJECTORY_ACCURACY_PROMPT,
)

async_evaluator = create_async_trajectory_match_evaluator(
    trajectory_match_mode="strict",
)

async def test_async_evaluation():
    result = await agent.ainvoke({
        "messages": [HumanMessage(content="What's the weather?")]
    })

    evaluation = await async_judge(outputs=result["messages"])
    assert evaluation["score"] is True

LangSmith 集成

为了跟踪随时间进行的实验,您可以将评估器结果记录到LangSmith,这是一个用于构建生产级LLM应用程序的平台,包括跟踪、评估和实验工具。 首先,通过设置所需的环境变量来设置 LangSmith:
export LANGSMITH_API_KEY="your_langsmith_api_key"
export LANGSMITH_TRACING="true"
LangSmith提供了两种主要的运行评估方法:pytest集成和evaluate函数。
import pytest
from langsmith import testing as t
from agentevals.trajectory.llm import create_trajectory_llm_as_judge, TRAJECTORY_ACCURACY_PROMPT

trajectory_evaluator = create_trajectory_llm_as_judge(
    model="openai:o3-mini",
    prompt=TRAJECTORY_ACCURACY_PROMPT,
)

@pytest.mark.langsmith
def test_trajectory_accuracy():
    result = agent.invoke({
        "messages": [HumanMessage(content="What's the weather in SF?")]
    })

    reference_trajectory = [
        HumanMessage(content="What's the weather in SF?"),
        AIMessage(content="", tool_calls=[
            {"id": "call_1", "name": "get_weather", "args": {"city": "SF"}},
        ]),
        ToolMessage(content="It's 75 degrees and sunny in SF.", tool_call_id="call_1"),
        AIMessage(content="The weather in SF is 75 degrees and sunny."),
    ]

    # Log inputs, outputs, and reference outputs to LangSmith
    t.log_inputs({})
    t.log_outputs({"messages": result["messages"]})
    t.log_reference_outputs({"messages": reference_trajectory})

    trajectory_evaluator(
        outputs=result["messages"],
        reference_outputs=reference_trajectory
    )
运行pytest进行评估:
pytest test_trajectory.py --langsmith-output
结果将自动记录到LangSmith。
或者,您可以在LangSmith中创建一个数据集并使用 evaluate 函数:
from langsmith import Client
from agentevals.trajectory.llm import create_trajectory_llm_as_judge, TRAJECTORY_ACCURACY_PROMPT

client = Client()

trajectory_evaluator = create_trajectory_llm_as_judge(
    model="openai:o3-mini",
    prompt=TRAJECTORY_ACCURACY_PROMPT,
)

def run_agent(inputs):
    """Your agent function that returns trajectory messages."""
    return agent.invoke(inputs)["messages"]

experiment_results = client.evaluate(
    run_agent,
    data="your_dataset_name",
    evaluators=[trajectory_evaluator]
)
结果将自动记录到LangSmith。
要了解更多关于评估您的智能体的信息,请参阅LangSmith 文档

记录与回放HTTP调用

集成调用真实LLM API的测试可能很慢且成本高昂,尤其是在CI/CD管道中频繁运行时。我们建议使用一个库来记录HTTP请求和响应,然后在后续运行中重新播放它们,而不进行实际的网络调用。 您可以使用 vcrpy 来实现这一点。如果您正在使用 pytest,则 pytest-recording 插件 提供了一种简单的方法,通过最小配置来启用此功能。请求/响应记录在磁带上,然后在后续运行中用于模拟真实的网络调用。 设置您的 conftest.py 文件以从磁带上过滤敏感信息:
conftest.py
import pytest

@pytest.fixture(scope="session")
def vcr_config():
    return {
        "filter_headers": [
            ("authorization", "XXXX"),
            ("x-api-key", "XXXX"),
            # ... other headers you want to mask
        ],
        "filter_query_parameters": [
            ("api_key", "XXXX"),
            ("key", "XXXX"),
        ],
    }
然后配置您的项目以识别 vcr 标记:
[pytest]
markers =
    vcr: record/replay HTTP via VCR
addopts = --record-mode=once
第一次运行时,--record-mode=once 选项会记录 HTTP 交互,并在后续运行中回放它们。
现在,只需用 vcr 标记装饰您的测试即可:
@pytest.mark.vcr()
def test_agent_trajectory():
    # ...
第一次运行此测试时,您的智能体会进行真实的网络调用,pytest将在tests/cassettes目录下生成一个cassette文件test_agent_trajectory.yaml。后续运行将使用该cassette来模拟真实网络调用,前提是智能体的请求没有从上次运行中更改。如果它们确实更改了,测试将失败,您需要删除cassette并重新运行测试以记录新的交互。
当您修改提示、添加新工具或更改预期轨迹时,您保存的磁带将过时,您现有的测试将失败。您应该删除相应的磁带文件并重新运行测试以记录新的交互。