-
Notifications
You must be signed in to change notification settings - Fork 3.7k
conversation-aware STT recognition (keyterms + chat context) #6039
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
Changes from 9 commits
59f1a58
ebbe1b5
8234757
73eb033
a6096af
12672e0
65cd307
2ca544a
ffd105c
e7b7c92
cb0b308
7780b6f
603417b
ef23cc3
d734805
d140b9e
abf645f
7cf1d68
86f4f81
2e82b55
0a93cb8
8ba5370
04751d1
b442136
a4a0012
5328bbd
a7acb41
1f6f803
c744533
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 |
|---|---|---|
|
|
@@ -192,6 +192,24 @@ def _diarization_enabled(extra_kwargs: dict[str, Any] | None) -> bool: | |
| return False | ||
|
|
||
|
|
||
| def _keyterms_extra_for_model(model: NotGivenOr[str], keyterms: list[str]) -> dict[str, Any] | None: | ||
| """Map a provider-agnostic keyterms list onto the active provider's extra_kwargs key. | ||
|
|
||
| Returns None when the model does not support keyterm prompting. Called with an empty | ||
| list, it doubles as a capability check (non-None ⇒ supported). Keep every provider's | ||
| keyterm key here so capability inference and _update_keyterms can't diverge. | ||
| """ | ||
| if not (is_given(model) and isinstance(model, str)): | ||
| return None | ||
| if model.startswith("deepgram/"): | ||
| return {"keyterm": list(keyterms)} | ||
| if model.startswith("assemblyai/"): | ||
| return {"keyterms_prompt": list(keyterms)} | ||
| if model.startswith("speechmatics/"): | ||
|
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. would it make sense to add STTCapabilities.keyterms to the speechmatics plugin? |
||
| return {"additional_vocab": [{"content": term} for term in keyterms]} | ||
| return None | ||
|
|
||
|
|
||
| STTLanguages = Literal["multi", "en", "de", "es", "fr", "ja", "pt", "zh", "hi"] | ||
|
|
||
|
|
||
|
|
@@ -517,6 +535,7 @@ def __init__( | |
| diarization=diarization_enabled, | ||
| aligned_transcript="word", | ||
| offline_recognize=False, | ||
| keyterms=_keyterms_extra_for_model(model, []) is not None, | ||
| ), | ||
| ) | ||
|
|
||
|
|
@@ -634,6 +653,10 @@ def update_options( | |
|
|
||
| self._opts.model = model | ||
| self._vad = _resolve_vad_for_model(model, self._vad) | ||
| self._capabilities = replace( | ||
| self._capabilities, | ||
| keyterms=_keyterms_extra_for_model(self._opts.model, []) is not None, | ||
| ) | ||
| if is_given(language): | ||
| self._opts.language = LanguageCode(language) | ||
| if is_given(extra): | ||
|
|
@@ -646,6 +669,13 @@ def update_options( | |
| for stream in self._streams: | ||
| stream.update_options(model=model, language=language, extra=extra) | ||
|
|
||
| def _update_keyterms(self, keyterms: list[str]) -> None: | ||
| extra = _keyterms_extra_for_model(self._opts.model, keyterms) | ||
| if extra is None: | ||
| super()._update_keyterms(keyterms) # warn-and-skip for unsupported models | ||
| return | ||
| self.update_options(extra=extra) | ||
|
longcw marked this conversation as resolved.
Outdated
|
||
|
|
||
| def _sanitize_options( | ||
| self, *, language: NotGivenOr[STTLanguages | str] = NOT_GIVEN | ||
| ) -> STTOptions: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -61,6 +61,7 @@ | |
| UserStateChangedEvent, | ||
| ) | ||
| from .ivr import IVRActivity | ||
| from .keyterms import KeytermDetectionOptions, KeytermDetector, KeytermOptions, _resolve_detection | ||
| from .recorder_io import RecorderIO | ||
| from .remote_session import RoomSessionTransport, SessionHost, SessionTransport | ||
| from .run_result import RunResult | ||
|
|
@@ -142,6 +143,7 @@ class SessionConnectOptions: | |
| @dataclass | ||
| class AgentSessionOptions: | ||
| turn_handling: TurnHandlingOptions | ||
| keyterm_detection: KeytermDetectionOptions | ||
| max_tool_steps: int | ||
| user_away_timeout: float | None | ||
| min_consecutive_speech_delay: float | ||
|
|
@@ -229,6 +231,7 @@ def __init__( | |
| llm: NotGivenOr[llm.LLM | llm.RealtimeModel | LLMModels | str] = NOT_GIVEN, | ||
| tts: NotGivenOr[tts.TTS | TTSModels | str] = NOT_GIVEN, | ||
| turn_handling: NotGivenOr[TurnHandlingOptions] = NOT_GIVEN, | ||
| keyterm_options: NotGivenOr[KeytermOptions] = NOT_GIVEN, | ||
| # Tool settings | ||
| tools: NotGivenOr[list[llm.Tool | llm.Toolset]] = NOT_GIVEN, | ||
| tool_handling: NotGivenOr[ToolHandlingOptions] = NOT_GIVEN, | ||
|
|
@@ -286,6 +289,9 @@ def __init__( | |
| providing external tools for the agent to use. | ||
| userdata (Userdata_T, optional): Arbitrary per-session user data. | ||
| turn_handling (TurnHandlingOptions, optional): Configuration for turn handling. | ||
| keyterm_options (KeytermOptions, optional): Keyterm prompting for the STT. Holds | ||
| user-defined ``terms`` and optional automatic ``detection`` config. Applies to | ||
| supported STTs; unsupported ones warn and ignore it. | ||
| max_endpointing_delay (float): Maximum time-in-seconds the agent | ||
| will wait before terminating the turn. Default ``3.0`` s. | ||
| max_tool_steps (int): Maximum consecutive tool calls per LLM turn. | ||
|
|
@@ -368,6 +374,8 @@ def __init__( | |
| user_turn_limit = _resolve_user_turn_limit(turn_handling.get("user_turn_limit")) | ||
| raw_turn_detection = turn_handling.get("turn_detection", None) | ||
|
|
||
| keyterm_opts: KeytermOptions = keyterm_options if is_given(keyterm_options) else {} | ||
|
|
||
| # This is the "global" chat_context, it holds the entire conversation history | ||
| self._chat_ctx = ChatContext.empty() | ||
| self._opts = AgentSessionOptions( | ||
|
|
@@ -378,6 +386,7 @@ def __init__( | |
| preemptive_generation=preemptive_gen, | ||
| user_turn_limit=user_turn_limit, | ||
| ), | ||
| keyterm_detection=_resolve_detection(keyterm_opts.get("detection")), | ||
| max_tool_steps=max_tool_steps, | ||
| user_away_timeout=user_away_timeout, | ||
| min_consecutive_speech_delay=min_consecutive_speech_delay, | ||
|
|
@@ -410,6 +419,11 @@ def __init__( | |
| self._llm = llm or None | ||
| self._tts = tts or None | ||
|
|
||
| self._keyterm_detector = KeytermDetector( | ||
| user_keyterms=keyterm_opts.get("terms"), | ||
| options=self._opts.keyterm_detection, | ||
| ) | ||
|
|
||
| self._turn_detection = raw_turn_detection | ||
| self._interruption_detection = interruption.get("mode", NOT_GIVEN) | ||
| self._mcp_servers = mcp_servers or None | ||
|
|
@@ -558,6 +572,11 @@ def conn_options(self) -> SessionConnectOptions: | |
| def history(self) -> llm.ChatContext: | ||
| return self._chat_ctx | ||
|
|
||
| @property | ||
| def keyterms(self) -> list[str]: | ||
| """The effective keyterms (user-defined + auto-detected) currently applied to the STT.""" | ||
| return self._keyterm_detector.keyterms | ||
|
Comment on lines
+580
to
+583
Member
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. I think we don't need a new property if it's inside AgentSessionOptions
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. this is the keyterms applied to the STT (including the detected terms), expose it here in case user want to save the keyterms. |
||
|
|
||
| @property | ||
| def current_speech(self) -> SpeechHandle | None: | ||
| return self._activity.current_speech if self._activity is not None else None | ||
|
|
@@ -1079,6 +1098,7 @@ def update_options( | |
| *, | ||
| endpointing_opts: NotGivenOr[EndpointingOptions] = NOT_GIVEN, | ||
| turn_detection: NotGivenOr[TurnDetectionMode | None] = NOT_GIVEN, | ||
| keyterms: NotGivenOr[list[str]] = NOT_GIVEN, | ||
| # deprecated | ||
| min_endpointing_delay: NotGivenOr[float] = NOT_GIVEN, | ||
| max_endpointing_delay: NotGivenOr[float] = NOT_GIVEN, | ||
|
|
@@ -1090,9 +1110,13 @@ def update_options( | |
| endpointing_opts (NotGivenOr[EndpointingOptions], optional): Endpointing options. | ||
| turn_detection (NotGivenOr[TurnDetectionMode | None], optional): Strategy for deciding | ||
| when the user has finished speaking. ``None`` reverts to automatic selection. | ||
| keyterms (NotGivenOr[list[str]], optional): Replace the user-defined keyterms applied | ||
| to the STT. Auto-detected keyterms are left untouched. | ||
| min_endpointing_delay: Deprecated, use ``endpointing_opts`` instead. | ||
| max_endpointing_delay: Deprecated, use ``endpointing_opts`` instead. | ||
| """ | ||
| if is_given(keyterms): | ||
| self._keyterm_detector.set_user_keyterms(keyterms) | ||
| if is_given(min_endpointing_delay) or is_given(max_endpointing_delay): | ||
| logger.warning( | ||
| "min_endpointing_delay and max_endpointing_delay are deprecated, " | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.