> ## Documentation Index
> Fetch the complete documentation index at: https://langchain.idochub.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# 测试

<Warning>
  **Alpha 版本提示：** 本文档涵盖的是 **v1-alpha** 版本。内容尚不完整，且可能随时变更。

  如需查阅最新稳定版本，请参阅 v0 版本的 [LangChain Python 文档](https://python.langchain.com/docs/introduction/) 或 [LangChain JavaScript 文档](https://js.langchain.com/docs/introduction/)。
</Warning>

智能体应用允许大语言模型（LLM）自主决定解决问题所需的下一步操作。这种灵活性非常强大，但由于模型的黑箱特性，很难预测对智能体某一部分的微小调整将如何影响其他部分。要构建可用于生产的智能体，全面的测试至关重要。

测试智能体有几种方法：

* [单元测试](#unit-testing) 使用内存中的模拟对象独立测试智能体中的小而确定性的部分，以便快速、确定性地断言精确行为。

* [集成测试](#integration-testing) 使用真实的网络调用测试智能体，以确认各组件协同工作、凭证和模式匹配，且延迟在可接受范围内。

由于智能体应用通常将多个组件串联在一起，并且必须处理由LLM非确定性引起的不稳定性，因此它们更倾向于依赖集成测试。

## 单元测试

### 模拟聊天模型

对于不需要API调用的逻辑，您可以使用内存中的存根来模拟响应。

LangChain 提供了 [`GenericFakeChatModel`](https://python.langchain.com/api_reference/core/language_models/langchain_core.language_models.fake_chat_models.GenericFakeChatModel.html) 用于模拟文本响应。它接受一个响应迭代器（AIMessages 或字符串），每次调用返回一个响应。它同时支持常规和流式使用。

```python theme={null}
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'}])
```

如果再次调用模型，它将返回迭代器中的下一个项目：

```python theme={null}
model.invoke("hello, again!")
# AIMessage(content='bar', ...)
```

### InMemorySaver 检查点器

为了在测试期间启用持久化，您可以使用 `InMemorySaver` 检查点器。这允许您模拟多轮对话以测试依赖状态的行为：

```python theme={null}
from langgraph.checkpoint.memory import InMemorySaver

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

# 第一次调用
agent.invoke(HumanMessage(content="我住在澳大利亚悉尼。"))

# 第二次调用：第一条消息已持久化（悉尼位置），因此模型返回 GMT+10 时间
agent.invoke(HumanMessage(content="我的当地时间是几点？"))
```

## 集成测试

许多智能体行为只有在使用真实LLM时才会显现，例如智能体决定调用哪个工具、如何格式化响应，或提示词修改是否会影响整个执行轨迹。LangChain 的 [`agentevals`](https://github.com/langchain-ai/agentevals) 包提供了专门设计用于使用实时模型测试智能体轨迹的评估器。

AgentEvals 允许您通过执行**轨迹匹配**或使用**LLM裁判**轻松评估智能体的轨迹（确切的消息序列，包括工具调用）：

<Card title="轨迹匹配" icon="equals" arrow="true" href="#trajectory-match-evaluator">
  为给定输入硬编码一个参考轨迹，并通过逐步比较验证运行结果。

  适用于测试定义明确的工作流，您知道预期的行为。当您对应该调用哪些工具及其顺序有具体期望时使用。此方法是确定性的、快速的且成本效益高，因为它不需要额外的LLM调用。
</Card>

<Card title="LLM作为裁判" icon="gavel" arrow="true" href="#llm-as-judge-evaluator">
  使用LLM定性验证智能体的执行轨迹。“裁判”LLM根据提示评分标准（可以包括参考轨迹）审查智能体的决策。

  更灵活，可以评估效率和适当性等细微方面，但需要LLM调用且确定性较低。当您想评估智能体轨迹的整体质量和合理性，而无需严格要求工具调用或顺序时使用。
</Card>

### 安装 AgentEvals

```bash theme={null}
pip install agentevals
```

或者，直接克隆 [AgentEvals 仓库](https://github.com/langchain-ai/agentevals)。

### 轨迹匹配评估器

AgentEvals 提供 `create_trajectory_match_evaluator` 函数，用于将智能体的轨迹与参考轨迹进行匹配。有四种模式可供选择：

| 模式          | 描述                      | 使用场景                 |
| ----------- | ----------------------- | -------------------- |
| `strict`    | 消息和工具调用完全按相同顺序匹配        | 测试特定序列（例如，策略查找在授权之前） |
| `unordered` | 允许相同工具调用以任意顺序出现         | 验证信息检索时顺序无关紧要的情况     |
| `subset`    | 智能体仅调用参考轨迹中的工具（无额外工具）   | 确保智能体未超出预期范围         |
| `superset`  | 智能体至少调用参考轨迹中的工具（允许额外工具） | 验证是否采取了所需的最低限度操作     |

<Accordion title="严格匹配">
  `strict` 模式确保轨迹包含相同顺序的相同消息和相同工具调用，但允许消息内容有所不同。当您需要强制执行特定操作序列时（例如要求在授权前进行策略查找），这非常有用。

  ```python highlight={13-15} theme={null}
  from langchain.agents import create_agent
  from langchain_core.tools import tool
  from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
  from agentevals.trajectory.match import create_trajectory_match_evaluator

  @tool
  def get_weather(city: str):
      """获取城市天气信息。"""
      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="旧金山的天气怎么样？")]
      })

      reference_trajectory = [
          HumanMessage(content="旧金山的天气怎么样？"),
          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="旧金山的天气是75度，晴天。"),
      ]

      evaluation = evaluator(
          outputs=result["messages"],
          reference_outputs=reference_trajectory
      )
      # {
      #     'key': 'trajectory_strict_match',
      #     'score': True,
      #     'comment': None,
      # }
      assert evaluation["score"] is True
  ```
</Accordion>

<Accordion title="无序匹配">
  `unordered` 模式允许以任意顺序调用相同的工具，当您想验证特定信息是否被检索但不关心顺序时非常有用。例如，智能体可能需要同时检查城市的天气和活动，但顺序无关紧要。

  ```python highlight={18-20} theme={null}
  from langchain.agents import create_agent
  from langchain_core.tools import tool
  from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
  from agentevals.trajectory.match import create_trajectory_match_evaluator

  @tool
  def get_weather(city: str):
      """获取城市天气信息。"""
      return f"It's 75 degrees and sunny in {city}."

  @tool
  def get_events(city: str):
      """获取城市中正在发生的活动。"""
      return f"今晚在{city}的公园有音乐会。"

  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="今天旧金山有什么活动？")]
      })

      # 参考轨迹显示工具调用顺序与实际执行顺序不同
      reference_trajectory = [
          HumanMessage(content="今天旧金山有什么活动？"),
          AIMessage(content="", tool_calls=[
              {"id": "call_1", "name": "get_events", "args": {"city": "SF"}},
              {"id": "call_2", "name": "get_weather", "args": {"city": "SF"}},
          ]),
          ToolMessage(content="今晚在旧金山的公园有音乐会。", tool_call_id="call_1"),
          ToolMessage(content="旧金山75度，晴天。", tool_call_id="call_2"),
          AIMessage(content="今天在旧金山：75度晴天，晚上公园有音乐会。"),
      ]

      evaluation = evaluator(
          outputs=result["messages"],
          reference_outputs=reference_trajectory,
      )
      # {
      #     'key': 'trajectory_unordered_match',
      #     'score': True,
      # }
      assert evaluation["score"] is True
  ```
</Accordion>

<Accordion title="子集和超集匹配">
  `superset` 和 `subset` 模式匹配部分轨迹。`superset` 模式验证智能体至少调用了参考轨迹中的工具，允许额外的工具调用。`subset` 模式确保智能体未调用参考轨迹之外的任何工具。

  ```python highlight={18-20} theme={null}
  from langchain.agents import create_agent
  from langchain_core.tools import tool
  from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
  from agentevals.trajectory.match import create_trajectory_match_evaluator

  @tool
  def get_weather(city: str):
      """获取城市天气信息。"""
      return f"It's 75 degrees and sunny in {city}."

  @tool
  def get_detailed_forecast(city: str):
      """获取城市的详细天气预报。"""
      return f"{city}的详细预报：整周晴天。"

  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="波士顿的天气怎么样？")]
      })

      # 参考轨迹仅要求 get_weather，但智能体可能会调用其他工具
      reference_trajectory = [
          HumanMessage(content="波士顿的天气怎么样？"),
          AIMessage(content="", tool_calls=[
              {"id": "call_1", "name": "get_weather", "args": {"city": "Boston"}},
          ]),
          ToolMessage(content="波士顿75度，晴天。", tool_call_id="call_1"),
          AIMessage(content="波士顿的天气是75度，晴天。"),
      ]

      evaluation = evaluator(
          outputs=result["messages"],
          reference_outputs=reference_trajectory,
      )
      # {
      #     'key': 'trajectory_superset_match',
      #     'score': True,
      #     'comment': None,
      # }
      assert evaluation["score"] is True
  ```
</Accordion>

<Info>
  您还可以设置 `tool_args_match_mode` 属性和/或 `tool_args_match_overrides` 来自定义评估器如何考虑实际轨迹与参考轨迹中工具调用的相等性。默认情况下，仅考虑具有相同参数的相同工具的工具调用相等。有关更多详细信息，请访问 [仓库](https://github.com/langchain-ai/agentevals?tab=readme-ov-file#tool-args-match-modes)。
</Info>

### LLM作为裁判评估器

您还可以使用LLM通过 `create_trajectory_llm_as_judge` 函数评估智能体的执行路径。与轨迹匹配评估器不同，它不需要参考轨迹，但如果可用，可以提供参考轨迹。

<Accordion title="无参考轨迹">
  ```python highlight={13-16} theme={null}
  from langchain.agents import create_agent
  from langchain_core.tools import tool
  from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
  from agentevals.trajectory.llm import create_trajectory_llm_as_judge, TRAJECTORY_ACCURACY_PROMPT

  @tool
  def get_weather(city: str):
      """获取城市天气信息。"""
      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="西雅图的天气怎么样？")]
      })

      evaluation = evaluator(
          outputs=result["messages"],
      )
      # {
      #     'key': 'trajectory_accuracy',
      #     'score': True,
      #     'comment': '提供的智能体轨迹是合理的...'
      # }
      assert evaluation["score"] is True
  ```
</Accordion>

<Accordion title="有参考轨迹">
  如果您有参考轨迹，可以在提示中添加一个额外变量并传入参考轨迹。下面，我们使用预构建的 `TRAJECTORY_ACCURACY_PROMPT_WITH_REFERENCE` 提示并配置 `reference_outputs` 变量：

  ```python theme={null}
  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,
  )
  ```
</Accordion>

<Info>
  如需更多关于LLM如何评估轨迹的可配置性，请访问 [仓库](https://github.com/langchain-ai/agentevals?tab=readme-ov-file#trajectory-llm-as-judge)。
</Info>

### 异步支持

所有 `agentevals` 评估器都支持 Python asyncio。对于使用工厂函数的评估器，可以通过在函数名中 `create_` 后添加 `async` 来获得异步版本。

<Accordion title="异步裁判和评估器示例">
  ```python theme={null}
  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="天气怎么样？")]
      })

      evaluation = await async_judge(outputs=result["messages"])
      assert evaluation["score"] is True
  ```
</Accordion>

## LangSmith 集成

为了随时间跟踪实验，您可以将评估器结果记录到 [LangSmith](https://smith.langchain.com/)，这是一个用于构建生产级LLM应用的平台，包括跟踪、评估和实验工具。

首先，通过设置必要的环境变量来设置 LangSmith：

```bash theme={null}
export LANGSMITH_API_KEY="your_langsmith_api_key"
export LANGSMITH_TRACING="true"
```

LangSmith 提供两种主要的评估运行方法：[pytest](/langsmith/pytest) 集成和 `evaluate` 函数。

<Accordion title="使用 pytest 集成">
  ```python theme={null}
  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="旧金山的天气怎么样？")]
      })

      reference_trajectory = [
          HumanMessage(content="旧金山的天气怎么样？"),
          AIMessage(content="", tool_calls=[
              {"id": "call_1", "name": "get_weather", "args": {"city": "SF"}},
          ]),
          ToolMessage(content="旧金山75度，晴天。", tool_call_id="call_1"),
          AIMessage(content="旧金山的天气是75度，晴天。"),
      ]

      # 将输入、输出和参考输出记录到 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 运行评估：

  ```bash theme={null}
  pytest test_trajectory.py --langsmith-output
  ```

  结果将自动记录到 LangSmith。
</Accordion>

<Accordion title="使用 evaluate 函数">
  或者，您可以在 LangSmith 中创建数据集并使用 `evaluate` 函数：

  ```python theme={null}
  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):
      """返回轨迹消息的智能体函数。"""
      return agent.invoke(inputs)["messages"]

  experiment_results = client.evaluate(
      run_agent,
      data="your_dataset_name",
      evaluators=[trajectory_evaluator]
  )
  ```

  结果将自动记录到 LangSmith。
</Accordion>

<Tip>
  要了解更多关于评估智能体的信息，请参阅 [LangSmith 文档](/langsmith/pytest)。
</Tip>

## 记录和重放 HTTP 调用

调用真实LLM API的集成测试可能很慢且昂贵，尤其是在CI/CD管道中频繁运行时。我们建议使用一个库来记录HTTP请求和响应，然后在后续运行中重放它们，而无需进行实际的网络调用。

您可以使用 [`vcrpy`](https://pypi.org/project/vcrpy/1.5.2/) 来实现这一点。如果您使用 `pytest`，[`pytest-recording` 插件](https://pypi.org/project/pytest-recording/) 提供了一种简单的方法，只需最少的配置即可启用此功能。请求/响应被记录在磁带中，然后在后续运行中用于模拟真实的网络调用。

在 `conftest.py` 文件中设置过滤掉磁带中的敏感信息：

```py conftest.py theme={null}
import pytest

@pytest.fixture(scope="session")
def vcr_config():
    return {
        "filter_headers": [
            ("authorization", "XXXX"),
            ("x-api-key", "XXXX"),
            # ... 您想屏蔽的其他标头
        ],
        "filter_query_parameters": [
            ("api_key", "XXXX"),
            ("key", "XXXX"),
        ],
    }
```

然后配置您的项目以识别 `vcr` 标记：

<CodeGroup>
  ```ini pytest.ini theme={null}
  [pytest]
  markers =
      vcr: 通过VCR记录/重放HTTP
  addopts = --record-mode=once
  ```

  ```toml pyproject.toml theme={null}
  [tool.pytest.ini_options]
  markers = [
    "vcr: 通过VCR记录/重放HTTP"
  ]
  addopts = "--record-mode=once"
  ```
</CodeGroup>

<Info>
  `--record-mode=once` 选项在第一次运行时记录HTTP交互，并在后续运行中重放它们。
</Info>

现在，只需用 `vcr` 标记装饰您的测试：

```python theme={null}
@pytest.mark.vcr()
def test_agent_trajectory():
    # ...
```

第一次运行此测试时，您的智能体会进行真实的网络调用，pytest 会在 `tests/cassettes` 目录中生成一个磁带文件 `test_agent_trajectory.yaml`。后续运行将使用该磁带模拟真实的网络调用，前提是智能体的请求与上一次运行相比没有变化。如果发生变化，测试将失败，您需要删除磁带并重新运行测试以记录新的交互。

<Warning>
  当您修改提示词、添加新工具或更改预期轨迹时，保存的磁带将过时，您现有的测试**将失败**。您应删除相应的磁带文件并重新运行测试以记录新的交互。
</Warning>
