Repository navigation
Conversation
pytest 8.4 removed PytestUnhandledCoroutineWarning, so the runner's -W filter failed to parse with the newest pytest 8.x. pytest >= 8.4 fails unrun async tests by itself; require it and drop the filter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ilures - buffer_duration_ms must be 0..30000 and the disconnected buffer is capped at 10 MiB; overflow drops the oldest frames into the dropped-audio timeline. - A non-retryable error from the handshake (HTTP 400 on upgrade, or an error event before session.ready with retryable=false) is fatal: one fatal error, latched, no reconnect task. One classification drives latch and report. - A commit the Router does not acknowledge (provider already finalized the turn) keeps the session: warn, no asr_error, one asr_finalize_end, and the turn cursor advances as for an acknowledged commit. An empty transcript.final acknowledges the commit. Real disconnects still reset. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Wrap JSON decode failures of non-streaming responses in SpekoRouterError.
- Accept {"error": "text"} envelopes over HTTP and SSE.
- Backstop: any other request-path failure, including an invalid TEN request,
is emitted as one TEN error with vendor_info before the request ends.
- on_stop cancels in-flight streams and closes the client before the base
stops, and never raises.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d frames - on_stop closes the session without draining and logs cleanup failures; an exception escaping on_stop made TEN exit the process (guarder test_invalid_text_handling). - The close drain treats ConnectionClosed as closed and has a total bound. - Whitespace-only chunks are not sent to the Router. - tts_audio_end duration counts bytes emitted as frames and its interval runs from the request's tts_audio_start; a final-chunk error reports one disconnect; a close failure during shutdown never finishes as success. - TTFB starts right before the input.append send and is rounded. - tests/test_audio_accounting.py drives known PCM through the real base. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SpekoObserver times the first non-whitespace input.append frame to the first nonempty binary audio frame per connection, excluding control frames and Router JSON events. The guarder maps base_url to the Router TTS socket itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The live guarder's provider-boundary reference saw TTFB 192.5 ms where the extension reported 195 ms (1 ms tolerance). The arrival time was read after asyncio.wait_for's task hop back to the caller, which can take milliseconds under load. Take it in the receive coroutine, right after recv returns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A caller queued on _connection_lock behind a handshake that failed fatally passed the unlocked latch check, so the base start_connection wrapper emitted a second connecting -> disconnected pair for the same root cause. Re-check the latch under the lock. Audio arriving before on_start also scheduled a reconnect that raced the first handshake and spent retry budget. Schedule audio-driven reconnects only after a connection has been requested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
[P1] Keep buffered ASR audio within its owning session When frames from two [P1] Do not infer a final's audio boundary from receive time If the provider auto-finalizes the first utterance, but audio for the next utterance is sent before that final reaches the listener, [P1] Supply passing current-head TTS guarder evidence This is a new TTS extension. The PR has no comments, and the body reports a TTS guarder run at [P2] Isolate late finalize acknowledgements after a timeout After a commit times out, its waiter is discarded while the same socket remains active. If a new turn starts and its commit is pending when the first commit's delayed [P2] Treat normal WebSocket closure as a normal disconnect The WebSocket iterator ends normally for a completed code-1000/1001 close, so [P2] Keep transient ASR recovery available after a short outage A transient outage lasting through five attempts permanently latches [P2] Wait for non-empty PCM before measuring TTS first audio A zero-length binary WebSocket frame consumes [P2] Write only emitted PCM to the TTS dump The recorder writes received bytes before the base aligns frames. If a request ends or is interrupted with an odd trailing byte, the base retains that byte and ASR checklist
|
The base class emits TTLW only from a final asr_result that arrives after asr_finalize. When the provider had already finalized the turn (the client skips the commit) or the Router acknowledged the commit with an empty final, the extension completed the finalize without a result, so no TTLW was ever sent and the guarder's test_metrics timed out on auto-routed sessions. Empty finals stay suppressed; instead, an acknowledged commit that no final consumed reports TTLW as the latency to that acknowledgement. A commit timeout or disconnect still reports none. Log the Router request_id, route and attempt_id on session.ready so support reports can cite them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
[P2] Attribute finalize latency to the finalize session When a commit is acknowledged without a non-empty final, this emits TTLW before switching [P2] Preserve array item schemas in tool definitions For a tool with an array parameter, this copies only its type and description and drops [P2] Cover actual PCM formats and request isolation in the TTS local suite The recording harness captures only frame lengths, and its exercised configuration is the default 24 kHz mono. The local suite does not establish that PCM bytes and declared frame format are correct for two supported configurations, or that rapidly interleaved request IDs retain their own audio and end events; recorder tests also mock the flush rather than compare the dump with emitted bytes. These are mandatory extension-local TTS review cases and can hide channel/rate or cross-request corruption even when duration arithmetic passes. Record the actual PCM and frame properties, exercise two output configurations and interleaved requests, and compare the real dump with emitted bytes on the terminal paths. The findings in the previous review remain applicable at head ASR checklist
|
…sion Address the PR TEN-framework#2401 review of speko_asr_python: - The Router answers every input.commit with exactly one transcript.final, in order. Commit whenever audio was sent since the last commit and pair finals with outstanding commits FIFO. A timed-out commit stays outstanding, so its late final is reported under its own turn and does not complete the next finalize. Finals with no commit outstanding are unsolicited provider results. - Audio held while disconnected belongs to one session. A frame from a new session, or a finalize while disconnected, drops the older session's held frames and records them as dropped audio instead of replaying them into the next stream. - A normal close (1000/1001, session.closed) reports disconnected and reconnects without an asr_error. - Reconnect attempts are unlimited by default with backoff capped at 4 s. max_reconnect_attempts sets a ceiling that latches retry_exhausted. - The acknowledged-finalize TTLW and finalize_end are reported under the finalize's own metadata and session_id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Array tool parameters lost their items schema, so MCP-style tools with array arguments reached the model without element types. Forward items like the other LLM2 adapters do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A zero-length binary frame no longer starts audio or stamps TTFB. - The dump holds exactly the PCM sent to the graph. The unaligned tail the base keeps as leftover is not written. - The local suite records frame bytes and format. It covers two output configurations, back-to-back requests keeping their own audio, metadata and audio_end, and the real dump equal to the emitted PCM on completion, flush and error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
[P1] Preserve session ownership when replay meets queued audio If session A has buffered frames and session B's frame is still in [P1] Distinguish automatic finals from commit acknowledgements The first [P1] Supply passing current-head TTS guarder evidence This is a new TTS extension, and the latest reported guarder run is for [P2] Recover when a timed-out commit never produces a final The timeout deliberately leaves the abandoned commit in ASR checklist
|
Address the third PR TEN-framework#2401 review of speko_asr_python: - Each input.commit carries a fresh commit_id. On a Router that stamps answers (commit_ids in session.ready, or a first answer carrying its commit_id), only the final with a commit's own id answers it. A final without an id is the provider's own endpoint and is emitted as a normal result, so an auto-final in flight when a commit is sent no longer completes that commit. A late answer to a timed-out commit is reported under that turn's metadata and resolves no other commit. A permanently missing answer shifts nothing, so the session is kept. - A Router that does not stamp answers still pairs them in order. There a commit timeout raises a plain error and the session is reset, so later answers cannot be paired one behind. - On session.ready, frames still queued are held to the same session boundary as the disconnected buffer before replay. An older session's held audio is recorded as dropped instead of sharing the stream. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Router advertises "commit_ids": true in session.ready when it stamps every commit's answer with its commit_id. That flag alone now selects correlated mode; without it, answers pair in order and a commit timeout resets the session. The guessing from the first answer is removed, so an auto-final in flight at a session's first commit can no longer flip the session to in-order pairing. A test covers one provider final answering two pending commits: an empty answer for the older id, then the words for the newest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
[P1] Enforce the session boundary when only queued audio exists If frames for sessions A and B are queued during the handshake but the consumer has not yet moved any frame into [P1] Do not treat an untagged final as a commit answer before capability is known When [P1] Supply passing current-head TTS guarder evidence This is a new TTS extension. The latest reported run is for [P2] Keep late finalize timestamps within the original turn After a correlated commit times out, its late final may arrive after audio from the next turn has been sent. For a final without segments, these lines use the current global cursor and total audio duration, not the saved commit boundary: for example, one second of turn A followed by one second of turn B yields a late A result starting at 1000 ms with B's 1000 ms duration. It also advances [P2] Exercise truly interleaved request IDs in the TTS local suite The new request-isolation test sends two complete requests ( ASR checklist
|
|
[P1] Enforce the session boundary when only queued audio exists If frames for sessions A and B queue during the handshake before the consumer moves any frame into [P1] Do not pair uncorrelated finals with commits by arrival order When [P1] Supply passing current-head TTS guarder evidence This is a new TTS extension. The PR body reports a guarder run at [P2] Keep late finalize timestamps within the original turn After a correlated commit times out, its final can arrive after audio from the next turn has been sent. For a final without segments, these lines use the current global cursor and total audio duration instead of the saved commit boundary: one second of A followed by one second of B can produce an A result starting at 1000 ms with B's duration. It also advances the cursor to the end of B, so B's own result can have zero duration. Save the turn's timeline bounds with the pending commit and use them for its late result; test both results' timestamps. This violates the ASR timeline MUST and blocks merge. [P2] Exercise interleaved request IDs in the TTS local suite This test queues two complete requests; it does not cover appended input interleaved across IDs, such as ASR checklist
|
|
Moved back to #2307: Abat gave me push access to his fork, so the same commits (head |
Continues #2307 (Speko router extensions:
speko_asr_python,speko_llm2_python,speko_tts2_python). Abat, the author of #2307, is away, so I'm taking it over from Speko's fork. His four commits are kept as they were, rebased onto currentmain, and the review fixes follow on top. Once this one is open, #2307 can be closed.Reviewed against @nitin4real's review of
8163fb0a.Review items
ASR (
speko_asr_python)buffer_duration_msmust be between 0 and 30000. A bad value fails init and latches as one fatal error, with no connection attempt.retryable=falseis fatal. That covers an HTTP 400 on upgrade and anerrorbeforesession.ready.asr_errorandconnection_statusall use one classification.trigger_connect.LLM (
speko_llm2_python)SpekoRouterError, and so are{"error": "text"}envelopes over both HTTP and SSE.vendor_info.vendor == "speko"for each case.on_stopnow cancels in-flight requests and closes the client before the base stops.TTS (
speko_tts2_python)tests/test_audio_accounting.pydrives known PCM through the realAsyncTTS2BaseExtensionpath, with a recordingten_env, a fake clock and a scripted client.tts_audio_end, duration from emitted bytes, and an interval from the matchingtts_audio_start.tts_audio_start.tts_guarder/tests/ttfb_hooks/speko.pyregisters a WebSocket observer in the current guarder TTFB infrastructure. It times the first non-emptyinput.appendsend and the first non-empty binary audio frame per request.session.configure) are excluded, and so are control frames (commit/cancel/close) and all JSON events.base_urlinside the guarder.Live guarder failures from your run
autorouting, en-US went to a provider whose realtime API has no client turn-commit and closes streams when audio stops. Soinput.commitsometimes got notranscript.final, and the session was then dropped.input.commitnow gets exactly onetranscript.final, which is empty if nothing was pending. Silence between turns no longer ends a session.asr_finalize_end, with no error.test_invalid_text_handling. An exception escapedon_stopwhile draining the Router socket, and TEN exits the process when that happens.Rebased onto
mainwith both guarder conflicts resolved:fish_audio_tts_pythonandspeko_tts2_pythonare both kept, and shared files are not reformatted.Validation (image
ten_agent_build:0.7.14, live againstrouter.speko.dev)6ceb7a530google/cosymodules missing from the image;mainfails the same way)89c4bc849test_long_duration_stream, deselected by the guarder'sbin/start). This run pinned routing tosoniox/stt-rt-v5, and everyasr_finalizewas acknowledged. The run with the default config (auto) will follow in a comment once the Router rollout finishes in every region.89c4bc84989c4bc849test_sequential_requests_ttfb: the extension reported 184/199 ms, matching the guarder's independent reference within 1 ms.TTS screenshot:
ASR screenshot:
TTS PCM output, from
tests/configs/property_dump.json: speko_tts_test_interleaved_request_id_1.pcm (.wav). It is 24000 Hz, 16-bit signed little-endian, mono, 1,036,800 bytes (21.6 s).Still to come: the quick check in the voice-assistant example (from your Slack note). I'll add the result here.
Open questions
REQUEST_END, and the error is reported separately before the end, as the base class does today. Say if you'd rather it end withERROR.