A recording is read with its speakers - #721
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: WaylandYang <wayland0916@gmail.com>
This was referenced Sep 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is cut 3 of decision record 0040. Recordings are now read by a diarizing transcription model configured per workspace. A transcript without speaker labels is refused, as the maintainer decided: it degrades like a missing model.
What changes
llm_settingsgainstranscribe_base_url,transcribe_api_keyandtranscribe_model. The key is sealed on write and by the startup backfill.GET .../settingsreturnstranscribe_base_url,transcribe_modelandhas_transcribe_key.PUT /workspaces/{id}/settings/transcribe. It re-queues the workspace's recordings waiting for transcription, including ones a previous model could not label.POST .../settings/testgainstranscribe: it sends one second of silence to the endpoint./audio/transcriptionswithresponse_format=diarized_json,chunking_strategy=autoand a Bearer key.utopia_ingest::transcript):Speaker A:, using the model's label, so extraction sees who said what.{"start_ms", "end_ms", "speaker": [...]}: the earliest start, the latest end, and the speakers heard in the chunk, in order.textwith no segments, returnsNoSpeakers.Unreadable.NoSpeakersis handled likeNeedsReader. The document endsfailedwithreader_needed = 'transcribe', thedocument.needs_readeralert carries the actual reason, and the error isTerminal. The alert observer now takes the reader and the message.Reading: extracted from the MinerU module (utopia_ingest::Reading). Page regions widen the chunk's bbox, and time regions widen its time range and speaker list.speaker: [..]and is required, because a chunk spans turns;The interface cut comes next: Models-page cards for the OCR service and transcription model, and the origin shown on evidence.
Verification
cargo test --workspace824 passed, 0 failed.cargo clippy --workspace --all-targets -D warningsis clean, andpnpm buildpasses.transcriptunit tests: turns and prefixes, a chunk's times and speakers at two budgets, refusal without speakers, silence is unreadable.a_recording_is_read_with_who_said_what(wiremock endpoint): one request with the key,diarized_jsonand the model; ready with onetranscribedchunk and anchor{0, 7250, ["A","B"]}.a_transcript_that_cannot_say_who_spoke_waits_for_one_that_can: unlabelled means failed +reader_needed+ alert + Terminal with no chunks kept. After saving a labelling model and re-queueing, the recording endsready.failed, waiting for transcription.PUT transcribewithwhisper-1: answersrequeued: 1; the endpoint returns unlabelled segments, and the document endsfailedwith "did not say who spoke".PUTwithgpt-4o-transcribe-diarizeand no key:requeued: 1, the old key is kept (the call still carries Bearer), and the document endsreadywith one chunk.{"start_ms": 0, "end_ms": 9750, "speaker": ["A","B"]};Speaker A: … Who signs for Beta? / Speaker B: I sign for Beta Robotics….enc:v1:;transcribe ok;🤖 Generated with Claude Code