-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Only send modern transcriptions to client sdks which can auto back convert modern -> legacy transcriptions internally #7240
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
779dc4e
e99478a
450810a
31a1b55
c550938
66ee8de
970231a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ | |
| ATTRIBUTE_TRANSCRIPTION_FINAL, | ||
| ATTRIBUTE_TRANSCRIPTION_SEGMENT_ID, | ||
| ATTRIBUTE_TRANSCRIPTION_TRACK_ID, | ||
| CLIENT_PROTOCOL_TRANSCRIPTION_STREAMS, | ||
| TOPIC_TRANSCRIPTION, | ||
| TimedString, | ||
| ) | ||
|
|
@@ -264,6 +265,46 @@ async def _forward_audio(self) -> None: | |
| self._forwarding_idle.set() | ||
|
|
||
|
|
||
| def _legacy_transcription_needed(room: rtc.Room) -> bool: | ||
| """True while some remote participant may still rely on the deprecated | ||
| ``rtc.Transcription`` data packet. | ||
|
|
||
| ``publish_transcription`` has no destination parameter, so this is all-or-nothing for the | ||
| room: the packet is dropped only once every considered participant advertises | ||
| ``client_protocol >= CLIENT_PROTOCOL_TRANSCRIPTION_STREAMS``, meaning it rebuilds | ||
| transcription events from the ``lk.transcription`` stream channel instead. | ||
|
|
||
| The client protocol is read from the private ``_info``, because livekit-rtc exposes no | ||
| public property for it. There is no fallback: the field is ``required`` in the FFI | ||
| protobuf, and ``livekit`` is pinned to an exact version, so it is always present. A | ||
| rename must fail loudly here rather than report 0 for every participant, which would | ||
| silently keep legacy publishing on for good. | ||
|
|
||
| Only STANDARD participants -- user-created client SDK instances -- are considered. SIP, | ||
| INGRESS, AGENT (including avatar workers), CONNECTOR and BRIDGE participants never render | ||
| legacy transcripts, so their client protocol tells us nothing about whether the legacy | ||
| packet is still necessary. Counting them would also keep legacy publishing alive in every | ||
| telephony room for good: the Go SDK does not send a client protocol at all, so SIP and | ||
| INGRESS participants report 0 permanently. EGRESS participants join hidden and never | ||
| reach ``remote_participants``. | ||
| """ | ||
| local_identity = room.local_participant.identity | ||
| for p in room.remote_participants.values(): | ||
| if p.kind != rtc.ParticipantKind.PARTICIPANT_KIND_STANDARD: | ||
| continue | ||
|
|
||
| # an out-of-repo avatar worker that joined as STANDARD rather than AGENT. Note this | ||
| # must not be `_is_local_proxy_participant`, which also matches the participant the | ||
| # output is attributed to -- for the user output that is the user themselves. | ||
| if p.attributes.get(ATTRIBUTE_PUBLISH_ON_BEHALF) == local_identity: | ||
| continue | ||
|
|
||
| if p._info.client_protocol < CLIENT_PROTOCOL_TRANSCRIPTION_STREAMS: | ||
| return True | ||
|
|
||
| return False | ||
|
|
||
|
|
||
| class _ParticipantLegacyTranscriptionOutput: | ||
| def __init__( | ||
| self, | ||
|
|
@@ -273,6 +314,9 @@ def __init__( | |
| participant: rtc.Participant | str | None = None, | ||
| ): | ||
| self._room, self._is_delta_stream = room, is_delta_stream | ||
| # the last status written to the log, so only transitions are logged. This never | ||
| # takes part in the decision itself. | ||
| self._legacy_status_logged: bool | None = None | ||
| self._track_id: str | None = None | ||
| self._participant_identity: str | None = None | ||
|
|
||
|
|
@@ -366,10 +410,30 @@ async def aclose(self) -> None: | |
| if self._flush_task: | ||
| await self._flush_task | ||
|
|
||
| def _should_publish(self) -> bool: | ||
| needed = _legacy_transcription_needed(self._room) | ||
| if needed != self._legacy_status_logged: | ||
| self._legacy_status_logged = needed | ||
| logger.debug( | ||
| "legacy transcription publishing %s", | ||
| "enabled" if needed else "disabled", | ||
| extra={"participant": self._participant_identity}, | ||
| ) | ||
|
Comment on lines
+417
to
+421
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, interesting - should I get rid of this logging behavior all together? Or if not - I'm assuming that in other places participant ids are logged within the framework's logs. Are there any templates which I can follow on how best to handle this? |
||
|
|
||
| return needed | ||
|
|
||
| async def _publish_transcription(self, id: str, text: str, final: bool) -> None: | ||
| if self._participant_identity is None or self._track_id is None: | ||
| return | ||
|
|
||
| # Gate here, not in capture_text: every legacy publish carries the whole accumulated | ||
| # segment under a stable id, so _pushed_text/_current_id must stay warm. A legacy | ||
| # client that joins mid-segment then gets the complete segment on the very next | ||
| # publish, and a client that only ever sees the final=True packet still gets a | ||
| # complete, correctly-closed segment. | ||
| if not self._should_publish(): | ||
| return | ||
|
|
||
| transcription = rtc.Transcription( | ||
| participant_identity=self._represented_by or self._participant_identity, | ||
| track_sid=self._track_id, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how often this _legacy_transcription_needed will be called ?
If it is called frequently, can we improve the code to reduce the overhead ?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is called on every transcription event, which in practice for most agent interactions will be under 10 times a second. This function does have an
O(n)loop, butnis fairly small (nis ~number of remote participants, so for most agent interactions, this will probably be 1), and the loop has an early bail out if client protocol is under 3 for any participant so it's likelynwill be smaller in practice for larger rooms.Given this context - Is there something you have in mind here to reduce overhead? I'm not sure there's an obvious lever I am seeing which would cause a significant impact.