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.
- {understudy-0.3.0 → understudy-0.5.0}/PKG-INFO +116 -27
- {understudy-0.3.0 → understudy-0.5.0}/README.md +107 -25
- {understudy-0.3.0 → understudy-0.5.0}/pyproject.toml +7 -2
- understudy-0.5.0/src/understudy/__init__.py +149 -0
- {understudy-0.3.0 → understudy-0.5.0}/src/understudy/adk/__init__.py +30 -10
- understudy-0.5.0/src/understudy/agentic/__init__.py +53 -0
- understudy-0.5.0/src/understudy/agentic/check.py +198 -0
- understudy-0.5.0/src/understudy/agentic/metrics.py +170 -0
- understudy-0.5.0/src/understudy/agentic/models.py +264 -0
- understudy-0.5.0/src/understudy/agentic/runner.py +150 -0
- understudy-0.5.0/src/understudy/batch.py +53 -0
- understudy-0.5.0/src/understudy/check.py +313 -0
- understudy-0.5.0/src/understudy/cli.py +1398 -0
- understudy-0.5.0/src/understudy/diff.py +233 -0
- {understudy-0.3.0 → understudy-0.5.0}/src/understudy/http/__init__.py +0 -6
- understudy-0.5.0/src/understudy/judge_backends.py +147 -0
- understudy-0.5.0/src/understudy/judges.py +261 -0
- understudy-0.5.0/src/understudy/langgraph/__init__.py +162 -0
- understudy-0.5.0/src/understudy/langgraph/tools.py +100 -0
- understudy-0.5.0/src/understudy/metrics/__init__.py +16 -0
- understudy-0.5.0/src/understudy/metrics/builtins.py +112 -0
- understudy-0.5.0/src/understudy/metrics/registry.py +90 -0
- understudy-0.5.0/src/understudy/metrics/templates/clarity.jinja +13 -0
- understudy-0.5.0/src/understudy/metrics/templates/policy_compliance.jinja +17 -0
- understudy-0.5.0/src/understudy/metrics/templates/tone_empathy.jinja +13 -0
- {understudy-0.3.0 → understudy-0.5.0}/src/understudy/mocks.py +3 -5
- {understudy-0.3.0 → understudy-0.5.0}/src/understudy/models.py +53 -12
- understudy-0.5.0/src/understudy/pytest_plugin.py +221 -0
- understudy-0.5.0/src/understudy/replay.py +196 -0
- {understudy-0.3.0 → understudy-0.5.0}/src/understudy/reports.py +74 -69
- understudy-0.5.0/src/understudy/runner.py +358 -0
- understudy-0.5.0/src/understudy/server/__init__.py +142 -0
- understudy-0.5.0/src/understudy/server/models.py +123 -0
- understudy-0.5.0/src/understudy/server/sessions.py +192 -0
- understudy-0.5.0/src/understudy/server/ui_simulator.py +160 -0
- understudy-0.5.0/src/understudy/storage.py +535 -0
- {understudy-0.3.0 → understudy-0.5.0}/src/understudy/suite.py +67 -45
- {understudy-0.3.0 → understudy-0.5.0}/src/understudy/templates/base.html +1 -5
- understudy-0.5.0/src/understudy/templates/index.html +271 -0
- understudy-0.5.0/src/understudy/templates/init/conftest.py.template +70 -0
- understudy-0.5.0/src/understudy/templates/init/example_scene.yaml +36 -0
- understudy-0.5.0/src/understudy/templates/init/test_agent.py.template +43 -0
- understudy-0.5.0/src/understudy/templates/init/understudy.yaml.template +16 -0
- {understudy-0.3.0 → understudy-0.5.0}/src/understudy/templates/run_detail.html +53 -1
- {understudy-0.3.0 → understudy-0.5.0}/src/understudy/trace.py +49 -20
- understudy-0.5.0/src/understudy/validation.py +173 -0
- understudy-0.3.0/src/understudy/__init__.py +0 -79
- understudy-0.3.0/src/understudy/check.py +0 -138
- understudy-0.3.0/src/understudy/cli.py +0 -379
- understudy-0.3.0/src/understudy/judges.py +0 -105
- understudy-0.3.0/src/understudy/loaders/__init__.py +0 -0
- understudy-0.3.0/src/understudy/personas/__init__.py +0 -0
- understudy-0.3.0/src/understudy/runner.py +0 -183
- understudy-0.3.0/src/understudy/storage.py +0 -211
- understudy-0.3.0/src/understudy/templates/index.html +0 -116
- understudy-0.3.0/src/understudy/templates/metrics.html +0 -128
- {understudy-0.3.0 → understudy-0.5.0}/src/understudy/compare.py +0 -0
- {understudy-0.3.0 → understudy-0.5.0}/src/understudy/prompts/__init__.py +0 -0
- {understudy-0.3.0 → understudy-0.5.0}/src/understudy/prompts/rubrics.py +0 -0
- {understudy-0.3.0 → understudy-0.5.0}/src/understudy/simulator.py +0 -0
- {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
|
+
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
|
[](https://badge.fury.io/py/understudy)
|
|
49
|
-
[](https://pepy.tech/project/understudy)
|
|
56
|
+
[](https://pepy.tech/project/understudy)
|
|
50
57
|
[](https://www.python.org/downloads/)
|
|
51
58
|
[](https://gojiplus.github.io/understudy/)
|
|
52
59
|
[](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,
|
|
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
|
|
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/
|
|
70
|
-
- [
|
|
71
|
-
- [
|
|
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
|
-
|
|
130
|
-
-
|
|
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
|
|
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.
|
|
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
|
|
176
|
+
## Suites and Batch Runs
|
|
153
177
|
|
|
154
|
-
Run multiple scenes
|
|
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
|
-
#
|
|
163
|
-
results = suite.run(
|
|
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
|
-
#
|
|
171
|
-
understudy
|
|
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
|
-
#
|
|
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
|
|
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**
|
|
226
|
-
- **Avg turns**
|
|
227
|
-
- **Tool usage**
|
|
228
|
-
- **
|
|
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
|
[](https://badge.fury.io/py/understudy)
|
|
4
|
-
[](https://pepy.tech/project/understudy)
|
|
4
|
+
[](https://pepy.tech/project/understudy)
|
|
5
5
|
[](https://www.python.org/downloads/)
|
|
6
6
|
[](https://gojiplus.github.io/understudy/)
|
|
7
7
|
[](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,
|
|
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
|
|
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/
|
|
25
|
-
- [
|
|
26
|
-
- [
|
|
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
|
-
|
|
85
|
-
-
|
|
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
|
|
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.
|
|
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
|
|
124
|
+
## Suites and Batch Runs
|
|
108
125
|
|
|
109
|
-
Run multiple scenes
|
|
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
|
-
#
|
|
118
|
-
results = suite.run(
|
|
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
|
-
#
|
|
126
|
-
understudy
|
|
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
|
-
#
|
|
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
|
|
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**
|
|
181
|
-
- **Avg turns**
|
|
182
|
-
- **Tool usage**
|
|
183
|
-
- **
|
|
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.
|
|
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
|
+
]
|