understudy 0.3.0__tar.gz → 0.5.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 (61) hide show
  1. {understudy-0.3.0 → understudy-0.5.0}/PKG-INFO +116 -27
  2. {understudy-0.3.0 → understudy-0.5.0}/README.md +107 -25
  3. {understudy-0.3.0 → understudy-0.5.0}/pyproject.toml +7 -2
  4. understudy-0.5.0/src/understudy/__init__.py +149 -0
  5. {understudy-0.3.0 → understudy-0.5.0}/src/understudy/adk/__init__.py +30 -10
  6. understudy-0.5.0/src/understudy/agentic/__init__.py +53 -0
  7. understudy-0.5.0/src/understudy/agentic/check.py +198 -0
  8. understudy-0.5.0/src/understudy/agentic/metrics.py +170 -0
  9. understudy-0.5.0/src/understudy/agentic/models.py +264 -0
  10. understudy-0.5.0/src/understudy/agentic/runner.py +150 -0
  11. understudy-0.5.0/src/understudy/batch.py +53 -0
  12. understudy-0.5.0/src/understudy/check.py +313 -0
  13. understudy-0.5.0/src/understudy/cli.py +1398 -0
  14. understudy-0.5.0/src/understudy/diff.py +233 -0
  15. {understudy-0.3.0 → understudy-0.5.0}/src/understudy/http/__init__.py +0 -6
  16. understudy-0.5.0/src/understudy/judge_backends.py +147 -0
  17. understudy-0.5.0/src/understudy/judges.py +261 -0
  18. understudy-0.5.0/src/understudy/langgraph/__init__.py +162 -0
  19. understudy-0.5.0/src/understudy/langgraph/tools.py +100 -0
  20. understudy-0.5.0/src/understudy/metrics/__init__.py +16 -0
  21. understudy-0.5.0/src/understudy/metrics/builtins.py +112 -0
  22. understudy-0.5.0/src/understudy/metrics/registry.py +90 -0
  23. understudy-0.5.0/src/understudy/metrics/templates/clarity.jinja +13 -0
  24. understudy-0.5.0/src/understudy/metrics/templates/policy_compliance.jinja +17 -0
  25. understudy-0.5.0/src/understudy/metrics/templates/tone_empathy.jinja +13 -0
  26. {understudy-0.3.0 → understudy-0.5.0}/src/understudy/mocks.py +3 -5
  27. {understudy-0.3.0 → understudy-0.5.0}/src/understudy/models.py +53 -12
  28. understudy-0.5.0/src/understudy/pytest_plugin.py +221 -0
  29. understudy-0.5.0/src/understudy/replay.py +196 -0
  30. {understudy-0.3.0 → understudy-0.5.0}/src/understudy/reports.py +74 -69
  31. understudy-0.5.0/src/understudy/runner.py +358 -0
  32. understudy-0.5.0/src/understudy/server/__init__.py +142 -0
  33. understudy-0.5.0/src/understudy/server/models.py +123 -0
  34. understudy-0.5.0/src/understudy/server/sessions.py +192 -0
  35. understudy-0.5.0/src/understudy/server/ui_simulator.py +160 -0
  36. understudy-0.5.0/src/understudy/storage.py +535 -0
  37. {understudy-0.3.0 → understudy-0.5.0}/src/understudy/suite.py +67 -45
  38. {understudy-0.3.0 → understudy-0.5.0}/src/understudy/templates/base.html +1 -5
  39. understudy-0.5.0/src/understudy/templates/index.html +271 -0
  40. understudy-0.5.0/src/understudy/templates/init/conftest.py.template +70 -0
  41. understudy-0.5.0/src/understudy/templates/init/example_scene.yaml +36 -0
  42. understudy-0.5.0/src/understudy/templates/init/test_agent.py.template +43 -0
  43. understudy-0.5.0/src/understudy/templates/init/understudy.yaml.template +16 -0
  44. {understudy-0.3.0 → understudy-0.5.0}/src/understudy/templates/run_detail.html +53 -1
  45. {understudy-0.3.0 → understudy-0.5.0}/src/understudy/trace.py +49 -20
  46. understudy-0.5.0/src/understudy/validation.py +173 -0
  47. understudy-0.3.0/src/understudy/__init__.py +0 -79
  48. understudy-0.3.0/src/understudy/check.py +0 -138
  49. understudy-0.3.0/src/understudy/cli.py +0 -379
  50. understudy-0.3.0/src/understudy/judges.py +0 -105
  51. understudy-0.3.0/src/understudy/loaders/__init__.py +0 -0
  52. understudy-0.3.0/src/understudy/personas/__init__.py +0 -0
  53. understudy-0.3.0/src/understudy/runner.py +0 -183
  54. understudy-0.3.0/src/understudy/storage.py +0 -211
  55. understudy-0.3.0/src/understudy/templates/index.html +0 -116
  56. understudy-0.3.0/src/understudy/templates/metrics.html +0 -128
  57. {understudy-0.3.0 → understudy-0.5.0}/src/understudy/compare.py +0 -0
  58. {understudy-0.3.0 → understudy-0.5.0}/src/understudy/prompts/__init__.py +0 -0
  59. {understudy-0.3.0 → understudy-0.5.0}/src/understudy/prompts/rubrics.py +0 -0
  60. {understudy-0.3.0 → understudy-0.5.0}/src/understudy/simulator.py +0 -0
  61. {understudy-0.3.0 → understudy-0.5.0}/src/understudy/templates/comparison.html +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: understudy
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: Simulation and trace-based evaluation for agentic systems
5
5
  Keywords: agent,evaluation,simulation,testing,llm
