ag2-testing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTesting agents and tools
测试agents与工具
When to use
适用场景
Writing tests for code that builds AG2 s, custom functions, middleware, or response schemas — anywhere you don't want to make real LLM API calls.
Agent@tool为构建AG2 、自定义函数、中间件或响应模式的代码编写测试——适用于任何不想调用真实LLM API的场景。
Agent@tool60-second recipe — mock an LLM response
60秒快速上手——模拟LLM响应
python
import pytest
from ag2 import Agent
from ag2.testing import TestConfig
@pytest.mark.asyncio
async def test_mocked_response():
agent = Agent("test_agent")
reply = await agent.ask("Hi!", config=TestConfig("This is a mocked response."))
assert reply.body == "This is a mocked response."TestConfig(*responses)ask()ToolCallEventask()python
import pytest
from ag2 import Agent
from ag2.testing import TestConfig
@pytest.mark.asyncio
async def test_mocked_response():
agent = Agent("test_agent")
reply = await agent.ask("Hi!", config=TestConfig("This is a mocked response."))
assert reply.body == "This is a mocked response."TestConfig(*responses)ask()ToolCallEventask()Simulate a successful tool call
模拟成功的工具调用
Pass a first (the model "decides" to call the tool), then the final answer:
ToolCallEventpython
import pytest
from ag2 import Agent
from ag2.events import ToolCallEvent
from ag2.testing import TestConfig
@pytest.mark.asyncio
async def test_tool_success():
def my_tool() -> str:
return "tool execution result"
agent = Agent("test_agent", tools=[my_tool])
config = TestConfig(
ToolCallEvent(name="my_tool"),
"final result",
)
reply = await agent.ask("Please use my_tool", config=config)
assert reply.body == "final result"先传入(模型“决定”调用工具),再传入最终答案:
ToolCallEventpython
import pytest
from ag2 import Agent
from ag2.events import ToolCallEvent
from ag2.testing import TestConfig
@pytest.mark.asyncio
async def test_tool_success():
def my_tool() -> str:
return "tool execution result"
agent = Agent("test_agent", tools=[my_tool])
config = TestConfig(
ToolCallEvent(name="my_tool"),
"final result",
)
reply = await agent.ask("Please use my_tool", config=config)
assert reply.body == "final result"Test tool error paths
测试工具错误流程
If a tool raises, the exception propagates to :
ask()python
@pytest.mark.asyncio
async def test_tool_raises():
def failing_tool() -> str:
raise ValueError("Something went wrong")
config = TestConfig(
ToolCallEvent(name="failing_tool"),
"result",
)
agent = Agent("test_agent", config=config, tools=[failing_tool])
with pytest.raises(ValueError, match="Something went wrong"):
await agent.ask("Hi!")如果工具抛出异常,该异常会传播到:
ask()python
@pytest.mark.asyncio
async def test_tool_raises():
def failing_tool() -> str:
raise ValueError("Something went wrong")
config = TestConfig(
ToolCallEvent(name="failing_tool"),
"result",
)
agent = Agent("test_agent", config=config, tools=[failing_tool])
with pytest.raises(ValueError, match="Something went wrong"):
await agent.ask("Hi!")Tool not found
工具未找到
If the LLM calls a tool the agent doesn't have, the framework raises :
ToolNotFoundErrorpython
from ag2.exceptions import ToolNotFoundError
@pytest.mark.asyncio
async def test_tool_not_found():
config = TestConfig(ToolCallEvent(name="unregistered_tool"))
agent = Agent("test_agent", config=config)
with pytest.raises(ToolNotFoundError, match="Tool `unregistered_tool` not found"):
await agent.ask("Hi!")如果LLM调用了agent未注册的工具,框架会抛出:
ToolNotFoundErrorpython
from ag2.exceptions import ToolNotFoundError
@pytest.mark.asyncio
async def test_tool_not_found():
config = TestConfig(ToolCallEvent(name="unregistered_tool"))
agent = Agent("test_agent", config=config)
with pytest.raises(ToolNotFoundError, match="Tool `unregistered_tool` not found"):
await agent.ask("Hi!")Useful test patterns
实用测试模式
Override Depends
dependencies
Depends覆盖Depends
依赖
Dependspython
def get_production_db():
raise Exception("Do not call in tests!")
@tool
def read_data(db: Annotated[object, Depends(get_production_db)]) -> str:
return "Data"
agent = Agent("test", tools=[read_data])
agent.dependency_provider.override(get_production_db, lambda: "mock_db")python
def get_production_db():
raise Exception("Do not call in tests!")
@tool
def read_data(db: Annotated[object, Depends(get_production_db)]) -> str:
return "Data"
agent = Agent("test", tools=[read_data])
agent.dependency_provider.override(get_production_db, lambda: "mock_db")Override Inject
dependencies
Inject覆盖Inject
依赖
InjectJust pass to :
dependencies={...}agent.ask(...)python
await agent.ask("Read", dependencies={"database_pool": fake_pool})只需在中传入:
agent.ask(...)dependencies={...}python
await agent.ask("Read", dependencies={"database_pool": fake_pool})Capture stream events
捕获流事件
python
from ag2 import MemoryStream
from ag2.events import ToolCallEvent
stream = MemoryStream()
collected: list[ToolCallEvent] = []
stream.where(ToolCallEvent).subscribe(lambda e: collected.append(e))
await agent.ask("Test", stream=stream)
assert collected[0].name == "expected_tool"python
from ag2 import MemoryStream
from ag2.events import ToolCallEvent
stream = MemoryStream()
collected: list[ToolCallEvent] = []
stream.where(ToolCallEvent).subscribe(lambda e: collected.append(e))
await agent.ask("Test", stream=stream)
assert collected[0].name == "expected_tool"Multi-turn mock — the response list is per-ask()
, not per-conversation
ask()多轮模拟——响应列表按每个ask()
划分,而非按对话
ask()TestConfig(...)ask()TestConfig(ToolCallEvent("my_tool"), "final result")"final result"TestConfig.create()responses[0]ask()ask()reply.ask(...)For variation across multiple calls, either:
ask()- pass a fresh per turn via the per-
TestConfig(...)ask()override (config=), orawait agent.ask("…", config=TestConfig("turn-2 reply")) - mock the model with a and put the per-turn logic in a stateful tool — a closure or class instance that tracks how many times it's been called and returns accordingly. (Useful when something other than your test code drives the turn loop — e.g. a
ToolCallEvent/workflowchannel — so you can't inject a per-turndiscussion.)config=
TestConfig(...)ask()TestConfig(ToolCallEvent("my_tool"), "final result")"final result"TestConfig.create()responses[0]ask()ask()reply.ask(...)若要在多个调用中实现不同的响应,可选择以下两种方式:
ask()- 通过每个的
ask()参数覆盖,传入全新的config=(TestConfig(...)),或await agent.ask("…", config=TestConfig("turn-2 reply")) - 用模拟模型,并将每轮逻辑放入有状态的工具中——比如一个闭包或类实例,用于跟踪调用次数并返回相应结果。(当测试代码之外的其他组件驱动轮次循环时非常有用——例如
ToolCallEvent/workflow通道,此时无法注入每轮的discussion。)config=
Going deeper
深入了解
- Source doc: .
website/docs/user-guide/testing.mdx - Test markers / async config — repo . Use
pyproject.toml(the project uses pytest-asyncio).@pytest.mark.asyncio - Streams (for asserting events): .
website/docs/user-guide/advanced/stream.mdx
- 源文档:。
website/docs/user-guide/testing.mdx - 测试标记/异步配置——仓库。使用
pyproject.toml(项目依赖pytest-asyncio)。@pytest.mark.asyncio - 流(用于断言事件):。
website/docs/user-guide/advanced/stream.mdx
Common pitfalls
常见陷阱
- Forgetting — the test will skip or fail oddly.
@pytest.mark.asyncio - Mismatched response count — runs out of responses if the agent makes more LLM calls than you expect within one
TestConfig(e.g. tool error → another LLM call);ask()propagates. Add more positional args or assert the call sequence. (The cursor is per-StopIteration, so the count you need is "LLM calls in one round", not "turns in the conversation" — see Multi-turn mock above.)ask() - Mocking the LLM but not the tool — your tool function still runs (and may hit real APIs / disk). Mock the tool if you're isolating LLM behaviour, or override its to inject test doubles.
Depends - Asserting on when you set a
reply.body—response_schemais the raw text. Usebodyfor the validated value.await reply.content() - Sharing instances across async tests — agents carry mutable state (variables, dependencies). Construct fresh agents per test for isolation.
Agent - Using real provider clients in CI — wrap the provider config with per-test or via a fixture; never rely on
TestConfigetc. being available in test environments.OPENAI_API_KEY
- 忘记添加——测试会被跳过或出现异常失败。
@pytest.mark.asyncio - 响应数量不匹配——如果agent在单次内发起的LLM调用次数超出预期(例如工具出错→再次调用LLM),
ask()会耗尽响应,TestConfig异常会传播。需添加更多位置参数或断言调用序列。(游标按每个StopIteration独立,因此所需的响应数量是“一轮中的LLM调用次数”,而非“对话中的轮次”——详见上文的多轮模拟。)ask() - 仅模拟LLM但未模拟工具——工具函数仍会执行(可能调用真实API/访问磁盘)。若要隔离LLM行为,需模拟工具,或覆盖其依赖以注入测试替身。
Depends - 设置后断言
response_schema——reply.body是原始文本。需使用body获取验证后的值。await reply.content() - 在异步测试间共享实例——agents包含可变状态(变量、依赖)。为保证隔离性,需为每个测试创建全新的agent实例。
Agent - 在CI中使用真实服务商客户端——需为每个测试或通过fixture用包装服务商配置;切勿依赖测试环境中存在
TestConfig等密钥。OPENAI_API_KEY