AshishPype commited on
Commit
b99e6d3
·
verified ·
1 Parent(s): f6b0835

Model card, prompts, NOTICE, infographics and the Thought Hold clip

Browse files
.gitattributes CHANGED
@@ -33,3 +33,9 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ assets/cue-v1-english.png filter=lfs diff=lfs merge=lfs -text
37
+ assets/cue-v1-languages-cutoffs-600ms.png filter=lfs diff=lfs merge=lfs -text
38
+ assets/cue-v1-languages-cutoffs.png filter=lfs diff=lfs merge=lfs -text
39
+ assets/cue-v1-languages-latency-10pct.png filter=lfs diff=lfs merge=lfs -text
40
+ assets/cue-v1-languages-latency.png filter=lfs diff=lfs merge=lfs -text
41
+ assets/cue-v1-thought-hold.mp4 filter=lfs diff=lfs merge=lfs -text
NOTICE ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ Cue v1
2
+ Copyright 2026 Pype
3
+
4
+ This model is a fine-tune of Gemma 4 E4B (google/gemma-4-E4B-it) by Google, used under the Apache License,
5
+ Version 2.0. Cue v1's weights, adapter, prompts and documentation are released under the same licence.
6
+
7
+ The benchmark results in this repository were produced with LiveKit's eot-bench harness. The "× √Smart Turn
8
+ v3.2" setup refers to Smart Turn v3.2 by Pipecat (BSD 2-Clause), which is not included here.
README.md ADDED
@@ -0,0 +1,371 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ base_model: google/gemma-4-E4B-it
4
+ language:
5
+ - en
6
+ tags:
7
+ - end-of-turn
8
+ - turn-taking
9
+ - endpointing
10
+ - barge-in
11
+ - voice-agents
12
+ - decision-model
13
+ - gguf
14
+ - eot-bench
15
+ - interruptions
16
+ pipeline_tag: text-classification
17
+ ---
18
+
19
+ # Cue v1
20
+
21
+ **Cue v1 is a turn-taking decision model for voice agents.** It is Gemma 4 E4B, fine-tuned to answer two typed
22
+ questions: *when the caller pauses, are they done, and if not, why not?* and *when the caller speaks over the agent,
23
+ should the agent stop?* Each answer is read from the logits, as probabilities over named states instead of a single
24
+ score, so the agent knows not only *whether* to act but *how long to wait* and *whether it was really interrupted*:
25
+
26
+ | | Answer | What it means | What the agent does |
27
+ |---|---|---|---|
28
+ | **End of turn** (the caller pauses) | **DONE** | A complete question, answer, request or statement | Reply now |
29
+ | | **LIKELY_DONE** | Probably finished, but it trails off | A short grace, then reply |
30
+ | | **MID_THOUGHT** | The sentence is left hanging: a trailing "and", "so", "my number is", half a number | Wait: more is coming |
31
+ | | **HOLDING** | Telling a story or explaining something: the pause is thinking | Wait longer, don't cut in |
32
+ | **Interruptions** (the caller speaks while the agent talks) | **STOP** | A real interruption: a question, a correction, an objection, "wait", "hello?", or a full answer to the agent's question | Stop talking and listen |
33
+ | | **CONTINUE** | Only listening: "hmm", "okay", "haan", "achha", laughter, noise, a fragment | Keep talking |
34
+
35
+ Cue is the turn engine in **Shortwire**, Pype's open source voice AI stack, where decision models run the call and
36
+ the main LLM only talks: turns, holds, barge-in, guardrails, dictation and tool calls. In Shortwire, MID_THOUGHT and
37
+ HOLDING are what we call **Thought Hold**, and every answer and its probability shows up in the **Decision Trace**.
38
+ *Knows when to wait. Knows when to stop.*
39
+
40
+ <video controls playsinline width="100%" src="https://huggingface.co/Pype/cue-v1/resolve/main/assets/cue-v1-thought-hold.mp4"></video>
41
+
42
+ *Thought Hold on a real call: the caller pauses mid-sentence while giving their details, the end-of-turn decision
43
+ answers MID_THOUGHT (0.68), and the agent holds instead of cutting in. Recorded in Shortwire with the base Gemma
44
+ 4 E4B asking this same question, before the fine-tune; Cue v1 is that decision, trained. The phone number is masked.*
45
+
46
+ ## How it decides: logits, not text
47
+
48
+ Cue v1 never writes an answer. At every pause:
49
+
50
+ 1. **Ask one question.** The decision prompt lists the options as letters, followed by what was said so far.
51
+ - **End of turn:** A = DONE, B = LIKELY_DONE, C = MID_THOUGHT, D = HOLDING; with what the other side last said,
52
+ what the speaker has said in this turn, and how the transcript ends.
53
+ - **Interruptions:** A = STOP, B = CONTINUE; with what the agent is saying, the question it last asked, and what
54
+ the caller just said over it.
55
+ 2. **Let the model compute one token.** `max_tokens: 1`, with the top 20 log-probabilities returned
56
+ (`logprobs: true`). llama.cpp returns log-probabilities, the normalised form of the logits; for choosing between
57
+ options they are the same thing.
58
+ 3. **Read the probability of each letter** and renormalise them so they sum to 1:
59
+
60
+ ```
61
+ Agent asked: What's the best number to reach you on?
62
+ Caller said: Sure, it's nine eight four five,
63
+
64
+ MID_THOUGHT 0.999 DONE 0.001 LIKELY_DONE 0.000 HOLDING 0.000
65
+ ```
66
+
67
+ 4. **Turn the probabilities into an action**, as described in [Using the probabilities](#using-the-probabilities):
68
+ - **Caller and agent:** p(DONE) + p(LIKELY_DONE) is the end-of-turn score, multiplied by the square root of an
69
+ audio turn model's score.
70
+ - **Two people talking:** the probabilities set how long to wait in silence.
71
+ - **Interruptions:** p(STOP) decides whether the agent stops talking.
72
+
73
+ Why logits instead of an answer in words or JSON:
74
+
75
+ - **Typed by construction.** The answer is always one of the options. There is nothing to parse and nothing
76
+ that can come back malformed.
77
+ - **A full distribution, not a label.** On clear cases the answer is near-certain (0.999 above). On unclear ones
78
+ the probability splits between options, and that split carries into how long to wait.
79
+ - **Fast.** One forward pass and one token, about 100 to 300 ms on a T4.
80
+ - **The fine-tune moves the logits, nothing else.** Training put the loss on the answer letter only, so the model
81
+ learned where to put probability (the base Gemma almost never put it on HOLDING), while staying an ordinary
82
+ Gemma 4 E4B that any llama.cpp, Ollama or vLLM server can host.
83
+
84
+ ## Quick start
85
+
86
+ Serve the GGUF with llama.cpp:
87
+
88
+ ```bash
89
+ llama-server -m cue-v1-q8_0.gguf -c 8192 -np 4 -ngl 999 --jinja --port 8090
90
+ ```
91
+
92
+ Ask it about a pause. The answer is the first token's probability mass on each option letter; nothing is generated
93
+ beyond one token.
94
+
95
+ ```python
96
+ import json, math, httpx
97
+
98
+ END_OF_TURN = json.load(open("prompts/end_of_turn_phone.json"))
99
+ INTERRUPTION = json.load(open("prompts/barge_in.json"))
100
+ LETTERS = "ABCDEFGH"
101
+ LABELS = {"agent_question": "Agent asked", "caller_earlier": "Caller earlier", "caller_text": "Caller said",
102
+ "agent_said": "Agent is saying"}
103
+
104
+ def block(decision, inputs):
105
+ return "\n".join(f"{LABELS.get(k, k)}: {inputs.get(k) or '(nothing)'}" for k in decision["inputs"])
106
+
107
+ def system(decision):
108
+ ids = [o["id"] for o in decision["options"]]
109
+ lines = [decision["instruction"], "", "Options:"]
110
+ lines += [f"{LETTERS[i]}: {o['describe']}" for i, o in enumerate(decision["options"])]
111
+ lines += ["", "Answer with exactly one letter."]
112
+ if decision["examples"]:
113
+ lines += ["", "Examples:"]
114
+ for ex in decision["examples"]:
115
+ lines += ["", block(decision, ex["inputs"]), f"Answer: {LETTERS[ids.index(ex['answer'])]}"]
116
+ return "\n".join(lines)
117
+
118
+ def ask(decision, inputs):
119
+ r = httpx.post("http://localhost:8090/v1/chat/completions", json={
120
+ "messages": [{"role": "system", "content": system(decision)},
121
+ {"role": "user", "content": block(decision, inputs) + "\nAnswer:"}],
122
+ "max_tokens": 1, "temperature": 0, "logprobs": True, "top_logprobs": 20,
123
+ "reasoning_effort": "none", "chat_template_kwargs": {"enable_thinking": False}}).json()
124
+ raw = {}
125
+ for tp in r["choices"][0]["logprobs"]["content"][0]["top_logprobs"]:
126
+ t = tp["token"].strip()
127
+ if len(t) == 1 and t in LETTERS[:len(decision["options"])]:
128
+ opt = decision["options"][LETTERS.index(t)]["id"]
129
+ raw[opt] = raw.get(opt, 0.0) + math.exp(tp["logprob"])
130
+ total = sum(raw.values()) or 1.0
131
+ return {k: v / total for k, v in raw.items()}
132
+
133
+ probs = ask(END_OF_TURN, {"agent_question": "What's the best number to reach you on?",
134
+ "caller_earlier": "", "caller_text": "Sure, it's nine eight four five,",
135
+ "transcript_end": "no sentence end"})
136
+ print(probs) # {'MID_THOUGHT': 0.999, 'DONE': 0.001, 'LIKELY_DONE': 0.0002, 'HOLDING': 0.0001}: half a number
137
+ ```
138
+
139
+ ## Using the probabilities
140
+
141
+ Ask again whenever the transcript changes, and re-check the score as the silence grows. Until the first words of the
142
+ turn are transcribed, treat the end-of-turn score as 0.5 (or the wait as 1.0 s).
143
+
144
+ ### Caller and agent (voice agents, phone calls)
145
+
146
+ The words decide most; an audio turn model breaks ties with what the words can't carry, such as a falling tone.
147
+ We use Smart Turn v3.2 (BSD-2), rescored as the pause goes on.
148
+
149
+ ```python
150
+ p_end = probs.get("DONE", 0) + probs.get("LIKELY_DONE", 0)
151
+ score = p_end * p_voice ** 0.5 # p_voice: the audio model's end-of-turn probability for this moment
152
+ if score >= THETA:
153
+ reply()
154
+ ```
155
+
156
+ THETA sets the trade between replying fast and cutting callers off. Choose it on your own calls for the false
157
+ cut-off rate you can accept. This is the setup behind the eot-bench numbers below: at a 5% false cut-off rate, Cue v1
158
+ replies after 719 ms on average in English.
159
+
160
+ ### Two people talking
161
+
162
+ Here the answer sets how long to wait. Give each option a silence budget and blend by probability:
163
+
164
+ ```python
165
+ WAITS = {"DONE": 0.5, "LIKELY_DONE": 1.0, "MID_THOUGHT": 1.75, "HOLDING": 2.25} # seconds of silence
166
+ wait = sum(p * WAITS[o] for o, p in probs.items())
167
+ if silence_so_far >= wait:
168
+ take_the_turn()
169
+ ```
170
+
171
+ A confident DONE ends the turn after half a second; a likely HOLDING waits over two seconds, so someone thinking
172
+ mid-story isn't cut off. Don't multiply by an audio model here: in our two-person tests, adding Smart Turn's score
173
+ made it slightly worse.
174
+
175
+ ### Interruptions
176
+
177
+ When the caller says something while the agent is talking, ask the interruption question with what the agent is
178
+ saying, the question it last asked, and the caller's words so far:
179
+
180
+ ```python
181
+ probs = ask(INTERRUPTION, {"agent_said": "...so the appointment is on Tuesday at", "agent_question": "",
182
+ "caller_text": "wait, Tuesday doesn't work"})
183
+ # {'STOP': 0.999, 'CONTINUE': 0.001}. The same question with "hmm" gives {'CONTINUE': 1.0, 'STOP': 0.0}
184
+ if probs.get("STOP", 0) >= 0.5:
185
+ stop_speaking() # a real interruption: listen
186
+ # else: a backchannel ("hmm", "okay"), keep talking
187
+ ```
188
+
189
+ Ask again as more of the caller's words arrive: a fragment can read like a backchannel until the rest of it comes in.
190
+ The threshold trades stopping on noise against talking over a caller; 0.5 is a sensible start.
191
+
192
+ ## What it is
193
+
194
+ - **Base:** Gemma 4 E4B instruction-tuned, fine-tuned with a LoRA adapter (rank 16) using Unsloth, merged and
195
+ quantised to q8_0 GGUF.
196
+ - **Input:** text. What the other side last said, what the speaker has said so far in this turn, and how the
197
+ transcript ends (a question mark, a full stop, or no sentence end). Audio is read by your ASR and, optionally, an
198
+ audio turn model.
199
+ - **Output:** a probability for each option, from one forward pass. No JSON, no generated text, no parsing.
200
+ - **Prompts are part of the model.** The options, their descriptions and any worked examples live in
201
+ `prompts/*.json`. Changing a description or adding examples in another language changes behaviour without
202
+ retraining.
203
+
204
+ ## Training data: simulated conversations
205
+
206
+ The fine-tune never saw a real recording or a benchmark transcript.
207
+
208
+ - **310 conversations written by an LLM** to a written specification, each on a different topic: 150 phone calls
209
+ between a caller and an agent, and 160 two-person conversations (casual, storytelling, instructional,
210
+ argumentative, collaborative, transactional).
211
+ - **About 10,600 labelled pauses**, plus 600 backchannels and 430 real interruptions for the barge-in decision.
212
+ - **Speech textures written in:** fillers and false starts, repeated words, trailing conjunctions, unpunctuated
213
+ endings, and numbers and addresses read out in pieces. A few Indian-English backchannels ("haan", "achha") appear,
214
+ but the data is English.
215
+ - **Shaped by a few aggregate statistics** from TurnBench's public dev split: the share of pauses that are holds
216
+ (about 35%), the share of holds that follow a complete sentence (about 70%), and the share of turn ends without
217
+ punctuation (about 25%). No dev audio, transcript or label was used as a training example. The TurnBench test split
218
+ and otoSpeech were not used at all.
219
+ - **The loss is on the answer letter only**, so the model learns where to put probability, not how to write.
220
+
221
+ On held-out simulated conversations (10% of the conversations, never trained on):
222
+
223
+ | | Base Gemma 4 E4B | Cue v1 |
224
+ |---|---|---|
225
+ | Exact answer, every held-out question | 38.3% | **83.2%** |
226
+ | End of turn: done vs not done | 58.2% | **86.9%** |
227
+ | Interruptions: STOP vs CONTINUE (90 questions) | 93.3% | **98.9%** |
228
+
229
+ The base model almost never answers HOLDING. That is the main thing the fine-tune teaches.
230
+
231
+ ## Evaluation: eot-bench, English
232
+
233
+ [LiveKit's eot-bench](https://livekit.com/benchmarks/eot-bench), all 400 English turns, scored with the harness's
234
+ own metrics. Latency at a 5% and 10% false cut-off rate, and false cut-offs at a 300 ms and 600 ms latency budget.
235
+
236
+ ![Cue v1 on eot-bench, English, against 13 other turn detectors](assets/cue-v1-english.png)
237
+
238
+ | Model | Latency @5% | Latency @10% | Cut-offs @300 ms | Cut-offs @600 ms |
239
+ |---|---|---|---|---|
240
+ | LiveKit turn-detector v1 (hosted) | 543 ms | 295 ms | 9.9% | 4.5% |
241
+ | Baton v1 | 577 ms | 350 ms | 12.3% | 4.8% |
242
+ | Soniox stt-rt-preview | 647 ms | 512 ms | – | 5.5% |
243
+ | **Cue v1 × √Smart Turn v3.2** | **719 ms** | **532 ms** | **26.8%** | **7.5%** |
244
+ | Cue v1, words only | 750 ms | 552 ms | 34.0% | 7.8% |
245
+ | Deepgram flux-general-multi | 1151 ms | 548 ms | 12.9% | 9.9% |
246
+ | ultraVAD | 899 ms | 663 ms | 27.7% | 11.9% |
247
+ | LiveKit turn-detector v1-mini (local) | 1070 ms | 698 ms | 27.8% | 12.1% |
248
+ | Smart Turn v3.2 | 1051 ms | 739 ms | 35.2% | 14.8% |
249
+
250
+ Cue v1 × √Smart Turn v3.2 is **4th on all four views** (AUC 0.961), behind LiveKit v1, Baton v1 and Soniox, and
251
+ ahead of Deepgram Flux, ultraVAD, LiveKit's local model and Smart Turn v3.2 on its own.
252
+
253
+ **Honest notes on this table:**
254
+ - The same pipeline with the *base* Gemma and the same prompt scores 723 / 502 ms: on this benchmark the fine-tune is
255
+ a tie with a well-prompted base model. Its gains show on holds (below) and on two-person conversation.
256
+ - Our decision's compute time (about 100 to 300 ms on a T4) is not part of these latencies.
257
+
258
+ ## 14 languages, one model, one English prompt
259
+
260
+ Cue v1 × √Smart Turn v3.2 on all 14 eot-bench languages, with **one model and the same English prompt, English
261
+ examples included**: no translation, no per-language examples, no per-language tuning. The fine-tune itself only
262
+ ever saw English. The one language-aware step is automatic. Where most caller turns end without punctuation in the
263
+ transcript (Arabic, Hindi, Indonesian, Japanese, Korean, Chinese), the "how the transcript ends" input is set to
264
+ neutral, because a missing full stop means nothing there.
265
+
266
+ | Language | Latency @5% | Latency @10% | Cut-offs @300 ms | Cut-offs @600 ms |
267
+ |---|---|---|---|---|
268
+ | English | 719 ms (4th of 13) | 532 ms (4th) | 26.8% (4th) | 7.5% (4th) |
269
+ | Portuguese | **681 ms (1st of 11)** | 498 ms (3rd) | 27.7% (3rd) | 6.5% (**2nd**) |
270
+ | Spanish | 792 ms (**2nd** of 11) | 603 ms (3rd) | 29.9% (4th) | 10.1% (3rd) |
271
+ | French | 647 ms (3rd of 11) | 449 ms (3rd) | 24.4% (4th) | 5.2% (**2nd**) |
272
+ | German | 786 ms (3rd of 11) | 559 ms (4th) | 22.7% (3rd) | 9.2% (4th) |
273
+ | Italian | 912 ms (3rd of 11) | 595 ms (3rd) | 31.6% (3rd) | 9.9% (3rd) |
274
+ | Dutch | 770 ms (3rd of 10) | 554 ms (4th) | 24.4% (3rd) | 9.4% (4th) |
275
+ | Japanese | 557 ms (3rd of 10) | 406 ms (3rd) | 15.1% (3rd) | 4.6% (3rd) |
276
+ | Korean | 608 ms (**2nd** of 9) | 406 ms (3rd) | 21.2% (3rd) | 5.5% (**2nd**) |
277
+ | Chinese | 838 ms (**2nd** of 9) | 637 ms (3rd) | 35.5% (3rd) | 11.7% (3rd) |
278
+ | Indonesian | 871 ms (**2nd** of 9) | 598 ms (**2nd**) | 34.4% (**2nd**) | 9.9% (**2nd**) |
279
+ | Hindi | 607 ms (4th of 10) | 494 ms (3rd) | 30.8% (4th) | 5.3% (4th) |
280
+ | Turkish | 1012 ms (3rd of 9) | 692 ms (3rd) | 33.9% (**2nd**) | 11.6% (3rd) |
281
+ | Arabic | 815 ms (6th of 9) | 640 ms (5th) | 39.9% (4th) | 12.4% (4th) |
282
+
283
+ Ranks are among the models the benchmark reports for that language. Not every model covers every language.
284
+
285
+ **In Portuguese, Cue v1 is 1st at a 5% false cut-off rate**, ahead of LiveKit v1 and Soniox (both 702 ms).
286
+
287
+ **Fine-tuning on English made the other languages better too.** Against the same recipe with the base Gemma, the
288
+ fine-tune is faster at the 5% budget in 11 of 14 languages and at the 10% budget in 11 of 14. The biggest moves:
289
+ Hindi from 7th to 4th, Portuguese from 4th to 1st, French 698 to 647 ms, Korean 485 to 406 ms at 10%. It is slower
290
+ in Dutch (688 to 770 ms at 5%), and at the 10% budget in English (502 to 532 ms) and Chinese (606 to 637 ms).
291
+
292
+ The whole board, with Cue v1 in the outlined column. The small rank under each Cue value counts every model eot-bench
293
+ reports for that language; the outlined cell in each row is the best. Grey cells are languages a model does not support.
294
+
295
+ ![Latency at a 5% false cut-off rate, 14 languages](assets/cue-v1-languages-latency.png)
296
+
297
+ ![False cut-offs at a 300 ms latency budget, 14 languages](assets/cue-v1-languages-cutoffs.png)
298
+
299
+ <details>
300
+ <summary>The other two views: latency at 10% cut-offs, and cut-offs at a 600 ms budget</summary>
301
+
302
+ ![Latency at a 10% false cut-off rate, 14 languages](assets/cue-v1-languages-latency-10pct.png)
303
+
304
+ ![False cut-offs at a 600 ms latency budget, 14 languages](assets/cue-v1-languages-cutoffs-600ms.png)
305
+
306
+ </details>
307
+
308
+ Across the 14 languages, counting the 5% and 10% budgets:
309
+
310
+ | Against | Cue v1 is faster in |
311
+ |---|---|
312
+ | OpenAI GPT Realtime 2 (semantic VAD) | 28 of 28 |
313
+ | VAP | 28 of 28 |
314
+ | Gradium | 28 of 28 |
315
+ | Smart Turn v3.2 | 26 of 28 |
316
+ | ultraVAD | 25 of 28 |
317
+ | LiveKit turn-detector v1-mini (local) | 25 of 28 |
318
+ | Deepgram Flux | 12 of 18 |
319
+ | Soniox | 12 of 28 |
320
+ | LiveKit turn-detector v1 (hosted) | 1 of 28: still the one to beat |
321
+
322
+ Arabic is the weak spot (6th at 5%).
323
+
324
+ ## Compute
325
+
326
+ - One decision is one forward pass of a 4B model plus one token: about 100 to 300 ms on an NVIDIA T4 with llama.cpp,
327
+ 4 concurrent calls per T4 in our deployment.
328
+ - The q8_0 GGUF is 7.5 GB.
329
+
330
+ ## Files
331
+
332
+ | File | What |
333
+ |---|---|
334
+ | `cue-v1-q8_0.gguf` | Merged model, q8_0, for llama.cpp |
335
+ | `adapter/` | The LoRA adapter (safetensors) for `google/gemma-4-E4B-it` |
336
+ | `prompts/end_of_turn_phone.json` | End of turn, caller and agent on a phone call |
337
+ | `prompts/end_of_turn_dialogue.json` | End of turn, two people talking |
338
+ | `prompts/barge_in.json` | Interruptions: STOP or CONTINUE while the agent is speaking |
339
+ | `assets/` | The English table, the 14-language heatmaps and the script that draws them; the Thought Hold demo clip |
340
+
341
+ ## Intended use
342
+
343
+ Turn-taking inside voice agents and other spoken-dialogue systems that already have an ASR: deciding when to
344
+ reply, when to keep listening, and whether something the caller says over the agent should stop it.
345
+
346
+ ## Limits
347
+
348
+ - **Reads words, not prosody.** On its own it cannot hear a falling tone or a breath. Pair it with an audio turn model
349
+ for that.
350
+ - **Short objections can pass as backchannels.** "Hmm, actually no" said over the agent comes back CONTINUE (0.94).
351
+ Clear interruptions ("wait, Tuesday doesn't work") are STOP at 0.999, but a lone "no" or "actually" can be missed.
352
+ - **Needs an ASR and a GPU.** It is not an on-device model.
353
+ - **Trained on English only.** It carries over to 13 other languages with the English prompt, but every
354
+ language except Portuguese still has a faster hosted model, and Arabic is behind most of the board.
355
+ - **Trained on simulated conversations.** Real speech is messier than any written transcript. Measure it on your
356
+ own calls before relying on its probabilities.
357
+
358
+ ## Licence
359
+
360
+ Apache 2.0, the same as the base model, Gemma 4 E4B.
361
+
362
+ ## Citation
363
+
364
+ ```bibtex
365
+ @misc{pype2026cue,
366
+ title = {Cue v1: turn detection by a fine-tuned decision model},
367
+ author = {Pype AI},
368
+ year = {2026},
369
+ url = {https://huggingface.co/Pype/cue-v1}
370
+ }
371
+ ```
assets/cue-v1-english.png ADDED

Git LFS Details

  • SHA256: 499ff823304d2b4292ac172d96ca90738752de55f3a23c528eafffc501906bd7
  • Pointer size: 131 Bytes
  • Size of remote file: 283 kB
assets/cue-v1-languages-cutoffs-600ms.png ADDED

Git LFS Details

  • SHA256: abf35e33312838570d659d45294d782319e5c024c5455f2458a65d4467788eea
  • Pointer size: 131 Bytes
  • Size of remote file: 491 kB
assets/cue-v1-languages-cutoffs.png ADDED

Git LFS Details

  • SHA256: 3dcd0db7e828eeafab1fe5f610b2dc0b5d56d29a813d795575b3d4516c9c1962
  • Pointer size: 131 Bytes
  • Size of remote file: 498 kB
assets/cue-v1-languages-latency-10pct.png ADDED

Git LFS Details

  • SHA256: 9bf19fc31d04c4b2aabf494f6f98e5539056b296d8480d37d852b2719aee2c26
  • Pointer size: 131 Bytes
  • Size of remote file: 499 kB
assets/cue-v1-languages-latency.png ADDED

Git LFS Details

  • SHA256: b5ce1429d8e5aea3f9747a38834b6acc6530326b9287d15e3a57b5b2292a5bff
  • Pointer size: 131 Bytes
  • Size of remote file: 498 kB
assets/cue-v1-thought-hold.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0fbb0f47b294625195378cb405cf9e30bb46b71d5cbb51717dd48069af07181e
3
+ size 2612863
assets/make_infographics.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Cue v1 launch infographics on an off-white surface: the English table and the 14-language heatmaps."""
2
+ import json
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ import matplotlib
7
+ matplotlib.use("Agg")
8
+ import matplotlib.pyplot as plt
9
+ from matplotlib.colors import LinearSegmentedColormap
10
+ from matplotlib.patches import FancyBboxPatch, Rectangle
11
+
12
+ SUMMARY = Path(sys.argv[1])
13
+ OUT = Path(sys.argv[2])
14
+ s = json.loads(SUMMARY.read_text())
15
+ V = s["values"]
16
+ L5, L10 = "best_mean_latency_at_5pct_cutoff", "best_mean_latency_at_10pct_cutoff"
17
+ C3, C6 = "best_cutoff_rate_at_0_3s_latency", "best_cutoff_rate_at_0_6s_latency"
18
+ CUE = "Shortwire Cue v1 (fine-tuned)"
19
+
20
+ SURFACE = "#faf8f3"
21
+ INK, INK2, MUTED = "#1b1b1a", "#5f5e5a", "#9a9890"
22
+ RULE = "#e6e3da"
23
+ BLUE, BLUE_TINT = "#2a78d6", "#e6f0fb"
24
+ RAMP = ["#cde2fb", "#b7d3f6", "#9ec5f4", "#86b6ef", "#6da7ec", "#5598e7", "#3987e5", "#2a78d6", "#256abf",
25
+ "#1c5cab", "#184f95", "#104281", "#0d366b"]
26
+ plt.rcParams.update({"font.family": ["Helvetica Neue", "Helvetica", "Arial"], "text.color": INK,
27
+ "axes.edgecolor": SURFACE, "figure.facecolor": SURFACE, "axes.facecolor": SURFACE})
28
+
29
+ NAMES = {CUE: "Cue v1 (Shortwire)", "LiveKit Turn Detector v1": "LiveKit Turn Detector v1",
30
+ "LiveKit Turn Detector v1-mini": "LiveKit v1-mini (local)", "SmartTurn v3.2": "Smart Turn v3.2",
31
+ "OpenAI GPT Realtime 2": "OpenAI GPT Realtime 2", "JoinIn AI Baton": "JoinIn AI Baton",
32
+ "VAP (silent agent)": "VAP", "VAD baseline": "Silence only (VAD)"}
33
+ LANG = {"en": "English", "pt": "Portuguese", "es": "Spanish", "fr": "French", "de": "German", "it": "Italian",
34
+ "nl": "Dutch", "ja": "Japanese", "ko": "Korean", "zh": "Chinese", "id": "Indonesian", "hi": "Hindi",
35
+ "tr": "Turkish", "ar": "Arabic"}
36
+
37
+
38
+ def english_table(path: Path) -> None:
39
+ rows = []
40
+ for m in s["models"]:
41
+ v = [V[k]["en"].get(m) for k in (L5, L10, C3, C6)]
42
+ if v[0] is None:
43
+ continue
44
+ rows.append((NAMES.get(m, m), v[0] * 1000, None if v[1] is None else v[1] * 1000,
45
+ None if v[2] is None else v[2] * 100, None if v[3] is None else v[3] * 100, m == CUE, False))
46
+ rows.append(("turn-1-mini †", 849.0, None, 22.6, None, False, True))
47
+ base = [r for r in rows if r[0] == "Silence only (VAD)"]
48
+ rows = sorted([r for r in rows if r[0] != "Silence only (VAD)"], key=lambda r: r[1]) + base
49
+
50
+ n = len(rows)
51
+ fig = plt.figure(figsize=(12, 1.5 + 0.46 * n), dpi=200)
52
+ ax = fig.add_axes([0.04, 0.07, 0.92, 0.79])
53
+ ax.set_xlim(0, 100)
54
+ ax.set_ylim(n, -0.75)
55
+ ax.axis("off")
56
+ fig.text(0.04, 0.94, "Cue v1 on eot-bench, English", fontsize=20, fontweight="bold")
57
+ fig.text(0.04, 0.905, "400 caller turns, scored with the benchmark's own harness. Lower is better in every column.",
58
+ fontsize=11, color=INK2)
59
+ cols = [(3, "#", "left"), (7, "Model", "left"), (38, "Latency @ 5% cut-offs", "left"), (66, "@ 10%", "right"),
60
+ (84, "Cut-offs @ 300 ms", "right"), (97, "@ 600 ms", "right")]
61
+ for x, label, ha in cols:
62
+ ax.text(x, -0.45, label, fontsize=10, color=INK2, ha=ha, va="center", fontweight="bold")
63
+ ax.plot([0, 100], [-0.05, -0.05], color=INK, lw=0.8)
64
+ vmax = max(r[1] for r in rows)
65
+ for i, (name, l5, l10, c3, c6, cue, reported) in enumerate(rows):
66
+ y = i + 0.5
67
+ if cue:
68
+ ax.add_patch(FancyBboxPatch((0.4, i + 0.08), 99.2, 0.84, boxstyle="round,pad=0,rounding_size=0.6",
69
+ fc=BLUE_TINT, ec="none", zorder=0))
70
+ else:
71
+ ax.plot([0, 100], [i + 1, i + 1], color=RULE, lw=0.7)
72
+ weight = "bold" if cue else "normal"
73
+ ink = MUTED if name.startswith("Silence") else INK
74
+ rank = "" if name.startswith("Silence") else str(i + 1)
75
+ ax.text(3, y, rank, fontsize=11, color=INK2, va="center")
76
+ ax.text(7, y, name, fontsize=12, color=ink, va="center", fontweight=weight, style="italic" if reported else "normal")
77
+ w = 14 * l5 / vmax # inline bar for the headline column
78
+ ax.add_patch(FancyBboxPatch((38, y - 0.13), w, 0.26, boxstyle="round,pad=0,rounding_size=0.12",
79
+ fc=BLUE if cue else ("#c9c6bc" if not name.startswith("Silence") else "#e2dfd6"),
80
+ ec="none"))
81
+ ax.text(38 + w + 0.8, y, f"{l5:,.0f} ms", fontsize=11, va="center", color=ink, fontweight=weight)
82
+ for x, val, unit in ((66, l10, " ms"), (84, c3, "%"), (97, c6, "%")):
83
+ txt = "–" if val is None else (f"{val:,.0f}{unit}" if unit == " ms" else f"{val:.1f}{unit}")
84
+ ax.text(x, y, txt, fontsize=11, ha="right", va="center", color=MUTED if val is None else ink,
85
+ fontweight=weight)
86
+ fig.text(0.04, 0.025, "† turn-1-mini as published by P99Lab on the same benchmark (two views reported). "
87
+ "– means the view isn't reported. Cue v1 = fine-tuned Gemma 4 E4B × √Smart Turn v3.2.",
88
+ fontsize=9, color=INK2)
89
+ fig.savefig(path, facecolor=SURFACE)
90
+ plt.close(fig)
91
+
92
+
93
+ def heatmap(metric: str, title: str, sub: str, fmt, path: Path, lo: float, hi: float, legend_label: str = "best in the row") -> None:
94
+ langs = ["en", "pt", "es", "fr", "de", "it", "nl", "ja", "ko", "zh", "id", "hi", "tr", "ar"]
95
+ models = [CUE, "LiveKit Turn Detector v1", "Soniox", "Deepgram Flux", "Gradium", "ultraVAD",
96
+ "LiveKit Turn Detector v1-mini", "SmartTurn v3.2", "OpenAI GPT Realtime 2", "AssemblyAI", "VAP (silent agent)"]
97
+ models = [m for m in models if any(V[metric][l].get(m) is not None for l in langs)]
98
+ cmap = LinearSegmentedColormap.from_list("blue", RAMP)
99
+ nr, nc = len(langs), len(models)
100
+ fig = plt.figure(figsize=(14, 9.6), dpi=200)
101
+ ax = fig.add_axes([0.11, 0.07, 0.86, 0.68])
102
+ ax.set_xlim(0, nc)
103
+ ax.set_ylim(nr, 0)
104
+ ax.axis("off")
105
+ fig.text(0.03, 0.935, title, fontsize=21, fontweight="bold")
106
+ fig.text(0.03, 0.895, sub, fontsize=11.5, color=INK2)
107
+ for j, m in enumerate(models):
108
+ label = NAMES.get(m, m).replace(" (Shortwire)", "")
109
+ ax.text(j + 0.5, -0.25, label, rotation=35, ha="left", va="bottom", fontsize=10.5,
110
+ color=BLUE if m == CUE else INK, fontweight="bold" if m == CUE else "normal",
111
+ rotation_mode="anchor")
112
+ for i, l in enumerate(langs):
113
+ ax.text(-0.15, i + 0.5, LANG[l], ha="right", va="center", fontsize=11.5, color=INK)
114
+ vals = {m: V[metric][l].get(m) for m in models}
115
+ present = {m: v for m, v in vals.items() if v is not None}
116
+ best = min(present, key=present.get)
117
+ board = {k: x for k, x in V[metric][l].items() if x is not None and k not in ("VAD baseline", "Shortwire cue-v1 (Gemma)", "Shortwire cue-v1 (Jev)")}
118
+ order = sorted(board, key=board.get)
119
+ for j, m in enumerate(models):
120
+ v = vals[m]
121
+ g = 0.035
122
+ if v is None:
123
+ ax.add_patch(Rectangle((j + g, i + g), 1 - 2 * g, 1 - 2 * g, fc="#efece4", ec="none"))
124
+ continue
125
+ t = float(min(1.0, max(0.0, (v - lo) / (hi - lo))))
126
+ c = cmap(t)
127
+ ax.add_patch(FancyBboxPatch((j + g, i + g), 1 - 2 * g, 1 - 2 * g, boxstyle="round,pad=0,rounding_size=0.06",
128
+ fc=c, ec="none"))
129
+ dark = t > 0.55
130
+ txt = fmt(v)
131
+ ax.text(j + 0.5, i + 0.5 - (0.1 if m == CUE else 0), txt, ha="center", va="center", fontsize=11,
132
+ color="white" if dark else INK, fontweight="bold" if (m == best or m == CUE) else "normal")
133
+ if m == CUE:
134
+ r = order.index(m) + 1
135
+ ax.text(j + 0.5, i + 0.78, {1: "1st", 2: "2nd", 3: "3rd"}.get(r, f"{r}th"), ha="center", va="center",
136
+ fontsize=8.5, color="white" if dark else INK2)
137
+ if m == best:
138
+ ax.add_patch(Rectangle((j + 0.07, i + 0.07), 0.86, 0.86, fc="none", ec=INK, lw=1.6))
139
+ ax.add_patch(FancyBboxPatch((0.0, -0.02), 1.0, nr + 0.04, boxstyle="round,pad=0,rounding_size=0.08",
140
+ fc="none", ec=BLUE, lw=2.4))
141
+ # legend: ramp + best marker
142
+ lx = fig.add_axes([0.62, 0.036, 0.2, 0.012])
143
+ lx.imshow([[i / 100 for i in range(101)]], cmap=cmap, aspect="auto")
144
+ lx.set_xticks([0, 100])
145
+ lx.set_xticklabels([fmt(lo), fmt(hi) + "+"], fontsize=9, color=INK2)
146
+ lx.set_yticks([])
147
+ for sp in lx.spines.values():
148
+ sp.set_visible(False)
149
+ lx.tick_params(length=0)
150
+ bx = fig.add_axes([0.875, 0.028, 0.014, 0.022]); bx.axis("off")
151
+ bx.add_patch(Rectangle((0.05, 0.05), 0.9, 0.9, fc="none", ec=INK, lw=1.6))
152
+ fig.text(0.893, 0.033, legend_label, fontsize=10, color=INK)
153
+ fig.text(0.11, 0.045, "Cue v1 = fine-tuned Gemma 4 E4B × √Smart Turn v3.2: one model, one English prompt.", fontsize=9.5, color=INK2)
154
+ fig.text(0.11, 0.022, "Rank under Cue counts every model eot-bench reports for that language. Grey: not covered.", fontsize=9.5, color=INK2)
155
+ fig.savefig(path, facecolor=SURFACE)
156
+ plt.close(fig)
157
+
158
+
159
+ OUT.mkdir(parents=True, exist_ok=True)
160
+ english_table(OUT / "cue-v1-english.png")
161
+ heatmap(L5, "Latency at a 5% false cut-off rate, 14 languages",
162
+ "How long each model waits after the caller stops, when at most 5% of turns get cut off. Lighter is faster.",
163
+ lambda v: f"{v * 1000:.0f}", OUT / "cue-v1-languages-latency.png", 0.3, 1.15, "fastest in the row")
164
+ heatmap(C3, "False cut-offs at a 300 ms latency budget, 14 languages",
165
+ "Share of turns cut off when the model must answer within 300 ms. Lighter is better.",
166
+ lambda v: f"{v * 100:.0f}%", OUT / "cue-v1-languages-cutoffs.png", 0.05, 0.55, "fewest in the row")
167
+ heatmap(L10, "Latency at a 10% false cut-off rate, 14 languages",
168
+ "How long each model waits after the caller stops, when at most 10% of turns get cut off. Lighter is faster.",
169
+ lambda v: f"{v * 1000:.0f}", OUT / "cue-v1-languages-latency-10pct.png", 0.2, 0.9, "fastest in the row")
170
+ heatmap(C6, "False cut-offs at a 600 ms latency budget, 14 languages",
171
+ "Share of turns cut off when the model must answer within 600 ms. Lighter is better.",
172
+ lambda v: f"{v * 100:.0f}%", OUT / "cue-v1-languages-cutoffs-600ms.png", 0.03, 0.2, "fewest in the row")
173
+ print("ok")
prompts/barge_in.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "barge_in",
3
+ "clock": "partial",
4
+ "priority": 1,
5
+ "instruction": "The agent is speaking on a phone call and the caller just said something over her. Should she stop?",
6
+ "options": [
7
+ {
8
+ "id": "STOP",
9
+ "describe": "a real interruption: a question, request, objection, correction, 'wait', 'hello?', or a full answer to her question"
10
+ },
11
+ {
12
+ "id": "CONTINUE",
13
+ "describe": "only listening or not for her: 'hmm', 'haan', 'ji', 'okay', 'achha', laughter, noise, a fragment"
14
+ }
15
+ ],
16
+ "inputs": [
17
+ "agent_said",
18
+ "agent_question",
19
+ "caller_text"
20
+ ],
21
+ "examples": [],
22
+ "deadline_ms": 400,
23
+ "default": "CONTINUE"
24
+ }
prompts/end_of_turn_dialogue.json ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "end_of_turn",
3
+ "clock": "pause",
4
+ "priority": 2,
5
+ "instruction": "Two people are talking. The speaker has just paused. Is it the other person's turn now? The speaker is done once they have finished their sentence and their point: answered or asked something, made their point, or reacted to what the other person said. They are not done if the last words leave the sentence hanging, if they are partway through a list, a story or an explanation that is clearly going on, or if they have only set up what they are about to say. How the speaker's words end is given below: a question mark almost always means they are done; no sentence end usually means more is coming.",
6
+ "options": [
7
+ {
8
+ "id": "DONE",
9
+ "describe": "finished: they asked something, answered, reacted or made their point, in a complete sentence. The other person can speak now"
10
+ },
11
+ {
12
+ "id": "LIKELY_DONE",
13
+ "describe": "probably finished: a complete statement that might still get one more sentence"
14
+ },
15
+ {
16
+ "id": "MID_THOUGHT",
17
+ "describe": "not finished: the sentence is left hanging (it stops on 'and', 'the', 'with', 'because', a repeated word, or a noun that needs more), or they have only set up what they are about to say. They will continue"
18
+ },
19
+ {
20
+ "id": "HOLDING",
21
+ "describe": "telling something long, a story or an explanation: a pause is them thinking. They will continue"
22
+ }
23
+ ],
24
+ "inputs": [
25
+ "Other person last said",
26
+ "Speaker said",
27
+ "Speaker's words end with"
28
+ ],
29
+ "examples": [],
30
+ "deadline_ms": 400,
31
+ "default": "DONE"
32
+ }
prompts/end_of_turn_phone.json ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "end_of_turn",
3
+ "clock": "pause",
4
+ "priority": 2,
5
+ "instruction": "The caller has paused on a phone call with an agent. Is it the agent's turn now? The caller is done once they have asked their question, made their request, or answered what the agent asked (even in a different form than asked: a name, a date, a code read out), in a complete sentence. They are not done if the last words leave the sentence hanging, if they are partway through reading out a number, or if they have only greeted, given their name or set the scene without yet saying what they want. How the transcript ends is given below: a question mark almost always means they are done; no sentence end usually means more is coming.",
6
+ "options": [
7
+ {
8
+ "id": "DONE",
9
+ "describe": "finished: they asked a question, made a request, answered the agent, or confirmed or declined, in a complete sentence. Reply now"
10
+ },
11
+ {
12
+ "id": "LIKELY_DONE",
13
+ "describe": "probably finished: a complete answer or statement that might still get one more sentence"
14
+ },
15
+ {
16
+ "id": "MID_THOUGHT",
17
+ "describe": "not finished: the sentence is left hanging (it stops on 'and', 'the', 'with', 'for', a repeated word, or a noun that needs more), a number is only partly read out, or they have only greeted, given their name or the background, without yet saying what they want. They will continue"
18
+ },
19
+ {
20
+ "id": "HOLDING",
21
+ "describe": "telling something long, a story or an explanation: a pause is them thinking. They will continue"
22
+ }
23
+ ],
24
+ "inputs": [
25
+ "agent_question",
26
+ "caller_earlier",
27
+ "caller_text",
28
+ "transcript_end"
29
+ ],
30
+ "examples": [
31
+ {
32
+ "inputs": {
33
+ "agent_question": "How can I help you today?",
34
+ "caller_text": "Hi, this is Priya.",
35
+ "transcript_end": "a full stop"
36
+ },
37
+ "answer": "MID_THOUGHT"
38
+ },
39
+ {
40
+ "inputs": {
41
+ "agent_question": "How can I help you today?",
42
+ "caller_text": "Hello, I booked a room with you last week and I need to change the dates.",
43
+ "transcript_end": "a full stop"
44
+ },
45
+ "answer": "DONE"
46
+ },
47
+ {
48
+ "inputs": {
49
+ "agent_question": "How can I help you today?",
50
+ "caller_text": "Is there a slot on Friday morning?",
51
+ "transcript_end": "a question mark"
52
+ },
53
+ "answer": "DONE"
54
+ },
55
+ {
56
+ "inputs": {
57
+ "agent_question": "What is your booking reference?",
58
+ "caller_text": "it's K L nine four two two",
59
+ "transcript_end": "no sentence end"
60
+ },
61
+ "answer": "DONE"
62
+ },
63
+ {
64
+ "inputs": {
65
+ "agent_question": "What's your phone number?",
66
+ "caller_text": "sure, it's three one two, five five",
67
+ "transcript_end": "no sentence end"
68
+ },
69
+ "answer": "MID_THOUGHT"
70
+ },
71
+ {
72
+ "inputs": {
73
+ "agent_question": "Anything else with the pizza?",
74
+ "caller_text": "yes, can I get that with extra",
75
+ "transcript_end": "no sentence end"
76
+ },
77
+ "answer": "MID_THOUGHT"
78
+ },
79
+ {
80
+ "inputs": {
81
+ "agent_question": "Which city are you in?",
82
+ "caller_text": "I stay in Pune but",
83
+ "transcript_end": "no sentence end"
84
+ },
85
+ "answer": "MID_THOUGHT"
86
+ },
87
+ {
88
+ "inputs": {
89
+ "agent_question": "Does that sound right?",
90
+ "caller_text": "Yes, that's right.",
91
+ "transcript_end": "a full stop"
92
+ },
93
+ "answer": "DONE"
94
+ },
95
+ {
96
+ "inputs": {
97
+ "caller_text": "yeah it was fine I guess",
98
+ "transcript_end": "no sentence end"
99
+ },
100
+ "answer": "LIKELY_DONE"
101
+ },
102
+ {
103
+ "inputs": {
104
+ "caller_earlier": "can I tell you what happened last year?",
105
+ "caller_text": "So in March my father was admitted to the hospital.",
106
+ "transcript_end": "a full stop"
107
+ },
108
+ "answer": "HOLDING"
109
+ },
110
+ {
111
+ "inputs": {
112
+ "agent_question": "Is there anything else I can help with?",
113
+ "caller_text": "No, that's all, thank you so much.",
114
+ "transcript_end": "a full stop"
115
+ },
116
+ "answer": "DONE"
117
+ }
118
+ ],
119
+ "deadline_ms": 400,
120
+ "default": "DONE"
121
+ }