6
6
  Author: Gaurav Sood
@@ -17,7 +17,7 @@ Requires-Dist: pyyaml>=6.0
17
17
  Requires-Dist: litellm>=1.50
18
18
  Requires-Dist: google-adk>=1.0 ; extra == 'adk'
19
19
  Requires-Dist: python-dotenv>=1.0 ; extra == 'adk'
20
- Requires-Dist: understudy[adk,http,reports] ; extra == 'all'
20
+ Requires-Dist: understudy[adk,http,langgraph,server,reports] ; extra == 'all'
21
21
  Requires-Dist: understudy[all,docs] ; extra == 'dev'
22
22
  Requires-Dist: pytest>=8.0 ; extra == 'dev'
23
23
  Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
@@ -28,8 +28,13 @@ Requires-Dist: furo>=2024.0 ; extra == 'docs'
28
28
  Requires-Dist: myst-parser>=3.0 ; extra == 'docs'
29
29
  Requires-Dist: sphinx-autodoc-typehints>=2.0 ; extra == 'docs'
30
30
  Requires-Dist: httpx>=0.27 ; extra == 'http'
31
+ Requires-Dist: langgraph>=0.2 ; extra == 'langgraph'
32
+ Requires-Dist: langchain-core>=0.3 ; extra == 'langgraph'
33
+ Requires-Dist: langchain-openai>=0.3 ; extra == 'langgraph'
31
34
  Requires-Dist: jinja2>=3.0 ; extra == 'reports'
32
35
  Requires-Dist: click>=8.0 ; extra == 'reports'
36
+ Requires-Dist: fastapi>=0.115 ; extra == 'server'
37
+ Requires-Dist: uvicorn>=0.32 ; extra == 'server'
33
38
  Requires-Python: >=3.12
34
39
  Project-URL: Homepage, https://github.com/gojiplus/understudy
35
40
  Project-URL: Documentation, https://gojiplus.github.io/understudy
@@ -40,19 +45,21 @@ Provides-Extra: all
40
45
  Provides-Extra: dev
41
46
  Provides-Extra: docs
42
47
  Provides-Extra: http
48
+ Provides-Extra: langgraph
43
49
  Provides-Extra: reports
50
+ Provides-Extra: server
44
51
  Description-Content-Type: text/markdown
45
52
 
46
53
  ## understudy: Scenario Testing for AI Agents
47
54
 
