understudy 0.1.0__tar.gz → 0.4.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.1.0 → understudy-0.4.0}/PKG-INFO +135 -26
- understudy-0.4.0/README.md +269 -0
- {understudy-0.1.0 → understudy-0.4.0}/pyproject.toml +7 -4
- {understudy-0.1.0 → understudy-0.4.0}/src/understudy/__init__.py +36 -8
- {understudy-0.1.0 → understudy-0.4.0}/src/understudy/adk/__init__.py +73 -13
- understudy-0.4.0/src/understudy/check.py +296 -0
- understudy-0.4.0/src/understudy/cli.py +837 -0
- understudy-0.4.0/src/understudy/compare.py +186 -0
- {understudy-0.1.0 → understudy-0.4.0}/src/understudy/http/__init__.py +0 -6
- understudy-0.4.0/src/understudy/judges.py +214 -0
- understudy-0.4.0/src/understudy/langgraph/__init__.py +162 -0
- understudy-0.4.0/src/understudy/langgraph/tools.py +100 -0
- understudy-0.4.0/src/understudy/metrics/__init__.py +16 -0
- understudy-0.4.0/src/understudy/metrics/builtins.py +112 -0
- understudy-0.4.0/src/understudy/metrics/registry.py +90 -0
- understudy-0.4.0/src/understudy/metrics/templates/clarity.jinja +13 -0
- understudy-0.4.0/src/understudy/metrics/templates/policy_compliance.jinja +17 -0
- understudy-0.4.0/src/understudy/metrics/templates/tone_empathy.jinja +13 -0
- {understudy-0.1.0 → understudy-0.4.0}/src/understudy/mocks.py +3 -5
- {understudy-0.1.0 → understudy-0.4.0}/src/understudy/models.py +4 -2
- understudy-0.4.0/src/understudy/reports.py +281 -0
- understudy-0.4.0/src/understudy/runner.py +337 -0
- understudy-0.4.0/src/understudy/server/__init__.py +142 -0
- understudy-0.4.0/src/understudy/server/models.py +123 -0
- understudy-0.4.0/src/understudy/server/sessions.py +192 -0
- understudy-0.4.0/src/understudy/server/ui_simulator.py +160 -0
- understudy-0.4.0/src/understudy/storage.py +490 -0
- {understudy-0.1.0 → understudy-0.4.0}/src/understudy/suite.py +28 -8
- {understudy-0.1.0 → understudy-0.4.0}/src/understudy/templates/base.html +1 -5
- understudy-0.4.0/src/understudy/templates/comparison.html +150 -0
- understudy-0.4.0/src/understudy/templates/index.html +271 -0
- {understudy-0.1.0 → understudy-0.4.0}/src/understudy/templates/run_detail.html +62 -1
- {understudy-0.1.0 → understudy-0.4.0}/src/understudy/trace.py +49 -20
- understudy-0.1.0/README.md +0 -168
- understudy-0.1.0/src/understudy/check.py +0 -138
- understudy-0.1.0/src/understudy/cli.py +0 -258
- understudy-0.1.0/src/understudy/judges.py +0 -105
- understudy-0.1.0/src/understudy/loaders/__init__.py +0 -0
- understudy-0.1.0/src/understudy/personas/__init__.py +0 -0
- understudy-0.1.0/src/understudy/reports.py +0 -191
- understudy-0.1.0/src/understudy/runner.py +0 -171
- understudy-0.1.0/src/understudy/storage.py +0 -206
- understudy-0.1.0/src/understudy/templates/index.html +0 -110
- understudy-0.1.0/src/understudy/templates/metrics.html +0 -128
- {understudy-0.1.0 → understudy-0.4.0}/src/understudy/prompts/__init__.py +0 -0
- {understudy-0.1.0 → understudy-0.4.0}/src/understudy/prompts/rubrics.py +0 -0
- {understudy-0.1.0 → understudy-0.4.0}/src/understudy/simulator.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: understudy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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
|
|
@@ -14,20 +14,27 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
14
14
|
Classifier: Topic :: Software Development :: Testing
|
|
15
15
|
Requires-Dist: pydantic>=2.0
|
|
16
16
|
Requires-Dist: pyyaml>=6.0
|
|
17
|
+
Requires-Dist: litellm>=1.50
|
|
17
18
|
Requires-Dist: google-adk>=1.0 ; extra == 'adk'
|
|
18
|
-
Requires-Dist:
|
|
19
|
+
Requires-Dist: python-dotenv>=1.0 ; extra == 'adk'
|
|
20
|
+
Requires-Dist: understudy[adk,http,langgraph,server,reports] ; extra == 'all'
|
|
19
21
|
Requires-Dist: understudy[all,docs] ; extra == 'dev'
|
|
20
22
|
Requires-Dist: pytest>=8.0 ; extra == 'dev'
|
|
21
23
|
Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest-cov>=4.0 ; extra == 'dev'
|
|
22
25
|
Requires-Dist: ruff>=0.4 ; extra == 'dev'
|
|
23
26
|
Requires-Dist: sphinx>=7.0 ; extra == 'docs'
|
|
24
27
|
Requires-Dist: furo>=2024.0 ; extra == 'docs'
|
|
25
28
|
Requires-Dist: myst-parser>=3.0 ; extra == 'docs'
|
|
26
29
|
Requires-Dist: sphinx-autodoc-typehints>=2.0 ; extra == 'docs'
|
|
27
30
|
Requires-Dist: httpx>=0.27 ; extra == 'http'
|
|
28
|
-
Requires-Dist:
|
|
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'
|
|
29
34
|
Requires-Dist: jinja2>=3.0 ; extra == 'reports'
|
|
30
35
|
Requires-Dist: click>=8.0 ; extra == 'reports'
|
|
36
|
+
Requires-Dist: fastapi>=0.115 ; extra == 'server'
|
|
37
|
+
Requires-Dist: uvicorn>=0.32 ; extra == 'server'
|
|
31
38
|
Requires-Python: >=3.12
|
|
32
39
|
Project-URL: Homepage, https://github.com/gojiplus/understudy
|
|
33
40
|
Project-URL: Documentation, https://gojiplus.github.io/understudy
|
|
@@ -38,17 +45,37 @@ Provides-Extra: all
|
|
|
38
45
|
Provides-Extra: dev
|
|
39
46
|
Provides-Extra: docs
|
|
40
47
|
Provides-Extra: http
|
|
41
|
-
Provides-Extra:
|
|
48
|
+
Provides-Extra: langgraph
|
|
42
49
|
Provides-Extra: reports
|
|
50
|
+
Provides-Extra: server
|
|
43
51
|
Description-Content-Type: text/markdown
|
|
44
52
|
|
|
45
|
-
|
|
53
|
+
## understudy: Scenario Testing for AI Agents
|
|
46
54
|
|
|
47
55
|
[](https://badge.fury.io/py/understudy)
|
|
56
|
+
[](https://pepy.tech/project/understudy)
|
|
48
57
|
[](https://www.python.org/downloads/)
|
|
58
|
+
[](https://gojiplus.github.io/understudy/)
|
|
49
59
|
[](https://opensource.org/licenses/MIT)
|
|
50
60
|
|
|
51
|
-
|
|
61
|
+
|
|
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.
|
|
63
|
+
|
|
64
|
+
## How It Works
|
|
65
|
+
|
|
66
|
+
Testing with understudy is **4 steps**:
|
|
67
|
+
|
|
68
|
+
1. **Wrap your agent** — Adapt your agent (ADK, LangGraph, HTTP) to understudy's interface
|
|
69
|
+
2. **Mock your tools** — Register handlers that return test data instead of calling real services
|
|
70
|
+
3. **Write scenes** — YAML files defining what the simulated user wants and what you expect
|
|
71
|
+
4. **Run and assert** — Execute simulations, check traces, generate reports
|
|
72
|
+
|
|
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
|
+
**See real examples:**
|
|
76
|
+
- [Example scene](https://github.com/gojiplus/understudy/blob/main/example/scenes/return_eligible_backpack.yaml) — YAML defining a test scenario
|
|
77
|
+
- [ADK test file](https://github.com/gojiplus/understudy/blob/main/example/adk/test_returns.py) — pytest assertions against traces
|
|
78
|
+
- [LangGraph test file](https://github.com/gojiplus/understudy/blob/main/example/langgraph/test_returns.py) — same tests, different framework
|
|
52
79
|
|
|
53
80
|
## Installation
|
|
54
81
|
|
|
@@ -106,21 +133,21 @@ expectations:
|
|
|
106
133
|
required_tools:
|
|
107
134
|
- lookup_order
|
|
108
135
|
- create_return
|
|
109
|
-
|
|
110
|
-
-
|
|
136
|
+
forbidden_tools:
|
|
137
|
+
- issue_refund
|
|
111
138
|
```
|
|
112
139
|
|
|
113
140
|
### 4. Run simulation
|
|
114
141
|
|
|
115
142
|
```python
|
|
116
|
-
from understudy import Scene, run
|
|
143
|
+
from understudy import Scene, run
|
|
117
144
|
|
|
118
145
|
scene = Scene.from_file("scenes/return_backpack.yaml")
|
|
119
146
|
trace = run(app, scene, mocks=mocks)
|
|
120
147
|
|
|
121
148
|
assert trace.called("lookup_order")
|
|
122
149
|
assert trace.called("create_return")
|
|
123
|
-
assert trace.
|
|
150
|
+
assert not trace.called("issue_refund")
|
|
124
151
|
```
|
|
125
152
|
|
|
126
153
|
Or with pytest (define `app` and `mocks` fixtures in conftest.py):
|
|
@@ -129,27 +156,109 @@ Or with pytest (define `app` and `mocks` fixtures in conftest.py):
|
|
|
129
156
|
pytest test_returns.py -v
|
|
130
157
|
```
|
|
131
158
|
|
|
132
|
-
##
|
|
159
|
+
## Suites and Batch Runs
|
|
160
|
+
|
|
161
|
+
Run multiple scenes with multiple simulations per scene:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
from understudy import Suite, RunStorage
|
|
165
|
+
|
|
166
|
+
suite = Suite.from_directory("scenes/")
|
|
167
|
+
storage = RunStorage()
|
|
168
|
+
|
|
169
|
+
# Run each scene 3 times and tag for comparison
|
|
170
|
+
results = suite.run(
|
|
171
|
+
app,
|
|
172
|
+
mocks=mocks,
|
|
173
|
+
storage=storage,
|
|
174
|
+
n_sims=3,
|
|
175
|
+
tags={"version": "v1"},
|
|
176
|
+
)
|
|
177
|
+
print(f"{results.pass_count}/{len(results.results)} passed")
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Simulation and Evaluation
|
|
133
181
|
|
|
134
|
-
|
|
182
|
+
Understudy separates simulation (generating traces) from evaluation (checking traces). Use together or separately:
|
|
183
|
+
|
|
184
|
+
### Combined (most common)
|
|
135
185
|
|
|
136
186
|
```bash
|
|
137
|
-
|
|
138
|
-
|
|
187
|
+
understudy run \
|
|
188
|
+
--app mymodule:agent_app \
|
|
189
|
+
--scene ./scenes/ \
|
|
190
|
+
--n-sims 3 \
|
|
191
|
+
--junit results.xml
|
|
192
|
+
```
|
|
139
193
|
|
|
140
|
-
|
|
141
|
-
|
|
194
|
+
### Separate workflows
|
|
195
|
+
|
|
196
|
+
Generate traces only:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
understudy simulate \
|
|
200
|
+
--app mymodule:agent_app \
|
|
201
|
+
--scenes ./scenes/ \
|
|
202
|
+
--output ./traces/ \
|
|
203
|
+
--n-sims 3
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Evaluate existing traces:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
understudy evaluate \
|
|
210
|
+
--traces ./traces/ \
|
|
211
|
+
--output ./results/ \
|
|
212
|
+
--junit results.xml
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Python API:
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from understudy import simulate_batch, evaluate_batch
|
|
219
|
+
|
|
220
|
+
# Generate traces
|
|
221
|
+
traces = simulate_batch(
|
|
222
|
+
app=agent_app,
|
|
223
|
+
scenes="./scenes/",
|
|
224
|
+
n_sims=3,
|
|
225
|
+
output="./traces/",
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Evaluate later
|
|
229
|
+
results = evaluate_batch(
|
|
230
|
+
traces="./traces/",
|
|
231
|
+
output="./results/",
|
|
232
|
+
)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## CLI Commands
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
# Run simulations
|
|
239
|
+
understudy run --app mymodule:app --scene ./scenes/
|
|
240
|
+
understudy simulate --app mymodule:app --scenes ./scenes/
|
|
241
|
+
understudy evaluate --traces ./traces/
|
|
142
242
|
|
|
143
|
-
#
|
|
243
|
+
# View results
|
|
244
|
+
understudy list
|
|
144
245
|
understudy show <run_id>
|
|
246
|
+
understudy summary
|
|
145
247
|
|
|
146
|
-
#
|
|
147
|
-
understudy
|
|
248
|
+
# Compare runs by tag
|
|
249
|
+
understudy compare --tag version --before v1 --after v2
|
|
148
250
|
|
|
149
|
-
#
|
|
251
|
+
# Generate reports
|
|
252
|
+
understudy report -o report.html
|
|
253
|
+
understudy compare --tag version --before v1 --after v2 --html comparison.html
|
|
254
|
+
|
|
255
|
+
# Interactive browser
|
|
150
256
|
understudy serve --port 8080
|
|
151
257
|
|
|
152
|
-
#
|
|
258
|
+
# HTTP simulator server (for browser/UI testing)
|
|
259
|
+
understudy serve-api --port 8000
|
|
260
|
+
|
|
261
|
+
# Cleanup
|
|
153
262
|
understudy delete <run_id>
|
|
154
263
|
understudy clear
|
|
155
264
|
```
|
|
@@ -185,11 +294,10 @@ from understudy.judges import (
|
|
|
185
294
|
## Report Contents
|
|
186
295
|
|
|
187
296
|
The `understudy summary` command shows:
|
|
188
|
-
- **Pass rate**
|
|
189
|
-
- **Avg turns**
|
|
190
|
-
- **Tool usage**
|
|
191
|
-
- **
|
|
192
|
-
- **Agents** - which agents were invoked
|
|
297
|
+
- **Pass rate** — percentage of scenes that passed all expectations
|
|
298
|
+
- **Avg turns** — average conversation length
|
|
299
|
+
- **Tool usage** — distribution of tool calls across runs
|
|
300
|
+
- **Agents** — which agents were invoked
|
|
193
301
|
|
|
194
302
|
The HTML report (`understudy report`) includes:
|
|
195
303
|
- All metrics above
|
|
@@ -204,6 +312,7 @@ See the [full documentation](https://gojiplus.github.io/understudy) for:
|
|
|
204
312
|
- [Installation guide](https://gojiplus.github.io/understudy/installation.html)
|
|
205
313
|
- [Writing scenes](https://gojiplus.github.io/understudy/tutorial/scenes.html)
|
|
206
314
|
- [ADK integration](https://gojiplus.github.io/understudy/adk-integration.html)
|
|
315
|
+
- [LangGraph integration](https://gojiplus.github.io/understudy/langgraph-integration.html)
|
|
207
316
|
- [HTTP client for deployed agents](https://gojiplus.github.io/understudy/tutorial/http.html)
|
|
208
317
|
- [API reference](https://gojiplus.github.io/understudy/api/index.html)
|
|
209
318
|
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
## understudy: Scenario Testing for AI Agents
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/py/understudy)
|
|
4
|
+
[](https://pepy.tech/project/understudy)
|
|
5
|
+
[](https://www.python.org/downloads/)
|
|
6
|
+
[](https://gojiplus.github.io/understudy/)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
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, and handoffs, and then evaluates behavior with deterministic checks, optional LLM judges, and run reports.
|
|
11
|
+
|
|
12
|
+
## How It Works
|
|
13
|
+
|
|
14
|
+
Testing with understudy is **4 steps**:
|
|
15
|
+
|
|
16
|
+
1. **Wrap your agent** — Adapt your agent (ADK, LangGraph, HTTP) to understudy's interface
|
|
17
|
+
2. **Mock your tools** — Register handlers that return test data instead of calling real services
|
|
18
|
+
3. **Write scenes** — YAML files defining what the simulated user wants and what you expect
|
|
19
|
+
4. **Run and assert** — Execute simulations, check traces, generate reports
|
|
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).
|
|
22
|
+
|
|
23
|
+
**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
|
+
- [ADK test file](https://github.com/gojiplus/understudy/blob/main/example/adk/test_returns.py) — pytest assertions against traces
|
|
26
|
+
- [LangGraph test file](https://github.com/gojiplus/understudy/blob/main/example/langgraph/test_returns.py) — same tests, different framework
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install understudy[all]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
### 1. Wrap your agent
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from understudy.adk import ADKApp
|
|
40
|
+
from my_agent import agent
|
|
41
|
+
|
|
42
|
+
app = ADKApp(agent=agent)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 2. Mock your tools
|
|
46
|
+
|
|
47
|
+
Your agent has tools that call external services. Mock them for testing:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from understudy.mocks import MockToolkit
|
|
51
|
+
|
|
52
|
+
mocks = MockToolkit()
|
|
53
|
+
|
|
54
|
+
@mocks.handle("lookup_order")
|
|
55
|
+
def lookup_order(order_id: str) -> dict:
|
|
56
|
+
return {"order_id": order_id, "items": [...], "status": "delivered"}
|
|
57
|
+
|
|
58
|
+
@mocks.handle("create_return")
|
|
59
|
+
def create_return(order_id: str, item_sku: str, reason: str) -> dict:
|
|
60
|
+
return {"return_id": "RET-001", "status": "created"}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 3. Write a scene
|
|
64
|
+
|
|
65
|
+
Create `scenes/return_backpack.yaml`:
|
|
66
|
+
|
|
67
|
+
```yaml
|
|
68
|
+
id: return_eligible_backpack
|
|
69
|
+
description: Customer wants to return a backpack
|
|
70
|
+
|
|
71
|
+
starting_prompt: "I'd like to return an item please."
|
|
72
|
+
conversation_plan: |
|
|
73
|
+
Goal: Return the hiking backpack from order ORD-10031.
|
|
74
|
+
- Provide order ID when asked
|
|
75
|
+
- Return reason: too small
|
|
76
|
+
|
|
77
|
+
persona: cooperative
|
|
78
|
+
max_turns: 15
|
|
79
|
+
|
|
80
|
+
expectations:
|
|
81
|
+
required_tools:
|
|
82
|
+
- lookup_order
|
|
83
|
+
- create_return
|
|
84
|
+
forbidden_tools:
|
|
85
|
+
- issue_refund
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 4. Run simulation
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from understudy import Scene, run
|
|
92
|
+
|
|
93
|
+
scene = Scene.from_file("scenes/return_backpack.yaml")
|
|
94
|
+
trace = run(app, scene, mocks=mocks)
|
|
95
|
+
|
|
96
|
+
assert trace.called("lookup_order")
|
|
97
|
+
assert trace.called("create_return")
|
|
98
|
+
assert not trace.called("issue_refund")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Or with pytest (define `app` and `mocks` fixtures in conftest.py):
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
pytest test_returns.py -v
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Suites and Batch Runs
|
|
108
|
+
|
|
109
|
+
Run multiple scenes with multiple simulations per scene:
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from understudy import Suite, RunStorage
|
|
113
|
+
|
|
114
|
+
suite = Suite.from_directory("scenes/")
|
|
115
|
+
storage = RunStorage()
|
|
116
|
+
|
|
117
|
+
# Run each scene 3 times and tag for comparison
|
|
118
|
+
results = suite.run(
|
|
119
|
+
app,
|
|
120
|
+
mocks=mocks,
|
|
121
|
+
storage=storage,
|
|
122
|
+
n_sims=3,
|
|
123
|
+
tags={"version": "v1"},
|
|
124
|
+
)
|
|
125
|
+
print(f"{results.pass_count}/{len(results.results)} passed")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Simulation and Evaluation
|
|
129
|
+
|
|
130
|
+
Understudy separates simulation (generating traces) from evaluation (checking traces). Use together or separately:
|
|
131
|
+
|
|
132
|
+
### Combined (most common)
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
understudy run \
|
|
136
|
+
--app mymodule:agent_app \
|
|
137
|
+
--scene ./scenes/ \
|
|
138
|
+
--n-sims 3 \
|
|
139
|
+
--junit results.xml
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Separate workflows
|
|
143
|
+
|
|
144
|
+
Generate traces only:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
understudy simulate \
|
|
148
|
+
--app mymodule:agent_app \
|
|
149
|
+
--scenes ./scenes/ \
|
|
150
|
+
--output ./traces/ \
|
|
151
|
+
--n-sims 3
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Evaluate existing traces:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
understudy evaluate \
|
|
158
|
+
--traces ./traces/ \
|
|
159
|
+
--output ./results/ \
|
|
160
|
+
--junit results.xml
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Python API:
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
from understudy import simulate_batch, evaluate_batch
|
|
167
|
+
|
|
168
|
+
# Generate traces
|
|
169
|
+
traces = simulate_batch(
|
|
170
|
+
app=agent_app,
|
|
171
|
+
scenes="./scenes/",
|
|
172
|
+
n_sims=3,
|
|
173
|
+
output="./traces/",
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Evaluate later
|
|
177
|
+
results = evaluate_batch(
|
|
178
|
+
traces="./traces/",
|
|
179
|
+
output="./results/",
|
|
180
|
+
)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## CLI Commands
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
# Run simulations
|
|
187
|
+
understudy run --app mymodule:app --scene ./scenes/
|
|
188
|
+
understudy simulate --app mymodule:app --scenes ./scenes/
|
|
189
|
+
understudy evaluate --traces ./traces/
|
|
190
|
+
|
|
191
|
+
# View results
|
|
192
|
+
understudy list
|
|
193
|
+
understudy show <run_id>
|
|
194
|
+
understudy summary
|
|
195
|
+
|
|
196
|
+
# Compare runs by tag
|
|
197
|
+
understudy compare --tag version --before v1 --after v2
|
|
198
|
+
|
|
199
|
+
# Generate reports
|
|
200
|
+
understudy report -o report.html
|
|
201
|
+
understudy compare --tag version --before v1 --after v2 --html comparison.html
|
|
202
|
+
|
|
203
|
+
# Interactive browser
|
|
204
|
+
understudy serve --port 8080
|
|
205
|
+
|
|
206
|
+
# HTTP simulator server (for browser/UI testing)
|
|
207
|
+
understudy serve-api --port 8000
|
|
208
|
+
|
|
209
|
+
# Cleanup
|
|
210
|
+
understudy delete <run_id>
|
|
211
|
+
understudy clear
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## LLM Judges
|
|
215
|
+
|
|
216
|
+
For qualities that can't be checked deterministically:
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
from understudy.judges import Judge
|
|
220
|
+
|
|
221
|
+
empathy_judge = Judge(
|
|
222
|
+
rubric="The agent acknowledged frustration and was empathetic while enforcing policy.",
|
|
223
|
+
samples=5,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
result = empathy_judge.evaluate(trace)
|
|
227
|
+
assert result.score == 1
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Built-in rubrics:
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
from understudy.judges import (
|
|
234
|
+
TOOL_USAGE_CORRECTNESS,
|
|
235
|
+
POLICY_COMPLIANCE,
|
|
236
|
+
TONE_EMPATHY,
|
|
237
|
+
ADVERSARIAL_ROBUSTNESS,
|
|
238
|
+
TASK_COMPLETION,
|
|
239
|
+
)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Report Contents
|
|
243
|
+
|
|
244
|
+
The `understudy summary` command shows:
|
|
245
|
+
- **Pass rate** — percentage of scenes that passed all expectations
|
|
246
|
+
- **Avg turns** — average conversation length
|
|
247
|
+
- **Tool usage** — distribution of tool calls across runs
|
|
248
|
+
- **Agents** — which agents were invoked
|
|
249
|
+
|
|
250
|
+
The HTML report (`understudy report`) includes:
|
|
251
|
+
- All metrics above
|
|
252
|
+
- Full conversation transcripts
|
|
253
|
+
- Tool call details with arguments
|
|
254
|
+
- Expectation check results
|
|
255
|
+
- Judge evaluation results (when used)
|
|
256
|
+
|
|
257
|
+
## Documentation
|
|
258
|
+
|
|
259
|
+
See the [full documentation](https://gojiplus.github.io/understudy) for:
|
|
260
|
+
- [Installation guide](https://gojiplus.github.io/understudy/installation.html)
|
|
261
|
+
- [Writing scenes](https://gojiplus.github.io/understudy/tutorial/scenes.html)
|
|
262
|
+
- [ADK integration](https://gojiplus.github.io/understudy/adk-integration.html)
|
|
263
|
+
- [LangGraph integration](https://gojiplus.github.io/understudy/langgraph-integration.html)
|
|
264
|
+
- [HTTP client for deployed agents](https://gojiplus.github.io/understudy/tutorial/http.html)
|
|
265
|
+
- [API reference](https://gojiplus.github.io/understudy/api/index.html)
|
|
266
|
+
|
|
267
|
+
## License
|
|
268
|
+
|
|
269
|
+
MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "understudy"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Simulation and trace-based evaluation for agentic systems"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -25,12 +25,14 @@ classifiers = [
|
|
|
25
25
|
dependencies = [
|
|
26
26
|
"pydantic>=2.0",
|
|
27
27
|
"pyyaml>=6.0",
|
|
28
|
+
"litellm>=1.50",
|
|
28
29
|
]
|
|
29
30
|
|
|
30
31
|
[project.optional-dependencies]
|
|
31
|
-
adk = ["google-adk>=1.0"]
|
|
32
|
-
judges = ["litellm>=1.50"]
|
|
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,11 +40,12 @@ docs = [
|
|
|
38
40
|
"myst-parser>=3.0",
|
|
39
41
|
"sphinx-autodoc-typehints>=2.0",
|
|
40
42
|
]
|
|
41
|
-
all = ["understudy[adk,
|
|
43
|
+
all = ["understudy[adk,http,langgraph,server,reports]"]
|
|
42
44
|
dev = [
|
|
43
45
|
"understudy[all,docs]",
|
|
44
46
|
"pytest>=8.0",
|
|
45
47
|
"pytest-asyncio>=0.23",
|
|
48
|
+
"pytest-cov>=4.0",
|
|
46
49
|
"ruff>=0.4",
|
|
47
50
|
]
|
|
48
51
|
|
|
@@ -5,8 +5,12 @@ You write scenes, run rehearsals, and check the performance —
|
|
|
5
5
|
not by reading the script, but by inspecting what actually happened.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from .
|
|
9
|
-
|
|
8
|
+
from importlib.metadata import version
|
|
9
|
+
|
|
10
|
+
from .check import CheckItem, CheckResult, EvaluationResult, check, evaluate, evaluate_batch
|
|
11
|
+
from .compare import ComparisonResult, SceneComparison, compare_runs
|
|
12
|
+
from .judges import FailureAnalysis, FailureAnalyzer, Judge, JudgeResult
|
|
13
|
+
from .metrics import MetricRegistry, MetricResult
|
|
10
14
|
from .mocks import MockToolkit, ToolError
|
|
11
15
|
from .models import Expectations, Persona, PersonaPreset, Scene
|
|
12
16
|
from .prompts import (
|
|
@@ -18,12 +22,13 @@ from .prompts import (
|
|
|
18
22
|
TONE_EMPATHY,
|
|
19
23
|
TOOL_USAGE_CORRECTNESS,
|
|
20
24
|
)
|
|
21
|
-
from .runner import AgentApp, AgentResponse, run
|
|
22
|
-
from .
|
|
25
|
+
from .runner import AgentApp, AgentResponse, LiteLLMBackend, run, simulate, simulate_batch
|
|
26
|
+
from .simulator import Simulator, SimulatorBackend
|
|
27
|
+
from .storage import EvaluationStorage, RunStorage, TraceStorage
|
|
23
28
|
from .suite import SceneResult, Suite, SuiteResults
|
|
24
|
-
from .trace import AgentTransfer, ToolCall, Trace, Turn
|
|
29
|
+
from .trace import AgentTransfer, StateSnapshot, ToolCall, Trace, TraceMetrics, Turn, TurnMetrics
|
|
25
30
|
|
|
26
|
-
__version__ = "
|
|
31
|
+
__version__ = version("understudy")
|
|
27
32
|
|
|
28
33
|
__all__ = [
|
|
29
34
|
# models
|
|
@@ -36,26 +41,49 @@ __all__ = [
|
|
|
36
41
|
"Turn",
|
|
37
42
|
"ToolCall",
|
|
38
43
|
"AgentTransfer",
|
|
39
|
-
|
|
44
|
+
"TraceMetrics",
|
|
45
|
+
"TurnMetrics",
|
|
46
|
+
"StateSnapshot",
|
|
47
|
+
# metrics
|
|
48
|
+
"MetricRegistry",
|
|
49
|
+
"MetricResult",
|
|
50
|
+
# runner / simulate
|
|
40
51
|
"run",
|
|
52
|
+
"simulate",
|
|
53
|
+
"simulate_batch",
|
|
41
54
|
"AgentApp",
|
|
42
55
|
"AgentResponse",
|
|
43
|
-
|
|
56
|
+
"LiteLLMBackend",
|
|
57
|
+
# check / evaluate
|
|
44
58
|
"check",
|
|
59
|
+
"evaluate",
|
|
60
|
+
"evaluate_batch",
|
|
45
61
|
"CheckResult",
|
|
46
62
|
"CheckItem",
|
|
63
|
+
"EvaluationResult",
|
|
47
64
|
# suite
|
|
48
65
|
"Suite",
|
|
49
66
|
"SuiteResults",
|
|
50
67
|
"SceneResult",
|
|
51
68
|
# storage
|
|
52
69
|
"RunStorage",
|
|
70
|
+
"TraceStorage",
|
|
71
|
+
"EvaluationStorage",
|
|
72
|
+
# compare
|
|
73
|
+
"compare_runs",
|
|
74
|
+
"ComparisonResult",
|
|
75
|
+
"SceneComparison",
|
|
53
76
|
# judges
|
|
54
77
|
"Judge",
|
|
55
78
|
"JudgeResult",
|
|
79
|
+
"FailureAnalyzer",
|
|
80
|
+
"FailureAnalysis",
|
|
56
81
|
# mocks
|
|
57
82
|
"MockToolkit",
|
|
58
83
|
"ToolError",
|
|
84
|
+
# simulator
|
|
85
|
+
"Simulator",
|
|
86
|
+
"SimulatorBackend",
|
|
59
87
|
# rubrics
|
|
60
88
|
"TOOL_USAGE_CORRECTNESS",
|
|
61
89
|
"POLICY_COMPLIANCE",
|