-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Multimodal EOU #4722
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?
Multimodal EOU #4722
Changes from all commits
87068d5
e0d5ec1
8eebccc
f92fbc0
d1086ff
0a02bb1
168d0d7
277db6e
03c0e2e
56b4796
be9a550
601229c
c4d92f8
e963d85
c529d79
09baed8
3830638
f214aa0
c922f44
f94a0dd
604bfdc
f9ec64a
ddbf594
d465564
6e7d6bf
200d634
dbd11b0
60004dd
6de53f4
4ed8a82
0db57ea
bbcfc3a
7e04332
04db92f
46fd3bf
e4e8ef6
fc94068
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 |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| from .detector import MultimodalTurnDetector, TurnDetectionEvent, TurnDetectorOptions | ||
| from .stream import MIN_SILENCE_DURATION_MS, TurnDetectionStream | ||
|
|
||
| __all__ = [ | ||
| "MultimodalTurnDetector", | ||
| "TurnDetectionStream", | ||
| "TurnDetectionEvent", | ||
| "TurnDetectorOptions", | ||
| "MIN_SILENCE_DURATION_MS", | ||
| ] |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,137 @@ | ||||||||||||||||||||||||||||||||||||
| from __future__ import annotations | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||
| import weakref | ||||||||||||||||||||||||||||||||||||
| from dataclasses import dataclass | ||||||||||||||||||||||||||||||||||||
| from typing import TYPE_CHECKING, Literal | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| import aiohttp | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| from ... import utils | ||||||||||||||||||||||||||||||||||||
| from ...language import LanguageCode | ||||||||||||||||||||||||||||||||||||
| from ...types import ( | ||||||||||||||||||||||||||||||||||||
| DEFAULT_API_CONNECT_OPTIONS, | ||||||||||||||||||||||||||||||||||||
| NOT_GIVEN, | ||||||||||||||||||||||||||||||||||||
| APIConnectOptions, | ||||||||||||||||||||||||||||||||||||
| NotGivenOr, | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
| from .languages import LANGUAGES | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| if TYPE_CHECKING: | ||||||||||||||||||||||||||||||||||||
| from .stream import TurnDetectionStream | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| DEFAULT_SAMPLE_RATE: int = 16000 | ||||||||||||||||||||||||||||||||||||
| DEFAULT_BASE_URL = "https://agent-gateway.livekit.cloud/v1" | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @dataclass | ||||||||||||||||||||||||||||||||||||
| class TurnDetectionEvent: | ||||||||||||||||||||||||||||||||||||
| type: Literal["eot_prediction"] | ||||||||||||||||||||||||||||||||||||
| end_of_turn_probability: float | ||||||||||||||||||||||||||||||||||||
| last_speaking_time: float | ||||||||||||||||||||||||||||||||||||
| detection_delay: float | None = None | ||||||||||||||||||||||||||||||||||||
| backend: Literal["multimodal", "text"] = "multimodal" | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @dataclass | ||||||||||||||||||||||||||||||||||||
| class TurnDetectorOptions: | ||||||||||||||||||||||||||||||||||||
| sample_rate: int | ||||||||||||||||||||||||||||||||||||
| base_url: str | ||||||||||||||||||||||||||||||||||||
| api_key: str | ||||||||||||||||||||||||||||||||||||
| api_secret: str | ||||||||||||||||||||||||||||||||||||
| conn_options: APIConnectOptions | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| class MultimodalTurnDetector: | ||||||||||||||||||||||||||||||||||||
| def __init__( | ||||||||||||||||||||||||||||||||||||
| self, | ||||||||||||||||||||||||||||||||||||
| *, | ||||||||||||||||||||||||||||||||||||
| base_url: NotGivenOr[str] = NOT_GIVEN, | ||||||||||||||||||||||||||||||||||||
| api_key: NotGivenOr[str] = NOT_GIVEN, | ||||||||||||||||||||||||||||||||||||
| api_secret: NotGivenOr[str] = NOT_GIVEN, | ||||||||||||||||||||||||||||||||||||
| sample_rate: int = DEFAULT_SAMPLE_RATE, | ||||||||||||||||||||||||||||||||||||
| http_session: aiohttp.ClientSession | None = None, | ||||||||||||||||||||||||||||||||||||
| conn_options: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS, | ||||||||||||||||||||||||||||||||||||
| ) -> None: | ||||||||||||||||||||||||||||||||||||
| lk_base_url = utils.resolve_env_var( | ||||||||||||||||||||||||||||||||||||
| base_url, "LIVEKIT_INFERENCE_URL", default=DEFAULT_BASE_URL | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
| lk_api_key = utils.resolve_env_var( | ||||||||||||||||||||||||||||||||||||
| api_key, "LIVEKIT_INFERENCE_API_KEY", "LIVEKIT_API_KEY", default="" | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
| lk_api_secret = utils.resolve_env_var( | ||||||||||||||||||||||||||||||||||||
| api_secret, "LIVEKIT_INFERENCE_API_SECRET", "LIVEKIT_API_SECRET", default="" | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
| if not lk_api_secret: | ||||||||||||||||||||||||||||||||||||
| raise ValueError( | ||||||||||||||||||||||||||||||||||||
| "api_secret is required, either as argument or set LIVEKIT_API_SECRET env var" | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
| if not lk_api_key: | ||||||||||||||||||||||||||||||||||||
| raise ValueError( | ||||||||||||||||||||||||||||||||||||
| "api_key is required, either as argument or set LIVEKIT_API_KEY env var" | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| self._worker_token = os.getenv("LIVEKIT_WORKER_TOKEN") | ||||||||||||||||||||||||||||||||||||
| self._opts = TurnDetectorOptions( | ||||||||||||||||||||||||||||||||||||
| sample_rate=sample_rate, | ||||||||||||||||||||||||||||||||||||
| base_url=lk_base_url, | ||||||||||||||||||||||||||||||||||||
| api_key=lk_api_key, | ||||||||||||||||||||||||||||||||||||
| api_secret=lk_api_secret, | ||||||||||||||||||||||||||||||||||||
| conn_options=conn_options, | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
|
devin-ai-integration[bot] marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| self._session = http_session | ||||||||||||||||||||||||||||||||||||
| self._streams: weakref.WeakSet[TurnDetectionStream] = weakref.WeakSet() | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @property | ||||||||||||||||||||||||||||||||||||
| def model(self) -> str: | ||||||||||||||||||||||||||||||||||||
| return "eot-multimodal" | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @property | ||||||||||||||||||||||||||||||||||||
| def provider(self) -> str: | ||||||||||||||||||||||||||||||||||||
| return "livekit" | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| def _ensure_session(self) -> aiohttp.ClientSession: | ||||||||||||||||||||||||||||||||||||
| if not self._session: | ||||||||||||||||||||||||||||||||||||
| self._session = utils.http_context.http_session() | ||||||||||||||||||||||||||||||||||||
| return self._session | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| def stream( | ||||||||||||||||||||||||||||||||||||
| self, | ||||||||||||||||||||||||||||||||||||
| *, | ||||||||||||||||||||||||||||||||||||
| conn_options: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS, | ||||||||||||||||||||||||||||||||||||
| ) -> TurnDetectionStream: | ||||||||||||||||||||||||||||||||||||
| from .stream import TurnDetectionStream | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| stream: TurnDetectionStream = TurnDetectionStream( | ||||||||||||||||||||||||||||||||||||
| detector=self, | ||||||||||||||||||||||||||||||||||||
| opts=self._opts, | ||||||||||||||||||||||||||||||||||||
| conn_options=conn_options, | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
| self._streams.add(stream) | ||||||||||||||||||||||||||||||||||||
| return stream | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| async def unlikely_threshold( | ||||||||||||||||||||||||||||||||||||
| self, language: LanguageCode | None, modality: Literal["multimodal", "text"] = "multimodal" | ||||||||||||||||||||||||||||||||||||
| ) -> float | None: | ||||||||||||||||||||||||||||||||||||
| thresholds = LANGUAGES.get( | ||||||||||||||||||||||||||||||||||||
| language.language if language is not None else "en", (0.35, 0.011) | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
| if modality == "multimodal": | ||||||||||||||||||||||||||||||||||||
| return thresholds[0] | ||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||
| return thresholds[1] | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| async def supports_language( | ||||||||||||||||||||||||||||||||||||
| self, language: LanguageCode | None, modality: Literal["multimodal", "text"] = "multimodal" | ||||||||||||||||||||||||||||||||||||
| ) -> bool: | ||||||||||||||||||||||||||||||||||||
| # default to english if no language is provided | ||||||||||||||||||||||||||||||||||||
| lang = language.language if language is not None else "en" | ||||||||||||||||||||||||||||||||||||
| return lang in LANGUAGES | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+126
to
+131
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. 🟡
Impact in audio_recognition.pyAt
Suggested change
Was this helpful? React with 👍 or 👎 to provide feedback. |
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| async def aclose(self) -> None: | ||||||||||||||||||||||||||||||||||||
| for stream in list(self._streams): | ||||||||||||||||||||||||||||||||||||
| await stream.aclose() | ||||||||||||||||||||||||||||||||||||
| self._streams.clear() | ||||||||||||||||||||||||||||||||||||
| self._session = None | ||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| LANGUAGES = { | ||
| # language code: (audio threshold, text threshold) | ||
| "en": (0.35, 0.011), | ||
| "fr": (0.35, 0.0078), | ||
| "de": (0.35, 0.0062), | ||
| "hi": (0.35, 0.0398), | ||
| "ja": (0.35, 0.0096), | ||
| "ko": (0.35, 0.0156), | ||
| "zh": (0.35, 0.0066), | ||
| "es": (0.35, 0.0058), | ||
| "nl": (None, 0.0077), | ||
| "pt": (None, 0.0069), | ||
| "it": (None, 0.0037), | ||
| "ru": (None, 0.0032), | ||
| "tr": (None, 0.0045), | ||
| "id": (None, 0.0132), | ||
| } |
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.
🔴 Example basic_agent.py hardcodes localhost URL for turn detection
The basic example hardcodes
base_url="http://0.0.0.0:8080/v1"for theMultimodalTurnDetector. This overrides the production defaultDEFAULT_BASE_URL = "https://agent-gateway.livekit.cloud/v1"(detector.py:24). Since this is the primary example users reference, anyone copying this code will get connection failures unless they happen to run a local turn detection service on port 8080. This appears to be a debugging leftover—the previous code usedMultilingualModel()with no special URL.Was this helpful? React with 👍 or 👎 to provide feedback.