48
55
  [![PyPI version](https://badge.fury.io/py/understudy.svg)](https://badge.fury.io/py/understudy)
49
- [![Downloads](https://pepy.tech/badge/understudy)](https://pepy.tech/project/understudy)
56
+ [![PyPI Downloads](https://static.pepy.tech/personalized-badge/understudy?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/project/understudy)
50
57
  [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
51
58
  [![Documentation](https://github.com/gojiplus/understudy/actions/workflows/docs.yml/badge.svg)](https://gojiplus.github.io/understudy/)
52
59
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
53
60
 
54
61
 
55
- Understudy is a scenario-driven testing framework for AI agents that simulates realistic multi-turn users, runs those scenes against an agent through a simple app adapter, records a structured execution trace of messages, tool calls, handoffs, and terminal states, and then evaluates behavior with deterministic checks, optional LLM judges, and run reports.
62
+ Understudy is a scenario-driven testing framework for AI agents that simulates realistic multi-turn users, runs those scenes against an agent through a simple app adapter, records a structured execution trace of messages, tool calls, and handoffs, and then evaluates behavior with deterministic checks, optional LLM judges, and run reports.
56
63
 
57
64
  ## How It Works
58
65
 
@@ -63,12 +70,29 @@ Testing with understudy is **4 steps**:
63
70
  3. **Write scenes** — YAML files defining what the simulated user wants and what you expect
64
71
  4. **Run and assert** — Execute simulations, check traces, generate reports
65
72
 
66
- The key insight: **assert against the trace, not the prose**. Don't check what the agent said—check what it did (tool calls, terminal state).
73
+ The key insight: **assert against the trace, not the prose**. Don't check what the agent said—check what it did (tool calls).
74
+
75
+ ## Two Evaluation Paradigms
76
+
77
+ ### Conversational Agent Evaluation
78
+ Simulate multi-turn conversations with personas to test dialogue agents.
79
+ - Use case: Customer service bots, assistants, chatbots
80
+ - Assert on tool calls: `trace.called("tool_name")`
81
+
82
+ ### Agentic Flow Evaluation
83
+ Evaluate autonomous agents executing multi-step tasks.
84
+ - Use case: Code agents, research agents, task automation
85
+ - Assert on actions: `trace.performed("action")`
86
+
87
+ See [examples/README.md](examples/README.md) for complete examples of both paradigms.
67
88
 
68
89
  **See real examples:**
69
- - [Example scene](https://github.com/gojiplus/understudy/blob/main/example/scenes/return_eligible_backpack.yaml) — YAML defining a test scenario
70
- - [Test file](https://github.com/gojiplus/understudy/blob/main/example/test_returns.py) — pytest assertions against traces
71
- - [Sample report](https://htmlpreview.github.io/?https://github.com/gojiplus/understudy/blob/main/example/sample_report.html) — HTML output from `understudy report`
90
+ - [Example scene](https://github.com/gojiplus/understudy/blob/main/examples/scenes/return_eligible_backpack.yaml) — YAML defining a test scenario
91
+ - [ADK test file](https://github.com/gojiplus/understudy/blob/main/examples/adk/test_adk_returns.py) — pytest assertions against traces
92
+ - [LangGraph test file](https://github.com/gojiplus/understudy/blob/main/examples/langgraph/test_langgraph_returns.py) — same tests, different framework
93
+ - [Agentic test file](https://github.com/gojiplus/understudy/blob/main/examples/agentic/test_agentic.py) — agentic flow evaluation
94
+ - [Agentic scene](https://github.com/gojiplus/understudy/blob/main/examples/agentic_scenes/code_review_task.yaml) — task-based scenario
95
+ - [Example report](https://htmlpreview.github.io/?https://github.com/gojiplus/understudy/blob/main/examples/langgraph/report/index.html) — HTML report with metrics and transcripts
72
96
 
73
97
  ## Installation
74
98
 
@@ -126,21 +150,21 @@ expectations:
126
150
  required_tools:
127
151
  - lookup_order
128
152
  - create_return
129
- allowed_terminal_states:
130
- - return_created
153
+ forbidden_tools:
154
+ - issue_refund
131
155
  ```
132
156
 
133
157
  ### 4. Run simulation
134
158
 
135
159
  ```python
136
- from understudy import Scene, run, check
160
+ from understudy import Scene, run
137
161
 
138
162
  scene = Scene.from_file("scenes/return_backpack.yaml")
139
163
  trace = run(app, scene, mocks=mocks)
140
164
 
141
165
  assert trace.called("lookup_order")
142
166
  assert trace.called("create_return")
143
- assert trace.terminal_state == "return_created"
167
+ assert not trace.called("issue_refund")
144
168
  ```
145
169
 
146
170
  Or with pytest (define `app` and `mocks` fixtures in conftest.py):
@@ -149,9 +173,9 @@ Or with pytest (define `app` and `mocks` fixtures in conftest.py):
149
173
  pytest test_returns.py -v
150
174
  ```
151
175
 
152
- ## Suites with Tags
176
+ ## Suites and Batch Runs
153
177
 
154
- Run multiple scenes and tag runs for comparison:
178
+ Run multiple scenes with multiple simulations per scene:
155
179
 
156
180
  ```python
157
181
  from understudy import Suite, RunStorage
@@ -159,33 +183,98 @@ from understudy import Suite, RunStorage
159
183
  suite = Suite.from_directory("scenes/")
160
184
  storage = RunStorage()
161
185
 
162
- # Tag runs for later comparison
163
- results = suite.run(app, mocks=mocks, storage=storage, tags={"version": "v1"})
186
+ # Run each scene 3 times and tag for comparison
187
+ results = suite.run(
188
+ app,
189
+ mocks=mocks,
190
+ storage=storage,
191
+ n_sims=3,
192
+ tags={"version": "v1"},
193
+ )
164
194
  print(f"{results.pass_count}/{len(results.results)} passed")
165
195
  ```
166
196
 
197
+ ## Simulation and Evaluation
198
+
199
+ Understudy separates simulation (generating traces) from evaluation (checking traces). Use together or separately:
200
+
201
+ ### Combined (most common)
202
+
203
+ ```bash
204
+ understudy run \
205
+ --app mymodule:agent_app \
206
+ --scene ./scenes/ \
207
+ --n-sims 3 \
208
+ --junit results.xml
209
+ ```
210
+
211
+ ### Separate workflows
212
+
213
+ Generate traces only:
214
+
215
+ ```bash
216
+ understudy simulate \
217
+ --app mymodule:agent_app \
218
+ --scenes ./scenes/ \
219
+ --output ./traces/ \
220
+ --n-sims 3
221
+ ```
222
+
223
+ Evaluate existing traces:
224
+
225
+ ```bash
226
+ understudy evaluate \
227
+ --traces ./traces/ \
228
+ --output ./results/ \
229
+ --junit results.xml
230
+ ```
231
+
232
+ Python API:
233
+
234
+ ```python
235
+ from understudy import simulate_batch, evaluate_batch
236
+
237
+ # Generate traces
238
+ traces = simulate_batch(
239
+ app=agent_app,
240
+ scenes="./scenes/",
241
+ n_sims=3,
242
+ output="./traces/",
243
+ )
244
+
245
+ # Evaluate later
246
+ results = evaluate_batch(
247
+ traces="./traces/",
248
+ output="./results/",
249
+ )
250
+ ```
251
+
167
252
  ## CLI Commands
168
253
 
169
254
  ```bash
170
- # List runs (shows tags)
171
- understudy list
255
+ # Run simulations
256
+ understudy run --app mymodule:app --scene ./scenes/
257
+ understudy simulate --app mymodule:app --scenes ./scenes/
258
+ understudy evaluate --traces ./traces/
172
259
 
173
- # Show run details
260
+ # View results
261
+ understudy list
174
262
  understudy show <run_id>
175
-
176
- # Aggregate metrics
177
263
  understudy summary
178
264
 
179
265
  # Compare runs by tag
180
266
  understudy compare --tag version --before v1 --after v2
181
267
 
182
- # Generate HTML reports
268
+ # Generate reports
183
269
  understudy report -o report.html
184
270
  understudy compare --tag version --before v1 --after v2 --html comparison.html
185
271
 
186
272
  # Interactive browser
187
273
  understudy serve --port 8080
188
274
 
275
+ # HTTP simulator server (for browser/UI testing)
276
+ understudy serve-api --port 8000
277
+
189
278
  # Cleanup
190
279
  understudy delete <run_id>
191
280
  understudy clear
@@ -222,11 +311,10 @@ from understudy.judges import (
222
311
  ## Report Contents
223
312
 
224
313
  The `understudy summary` command shows:
225
- - **Pass rate** - percentage of scenes that passed all expectations
226
- - **Avg turns** - average conversation length
227
- - **Tool usage** - distribution of tool calls across runs
228
- - **Terminal states** - breakdown of how conversations ended
229
- - **Agents** - which agents were invoked
314
+ - **Pass rate** — percentage of scenes that passed all expectations
315
+ - **Avg turns** — average conversation length
316
+ - **Tool usage** — distribution of tool calls across runs
317
+ - **Agents** — which agents were invoked
230
318
 
231
319
  The HTML report (`understudy report`) includes:
232
320
  - All metrics above
@@ -241,6 +329,7 @@ See the [full documentation](https://gojiplus.github.io/understudy) for:
241
329
  - [Installation guide](https://gojiplus.github.io/understudy/installation.html)
242
330
  - [Writing scenes](https://gojiplus.github.io/understudy/tutorial/scenes.html)
243
331
  - [ADK integration](https://gojiplus.github.io/understudy/adk-integration.html)
332
+ - [LangGraph integration](https://gojiplus.github.io/understudy/langgraph-integration.html)
244
333
  - [HTTP client for deployed agents](https://gojiplus.github.io/understudy/tutorial/http.html)
245
334
  - [API reference](https://gojiplus.github.io/understudy/api/index.html)
246
335
 
@@ -1,13 +1,13 @@
1
1
  ## understudy: Scenario Testing for AI Agents
2
2
 
3
3
  [![PyPI version](https://badge.fury.io/py/understudy.svg)](https://badge.fury.io/py/understudy)
4
- [![Downloads](https://pepy.tech/badge/understudy)](https://pepy.tech/project/understudy)
4
+ [![PyPI Downloads](https://static.pepy.tech/personalized-badge/understudy?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/project/understudy)
5
5
  [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
6
6
  [![Documentation](https://github.com/gojiplus/understudy/actions/workflows/docs.yml/badge.svg)](https://gojiplus.github.io/understudy/)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
8
 
9
9
 
10
- Understudy is a scenario-driven testing framework for AI agents that simulates realistic multi-turn users, runs those scenes against an agent through a simple app adapter, records a structured execution trace of messages, tool calls, handoffs, and terminal states, and then evaluates behavior with deterministic checks, optional LLM judges, and run reports.
10
+ Understudy is a scenario-driven testing framework for AI agents that simulates realistic multi-turn users, runs those scenes against an agent through a simple app adapter, records a structured execution trace of messages, tool calls, and handoffs, and then evaluates behavior with deterministic checks, optional LLM judges, and run reports.
11
11
 
12
12
  ## How It Works
13
13
 
@@ -18,12 +18,29 @@ Testing with understudy is **4 steps**:
18
18
  3. **Write scenes** — YAML files defining what the simulated user wants and what you expect
19
19
  4. **Run and assert** — Execute simulations, check traces, generate reports
20
20
 
21
- The key insight: **assert against the trace, not the prose**. Don't check what the agent said—check what it did (tool calls, terminal state).
21
+ The key insight: **assert against the trace, not the prose**. Don't check what the agent said—check what it did (tool calls).
22
+
23
+ ## Two Evaluation Paradigms
24
+
25
+ ### Conversational Agent Evaluation
26
+ Simulate multi-turn conversations with personas to test dialogue agents.
27
+ - Use case: Customer service bots, assistants, chatbots
28
+ - Assert on tool calls: `trace.called("tool_name")`
29
+
30
+ ### Agentic Flow Evaluation
31
+ Evaluate autonomous agents executing multi-step tasks.
32
+ - Use case: Code agents, research agents, task automation
33
+ - Assert on actions: `trace.performed("action")`
34
+
35
+ See [examples/README.md](examples/README.md) for complete examples of both paradigms.
22
36
 
23
37
  **See real examples:**
24
- - [Example scene](https://github.com/gojiplus/understudy/blob/main/example/scenes/return_eligible_backpack.yaml) — YAML defining a test scenario
25
- - [Test file](https://github.com/gojiplus/understudy/blob/main/example/test_returns.py) — pytest assertions against traces
26
- - [Sample report](https://htmlpreview.github.io/?https://github.com/gojiplus/understudy/blob/main/example/sample_report.html) — HTML output from `understudy report`
38
+ - [Example scene](https://github.com/gojiplus/understudy/blob/main/examples/scenes/return_eligible_backpack.yaml) — YAML defining a test scenario
39
+ - [ADK test file](https://github.com/gojiplus/understudy/blob/main/examples/adk/test_adk_returns.py) — pytest assertions against traces
40
+ - [LangGraph test file](https://github.com/gojiplus/understudy/blob/main/examples/langgraph/test_langgraph_returns.py) — same tests, different framework
41
+ - [Agentic test file](https://github.com/gojiplus/understudy/blob/main/examples/agentic/test_agentic.py) — agentic flow evaluation
42
+ - [Agentic scene](https://github.com/gojiplus/understudy/blob/main/examples/agentic_scenes/code_review_task.yaml) — task-based scenario
43
+ - [Example report](https://htmlpreview.github.io/?https://github.com/gojiplus/understudy/blob/main/examples/langgraph/report/index.html) — HTML report with metrics and transcripts
27
44
 
28
45
  ## Installation
29
46
 
@@ -81,21 +98,21 @@ expectations:
81
98
  required_tools:
82
99
  - lookup_order
83
100
  - create_return
84
- allowed_terminal_states:
85
- - return_created
101
+ forbidden_tools:
102
+ - issue_refund
86
103
  ```
87
104
 
88
105
  ### 4. Run simulation
89
106
 
90
107
  ```python
91
- from understudy import Scene, run, check
108
+ from understudy import Scene, run
92
109
 
93
110
  scene = Scene.from_file("scenes/return_backpack.yaml")
94
111
  trace = run(app, scene, mocks=mocks)
95
112
 
96
113
  assert trace.called("lookup_order")
97
114
  assert trace.called("create_return")
98
- assert trace.terminal_state == "return_created"
115
+ assert not trace.called("issue_refund")
99
116
  ```
100
117
 
101
118
  Or with pytest (define `app` and `mocks` fixtures in conftest.py):
@@ -104,9 +121,9 @@ Or with pytest (define `app` and `mocks` fixtures in conftest.py):
104
121
  pytest test_returns.py -v
105
122
  ```
106
123
 
107
- ## Suites with Tags
124
+ ## Suites and Batch Runs
108
125
 
109
- Run multiple scenes and tag runs for comparison:
126
+ Run multiple scenes with multiple simulations per scene:
110
127
 
111
128
  ```python
112
129
  from understudy import Suite, RunStorage
@@ -114,33 +131,98 @@ from understudy import Suite, RunStorage
114
131
  suite = Suite.from_directory("scenes/")
115
132
  storage = RunStorage()
116
133
 
117
- # Tag runs for later comparison
118
- results = suite.run(app, mocks=mocks, storage=storage, tags={"version": "v1"})
134
+ # Run each scene 3 times and tag for comparison
135
+ results = suite.run(
136
+ app,
137
+ mocks=mocks,
138
+ storage=storage,
139
+ n_sims=3,
140
+ tags={"version": "v1"},
141
+ )
119
142
  print(f"{results.pass_count}/{len(results.results)} passed")
120
143
  ```
121
144
 
145
+ ## Simulation and Evaluation
146
+
147
+ Understudy separates simulation (generating traces) from evaluation (checking traces). Use together or separately:
148
+
149
+ ### Combined (most common)
150
+
151
+ ```bash
152
+ understudy run \
153
+ --app mymodule:agent_app \
154
+ --scene ./scenes/ \
155
+ --n-sims 3 \
156
+ --junit results.xml
157
+ ```
158
+
159
+ ### Separate workflows
160
+
161
+ Generate traces only:
162
+
163
+ ```bash
164
+ understudy simulate \
165
+ --app mymodule:agent_app \
166
+ --scenes ./scenes/ \
167
+ --output ./traces/ \
168
+ --n-sims 3
169
+ ```
170
+
171
+ Evaluate existing traces:
172
+
173
+ ```bash
174
+ understudy evaluate \
175
+ --traces ./traces/ \
176
+ --output ./results/ \
177
+ --junit results.xml
178
+ ```
179
+
180
+ Python API:
181
+
182
+ ```python
183
+ from understudy import simulate_batch, evaluate_batch
184
+
185
+ # Generate traces
186
+ traces = simulate_batch(
187
+ app=agent_app,
188
+ scenes="./scenes/",
189
+ n_sims=3,
190
+ output="./traces/",
191
+ )
192
+
193
+ # Evaluate later
194
+ results = evaluate_batch(
195
+ traces="./traces/",
196
+ output="./results/",
197
+ )
198
+ ```
199
+
122
200
  ## CLI Commands
123
201
 
124
202
  ```bash
125
- # List runs (shows tags)
126
- understudy list
203
+ # Run simulations
204
+ understudy run --app mymodule:app --scene ./scenes/
205
+ understudy simulate --app mymodule:app --scenes ./scenes/
206
+ understudy evaluate --traces ./traces/
127
207
 
128
- # Show run details
208
+ # View results
209
+ understudy list
129
210
  understudy show <run_id>
130
-
131
- # Aggregate metrics
132
211
  understudy summary
133
212
 
134
213
  # Compare runs by tag
135
214
  understudy compare --tag version --before v1 --after v2
136
215
 
137
- # Generate HTML reports
216
+ # Generate reports
138
217
  understudy report -o report.html
139
218
  understudy compare --tag version --before v1 --after v2 --html comparison.html
140
219
 
141
220
  # Interactive browser
142
221
  understudy serve --port 8080
143
222
 
223
+ # HTTP simulator server (for browser/UI testing)
224
+ understudy serve-api --port 8000
225
+
144
226
  # Cleanup
145
227
  understudy delete <run_id>
146
228
  understudy clear
@@ -177,11 +259,10 @@ from understudy.judges import (
177
259
  ## Report Contents
178
260
 
179
261
  The `understudy summary` command shows:
180
- - **Pass rate** - percentage of scenes that passed all expectations
181
- - **Avg turns** - average conversation length
182
- - **Tool usage** - distribution of tool calls across runs
183
- - **Terminal states** - breakdown of how conversations ended
184
- - **Agents** - which agents were invoked
262
+ - **Pass rate** — percentage of scenes that passed all expectations
263
+ - **Avg turns** — average conversation length
264
+ - **Tool usage** — distribution of tool calls across runs
265
+ - **Agents** — which agents were invoked
185
266
 
186
267
  The HTML report (`understudy report`) includes:
187
268
  - All metrics above
@@ -196,6 +277,7 @@ See the [full documentation](https://gojiplus.github.io/understudy) for:
196
277
  - [Installation guide](https://gojiplus.github.io/understudy/installation.html)
197
278
  - [Writing scenes](https://gojiplus.github.io/understudy/tutorial/scenes.html)
198
279
  - [ADK integration](https://gojiplus.github.io/understudy/adk-integration.html)
280
+ - [LangGraph integration](https://gojiplus.github.io/understudy/langgraph-integration.html)
199
281
  - [HTTP client for deployed agents](https://gojiplus.github.io/understudy/tutorial/http.html)
200
282
  - [API reference](https://gojiplus.github.io/understudy/api/index.html)
201
283
 
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "understudy"
7
- version = "0.3.0"
7
+ version = "0.5.0"
8
8
  description = "Simulation and trace-based evaluation for agentic systems"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -31,6 +31,8 @@ dependencies = [
31
31
  [project.optional-dependencies]
32
32
  adk = ["google-adk>=1.0", "python-dotenv>=1.0"]
33
33
  http = ["httpx>=0.27"]
34
+ langgraph = ["langgraph>=0.2", "langchain-core>=0.3", "langchain-openai>=0.3"]
35
+ server = ["fastapi>=0.115", "uvicorn>=0.32"]
34
36
  reports = ["jinja2>=3.0", "click>=8.0"]
35
37
  docs = [
36
38
  "sphinx>=7.0",
@@ -38,7 +40,7 @@ docs = [
38
40
  "myst-parser>=3.0",
39
41
  "sphinx-autodoc-typehints>=2.0",
40
42
  ]
41
- all = ["understudy[adk,http,reports]"]
43
+ all = ["understudy[adk,http,langgraph,server,reports]"]
42
44
  dev = [
43
45
  "understudy[all,docs]",
44
46
  "pytest>=8.0",
@@ -50,6 +52,9 @@ dev = [
50
52
  [project.scripts]
51
53
  understudy = "understudy.cli:main"
52
54
 
55
+ [project.entry-points.pytest11]
56
+ understudy = "understudy.pytest_plugin"
57
+
53
58
  [project.urls]
54
59
  Homepage = "https://github.com/gojiplus/understudy"
55
60
  Documentation = "https://gojiplus.github.io/understudy"
@@ -0,0 +1,149 @@
1
+ """understudy: simulation and trace-based evaluation for agentic systems.
2
+
3
+ The simulated user is an understudy standing in for a real user.
4
+ You write scenes, run rehearsals, and check the performance —
5
+ not by reading the script, but by inspecting what actually happened.
6
+ """
7
+
8
+ from importlib.metadata import version
9
+
10
+ from . import agentic
11
+ from .agentic import (
12
+ AgenticApp,
13
+ AgenticCheckItem,
14
+ AgenticCheckResult,
15
+ AgenticExpectations,
16
+ AgenticMetricResult,
17
+ AgenticScene,
18
+ AgenticTrace,
19
+ Artifact,
20
+ Step,
21
+ StepResult,
22
+ Task,
23
+ check_agentic,
24
+ run_agentic,
25
+ )
26
+ from .check import CheckItem, CheckResult, EvaluationResult, check, evaluate, evaluate_batch
27
+ from .compare import ComparisonResult, SceneComparison, compare_runs
28
+ from .diff import ToolCallDiff, TraceDiff, diff_tool_sequences, diff_traces
29
+ from .judge_backends import CallbackBackend, JudgeBackend, LiteLLMBackend
30
+ from .judges import FailureAnalysis, FailureAnalyzer, Judge, JudgeResult
31
+ from .metrics import MetricRegistry, MetricResult
32
+ from .mocks import MockToolkit, ToolError
33
+ from .models import Expectations, Persona, PersonaPreset, Scene
34
+ from .prompts import (
35
+ ADVERSARIAL_ROBUSTNESS,
36
+ FACTUAL_GROUNDING,
37
+ INSTRUCTION_FOLLOWING,
38
+ POLICY_COMPLIANCE,
39
+ TASK_COMPLETION,
40
+ TONE_EMPATHY,
41
+ TOOL_USAGE_CORRECTNESS,
42
+ )
43
+ from .pytest_plugin import AssertionHelpers
44
+ from .replay import ReplayResult, create_replay_scene, load_trace, replay
45
+ from .runner import AgentApp, AgentResponse, run, simulate, simulate_batch
46
+ from .simulator import Simulator, SimulatorBackend
47
+ from .storage import EvaluationStorage, RunStorage, TraceStorage
48
+ from .suite import SceneResult, Suite, SuiteResults
49
+ from .trace import AgentTransfer, StateSnapshot, ToolCall, Trace, TraceMetrics, Turn, TurnMetrics
50
+ from .validation import SceneValidationError
51
+
52
+ __version__ = version("understudy")
53
+
54
+ __all__ = [
55
+ # models
56
+ "Scene",
57
+ "Persona",
58
+ "PersonaPreset",
59
+ "Expectations",
60
+ # trace
61
+ "Trace",
62
+ "Turn",
63
+ "ToolCall",
64
+ "AgentTransfer",
65
+ "TraceMetrics",
66
+ "TurnMetrics",
67
+ "StateSnapshot",
68
+ # metrics
69
+ "MetricRegistry",
70
+ "MetricResult",
71
+ # runner / simulate
72
+ "run",
73
+ "simulate",
74
+ "simulate_batch",
75
+ "AgentApp",
76
+ "AgentResponse",
77
+ "LiteLLMBackend",
78
+ # check / evaluate
79
+ "check",
80
+ "evaluate",
81
+ "evaluate_batch",
82
+ "CheckResult",
83
+ "CheckItem",
84
+ "EvaluationResult",
85
+ # suite
86
+ "Suite",
87
+ "SuiteResults",
88
+ "SceneResult",
89
+ # storage
90
+ "RunStorage",
91
+ "TraceStorage",
92
+ "EvaluationStorage",
93
+ # compare
94
+ "compare_runs",
95
+ "ComparisonResult",
96
+ "SceneComparison",
97
+ # diff
98
+ "diff_traces",
99
+ "diff_tool_sequences",
100
+ "TraceDiff",
101
+ "ToolCallDiff",
102
+ # replay
103
+ "replay",
104
+ "load_trace",
105
+ "create_replay_scene",
106
+ "ReplayResult",
107
+ # judges
108
+ "Judge",
109
+ "JudgeResult",
110
+ "FailureAnalyzer",
111
+ "FailureAnalysis",
112
+ # judge backends
113
+ "JudgeBackend",
114
+ "LiteLLMBackend",
115
+ "CallbackBackend",
116
+ # mocks
117
+ "MockToolkit",
118
+ "ToolError",
119
+ # validation
120
+ "SceneValidationError",
121
+ # pytest
122
+ "AssertionHelpers",
123
+ # simulator
124
+ "Simulator",
125
+ "SimulatorBackend",
126
+ # rubrics
127
+ "TOOL_USAGE_CORRECTNESS",
128
+ "POLICY_COMPLIANCE",
129
+ "TONE_EMPATHY",
130
+ "ADVERSARIAL_ROBUSTNESS",
131
+ "TASK_COMPLETION",
132
+ "FACTUAL_GROUNDING",
133
+ "INSTRUCTION_FOLLOWING",
134
+ # agentic module
135
+ "agentic",
136
+ "AgenticApp",
137
+ "AgenticCheckItem",
138
+ "AgenticCheckResult",
139
+ "AgenticExpectations",
140
+ "AgenticMetricResult",
141
+ "AgenticScene",
142
+ "AgenticTrace",
143
+ "Artifact",
144
+ "Step",
145
+ "StepResult",
146
+ "Task",
147
+ "check_agentic",
148
+ "run_agentic",
149
+ ]