agentverify 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. agentverify-0.1.0/LICENSE +21 -0
  2. agentverify-0.1.0/PKG-INFO +490 -0
  3. agentverify-0.1.0/README.md +443 -0
  4. agentverify-0.1.0/agentverify/__init__.py +42 -0
  5. agentverify-0.1.0/agentverify/assertions.py +324 -0
  6. agentverify-0.1.0/agentverify/cassette/__init__.py +1 -0
  7. agentverify-0.1.0/agentverify/cassette/adapters/__init__.py +1 -0
  8. agentverify-0.1.0/agentverify/cassette/adapters/anthropic.py +274 -0
  9. agentverify-0.1.0/agentverify/cassette/adapters/base.py +72 -0
  10. agentverify-0.1.0/agentverify/cassette/adapters/bedrock.py +263 -0
  11. agentverify-0.1.0/agentverify/cassette/adapters/gemini.py +363 -0
  12. agentverify-0.1.0/agentverify/cassette/adapters/litellm.py +129 -0
  13. agentverify-0.1.0/agentverify/cassette/adapters/openai.py +291 -0
  14. agentverify-0.1.0/agentverify/cassette/io.py +207 -0
  15. agentverify-0.1.0/agentverify/cassette/recorder.py +253 -0
  16. agentverify-0.1.0/agentverify/errors.py +149 -0
  17. agentverify-0.1.0/agentverify/fixtures.py +79 -0
  18. agentverify-0.1.0/agentverify/markers.py +18 -0
  19. agentverify-0.1.0/agentverify/matchers.py +48 -0
  20. agentverify-0.1.0/agentverify/models.py +109 -0
  21. agentverify-0.1.0/agentverify/plugin.py +26 -0
  22. agentverify-0.1.0/agentverify/py.typed +1 -0
  23. agentverify-0.1.0/agentverify.egg-info/PKG-INFO +490 -0
  24. agentverify-0.1.0/agentverify.egg-info/SOURCES.txt +40 -0
  25. agentverify-0.1.0/agentverify.egg-info/dependency_links.txt +1 -0
  26. agentverify-0.1.0/agentverify.egg-info/entry_points.txt +2 -0
  27. agentverify-0.1.0/agentverify.egg-info/requires.txt +28 -0
  28. agentverify-0.1.0/agentverify.egg-info/top_level.txt +1 -0
  29. agentverify-0.1.0/pyproject.toml +94 -0
  30. agentverify-0.1.0/setup.cfg +4 -0
  31. agentverify-0.1.0/tests/test_assertions.py +356 -0
  32. agentverify-0.1.0/tests/test_base_adapter.py +61 -0
  33. agentverify-0.1.0/tests/test_cassette_io.py +164 -0
  34. agentverify-0.1.0/tests/test_errors.py +173 -0
  35. agentverify-0.1.0/tests/test_fixtures.py +67 -0
  36. agentverify-0.1.0/tests/test_init.py +38 -0
  37. agentverify-0.1.0/tests/test_markers.py +12 -0
  38. agentverify-0.1.0/tests/test_matchers.py +44 -0
  39. agentverify-0.1.0/tests/test_models.py +136 -0
  40. agentverify-0.1.0/tests/test_plugin.py +32 -0
  41. agentverify-0.1.0/tests/test_properties.py +706 -0
  42. agentverify-0.1.0/tests/test_recorder.py +276 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 simukappu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,490 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentverify
