hotato 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 (75) hide show
  1. hotato-0.1.0/LICENSE +21 -0
  2. hotato-0.1.0/PKG-INFO +257 -0
  3. hotato-0.1.0/README.md +214 -0
  4. hotato-0.1.0/pyproject.toml +75 -0
  5. hotato-0.1.0/setup.cfg +4 -0
  6. hotato-0.1.0/src/hotato/__init__.py +33 -0
  7. hotato-0.1.0/src/hotato/_engine/__init__.py +47 -0
  8. hotato-0.1.0/src/hotato/_engine/__main__.py +131 -0
  9. hotato-0.1.0/src/hotato/_engine/audio.py +149 -0
  10. hotato-0.1.0/src/hotato/_engine/batch.py +160 -0
  11. hotato-0.1.0/src/hotato/_engine/score.py +358 -0
  12. hotato-0.1.0/src/hotato/_engine/vad.py +224 -0
  13. hotato-0.1.0/src/hotato/_stats.py +47 -0
  14. hotato-0.1.0/src/hotato/aggregate.py +346 -0
  15. hotato-0.1.0/src/hotato/benchmark.py +627 -0
  16. hotato-0.1.0/src/hotato/capture.py +615 -0
  17. hotato-0.1.0/src/hotato/cli.py +897 -0
  18. hotato-0.1.0/src/hotato/core.py +537 -0
  19. hotato-0.1.0/src/hotato/data/audio/01-hard-interruption.example.wav +0 -0
  20. hotato-0.1.0/src/hotato/data/audio/02-backchannel-mhm.example.wav +0 -0
  21. hotato-0.1.0/src/hotato/data/audio/03-filler-start.example.wav +0 -0
  22. hotato-0.1.0/src/hotato/data/audio/04-correction.example.wav +0 -0
  23. hotato-0.1.0/src/hotato/data/audio/05-telephony-8khz.example.wav +0 -0
  24. hotato-0.1.0/src/hotato/data/audio/06-double-talk.example.wav +0 -0
  25. hotato-0.1.0/src/hotato/data/audio/07-echo-bleed.example.wav +0 -0
  26. hotato-0.1.0/src/hotato/data/audio/08-rapid-turn-taking.example.wav +0 -0
  27. hotato-0.1.0/src/hotato/data/scenarios/01-hard-interruption.json +26 -0
  28. hotato-0.1.0/src/hotato/data/scenarios/02-backchannel-mhm.json +26 -0
  29. hotato-0.1.0/src/hotato/data/scenarios/03-filler-start.json +26 -0
  30. hotato-0.1.0/src/hotato/data/scenarios/04-correction.json +26 -0
  31. hotato-0.1.0/src/hotato/data/scenarios/05-telephony-8khz.json +26 -0
  32. hotato-0.1.0/src/hotato/data/scenarios/06-double-talk.json +26 -0
  33. hotato-0.1.0/src/hotato/data/scenarios/07-echo-bleed.json +29 -0
  34. hotato-0.1.0/src/hotato/data/scenarios/08-rapid-turn-taking.json +27 -0
  35. hotato-0.1.0/src/hotato/data/scenarios/manifest.json +76 -0
  36. hotato-0.1.0/src/hotato/export.py +204 -0
  37. hotato-0.1.0/src/hotato/fixmap.py +332 -0
  38. hotato-0.1.0/src/hotato/mcp_server.py +181 -0
  39. hotato-0.1.0/src/hotato/neural.py +101 -0
  40. hotato-0.1.0/src/hotato/pytest_plugin.py +127 -0
  41. hotato-0.1.0/src/hotato/report.py +1214 -0
  42. hotato-0.1.0/src/hotato/schema/envelope.v1.json +170 -0
  43. hotato-0.1.0/src/hotato/stackbench.py +534 -0
  44. hotato-0.1.0/src/hotato.egg-info/PKG-INFO +257 -0
  45. hotato-0.1.0/src/hotato.egg-info/SOURCES.txt +73 -0
  46. hotato-0.1.0/src/hotato.egg-info/dependency_links.txt +1 -0
  47. hotato-0.1.0/src/hotato.egg-info/entry_points.txt +6 -0
  48. hotato-0.1.0/src/hotato.egg-info/requires.txt +32 -0
  49. hotato-0.1.0/src/hotato.egg-info/top_level.txt +1 -0
  50. hotato-0.1.0/tests/test_backend.py +245 -0
  51. hotato-0.1.0/tests/test_benchmark.py +225 -0
  52. hotato-0.1.0/tests/test_capture.py +162 -0
  53. hotato-0.1.0/tests/test_cli.py +78 -0
  54. hotato-0.1.0/tests/test_core.py +124 -0
  55. hotato-0.1.0/tests/test_corpus_suites.py +377 -0
  56. hotato-0.1.0/tests/test_corpus_validate.py +145 -0
  57. hotato-0.1.0/tests/test_engine_parity.py +82 -0
  58. hotato-0.1.0/tests/test_examples_backchannel.py +66 -0
  59. hotato-0.1.0/tests/test_examples_funnel.py +79 -0
  60. hotato-0.1.0/tests/test_examples_latency.py +143 -0
  61. hotato-0.1.0/tests/test_export.py +131 -0
  62. hotato-0.1.0/tests/test_fixmap.py +184 -0
  63. hotato-0.1.0/tests/test_frozen_regression.py +43 -0
  64. hotato-0.1.0/tests/test_hostile_inputs.py +171 -0
  65. hotato-0.1.0/tests/test_mcp_parity.py +44 -0
  66. hotato-0.1.0/tests/test_mcp_report_path.py +56 -0
  67. hotato-0.1.0/tests/test_pr_comment.py +79 -0
  68. hotato-0.1.0/tests/test_pytest_plugin.py +114 -0
  69. hotato-0.1.0/tests/test_report.py +154 -0
  70. hotato-0.1.0/tests/test_report_analytics.py +218 -0
  71. hotato-0.1.0/tests/test_saa_card.py +58 -0
  72. hotato-0.1.0/tests/test_schema.py +99 -0
  73. hotato-0.1.0/tests/test_signals.py +306 -0
  74. hotato-0.1.0/tests/test_stackbench.py +332 -0
  75. hotato-0.1.0/tests/test_team.py +187 -0
