Observed since 20 Oct, 21:52 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Universal agent testing and evaluation toolkit - record, replay, mock, and benchmark AI agents
Universal testing and evaluation toolkit for AI agents.
Quick Start • Features • Evaluators • CLI • Architecture
Only 52% of teams shipping AI agents run evaluations. Agentest makes it dead simple to test, evaluate, and benchmark agents — regardless of which framework or LLM provider you use.
pip install agentest
The AI agent ecosystem has exploded, but testing and evaluation hasn't kept up. Most teams ship agents to production without systematic testing.
Agentest brings software engineering best practices to agent development:
| Capability | What You Get |
|---|---|
| Record & Replay | Capture real agent sessions, replay them deterministically — no expensive LLM calls needed for testing |
| Tool Mocking | Mock any tool call with a fluent, pytest-style API: .when(...).returns(...) |
| 7 Built-in Evaluators | Grade agents on task completion, safety, cost, latency, tool usage, and more |
| Model Comparison | Run the same tasks across Claude, GPT, Gemini — compare pass rates, cost, and latency |
| MCP Server Testing | Test MCP servers for protocol compliance and tool schema validation |
| pytest Plugin | Drop-in integration with auto-registered fixtures and custom markers |
| CLI & Web UI | agentest evaluate, agentest replay, agentest summary, and a FastAPI dashboard |
agentest.instrument() patches anthropic/openai clients to auto-record traces with zero code changes.from agentest import Recorder, TaskCompletionEvaluator, SafetyEvaluator, CostEvaluator
# Record an agent interaction
recorder = Recorder(task="Summarize README.md")
recorder.record_message("user", "Please summarize README.md")
recorder.record_tool_call(
name="read_file",
arguments={"path": "README.md"},
result="# My Project\nThis is a sample project.",
)
recorder.record_llm_response(
model="claude-sonnet-4-6",
content="This is a sample project.",
input_tokens=100,
output_tokens=20,
)
trace = recorder.finalize(success=True)
# Evaluate
for evaluator in [TaskCompletionEvaluator(), SafetyEvaluator(), CostEvaluator(max_cost=0.10)]:
result = evaluator.evaluate(trace)
print(f"{result.evaluator}: {'PASS' if result.passed else 'FAIL'} ({result.score:.2f})")
# Save for replay
recorder.save("traces/summarize.yaml")
from agentest import ToolMock, MockToolkit
toolkit = MockToolkit()
# Simple returns
toolkit.mock("read_file").returns("file contents")
# Conditional returns
toolkit.mock("search") \
.when(query="python").returns(["python result"]) \
.when(query="rust").returns(["rust result"]) \
.otherwise().returns([])
# Sequential returns (pagination, retries)
toolkit.mock("get_page").returns_sequence(["page 1", "page 2", "page 3"])
# Custom logic
toolkit.mock("calculator").responds_with(lambda args: args["a"] + args["b"])
# Error simulation
toolkit.mock("flaky_api").raises(TimeoutError("service unavailable"))
# Use them
result = toolkit.execute("read_file", path="test.txt") # "file contents"
result = toolkit.execute("search", query="python") # ["python result"]
# Assertions
toolkit.mock("read_file").assert_called()
toolkit.mock("read_file").assert_called_with(path="test.txt")
toolkit.assert_all_called()
from agentest import Recorder, Replayer
# Load a previously recorded trace
trace = Recorder.load("traces/summarize.yaml")
replayer = Replayer(trace, strict=True)
# Replay — get the exact same responses
response = replayer.next_llm_response()
tool_result = replayer.next_tool_result("read_file")
# Generate mock functions from the trace
mocks = replayer.create_tool_mock()
result = mocks["read_file"]() # Returns the recorded result
from agentest import BenchmarkRunner, ModelComparison
from agentest.benchmark.runner import BenchmarkTask
comparison = ModelComparison()
for model in ["claude-sonnet-4-6", "gpt-4o", "gpt-4o-mini"]:
runner = BenchmarkRunner(name=f"bench_{model}", evaluators=[...])
runner.add_task(BenchmarkTask(
name="summarize",
description="Summarize a document",
task_fn=lambda: run_your_agent(model, "Summarize README.md"),
))
comparison.add_result(model, runner.run())
# Compare
table = comparison.comparison_table()
best = comparison.best_model("avg_score")
diff = comparison.diff("claude-sonnet-4-6", "gpt-4o")
# Export
comparison.to_markdown("results.md")
comparison.to_csv("results.csv")
# tests/test_my_agent.py — fixtures auto-registered via entry point
def test_agent_completes_task(agent_recorder, agent_eval_suite):
agent_recorder.trace.task = "Summarize a document"
agent_recorder.record_tool_call(name="read_file", arguments={"path": "doc.txt"}, result="...")
agent_recorder.record_llm_response(model="claude-sonnet-4-6", content="Summary here.")
trace = agent_recorder.finalize(success=True)
results = agent_eval_suite.evaluate_all(trace)
assert all(r.passed for r in results)
def test_mocked_tools(agent_toolkit):
agent_toolkit.mock("search").when(query="python").returns(["result"])
assert agent_toolkit.execute("search", query="python") == ["result"]
def test_safety():
from agentest import Recorder, SafetyEvaluator
rec = Recorder(task="test")
rec.record_tool_call(name="bash", arguments={"command": "rm -rf /"}, result="")
assert not SafetyEvaluator().evaluate(rec.finalize()).passed
Run with:
pytest tests/ --agentest-max-cost=0.50 --agentest-max-tokens=100000
from agentest.mcp_testing import MCPServerTester, MCPAssertions
tester = MCPServerTester(command=["python", "-m", "my_mcp_server"])
# Run standard compliance tests
results = tester.run_standard_tests()
MCPAssertions(results).all_passed().has_tool("read_file").max_latency(5000)
# Test specific tools
result = tester.test_tool_call("read_file", arguments={"path": "/tmp/test.txt"})
assert result.passed
# Validate tool schemas
schema_results = tester.test_tool_schema_validation()
MCPAssertions(schema_results).all_passed()
import agentest
# Monkey-patch anthropic and openai clients globally
agentest.instrument()
# All API calls are now automatically recorded
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-6",
messages=[{"role": "user", "content": "Hello"}],
max_tokens=100,
)
# Get all recorded traces
traces = agentest.get_traces()
agentest.uninstrument() # Remove patches when done
# LangChain
from agentest.integrations.langchain import AgentestCallbackHandler
handler = AgentestCallbackHandler(task="My chain")
result = chain.invoke({"input": "Hello"}, config={"callbacks": [handler]})
trace = handler.get_trace()
# CrewAI
from agentest.integrations.crewai import record_crew
result, trace = record_crew(crew, inputs={"topic": "testing"})
# AutoGen
from agentest.integrations.autogen import record_autogen_chat
result, trace = record_autogen_chat(user_proxy, assistant, "Hello")
# Claude Agent SDK
from agentest.integrations.claude_agent_sdk import AgentestTracer
tracer = AgentestTracer(task="My agent")
result, trace = tracer.record(agent.run, "What is 2+2?")
# OpenAI Agents SDK
from agentest.integrations.openai_agents import AgentestTracer
tracer = AgentestTracer(task="My agent")
result, trace = tracer.record(runner.run_sync, agent, "Hello")
Install framework extras: pip install agentest[langchain,crewai,autogen,llamaindex]
# .github/workflows/agent-eval.yml
- uses: ColinHarker/agentest@v1
with:
traces-dir: traces/
evaluators: task_completion,safety,tool_usage
max-cost: "1.00"
check-safety: "true"
fail-on-error: "true"
Capture agent interactions as immutable trace snapshots (YAML or JSON). Replay them deterministically without making real LLM or tool calls — saving time and money during development.
# Record with context manager — auto-finalizes on exit
with Recorder(task="My task") as rec:
rec.record_message("user", "Do something")
rec.record_llm_response("claude-sonnet-4-6", "Done.", 100, 20)
# trace = rec.trace
Fluent builder API with conditional returns, sequences, regex matching, custom handlers, and full assertion support. Test agent logic without real integrations.
Built-in detection for:
rm -rf /, DROP TABLE, sudo chmod 777, eval(, curl | sh, etc.Automatic cost estimation with built-in pricing for Claude (Opus, Sonnet, Haiku), GPT-4o, GPT-4o-mini, O3, and O4-mini. Set budgets and latency limits as evaluator constraints.
Side-by-side benchmarking with export to CSV and Markdown. Compare pass rates, average scores, total cost, and latency across any number of models.
# Initialize Agentest in your project
agentest init
# Evaluate a recorded trace
agentest evaluate traces/my_trace.yaml --max-cost 0.50 --check-safety
# Replay a trace
agentest replay traces/my_trace.yaml
# Summarize all traces in a directory
agentest summary traces/ --format table
# Compare two traces side-by-side
agentest diff traces/v1.yaml traces/v2.yaml
# Watch a directory and re-evaluate on changes
agentest watch traces/ --check-safety --interval 5
# Launch the web UI dashboard
agentest serve --traces-dir traces/ --port 8000
| Evaluator | What it checks | Scoring |
|---|---|---|
TaskCompletionEvaluator | Success status, errors, message count, required tools, failed tool calls | -0.25 per issue (min 0.0) |
SafetyEvaluator | Dangerous commands, PII leakage, blocked tools, custom regex patterns | -0.2 per violation (min 0.0) |
CostEvaluator | Total cost, token count, LLM call count against budgets | Binary pass/fail |
LatencyEvaluator | Total duration and per-call latency against limits | 1.0 pass, 0.5 fail |
ToolUsageEvaluator | Required/forbidden tools, retry limits, error rates | -0.2 per issue (min 0.0) |
LLMJudgeEvaluator | LLM-graded evaluation against custom criteria (Anthropic or OpenAI) | LLM-assigned score |
CompositeEvaluator | Combines multiple evaluators with AND/OR logic | Average of all scores |
agentest/
├── core.py # Data models: AgentTrace, ToolCall, LLMResponse, TraceSession
├── recorder/
│ ├── recorder.py # Record agent sessions to YAML/JSON
│ └── replayer.py # Replay sessions deterministically
├── mocking/
│ └── tool_mock.py # ToolMock, MockToolkit — fluent builder + assertions
├── evaluators/
│ ├── base.py # Evaluator ABC, EvalResult, CompositeEvaluator, LLMJudge
│ └── builtin.py # TaskCompletion, Safety, Cost, Latency, ToolUsage
├── benchmark/
│ ├── runner.py # BenchmarkRunner (sync + async), BenchmarkTask, BenchmarkResult
│ └── comparison.py # ModelComparison, ModelScore — CSV/Markdown export
├── integrations/
│ ├── instrument.py # Auto-instrumentation for anthropic/openai
│ ├── langchain.py # LangChain callback handler adapter
│ ├── crewai.py # CrewAI crew recorder
│ ├── autogen.py # AutoGen conversation recorder
│ ├── llamaindex.py # LlamaIndex callback handler
│ ├── claude_agent_sdk.py # Claude Agent SDK tracer
│ └── openai_agents.py # OpenAI Agents SDK tracer
├── mcp_testing/
│ ├── server_tester.py # MCPServerTester — subprocess-based JSON-RPC testing
│ └── assertions.py # MCPAssertions — fluent assertion chains
├── reporters/
│ ├── console.py # Rich console output
│ └── json_reporter.py # Machine-readable JSON reports
├── server/
│ └── app.py # FastAPI web UI for trace exploration
├── pytest_plugin.py # Auto-registered fixtures, markers, and trace collectors
└── cli.py # Click CLI with 8 commands
| Feature | Agentest | LangSmith | LangFuse | Braintrust |
|---|---|---|---|---|
| Record & Replay | ✅ | — | — | — |
| Tool Mocking | ✅ | Basic | — | — |
| Safety Evaluator | ✅ | — | — | — |
| MCP Server Testing | ✅ | — | — | — |
| pytest Integration | ✅ | — | — | — |
| Framework-Agnostic | ✅ | ❌ | ❌ | ❌ |
| Open Source | ✅ | ❌ | ❌ | ❌ |
| Cost Tracking | ✅ | ✅ | ✅ | ✅ |
| Web UI | Basic | Full | Full | Full |
| Centralized Backend | — | ✅ | ✅ | ✅ |
| Auto-Instrumentation | ✅ | ✅ | ✅ | ✅ |
| Framework Adapters | ✅ (7) | ❌ | Partial | — |
| GitHub Action | ✅ | — | — | — |
Best for: Local development, CI/CD pipelines, deterministic testing, safety compliance, multi-model benchmarking.
pip install agentest # Core (recording, evaluation, CLI)
pip install agentest[web] # Web UI dashboard
pip install agentest[langchain] # LangChain adapter
pip install agentest[crewai] # CrewAI adapter
pip install agentest[autogen] # AutoGen adapter
pip install agentest[llamaindex] # LlamaIndex adapter
pip install agentest[all] # Everything
MIT
pypistats · 45 observations shown. Missing days are unknown. Source category: overall: without mirrors.
| Date / source period | Value | Observed at |
|---|---|---|
| 2026-10-20 – 2026-10-20 | 2 | 2026-10-21T14:49:10.4460676Z |
| 2026-10-18 – 2026-10-18 | 3 | 2026-10-21T14:49:10.4460676Z |
| 2026-10-17 – 2026-10-17 | 9 | 2026-10-21T14:49:10.4460676Z |
| 2026-10-16 – 2026-10-16 | 1 | 2026-10-21T14:49:10.4460676Z |
| 2026-10-15 – 2026-10-15 | 4 | 2026-10-21T14:49:10.4460676Z |
| 2026-10-14 – 2026-10-14 | 3 | 2026-10-21T14:49:10.4460676Z |
| 2026-10-13 – 2026-10-13 | 1 | 2026-10-21T14:49:10.4460676Z |
| 2026-10-12 – 2026-10-12 | 2 | 2026-10-21T14:49:10.4460676Z |
| 2026-10-11 – 2026-10-11 | 4 | 2026-10-21T14:49:10.4460676Z |
| 2026-10-10 – 2026-10-10 | 3 | 2026-10-21T14:49:10.4460676Z |
| 2026-10-09 – 2026-10-09 | 2 | 2026-10-21T14:49:10.4460676Z |
| 2026-10-06 – 2026-10-06 | 1 | 2026-10-21T14:49:10.4460676Z |
| 2026-10-02 – 2026-10-02 | 1 | 2026-10-21T14:49:10.4460676Z |
| 2026-09-30 – 2026-09-30 | 1 | 2026-10-21T14:49:10.4460676Z |
| 2026-09-29 – 2026-09-29 | 2 | 2026-10-21T14:49:10.4460676Z |
| 2026-09-27 – 2026-09-27 | 1 | 2026-10-21T14:49:10.4460676Z |
| 2026-09-26 – 2026-09-26 | 1 | 2026-10-21T14:49:10.4460676Z |
| 2026-09-23 – 2026-09-23 | 1 | 2026-10-21T14:49:10.4460676Z |
| 2026-09-22 – 2026-09-22 | 2 | 2026-10-21T14:49:10.4460676Z |
| 2026-09-20 – 2026-09-20 | 6 | 2026-10-21T14:49:10.4460676Z |
| 2026-09-19 – 2026-09-19 | 2 | 2026-10-21T14:49:10.4460676Z |
| 2026-09-18 – 2026-09-18 | 2 | 2026-10-21T14:49:10.4460676Z |
| 2026-09-16 – 2026-09-16 | 1 | 2026-10-21T14:49:10.4460676Z |
| 2026-09-15 – 2026-09-15 | 2 | 2026-10-21T14:49:10.4460676Z |
| 2026-09-14 – 2026-09-14 | 6 | 2026-10-21T14:49:10.4460676Z |
| 2026-09-12 – 2026-09-12 | 1 | 2026-10-21T14:49:10.4460676Z |
| 2026-09-10 – 2026-09-10 | 1 | 2026-10-21T14:49:10.4460676Z |
| 2026-09-07 – 2026-09-07 | 1 | 2026-10-21T14:49:10.4460676Z |
| 2026-09-02 – 2026-09-02 | 1 | 2026-10-21T14:49:10.4460676Z |
| 2026-09-01 – 2026-09-01 | 1 | 2026-10-21T14:49:10.4460676Z |
| 2026-08-31 – 2026-08-31 | 3 | 2026-10-21T14:49:10.4460676Z |
| 2026-08-28 – 2026-08-28 | 1 | 2026-10-21T14:49:10.4460676Z |
| 2026-08-24 – 2026-08-24 | 7 | 2026-10-21T14:49:10.4460676Z |
| 2026-08-23 – 2026-08-23 | 3 | 2026-10-21T14:49:10.4460676Z |
| 2026-08-17 – 2026-08-17 | 2 | 2026-10-21T14:49:10.4460676Z |
| 2026-08-15 – 2026-08-15 | 2 | 2026-10-21T14:49:10.4460676Z |
| 2026-08-14 – 2026-08-14 | 6 | 2026-10-21T14:49:10.4460676Z |
| 2026-08-09 – 2026-08-09 | 2 | 2026-10-21T14:49:10.4460676Z |
| 2026-08-04 – 2026-08-04 | 3 | 2026-10-21T14:49:10.4460676Z |
| 2026-08-03 – 2026-08-03 | 1 | 2026-10-21T14:49:10.4460676Z |
| 2026-08-02 – 2026-08-02 | 1 | 2026-10-21T14:49:10.4460676Z |
| 2026-07-31 – 2026-07-31 | 1 | 2026-10-21T14:49:10.4460676Z |
| 2026-07-27 – 2026-07-27 | 2 | 2026-10-21T14:49:10.4460676Z |
| 2026-07-24 – 2026-07-24 | 2 | 2026-10-21T14:49:10.4460676Z |
| 2026-07-23 – 2026-07-23 | 2 | 2026-10-21T14:49:10.4460676Z |
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 1.0.2 | 6 Mar 2026 | 23 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 21:52 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:2a741979ac3bbb5c6810279070d0e21c5373aff6b25bce6c1897d2918aa68ee6