3
+ Version: 0.1.0
4
+ Summary: Deterministic testing for AI agents. Assert tool calls, not vibes.
5
+ Author: simukappu
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/simukappu/agentverify
8
+ Project-URL: Repository, https://github.com/simukappu/agentverify
9
+ Project-URL: Issues, https://github.com/simukappu/agentverify/issues
10
+ Project-URL: Changelog, https://github.com/simukappu/agentverify/blob/main/CHANGELOG.md
11
+ Keywords: pytest,testing,ai,agent,llm,tool-calls,cassette,deterministic,ci
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Framework :: Pytest
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Testing
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: PyYAML
26
+ Requires-Dist: pytest
27
+ Provides-Extra: openai
28
+ Requires-Dist: openai; extra == "openai"
29
+ Provides-Extra: anthropic
30
+ Requires-Dist: anthropic; extra == "anthropic"
31
+ Provides-Extra: gemini
32
+ Requires-Dist: google-genai; extra == "gemini"
33
+ Provides-Extra: bedrock
34
+ Requires-Dist: boto3; extra == "bedrock"
35
+ Provides-Extra: litellm
36
+ Requires-Dist: litellm; extra == "litellm"
37
+ Provides-Extra: all
38
+ Requires-Dist: openai; extra == "all"
39
+ Requires-Dist: anthropic; extra == "all"
40
+ Requires-Dist: google-generativeai; extra == "all"
41
+ Requires-Dist: boto3; extra == "all"
42
+ Requires-Dist: litellm; extra == "all"
43
+ Provides-Extra: dev
44
+ Requires-Dist: hypothesis; extra == "dev"
45
+ Requires-Dist: pytest-cov; extra == "dev"
46
+ Dynamic: license-file
47
+
48
+ # agentverify
49
+
50
+ [![PyPI](https://img.shields.io/pypi/v/agentverify)](https://pypi.org/project/agentverify/)
51
+ [![Downloads](https://img.shields.io/pepy/dt/agentverify)](https://pepy.tech/projects/agentverify)
52
+ [![CI](https://github.com/simukappu/agentverify/actions/workflows/ci.yml/badge.svg)](https://github.com/simukappu/agentverify/actions/workflows/ci.yml)
53
+ [![Coverage](https://coveralls.io/repos/github/simukappu/agentverify/badge.svg?branch=main)](https://coveralls.io/github/simukappu/agentverify?branch=main)
54
+ [![License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)
55
+
56
+ **pytest for AI agents.** Assert tool calls, not vibes.
57
+
58
+ agentverify is a pytest plugin for deterministic testing of AI agent behavior. Record real LLM calls once, replay them in CI with zero cost, and assert exactly which tools were called, in what order, with what arguments — plus cost budgets and safety guardrails. Framework-agnostic, provider-agnostic, zero LLM cost in CI.
59
+
60
+ ## Why agentverify?
61
+
62
+ Most AI testing tools evaluate what an LLM *says*. agentverify tests what an agent *does*.
63
+
64
+ When agents move from prototype to production, the questions change: did the agent call the right tools in the right order? Did it stay within budget? Did it avoid dangerous operations? These are deterministic properties you can assert in CI, the same way you test any other code.
65
+
66
+ Unlike HTTP-level recorders that capture raw network traffic, agentverify records at the LLM SDK level — capturing tool calls, token usage, and model responses as first-class objects you can assert against. And unlike eval frameworks that score output quality with LLM-as-judge, agentverify asserts deterministic properties: routing correctness, cost control, and safety boundaries.
67
+
68
+ agentverify brings that discipline to agent development. It works with any framework — [Strands Agents](https://github.com/strands-agents/sdk-python), [LangChain](https://github.com/langchain-ai/langchain), [CrewAI](https://github.com/crewAIInc/crewAI), or plain Python — and any LLM provider. Just build an `ExecutionResult` from your agent's output and write pytest assertions.
69
+
70
+ ## Install
71
+
72
+ ```bash
73
+ pip install agentverify
74
+ ```
75
+
76
+ ## Quick Start — No LLM Required
77
+
78
+ Copy this into `test_agent.py` and run `pytest`. No API keys, no cassettes — just pure assertions.
79
+
80
+ ```python
81
+ from agentverify import (
82
+ ExecutionResult, ToolCall, ANY,
83
+ assert_tool_calls, assert_cost, assert_no_tool_call, assert_final_output,
84
+ )
85
+
86
+ # Build an ExecutionResult from your agent's output (or a dict)
87
+ result = ExecutionResult.from_dict({
88
+ "tool_calls": [
89
+ {"name": "get_location", "arguments": {"city": "Tokyo"}},
90
+ {"name": "get_weather", "arguments": {"lat": 35.6, "lon": 139.7}},
91
+ ],
92
+ "token_usage": {"input_tokens": 50, "output_tokens": 30},
93
+ "total_cost_usd": 0.002,
94
+ "final_output": "The weather in Tokyo is sunny, 22°C.",
95
+ })
96
+
97
+ def test_tool_sequence():
98
+ assert_tool_calls(result, expected=[
99
+ ToolCall("get_location", {"city": "Tokyo"}),
100
+ ToolCall("get_weather", {"lat": ANY, "lon": ANY}),
101
+ ])
102
+
103
+ def test_budget():
104
+ assert_cost(result, max_tokens=500, max_cost_usd=0.01)
105
+
106
+ def test_safety():
107
+ assert_no_tool_call(result, forbidden_tools=["delete_user", "drop_table"])
108
+
109
+ def test_output():
110
+ assert_final_output(result, contains="Tokyo")
111
+ ```
112
+
113
+ > **Using a real agent framework?** You don't need to build dicts by hand — write a small converter function instead. See [Building from Your Framework](#building-from-your-framework) below.
114
+
115
+ ```
116
+ $ pytest test_agent.py -v
117
+ test_agent.py::test_tool_sequence PASSED
118
+ test_agent.py::test_budget PASSED
119
+ test_agent.py::test_safety PASSED
120
+ test_agent.py::test_output PASSED
121
+ ```
122
+
123
+ ## 3 Steps to Test Your Agent
124
+
125
+ ### Step 1: Build an ExecutionResult
126
+
127
+ Build an `ExecutionResult` from your agent's output. You can construct one from a dict, or use a framework-specific converter (see [examples/](examples/) for Strands Agents and LangChain converters).
128
+
129
+ ```python
130
+ from agentverify import ExecutionResult
131
+
132
+ result = ExecutionResult.from_dict({
133
+ "tool_calls": [
134
+ {"name": "get_location", "arguments": {"city": "Tokyo"}},
135
+ {"name": "get_weather", "arguments": {"lat": 35.6, "lon": 139.7}},
136
+ ],
137
+ "token_usage": {"input_tokens": 50, "output_tokens": 30},
138
+ "total_cost_usd": 0.002,
139
+ "final_output": "The weather in Tokyo is sunny, 22°C.",
140
+ })
141
+ ```
142
+
143
+ `ExecutionResult.from_dict()` accepts the following keys:
144
+
145
+ | Key | Type | Description |
146
+ |---|---|---|
147
+ | `tool_calls` | `list[dict]` | Each dict has `name` (str, required), `arguments` (dict, optional), `result` (any, optional — tool execution result stored for reference; not used in assertions) |
148
+ | `token_usage` | `dict` or `None` | `{"input_tokens": int, "output_tokens": int}` |
149
+ | `total_cost_usd` | `float` or `None` | Total cost in USD (must be set manually — not auto-calculated from tokens) |
150
+ | `final_output` | `str` or `None` | The agent's final text response |
151
+
152
+ You can also use `ExecutionResult.from_json(json_string)` to parse from a JSON string, and `to_dict()` / `to_json()` for serialization.
153
+
154
+ #### Building from Your Framework
155
+
156
+ Every agent framework has its own output format. To use agentverify, you need a small converter function (~20–50 lines) that maps your framework's output to the dict schema above. Here's the general pattern:
157
+
158
+ ```python
159
+ from agentverify import ExecutionResult, ToolCall, TokenUsage
160
+
161
+ def my_framework_to_execution_result(agent_output) -> ExecutionResult:
162
+ # 1. Extract tool calls: map your framework's tool call objects
163
+ # to ToolCall(name=..., arguments=...)
164
+ tool_calls = [
165
+ ToolCall(name=tc.tool_name, arguments=tc.params)
166
+ for tc in agent_output.tool_history
167
+ ]
168
+
169
+ # 2. Extract token usage (if available)
170
+ token_usage = TokenUsage(
171
+ input_tokens=agent_output.metrics.prompt_tokens,
172
+ output_tokens=agent_output.metrics.completion_tokens,
173
+ )
174
+
175
+ # 3. Extract final output text
176
+ return ExecutionResult(
177
+ tool_calls=tool_calls,
178
+ token_usage=token_usage,
179
+ final_output=agent_output.response_text,
180
+ )
181
+ ```
182
+
183
+ See [`examples/strands-file-organizer/converter.py`](examples/strands-file-organizer/converter.py) and [`examples/langchain-issue-triage/converter.py`](examples/langchain-issue-triage/converter.py) for complete, production-ready converters. Built-in framework adapters are planned for a future release (see [Roadmap](#roadmap)).
184
+
185
+ ### Step 2: Assert
186
+
187
+ ```python
188
+ from agentverify import assert_tool_calls, assert_cost, assert_no_tool_call, assert_final_output, ToolCall, ANY
189
+
190
+ # Did the agent call the right tools in the right order?
191
+ assert_tool_calls(result, expected=[
192
+ ToolCall("get_location", {"city": "Tokyo"}),
193
+ ToolCall("get_weather", {"lat": ANY, "lon": ANY}),
194
+ ])
195
+
196
+ # Did it stay within budget?
197
+ assert_cost(result, max_tokens=500, max_cost_usd=0.01)
198
+
199
+ # Did it avoid dangerous tools?
200
+ assert_no_tool_call(result, forbidden_tools=["delete_user", "drop_table"])
201
+
202
+ # Did the final output contain the expected content?
203
+ # See "Final Output Assertions" below for equals and regex options.
204
+ assert_final_output(result, contains="Tokyo")
205
+ ```
206
+
207
+ ### Step 3: Record & Replay with Cassettes
208
+
209
+ Record real LLM API calls once. Replay them in CI forever — zero cost, deterministic.
210
+
211
+ **⚠️ Important: Cassette replay uses sequential matching** — responses are returned in recorded order without verifying request content. If your agent's prompts, tools, or model change, the cassette will still replay but may return stale/incorrect responses silently. **Delete and re-record cassettes after significant agent changes.** Request content matching is planned for a future release (see [Roadmap](#roadmap)).
212
+
213
+ The `cassette` fixture is a pytest fixture provided by the agentverify plugin. It creates an `LLMCassetteRecorder` that intercepts LLM SDK calls (not HTTP — it patches the SDK's chat completion method directly). Use `@pytest.mark.agentverify` to mark your test, and call your agent code inside the `with cassette(...)` block. After the block exits, call `rec.to_execution_result()` to build the result for assertions.
214
+
215
+ ```python
216
+ import pytest
217
+ from agentverify import assert_tool_calls, ToolCall, ANY
218
+
219
+ @pytest.mark.agentverify
220
+ def test_weather_agent(cassette):
221
+ with cassette("weather_agent.yaml", provider="openai") as rec:
222
+ # Replace this with your actual agent invocation, e.g.:
223
+ # agent.run("What's the weather in Tokyo?")
224
+ run_my_agent("What's the weather in Tokyo?")
225
+
226
+ # rec.to_execution_result() is called AFTER the with block exits
227
+ result = rec.to_execution_result()
228
+ assert_tool_calls(result, expected=[
229
+ ToolCall("get_location", {"city": "Tokyo"}),
230
+ ToolCall("get_weather", {"lat": ANY, "lon": ANY}),
231
+ ])
232
+ ```
233
+
234
+ Record the cassette once, then replay it forever:
235
+
236
+ ```bash
237
+ # First run: record real LLM calls to cassette file
238
+ pytest --cassette-mode=record
239
+
240
+ # All subsequent runs: replay from cassette (zero cost, deterministic)
241
+ pytest
242
+ ```
243
+
244
+ Cassettes are human-readable YAML (or JSON). Commit them to git, review in PRs.
245
+
246
+ **Cassette modes:**
247
+
248
+ | Mode | Behavior |
249
+ |---|---|
250
+ | `AUTO` (default) | If cassette file exists → REPLAY. Otherwise → call real LLM API but don't save (no cassette file is created). |
251
+ | `RECORD` | Always call real LLM API and save to cassette file. |
252
+ | `REPLAY` | Always replay from cassette file. Raises error if file is missing. |
253
+
254
+ To create a cassette, use `mode=CassetteMode.RECORD` explicitly or pass `--cassette-mode=record` on the command line. To re-record, simply run with `RECORD` again — the existing file is overwritten.
255
+
256
+ ```bash
257
+ # Record cassettes for all tests
258
+ pytest --cassette-mode=record
259
+
260
+ # Replay cassettes (default behavior when cassette files exist)
261
+ pytest
262
+ ```
263
+
264
+ **Other limitations:**
265
+
266
+ - `total_cost_usd` is not populated from cassettes. Use `assert_cost(max_tokens=...)` for cassette-based budget checks, or set `total_cost_usd` manually in your `ExecutionResult`.
267
+ - Be mindful not to include sensitive data (API keys, PII, confidential prompts) in cassette files checked into version control.
268
+
269
+ ## Assertion Modes
270
+
271
+ ```python
272
+ from agentverify import assert_tool_calls, OrderMode, ToolCall, ANY
273
+
274
+ # Exact match — same tools, same order, same count (default)
275
+ assert_tool_calls(result, expected=[...])
276
+
277
+ # Subsequence — these tools appeared in this order (other calls in between are OK)
278
+ assert_tool_calls(result, expected=[...], order=OrderMode.IN_ORDER)
279
+
280
+ # Set membership — these tools were called (order doesn't matter)
281
+ assert_tool_calls(result, expected=[...], order=OrderMode.ANY_ORDER)
282
+
283
+ # Partial args — only check the keys you care about
284
+ assert_tool_calls(result, expected=[
285
+ ToolCall("search", {"query": "Tokyo"}),
286
+ ], partial_args=True)
287
+
288
+ # Collect all failures at once (doesn't stop at first)
289
+ from agentverify import assert_all
290
+ assert_all(
291
+ result,
292
+ lambda r: assert_tool_calls(r, expected=[...]),
293
+ lambda r: assert_cost(r, max_tokens=1000),
294
+ lambda r: assert_no_tool_call(r, forbidden_tools=["delete_user"]),
295
+ lambda r: assert_final_output(r, contains="Tokyo"),
296
+ )
297
+ ```
298
+
299
+ ### Strict Cost Assertions
300
+
301
+ By default, `assert_cost()` silently passes when `token_usage` or `total_cost_usd` is `None` (e.g., during cassette replay where cost data may be unavailable). Use `strict=True` to require that the data is present:
302
+
303
+ ```python
304
+ # Fails if token_usage is None, even if the budget would pass
305
+ assert_cost(result, max_tokens=500, strict=True)
306
+
307
+ # Fails if total_cost_usd is None
308
+ assert_cost(result, max_cost_usd=0.01, strict=True)
309
+ ```
310
+
311
+ ### Final Output Assertions
312
+
313
+ `assert_final_output()` verifies the agent's final text response. Use `contains` for substring checks, `equals` for exact match, or `matches` for regex:
314
+
315
+ ```python
316
+ from agentverify import assert_final_output
317
+
318
+ # Substring check
319
+ assert_final_output(result, contains="Tokyo")
320
+
321
+ # Exact match
322
+ assert_final_output(result, equals="The weather in Tokyo is sunny, 22°C.")
323
+
324
+ # Regex match
325
+ assert_final_output(result, matches=r"\d+°C")
326
+ ```
327
+
328
+ ## Framework Integration
329
+
330
+ agentverify is framework-agnostic. Build an `ExecutionResult` from any agent framework's output using a converter function. The [`examples/`](examples/) directory includes ready-to-use converters:
331
+
332
+ | Framework | Converter | Description |
333
+ |---|---|---|
334
+ | [Strands Agents](https://github.com/strands-agents/sdk-python) | [`strands-file-organizer/converter.py`](examples/strands-file-organizer/converter.py) | Converts `AgentResult` → `ExecutionResult` |
335
+ | [LangChain](https://github.com/langchain-ai/langchain) | [`langchain-issue-triage/converter.py`](examples/langchain-issue-triage/converter.py) | Converts `AgentExecutor` output → `ExecutionResult` |
336
+
337
+ These converters are small (~50 lines) and easy to adapt for your own framework. Built-in framework adapters are planned for a future release (see [Roadmap](#roadmap)).
338
+
339
+ ## Supported LLM Providers
340
+
341
+ | Provider | Extra |
342
+ |---|---|
343
+ | OpenAI | `pip install agentverify[openai]` |
344
+ | Amazon Bedrock | `pip install agentverify[bedrock]` |
345
+ | Google Gemini | `pip install agentverify[gemini]` |
346
+ | Anthropic | `pip install agentverify[anthropic]` |
347
+ | LiteLLM | `pip install agentverify[litellm]` |
348
+ | All providers | `pip install agentverify[all]` |
349
+
350
+ ## CI Integration
351
+
352
+ agentverify is designed for CI pipelines. Commit your cassette files to git and replay them in CI with zero LLM cost.
353
+
354
+ ### GitHub Actions
355
+
356
+ ```yaml
357
+ name: Agent Tests
358
+ on: [push, pull_request]
359
+
360
+ jobs:
361
+ test:
362
+ runs-on: ubuntu-latest
363
+ steps:
364
+ - uses: actions/checkout@v5
365
+ - uses: actions/setup-python@v6
366
+ with:
367
+ python-version: "3.12"
368
+ - run: pip install -e ".[dev]"
369
+ - run: pytest --tb=short -v
370
+ ```
371
+
372
+ Cassette files in `tests/cassettes/` are replayed automatically — no API keys or secrets needed in CI.
373
+
374
+ ## Error Messages
375
+
376
+ Clear, structured output when assertions fail:
377
+
378
+ ```
379
+ ToolCallSequenceError: Tool call sequence mismatch at index 1
380
+
381
+ Expected:
382
+ [0] get_location(city="Tokyo")
383
+ [1] get_news(topic="weather") ← first mismatch
384
+
385
+ Actual:
386
+ [0] get_location(city="Tokyo")
387
+ [1] search_web(query="Tokyo weather") ← actual
388
+ ```
389
+
390
+ ```
391
+ CostBudgetError: Token budget exceeded
392
+
393
+ Actual: 1,250 tokens
394
+ Limit: 1,000 tokens
395
+ Exceeded by: 250 tokens (25.0%)
396
+ ```
397
+
398
+ ```
399
+ SafetyRuleViolationError: 2 forbidden tool calls detected
400
+
401
+ [1] delete_database(table="users") at position 3
402
+ [2] drop_table(name="orders") at position 5
403
+ ```
404
+
405
+ ```
406
+ FinalOutputError: final_output does not contain expected substring
407
+
408
+ Substring: 'Berlin'
409
+ Actual: 'The weather in Tokyo is sunny, 22°C.'
410
+ ```
411
+
412
+ ## Requirements
413
+
414
+ - Python 3.10+
415
+ - pytest 7+
416
+
417
+ ## Examples
418
+
419
+ The [`examples/`](examples/) directory contains end-to-end examples with real agent frameworks and MCP servers. Each example ships with pre-recorded cassettes — run the tests without any API keys.
420
+
421
+ | Example | Framework | Description |
422
+ |---|---|---|
423
+ | [`strands-file-organizer`](examples/strands-file-organizer/) | Strands Agents + Bedrock | Scans a directory via Filesystem MCP, suggests organization. Read-only safety verified |
424
+ | [`langchain-issue-triage`](examples/langchain-issue-triage/) | LangChain + OpenAI | Triages GitHub issues via GitHub MCP. Label and priority suggestions |
425
+ | [`mcp-server`](examples/mcp-server/) | — | Mock GitHub MCP server for token-free testing |
426
+
427
+ Try it:
428
+
429
+ ```bash
430
+ git clone https://github.com/simukappu/agentverify.git
431
+ cd agentverify
432
+ python -m venv .venv && source .venv/bin/activate
433
+ pip install -e .
434
+
435
+ # Strands File Organizer
436
+ pip install -e "examples/strands-file-organizer/.[dev]"
437
+ pytest examples/strands-file-organizer/tests -v
438
+ ```
439
+
440
+ ```
441
+ tests/test_file_organizer.py::test_tool_call_sequence PASSED
442
+ tests/test_file_organizer.py::test_token_budget PASSED
443
+ tests/test_file_organizer.py::test_safety_read_only PASSED
444
+ ```
445
+
446
+ ```bash
447
+ # LangChain Issue Triage
448
+ pip install -e "examples/langchain-issue-triage/.[dev]"
449
+ pytest examples/langchain-issue-triage/tests -v
450
+ ```
451
+
452
+ ```
453
+ tests/test_issue_triage.py::TestIssueTriage_MockMCP::test_tool_call_sequence PASSED
454
+ tests/test_issue_triage.py::TestIssueTriage_MockMCP::test_safety_read_and_label_only PASSED
455
+ ```
456
+
457
+ See each example's README for agent execution instructions and recording mode details.
458
+
459
+ ## Roadmap
460
+
461
+ - Agent framework adapters — extract `ExecutionResult` directly from Strands Agents, LangChain, and others without writing a converter
462
+ - Tool mocking/stubbing — test agent routing logic without calling real tools
463
+ - Async support — first-class `asyncio` testing for async agents and tools
464
+ - Cassette request matching — verify request content during replay to detect stale cassettes
465
+ - Cassette sanitization — automatic masking of API keys and sensitive data in recorded cassettes
466
+ - Cost estimation from tokens — auto-calculate `total_cost_usd` from token usage and model pricing
467
+ - YAML/JSON test case definitions — declarative test cases for non-Python CI pipelines
468
+ - CLI test runner — run agent tests without pytest
469
+
470
+ ## Changelog
471
+
472
+ See [CHANGELOG.md](CHANGELOG.md) for release history.
473
+
474
+ ## License
475
+
476
+ [MIT](LICENSE)
477
+
478
+ ## Contributing
479
+
480
+ Contributions welcome. Please open an issue first to discuss what you'd like to change.
481
+
482
+ Development setup:
483
+
484
+ ```bash
485
+ git clone https://github.com/simukappu/agentverify.git
486
+ cd agentverify
487
+ python -m venv .venv && source .venv/bin/activate
488
+ pip install -e ".[dev]"
489
+ pytest
490
+ ```