hotato-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Attention Labs, Inc.
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.
hotato-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,257 @@
1
+ Metadata-Version: 2.4
2
+ Name: hotato
3
+ Version: 0.1.0
4
+ Summary: Hotato: the open turn-taking eval for voice agents. Does your agent drop the turn, or hog it? Score barge-in / talk-over / backchannel / endpointing from a call recording, offline, with an honest fix map. MIT.
5
+ Author: Attention Labs, Inc.
6
+ License: MIT
7
+ Project-URL: Repository, https://github.com/attenlabs/hotato
8
+ Project-URL: Homepage, https://hotato.dev
9
+ Project-URL: Open engine (upstream), https://github.com/quantumCF/voice-agent-barge-in-tests
10
+ Keywords: hotato,voice-ai,voice-agents,evals,barge-in,turn-taking,mcp,agent-native,observability,reliability,livekit,pipecat,vapi
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Analysis
14
+ Classifier: Topic :: Software Development :: Testing
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Provides-Extra: mcp
19
+ Requires-Dist: mcp>=1.2.0; extra == "mcp"
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=7; extra == "dev"
22
+ Requires-Dist: jsonschema>=4; extra == "dev"
23
+ Provides-Extra: vapi
24
+ Provides-Extra: twilio
25
+ Provides-Extra: livekit
26
+ Requires-Dist: livekit; extra == "livekit"
27
+ Requires-Dist: livekit-agents; extra == "livekit"
28
+ Provides-Extra: pipecat
29
+ Requires-Dist: pipecat-ai; extra == "pipecat"
30
+ Provides-Extra: neural
31
+ Requires-Dist: silero-vad>=5; extra == "neural"
32
+ Requires-Dist: onnxruntime>=1.16; extra == "neural"
33
+ Requires-Dist: numpy>=1.21; extra == "neural"
34
+ Provides-Extra: all
35
+ Requires-Dist: mcp>=1.2.0; extra == "all"
36
+ Requires-Dist: livekit; extra == "all"
37
+ Requires-Dist: livekit-agents; extra == "all"
38
+ Requires-Dist: pipecat-ai; extra == "all"
39
+ Requires-Dist: silero-vad>=5; extra == "all"
40
+ Requires-Dist: onnxruntime>=1.16; extra == "all"
41
+ Requires-Dist: numpy>=1.21; extra == "all"
42
+ Dynamic: license-file
43
+
44
+ <p align="center">
45
+ <a href="https://hotato.dev">
46
+ <img src=".github/banner.png" alt="hotato: the open, offline turn-taking eval for voice agents" width="840">
47
+ </a>
48
+ </p>
49
+
50
+ <h1 align="center">
51
+ <img src="docs/assets/mascot.svg" alt="" width="26" align="top"> hotato
52
+ </h1>
53
+
54
+ <p align="center"><b>An open, offline tool that scores voice-agent turn-taking from a call recording.</b></p>
55
+
56
+ <p align="center">
57
+ <a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
58
+ <img alt="Python 3.9 to 3.13" src="https://img.shields.io/badge/python-3.9%20to%203.13-blue.svg">
59
+ <img alt="tests: passing" src="https://img.shields.io/badge/tests-passing-brightgreen.svg">
60
+ <img alt="build: passing" src="https://img.shields.io/badge/build-passing-brightgreen.svg">
61
+ <a href="https://hotato.dev"><img alt="site: hotato.dev" src="https://img.shields.io/badge/site-hotato.dev-e2470f.svg"></a>
62
+ </p>
63
+
64
+ <p align="center">
65
+ Built for anyone shipping voice agents on LiveKit, Pipecat, Vapi, Retell, or Twilio.<br>
66
+ Runs on your machine, so your call audio stays with you.
67
+ </p>
68
+
69
+ Point it at one of your own call recordings. It shows where your agent talked over
70
+ the caller, or missed a real interruption, and points each failure at the config
71
+ knob that fixes it. Every result is reproducible timing you can verify frame by
72
+ frame and gate a PR on.
73
+
74
+ ## Quickstart (60 seconds)
75
+
76
+ Already have a two-channel WAV (caller on channel 0, agent on channel 1)?
77
+
78
+ ```bash
79
+ uvx hotato run --stereo your_call.wav --expect yield
80
+ ```
81
+
82
+ Or run the bundled self-test, offline, with zero setup:
83
+
84
+ ```bash
85
+ uvx hotato run --suite barge-in
86
+ ```
87
+
88
+ ```text
89
+ $ uvx hotato run --suite barge-in
90
+ hotato [suite] stack=generic offline=True
91
+ 8/8 events pass (failed=0)
92
+ [PASS] 01-hard-interruption: did_yield=True seconds_to_yield=0.50s talk_over=0.50s
93
+ [PASS] 02-backchannel-mhm: did_yield=False seconds_to_yield=- talk_over=1.57s
94
+ [PASS] 03-filler-start: did_yield=True seconds_to_yield=0.65s talk_over=0.56s
95
+ [PASS] 04-correction: did_yield=True seconds_to_yield=0.50s talk_over=0.50s
96
+ [PASS] 05-telephony-8khz: did_yield=True seconds_to_yield=0.50s talk_over=0.50s
97
+ [PASS] 06-double-talk: did_yield=True seconds_to_yield=1.05s talk_over=1.05s
98
+ [PASS] 07-echo-bleed: did_yield=False seconds_to_yield=- talk_over=3.00s
99
+ [PASS] 08-rapid-turn-taking: did_yield=True seconds_to_yield=0.50s talk_over=0.50s
100
+ exit_code=0
101
+ ```
102
+
103
+ This self-test proves the tool itself behaves. Point it at a real call to measure
104
+ your agent.
105
+
106
+ Even faster: one command that scores, renders the visual report, and opens it.
107
+
108
+ ```bash
109
+ uvx hotato doctor --stereo your_call.wav # or plain `uvx hotato doctor` for the self-test
110
+ ```
111
+
112
+ ## What you get
113
+
114
+ One scorer, and every surface you need around it. Each line below is one command.
115
+
116
+ - **`doctor`**, the 5-minute path: score a recording (or the bundled self-test),
117
+ write the visual HTML report, open it. `uvx hotato doctor --stereo call.wav`
118
+ - **`report`**, a self-contained visual report: per-event SVG timelines, analytics,
119
+ a per-frame inspector, print CSS for PDF, and regression deltas with
120
+ `--base base.json`. `uvx hotato report --stereo call.wav --out report.html`
121
+ - **`team`**, the trend view: aggregate a directory of runs into pass rate over
122
+ time plus mean/median/p90 talk-over and time-to-yield.
123
+ `uvx hotato team runs/ --html team.html`
124
+ - **`export`**, research-grade CSVs: `events.csv`, `frames.csv`, `envelope.json`,
125
+ columns documented in-file. `uvx hotato export --stereo call.wav --out research/`
126
+ - **Pytest plugin**, auto-registered on install: a `hotato_score` fixture and a
127
+ session gate that fails the run on a regression. `pytest --hotato-suite`
128
+ - **MCP tool**: one tool, `voice_eval_run`; pass `report_path` and the envelope
129
+ comes back with the written HTML report. `uvx --from "hotato[mcp]" hotato-mcp`
130
+ - **PR check**: a sticky PR comment with the results table and deltas, and a gate
131
+ on regression. Copy `.github/workflows/hotato.yml` into your repo.
132
+ - **Tiered corpus suites**: 112 deterministic scenarios across silver and gold
133
+ tiers, plus defect suites that must fail.
134
+ `hotato run --suite barge-in --scenarios corpus/suites/gold/scenarios --audio corpus/suites/gold/audio`
135
+
136
+ - **`benchmark`**, comparable stack runs: score your captured battery per stack, then compare result files side by side. `hotato benchmark --stack livekit --recordings captures/` then `hotato benchmark compare a.json b.json`
137
+ Deep detail: [`docs/REPORTS.md`](docs/REPORTS.md), [`docs/PYTEST.md`](docs/PYTEST.md),
138
+ [`docs/SUITES.md`](docs/SUITES.md), [`docs/CI.md`](docs/CI.md).
139
+
140
+ ## What it measures
141
+
142
+ It scores the audio timing of turn-taking. Per event, three objective signals:
143
+
144
+ - `did_yield`: did the agent stop talking for the caller?
145
+ - `seconds_to_yield`: how long that took.
146
+ - `talk_over_sec`: how many seconds it kept talking over the caller first.
147
+
148
+ From those it renders a `PASS` or `FAIL` against expected behavior: a good agent
149
+ yields to a real interruption and holds through a backchannel like "mhm".
150
+
151
+ The bundled 8-scenario battery covers hard interruption, backchannel,
152
+ filler-start, correction, 8 kHz telephony, double-talk, echo-bleed, and rapid
153
+ turn-taking.
154
+
155
+ ## Scope
156
+
157
+ Each limit is a property of how the scoring works.
158
+
159
+ - **Frame-by-frame timing you can verify.** Every number is a measurement against
160
+ a published, overridable threshold, inspectable with `--dump-frames`.
161
+ - **Speech energy over time is the whole signal**, so scoring is deterministic and
162
+ runs offline on any recording.
163
+ - **Two channels are ground truth for overlap.** Mono runs with an onset label and
164
+ reports its overlap as an estimate.
165
+ - **Synthetic fixtures are the floor**: deterministic, with exact known timings, a
166
+ runnable floor and a regression guard. Real validity comes from your own
167
+ labelled calls (see `CONTRIBUTING.md`).
168
+
169
+ Full method, and the optional neural cross-check (`--backend neural`, Silero VAD):
170
+ `METHODOLOGY.md`.
171
+
172
+ ## Install
173
+
174
+ `uvx` runs every command with zero install. To add it to a project:
175
+
176
+ ```bash
177
+ pip install hotato # core, stdlib-only, zero dependencies
178
+ pip install 'hotato[neural]' # optional Silero VAD cross-check
179
+ pip install 'hotato[livekit]' # LiveKit live capture
180
+ pip install 'hotato[pipecat]' # Pipecat live capture
181
+ ```
182
+
183
+ ## Usage
184
+
185
+ Capture a real call from your stack. Vapi and Twilio need only an API key:
186
+
187
+ ```bash
188
+ # Vapi: an API key and a call id
189
+ export VAPI_API_KEY=...
190
+ uvx hotato capture --stack vapi --call-id <call-id>
191
+
192
+ # Twilio dual-channel recording
193
+ export TWILIO_ACCOUNT_SID=AC... TWILIO_AUTH_TOKEN=...
194
+ uvx hotato capture --stack twilio --recording-sid RE...
195
+
196
+ # LiveKit or Pipecat: scaffold the recording config first
197
+ uvx hotato setup --stack livekit # then: hotato capture --stack livekit --caller a.wav --agent b.wav
198
+ uvx hotato setup --stack pipecat # then: hotato capture --stack pipecat --stereo captured.wav
199
+ ```
200
+
201
+ Every stack, and Retell's status, is in [`adapters/README.md`](adapters/README.md).
202
+
203
+ Every failing event carries exactly one fix:
204
+
205
+ - **`config`**: a concrete knob for your stack (`livekit`, `pipecat`, `vapi`, or
206
+ `generic`), the direction to move it, and the trade-off it makes.
207
+ - **`engagement-control`**: the one failure a sensitivity dial cannot solve.
208
+ Telling a genuine bid for the floor apart from a backchannel is an open research
209
+ problem, so the pointer names the kind of fix, high-level and vendor-neutral: a
210
+ learned engagement-control / addressee-detection layer. It fires only when a
211
+ battery fails on both axes at once.
212
+
213
+ ## The MCP tool
214
+
215
+ Run it as a one-tool MCP server (stdio), so an agent can run the eval mid-task:
216
+
217
+ ```bash
218
+ uvx --from "hotato[mcp]" hotato-mcp
219
+ ```
220
+
221
+ It exposes exactly one tool, `voice_eval_run`, returning the same JSON envelope as
222
+ the CLI, with the scope and limits stated inline in the tool description. Pass an
223
+ optional `report_path` and it also writes the visual HTML report; the returned
224
+ envelope carries the absolute path.
225
+
226
+ ## CI
227
+
228
+ `run` exits non-zero on a regression, straight into a PR gate:
229
+
230
+ ```bash
231
+ uvx hotato run --suite barge-in --format json
232
+ ```
233
+
234
+ Exit codes: `0` all pass (or `--no-fail`), `1` a regression, `2` usage or IO error.
235
+
236
+ Two ready-made gates sit on top. Copy `.github/workflows/hotato.yml` for a PR
237
+ check with a sticky results comment (`docs/CI.md`), or add `--hotato-suite` to
238
+ your existing pytest run (`docs/PYTEST.md`).
239
+
240
+ ## Contributing
241
+
242
+ Bug fixes, scorer tuning, docs, and new scenarios are all welcome. The highest
243
+ value contribution is a real, labelled call fixture: synthetic fixtures make the
244
+ eval runnable, real recordings make it credible. The full path is
245
+ [`docs/SUBMITTING.md`](docs/SUBMITTING.md), and the
246
+ [corpus-submission issue form](https://github.com/attenlabs/hotato/issues/new?template=corpus_submission.yml)
247
+ walks you through it. Governance: `CONTRIBUTING.md` and `docs/CORPUS-GOVERNANCE.md`.
248
+
249
+ ## Why "hotato"
250
+
251
+ Good turn-taking is a game of hot potato: take your turn, then pass it, fast and
252
+ clean. Fumble it and you miss a real interruption; clutch it and you talk over the
253
+ caller. Hotato catches both on your own recordings and points each at a fix.
254
+
255
+ ## License
256
+
257
+ MIT (`LICENSE`). The open core stays open and is never relicensed.
hotato-0.1.0/README.md ADDED
@@ -0,0 +1,214 @@
1
+ <p align="center">
2
+ <a href="https://hotato.dev">
3
+ <img src=".github/banner.png" alt="hotato: the open, offline turn-taking eval for voice agents" width="840">
4
+ </a>
5
+ </p>
6
+
7
+ <h1 align="center">
8
+ <img src="docs/assets/mascot.svg" alt="" width="26" align="top"> hotato
9
+ </h1>
10
+
11
+ <p align="center"><b>An open, offline tool that scores voice-agent turn-taking from a call recording.</b></p>
12
+
13
+ <p align="center">
14
+ <a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
15
+ <img alt="Python 3.9 to 3.13" src="https://img.shields.io/badge/python-3.9%20to%203.13-blue.svg">
16
+ <img alt="tests: passing" src="https://img.shields.io/badge/tests-passing-brightgreen.svg">
17
+ <img alt="build: passing" src="https://img.shields.io/badge/build-passing-brightgreen.svg">
18
+ <a href="https://hotato.dev"><img alt="site: hotato.dev" src="https://img.shields.io/badge/site-hotato.dev-e2470f.svg"></a>
19
+ </p>
20
+
21
+ <p align="center">
22
+ Built for anyone shipping voice agents on LiveKit, Pipecat, Vapi, Retell, or Twilio.<br>
23
+ Runs on your machine, so your call audio stays with you.
24
+ </p>
25
+
26
+ Point it at one of your own call recordings. It shows where your agent talked over
27
+ the caller, or missed a real interruption, and points each failure at the config
28
+ knob that fixes it. Every result is reproducible timing you can verify frame by
29
+ frame and gate a PR on.
30
+
31
+ ## Quickstart (60 seconds)
32
+
33
+ Already have a two-channel WAV (caller on channel 0, agent on channel 1)?
34
+
35
+ ```bash
36
+ uvx hotato run --stereo your_call.wav --expect yield
37
+ ```
38
+
39
+ Or run the bundled self-test, offline, with zero setup:
40
+
41
+ ```bash
42
+ uvx hotato run --suite barge-in
43
+ ```
44
+
45
+ ```text
46
+ $ uvx hotato run --suite barge-in
47
+ hotato [suite] stack=generic offline=True
48
+ 8/8 events pass (failed=0)
49
+ [PASS] 01-hard-interruption: did_yield=True seconds_to_yield=0.50s talk_over=0.50s
50
+ [PASS] 02-backchannel-mhm: did_yield=False seconds_to_yield=- talk_over=1.57s
51
+ [PASS] 03-filler-start: did_yield=True seconds_to_yield=0.65s talk_over=0.56s
52
+ [PASS] 04-correction: did_yield=True seconds_to_yield=0.50s talk_over=0.50s
53
+ [PASS] 05-telephony-8khz: did_yield=True seconds_to_yield=0.50s talk_over=0.50s
54
+ [PASS] 06-double-talk: did_yield=True seconds_to_yield=1.05s talk_over=1.05s
55
+ [PASS] 07-echo-bleed: did_yield=False seconds_to_yield=- talk_over=3.00s
56
+ [PASS] 08-rapid-turn-taking: did_yield=True seconds_to_yield=0.50s talk_over=0.50s
57
+ exit_code=0
58
+ ```
59
+
60
+ This self-test proves the tool itself behaves. Point it at a real call to measure
61
+ your agent.
62
+
63
+ Even faster: one command that scores, renders the visual report, and opens it.
64
+
65
+ ```bash
66
+ uvx hotato doctor --stereo your_call.wav # or plain `uvx hotato doctor` for the self-test
67
+ ```
68
+
69
+ ## What you get
70
+
71
+ One scorer, and every surface you need around it. Each line below is one command.
72
+
73
+ - **`doctor`**, the 5-minute path: score a recording (or the bundled self-test),
74
+ write the visual HTML report, open it. `uvx hotato doctor --stereo call.wav`
75
+ - **`report`**, a self-contained visual report: per-event SVG timelines, analytics,
76
+ a per-frame inspector, print CSS for PDF, and regression deltas with
77
+ `--base base.json`. `uvx hotato report --stereo call.wav --out report.html`
78
+ - **`team`**, the trend view: aggregate a directory of runs into pass rate over
79
+ time plus mean/median/p90 talk-over and time-to-yield.
80
+ `uvx hotato team runs/ --html team.html`
81
+ - **`export`**, research-grade CSVs: `events.csv`, `frames.csv`, `envelope.json`,
82
+ columns documented in-file. `uvx hotato export --stereo call.wav --out research/`
83
+ - **Pytest plugin**, auto-registered on install: a `hotato_score` fixture and a
84
+ session gate that fails the run on a regression. `pytest --hotato-suite`
85
+ - **MCP tool**: one tool, `voice_eval_run`; pass `report_path` and the envelope
86
+ comes back with the written HTML report. `uvx --from "hotato[mcp]" hotato-mcp`
87
+ - **PR check**: a sticky PR comment with the results table and deltas, and a gate
88
+ on regression. Copy `.github/workflows/hotato.yml` into your repo.
89
+ - **Tiered corpus suites**: 112 deterministic scenarios across silver and gold
90
+ tiers, plus defect suites that must fail.
91
+ `hotato run --suite barge-in --scenarios corpus/suites/gold/scenarios --audio corpus/suites/gold/audio`
92
+
93
+ - **`benchmark`**, comparable stack runs: score your captured battery per stack, then compare result files side by side. `hotato benchmark --stack livekit --recordings captures/` then `hotato benchmark compare a.json b.json`
94
+ Deep detail: [`docs/REPORTS.md`](docs/REPORTS.md), [`docs/PYTEST.md`](docs/PYTEST.md),
95
+ [`docs/SUITES.md`](docs/SUITES.md), [`docs/CI.md`](docs/CI.md).
96
+
97
+ ## What it measures
98
+
99
+ It scores the audio timing of turn-taking. Per event, three objective signals:
100
+
101
+ - `did_yield`: did the agent stop talking for the caller?
102
+ - `seconds_to_yield`: how long that took.
103
+ - `talk_over_sec`: how many seconds it kept talking over the caller first.
104
+
105
+ From those it renders a `PASS` or `FAIL` against expected behavior: a good agent
106
+ yields to a real interruption and holds through a backchannel like "mhm".
107
+
108
+ The bundled 8-scenario battery covers hard interruption, backchannel,
109
+ filler-start, correction, 8 kHz telephony, double-talk, echo-bleed, and rapid
110
+ turn-taking.
111
+
112
+ ## Scope
113
+
114
+ Each limit is a property of how the scoring works.
115
+
116
+ - **Frame-by-frame timing you can verify.** Every number is a measurement against
117
+ a published, overridable threshold, inspectable with `--dump-frames`.
118
+ - **Speech energy over time is the whole signal**, so scoring is deterministic and
119
+ runs offline on any recording.
120
+ - **Two channels are ground truth for overlap.** Mono runs with an onset label and
121
+ reports its overlap as an estimate.
122
+ - **Synthetic fixtures are the floor**: deterministic, with exact known timings, a
123
+ runnable floor and a regression guard. Real validity comes from your own
124
+ labelled calls (see `CONTRIBUTING.md`).
125
+
126
+ Full method, and the optional neural cross-check (`--backend neural`, Silero VAD):
127
+ `METHODOLOGY.md`.
128
+
129
+ ## Install
130
+
131
+ `uvx` runs every command with zero install. To add it to a project:
132
+
133
+ ```bash
134
+ pip install hotato # core, stdlib-only, zero dependencies
135
+ pip install 'hotato[neural]' # optional Silero VAD cross-check
136
+ pip install 'hotato[livekit]' # LiveKit live capture
137
+ pip install 'hotato[pipecat]' # Pipecat live capture
138
+ ```
139
+
140
+ ## Usage
141
+
142
+ Capture a real call from your stack. Vapi and Twilio need only an API key:
143
+
144
+ ```bash
145
+ # Vapi: an API key and a call id
146
+ export VAPI_API_KEY=...
147
+ uvx hotato capture --stack vapi --call-id <call-id>
148
+
149
+ # Twilio dual-channel recording
150
+ export TWILIO_ACCOUNT_SID=AC... TWILIO_AUTH_TOKEN=...
151
+ uvx hotato capture --stack twilio --recording-sid RE...
152
+
153
+ # LiveKit or Pipecat: scaffold the recording config first
154
+ uvx hotato setup --stack livekit # then: hotato capture --stack livekit --caller a.wav --agent b.wav
155
+ uvx hotato setup --stack pipecat # then: hotato capture --stack pipecat --stereo captured.wav
156
+ ```
157
+
158
+ Every stack, and Retell's status, is in [`adapters/README.md`](adapters/README.md).
159
+
160
+ Every failing event carries exactly one fix:
161
+
162
+ - **`config`**: a concrete knob for your stack (`livekit`, `pipecat`, `vapi`, or
163
+ `generic`), the direction to move it, and the trade-off it makes.
164
+ - **`engagement-control`**: the one failure a sensitivity dial cannot solve.
165
+ Telling a genuine bid for the floor apart from a backchannel is an open research
166
+ problem, so the pointer names the kind of fix, high-level and vendor-neutral: a
167
+ learned engagement-control / addressee-detection layer. It fires only when a
168
+ battery fails on both axes at once.
169
+
170
+ ## The MCP tool
171
+
172
+ Run it as a one-tool MCP server (stdio), so an agent can run the eval mid-task:
173
+
174
+ ```bash
175
+ uvx --from "hotato[mcp]" hotato-mcp
176
+ ```
177
+
178
+ It exposes exactly one tool, `voice_eval_run`, returning the same JSON envelope as
179
+ the CLI, with the scope and limits stated inline in the tool description. Pass an
180
+ optional `report_path` and it also writes the visual HTML report; the returned
181
+ envelope carries the absolute path.
182
+
183
+ ## CI
184
+
185
+ `run` exits non-zero on a regression, straight into a PR gate:
186
+
187
+ ```bash
188
+ uvx hotato run --suite barge-in --format json
189
+ ```
190
+
191
+ Exit codes: `0` all pass (or `--no-fail`), `1` a regression, `2` usage or IO error.
192
+
193
+ Two ready-made gates sit on top. Copy `.github/workflows/hotato.yml` for a PR
194
+ check with a sticky results comment (`docs/CI.md`), or add `--hotato-suite` to
195
+ your existing pytest run (`docs/PYTEST.md`).
196
+
197
+ ## Contributing
198
+
199
+ Bug fixes, scorer tuning, docs, and new scenarios are all welcome. The highest
200
+ value contribution is a real, labelled call fixture: synthetic fixtures make the
201
+ eval runnable, real recordings make it credible. The full path is
202
+ [`docs/SUBMITTING.md`](docs/SUBMITTING.md), and the
203
+ [corpus-submission issue form](https://github.com/attenlabs/hotato/issues/new?template=corpus_submission.yml)
204
+ walks you through it. Governance: `CONTRIBUTING.md` and `docs/CORPUS-GOVERNANCE.md`.
205
+
206
+ ## Why "hotato"
207
+
208
+ Good turn-taking is a game of hot potato: take your turn, then pass it, fast and
209
+ clean. Fumble it and you miss a real interruption; clutch it and you talk over the
210
+ caller. Hotato catches both on your own recordings and points each at a fix.
211
+
212
+ ## License
213
+
214
+ MIT (`LICENSE`). The open core stays open and is never relicensed.
@@ -0,0 +1,75 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "hotato"
7
+ version = "0.1.0"
8
+ description = "Hotato: the open turn-taking eval for voice agents. Does your agent drop the turn, or hog it? Score barge-in / talk-over / backchannel / endpointing from a call recording, offline, with an honest fix map. MIT."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Attention Labs, Inc." }]
13
+ keywords = [
14
+ "hotato", "voice-ai", "voice-agents", "evals", "barge-in", "turn-taking",
15
+ "mcp", "agent-native", "observability", "reliability", "livekit",
16
+ "pipecat", "vapi",
17
+ ]
18
+ classifiers = [
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Topic :: Multimedia :: Sound/Audio :: Analysis",
22
+ "Topic :: Software Development :: Testing",
23
+ ]
24
+ # The core has ZERO required third-party dependencies: it is stdlib-only so it
25
+ # runs anywhere with `uvx` and adds no supply-chain surface. numpy is used only
26
+ # as an optional accelerator if it already happens to be importable.
27
+ dependencies = []
28
+
29
+ [project.optional-dependencies]
30
+ # The one-tool MCP server needs the MCP SDK. Kept optional so the CLI stays
31
+ # dependency-free and zero-install.
32
+ mcp = ["mcp>=1.2.0"]
33
+ dev = ["pytest>=7", "jsonschema>=4"]
34
+
35
+ # Per-stack capture extras. Vapi and Twilio capture use only stdlib (urllib +
36
+ # your API key), so `uvx hotato capture --stack vapi --call-id <id>` needs
37
+ # NOTHING extra -- these two extras are intentionally empty and exist so the
38
+ # documented `uvx 'hotato[vapi]'` / `hotato[twilio]` install still resolves.
39
+ # LiveKit and Pipecat LIVE capture run inside your own pipeline and genuinely
40
+ # need their frameworks; those are imported lazily and only on the live path.
41
+ vapi = []
42
+ twilio = []
43
+ livekit = ["livekit", "livekit-agents"]
44
+ pipecat = ["pipecat-ai"]
45
+ # OPTIONAL, NON-REFERENCE neural VAD cross-check (Silero VAD, MIT). Strictly
46
+ # opt-in via `--backend neural`; the zero-dependency energy core stays the
47
+ # reference for every published/golden number and is never affected by this
48
+ # extra. onnxruntime keeps inference CPU-only and lightweight (no torch needed at
49
+ # runtime). Absent this extra, `--backend neural` errors cleanly, never falling
50
+ # back to energy.
51
+ neural = ["silero-vad>=5", "onnxruntime>=1.16", "numpy>=1.21"]
52
+ # Everything a contributor might want to exercise the live capture paths locally.
53
+ all = ["mcp>=1.2.0", "livekit", "livekit-agents", "pipecat-ai", "silero-vad>=5", "onnxruntime>=1.16", "numpy>=1.21"]
54
+
55
+ [project.scripts]
56
+ hotato = "hotato.cli:main"
57
+ hotato-mcp = "hotato.mcp_server:main"
58
+
59
+ # Pytest integration (the `hotato_score` fixture and the opt-in --hotato-suite
60
+ # session gate). Auto-discovered by pytest when hotato is installed; inert
61
+ # unless the fixture or flag is actually used, and needs nothing beyond pytest
62
+ # itself (the scoring core stays stdlib-only).
63
+ [project.entry-points.pytest11]
64
+ hotato = "hotato.pytest_plugin"
65
+
66
+ [project.urls]
67
+ Repository = "https://github.com/attenlabs/hotato"
68
+ Homepage = "https://hotato.dev"
69
+ "Open engine (upstream)" = "https://github.com/quantumCF/voice-agent-barge-in-tests"
70
+
71
+ [tool.setuptools.packages.find]
72
+ where = ["src"]
73
+
74
+ [tool.setuptools.package-data]
75
+ hotato = ["data/scenarios/*.json", "data/audio/*.wav", "schema/*.json"]
hotato-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,33 @@
1
+ """hotato: the open turn-taking eval for voice agents.
2
+
3
+ Does your agent drop the turn, or hog it? Free (MIT), self-hostable,
4
+ zero-install, and offline. Its primary consumer is an AI agent: grab it as a CLI
5
+ (``uvx hotato ...``) or as a one-tool MCP server mid-task. It scores voice-agent
6
+ turn-taking - barge-in, overlap/talk-over, and backchannel handling - from a
7
+ call recording, returns a machine-readable verdict, and is the only such tool
8
+ that points a surfaced failure at the KIND of fix it needs: a learned
9
+ engagement-control / addressee-detection layer (an open research problem, not a
10
+ config knob) when the failure is a discrimination one no threshold can solve.
11
+ The pointer is vendor-neutral and names no product.
12
+
13
+ Honesty is the point, not a footnote: there is no fabricated accuracy anywhere.
14
+ The numbers are reproducible timing measurements with an exposed method and an
15
+ explicit ceiling.
16
+ """
17
+
18
+ from .core import LIMITS, SUITE_ID, run_single, run_suite
19
+
20
+ from ._engine.vad import register_neural_backend as _register_neural_backend
21
+ from .neural import build_silero_backend as _build_silero_backend
22
+
23
+ # Register the OPTIONAL, non-reference neural VAD backend (Silero VAD, MIT). This
24
+ # only stores the factory reference: the model and its extra are imported lazily,
25
+ # and only if backend="neural" is actually requested -- so importing hotato stays
26
+ # zero-dependency and the energy reference path is untouched. With the [neural]
27
+ # extra absent, a neural request raises a clean BackendUnavailable (never a silent
28
+ # fallback to energy that could change a published number).
29
+ _register_neural_backend(_build_silero_backend)
30
+
31
+ __version__ = "0.1.0"
32
+
33
+ __all__ = ["run_single", "run_suite", "LIMITS", "SUITE_ID", "__version__"]
@@ -0,0 +1,47 @@
1
+ """voice-agent-barge-in-tests: a dependency-light harness for measuring the three
2
+ objective barge-in signals (did the agent yield, time to yield, talk-over
3
+ seconds) from a voice-agent call recording.
4
+
5
+ See the repository README for the failure mode this tests and how to wire it
6
+ into CI. This package measures timing from audio and makes no accuracy claim
7
+ about any detector.
8
+ """
9
+
10
+ from .audio import Signal, read_wav, write_wav
11
+ from .score import (
12
+ ScoreConfig,
13
+ ScoreResult,
14
+ Verdict,
15
+ evaluate,
16
+ score_channels,
17
+ score_stereo,
18
+ )
19
+ from .vad import (
20
+ BackendUnavailable,
21
+ VADParams,
22
+ clear_neural_backend,
23
+ energy_vad,
24
+ neural_vad,
25
+ register_neural_backend,
26
+ )
27
+
28
+ __version__ = "0.1.0"
29
+
30
+ __all__ = [
31
+ "Signal",
32
+ "read_wav",
33
+ "write_wav",
34
+ "ScoreConfig",
35
+ "ScoreResult",
36
+ "Verdict",
37
+ "evaluate",
38
+ "score_channels",
39
+ "score_stereo",
40
+ "VADParams",
41
+ "energy_vad",
42
+ "neural_vad",
43
+ "register_neural_backend",
44
+ "clear_neural_backend",
45
+ "BackendUnavailable",
46
+ "__version__",
47
+ ]