rasa-pro 3.13.6__py3-none-any.whl → 3.14.0.dev1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rasa-pro might be problematic. Click here for more details.
- rasa/agents/__init__.py +0 -0
- rasa/agents/agent_factory.py +122 -0
- rasa/agents/agent_manager.py +162 -0
- rasa/agents/constants.py +31 -0
- rasa/agents/core/__init__.py +0 -0
- rasa/agents/core/agent_protocol.py +108 -0
- rasa/agents/core/types.py +70 -0
- rasa/agents/exceptions.py +8 -0
- rasa/agents/protocol/__init__.py +5 -0
- rasa/agents/protocol/a2a/__init__.py +0 -0
- rasa/agents/protocol/a2a/a2a_agent.py +51 -0
- rasa/agents/protocol/mcp/__init__.py +0 -0
- rasa/agents/protocol/mcp/mcp_base_agent.py +697 -0
- rasa/agents/protocol/mcp/mcp_open_agent.py +275 -0
- rasa/agents/protocol/mcp/mcp_task_agent.py +447 -0
- rasa/agents/schemas/__init__.py +6 -0
- rasa/agents/schemas/agent_input.py +24 -0
- rasa/agents/schemas/agent_output.py +26 -0
- rasa/agents/schemas/agent_tool_result.py +51 -0
- rasa/agents/schemas/agent_tool_schema.py +112 -0
- rasa/agents/templates/__init__.py +0 -0
- rasa/agents/templates/mcp_open_agent_prompt_template.jinja2 +15 -0
- rasa/agents/templates/mcp_task_agent_prompt_template.jinja2 +13 -0
- rasa/agents/utils.py +72 -0
- rasa/api.py +5 -0
- rasa/cli/arguments/default_arguments.py +12 -0
- rasa/cli/arguments/run.py +2 -0
- rasa/cli/dialogue_understanding_test.py +4 -0
- rasa/cli/e2e_test.py +4 -0
- rasa/cli/inspect.py +3 -0
- rasa/cli/llm_fine_tuning.py +5 -0
- rasa/cli/run.py +4 -0
- rasa/cli/shell.py +3 -0
- rasa/cli/train.py +2 -2
- rasa/constants.py +6 -0
- rasa/core/actions/action.py +69 -39
- rasa/core/actions/action_run_slot_rejections.py +1 -1
- rasa/core/agent.py +16 -0
- rasa/core/available_agents.py +196 -0
- rasa/core/available_endpoints.py +30 -0
- rasa/core/channels/development_inspector.py +47 -14
- rasa/core/channels/inspector/dist/assets/{arc-0b11fe30.js → arc-2e78c586.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-9eef30a7.js → blockDiagram-38ab4fdb-806b712e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-03e94f28.js → c4Diagram-3d4e48cf-0745efa9.js} +1 -1
- rasa/core/channels/inspector/dist/assets/channel-c436ca7c.js +1 -0
- rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-95c09eba.js → classDiagram-70f12bd4-7bd1082b.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-38e8446c.js → classDiagram-v2-f2320105-d937ba49.js} +1 -1
- rasa/core/channels/inspector/dist/assets/clone-50dd656b.js +1 -0
- rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-57dc3038.js → createText-2e5e7dd3-a2a564ca.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-4bac0545.js → edges-e0da2a9e-b5256940.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-81795c90.js → erDiagram-9861fffd-e6883ad2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-89489ae6.js → flowDb-956e92f1-e576fc02.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-cd152627.js → flowDiagram-66a62f08-2e298d01.js} +1 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-2b2aeaf8.js +1 -0
- rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-3da369bc.js → flowchart-elk-definition-4a651766-dd7b150a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-85ec16f8.js → ganttDiagram-c361ad54-5b79575c.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-495bc140.js → gitGraphDiagram-72cf32ee-3016f40a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{graph-1ec4d266.js → graph-3e19170f.js} +1 -1
- rasa/core/channels/inspector/dist/assets/index-1bd9135e.js +1353 -0
- rasa/core/channels/inspector/dist/assets/{index-3862675e-0a0e97c9.js → index-3862675e-eb9c86de.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-4d54bcde.js → infoDiagram-f8f76790-b4280e4d.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-dc097114.js → journeyDiagram-49397b02-556091f8.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{layout-1a08981e.js → layout-08436411.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{line-95f7f1d3.js → line-683c4f3b.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{linear-97e69543.js → linear-cee6d791.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-8c71ff03.js → mindmap-definition-fc14e90a-a0bf0b1a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-f14c71c7.js → pieDiagram-8a3498a8-3730d5c4.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-f1d3c9ff.js → quadrantDiagram-120e2f19-12a20fed.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-bfa2412f.js → requirementDiagram-deff3bca-b9732102.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-53f2c97b.js → sankeyDiagram-04a897e0-a2e72776.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-319d7c0e.js → sequenceDiagram-704730f1-8b7a76bb.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-76a09418.js → stateDiagram-587899a1-e65853ac.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-a67f15d4.js → stateDiagram-v2-d93cdb3a-6f58a44b.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-0654e7c3.js → styles-6aaf32cf-df25b934.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-9a916d00-1394bb9d.js → styles-9a916d00-88357141.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-c10674c1-e4c5bdae.js → styles-c10674c1-d600174d.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-50957104.js → svgDrawCommon-08f97a94-4adc3e0b.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-b0885a6a.js → timeline-definition-85554ec2-42816fa1.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-79e6541a.js → xychartDiagram-e933f94c-621eb66a.js} +1 -1
- rasa/core/channels/inspector/dist/index.html +2 -2
- rasa/core/channels/inspector/index.html +1 -1
- rasa/core/channels/inspector/src/App.tsx +53 -7
- rasa/core/channels/inspector/src/components/Chat.tsx +3 -2
- rasa/core/channels/inspector/src/components/DiagramFlow.tsx +1 -1
- rasa/core/channels/inspector/src/components/DialogueStack.tsx +7 -5
- rasa/core/channels/inspector/src/components/LatencyDisplay.tsx +268 -0
- rasa/core/channels/inspector/src/components/LoadingSpinner.tsx +6 -2
- rasa/core/channels/inspector/src/helpers/audio/audiostream.ts +8 -3
- rasa/core/channels/inspector/src/helpers/formatters.ts +24 -3
- rasa/core/channels/inspector/src/theme/base/styles.ts +19 -1
- rasa/core/channels/inspector/src/types.ts +12 -0
- rasa/core/channels/studio_chat.py +125 -34
- rasa/core/channels/voice_ready/twilio_voice.py +1 -1
- rasa/core/channels/voice_stream/audiocodes.py +9 -6
- rasa/core/channels/voice_stream/browser_audio.py +39 -4
- rasa/core/channels/voice_stream/call_state.py +13 -2
- rasa/core/channels/voice_stream/genesys.py +16 -13
- rasa/core/channels/voice_stream/jambonz.py +13 -11
- rasa/core/channels/voice_stream/twilio_media_streams.py +14 -13
- rasa/core/channels/voice_stream/util.py +11 -1
- rasa/core/channels/voice_stream/voice_channel.py +101 -29
- rasa/core/constants.py +4 -0
- rasa/core/nlg/contextual_response_rephraser.py +11 -7
- rasa/core/nlg/generator.py +21 -5
- rasa/core/nlg/response.py +43 -6
- rasa/core/nlg/translate.py +8 -0
- rasa/core/policies/enterprise_search_policy.py +4 -2
- rasa/core/policies/flow_policy.py +2 -2
- rasa/core/policies/flows/flow_executor.py +374 -35
- rasa/core/policies/flows/mcp_tool_executor.py +240 -0
- rasa/core/processor.py +6 -1
- rasa/core/run.py +8 -1
- rasa/core/utils.py +21 -1
- rasa/dialogue_understanding/commands/__init__.py +8 -0
- rasa/dialogue_understanding/commands/cancel_flow_command.py +97 -4
- rasa/dialogue_understanding/commands/chit_chat_answer_command.py +11 -0
- rasa/dialogue_understanding/commands/continue_agent_command.py +91 -0
- rasa/dialogue_understanding/commands/knowledge_answer_command.py +11 -0
- rasa/dialogue_understanding/commands/restart_agent_command.py +146 -0
- rasa/dialogue_understanding/commands/start_flow_command.py +129 -8
- rasa/dialogue_understanding/commands/utils.py +6 -2
- rasa/dialogue_understanding/generator/command_parser.py +4 -0
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +50 -12
- rasa/dialogue_understanding/generator/prompt_templates/agent_command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +61 -0
- rasa/dialogue_understanding/generator/prompt_templates/agent_command_prompt_v2_gpt_4o_2024_11_20_template.jinja2 +61 -0
- rasa/dialogue_understanding/generator/prompt_templates/agent_command_prompt_v3_claude_3_5_sonnet_20240620_template.jinja2 +81 -0
- rasa/dialogue_understanding/generator/prompt_templates/agent_command_prompt_v3_gpt_4o_2024_11_20_template.jinja2 +81 -0
- rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +7 -6
- rasa/dialogue_understanding/generator/single_step/search_ready_llm_command_generator.py +7 -6
- rasa/dialogue_understanding/generator/single_step/single_step_based_llm_command_generator.py +41 -2
- rasa/dialogue_understanding/patterns/continue_interrupted.py +163 -1
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +51 -7
- rasa/dialogue_understanding/processor/command_processor.py +27 -11
- rasa/dialogue_understanding/stack/dialogue_stack.py +123 -2
- rasa/dialogue_understanding/stack/frames/flow_stack_frame.py +57 -0
- rasa/dialogue_understanding/stack/utils.py +3 -2
- rasa/dialogue_understanding_test/du_test_runner.py +7 -2
- rasa/dialogue_understanding_test/du_test_schema.yml +3 -3
- rasa/e2e_test/e2e_test_runner.py +5 -0
- rasa/e2e_test/e2e_test_schema.yml +3 -3
- rasa/model_manager/model_api.py +1 -1
- rasa/model_manager/socket_bridge.py +8 -2
- rasa/server.py +10 -0
- rasa/shared/agents/__init__.py +0 -0
- rasa/shared/agents/utils.py +35 -0
- rasa/shared/constants.py +5 -0
- rasa/shared/core/constants.py +12 -1
- rasa/shared/core/domain.py +5 -5
- rasa/shared/core/events.py +319 -0
- rasa/shared/core/flows/flows_list.py +2 -2
- rasa/shared/core/flows/flows_yaml_schema.json +101 -186
- rasa/shared/core/flows/steps/call.py +51 -5
- rasa/shared/core/flows/validation.py +45 -7
- rasa/shared/core/flows/yaml_flows_io.py +3 -3
- rasa/shared/providers/llm/_base_litellm_client.py +39 -7
- rasa/shared/providers/llm/litellm_router_llm_client.py +8 -4
- rasa/shared/providers/llm/llm_client.py +7 -3
- rasa/shared/providers/llm/llm_response.py +49 -0
- rasa/shared/providers/llm/self_hosted_llm_client.py +8 -4
- rasa/shared/utils/common.py +2 -1
- rasa/shared/utils/llm.py +28 -5
- rasa/shared/utils/mcp/__init__.py +0 -0
- rasa/shared/utils/mcp/server_connection.py +157 -0
- rasa/shared/utils/schemas/events.py +42 -0
- rasa/tracing/instrumentation/instrumentation.py +4 -2
- rasa/utils/common.py +53 -0
- rasa/utils/licensing.py +21 -10
- rasa/utils/plotting.py +1 -1
- rasa/version.py +1 -1
- {rasa_pro-3.13.6.dist-info → rasa_pro-3.14.0.dev1.dist-info}/METADATA +16 -15
- {rasa_pro-3.13.6.dist-info → rasa_pro-3.14.0.dev1.dist-info}/RECORD +174 -137
- rasa/core/channels/inspector/dist/assets/channel-51d02e9e.js +0 -1
- rasa/core/channels/inspector/dist/assets/clone-cc738fa6.js +0 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-0c716443.js +0 -1
- rasa/core/channels/inspector/dist/assets/index-c804b295.js +0 -1335
- {rasa_pro-3.13.6.dist-info → rasa_pro-3.14.0.dev1.dist-info}/NOTICE +0 -0
- {rasa_pro-3.13.6.dist-info → rasa_pro-3.14.0.dev1.dist-info}/WHEEL +0 -0
- {rasa_pro-3.13.6.dist-info → rasa_pro-3.14.0.dev1.dist-info}/entry_points.txt +0 -0
|
@@ -26,6 +26,7 @@ from rasa.core.channels.voice_ready.utils import (
|
|
|
26
26
|
from rasa.core.channels.voice_stream.audio_bytes import RasaAudioBytes
|
|
27
27
|
from rasa.core.channels.voice_stream.call_state import call_state
|
|
28
28
|
from rasa.core.channels.voice_stream.tts.tts_engine import TTSEngine
|
|
29
|
+
from rasa.core.channels.voice_stream.util import repack_voice_credentials
|
|
29
30
|
from rasa.core.channels.voice_stream.voice_channel import (
|
|
30
31
|
ContinueConversationAction,
|
|
31
32
|
EndConversationAction,
|
|
@@ -120,20 +121,20 @@ class TwilioMediaStreamsInputChannel(VoiceInputChannel):
|
|
|
120
121
|
cls,
|
|
121
122
|
credentials: Optional[Dict[str, Any]],
|
|
122
123
|
) -> VoiceInputChannel:
|
|
123
|
-
credentials
|
|
124
|
+
cls.validate_credentials(credentials)
|
|
125
|
+
new_creds = repack_voice_credentials(credentials)
|
|
126
|
+
return cls(**new_creds)
|
|
124
127
|
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
@classmethod
|
|
129
|
+
def validate_credentials(
|
|
130
|
+
cls,
|
|
131
|
+
credentials: Optional[Dict[str, Any]],
|
|
132
|
+
) -> None:
|
|
133
|
+
cls.validate_basic_credentials(credentials)
|
|
134
|
+
username = credentials.get("username") if credentials else None
|
|
135
|
+
password = credentials.get("password") if credentials else None
|
|
127
136
|
validate_username_password_credentials(username, password, "TwilioMediaStreams")
|
|
128
137
|
|
|
129
|
-
return cls(
|
|
130
|
-
credentials["server_url"],
|
|
131
|
-
credentials["asr"],
|
|
132
|
-
credentials["tts"],
|
|
133
|
-
username=username,
|
|
134
|
-
password=password,
|
|
135
|
-
)
|
|
136
|
-
|
|
137
138
|
@classmethod
|
|
138
139
|
def name(cls) -> str:
|
|
139
140
|
return "twilio_media_streams"
|
|
@@ -175,14 +176,14 @@ class TwilioMediaStreamsInputChannel(VoiceInputChannel):
|
|
|
175
176
|
elif data["event"] == "mark":
|
|
176
177
|
if data["mark"]["name"] == call_state.latest_bot_audio_id:
|
|
177
178
|
# Just finished streaming last audio bytes
|
|
178
|
-
call_state.is_bot_speaking = False
|
|
179
|
+
call_state.is_bot_speaking = False
|
|
179
180
|
if call_state.should_hangup:
|
|
180
181
|
logger.debug(
|
|
181
182
|
"twilio_streams.hangup", marker=call_state.latest_bot_audio_id
|
|
182
183
|
)
|
|
183
184
|
return EndConversationAction()
|
|
184
185
|
else:
|
|
185
|
-
call_state.is_bot_speaking = True
|
|
186
|
+
call_state.is_bot_speaking = True
|
|
186
187
|
return ContinueConversationAction()
|
|
187
188
|
|
|
188
189
|
def create_output_channel(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import audioop
|
|
2
2
|
import wave
|
|
3
3
|
from dataclasses import asdict, dataclass
|
|
4
|
-
from typing import Optional, Type, TypeVar
|
|
4
|
+
from typing import Dict, Optional, Type, TypeVar
|
|
5
5
|
|
|
6
6
|
import structlog
|
|
7
7
|
|
|
@@ -55,3 +55,13 @@ class MergeableConfig:
|
|
|
55
55
|
@classmethod
|
|
56
56
|
def from_dict(cls: Type[T], data: dict[str, Optional[str]]) -> T:
|
|
57
57
|
return cls(**data)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def repack_voice_credentials(
|
|
61
|
+
credentials: Dict[str, str],
|
|
62
|
+
) -> Dict[str, str]:
|
|
63
|
+
"""Repack voice credentials to ensure they are in the correct format."""
|
|
64
|
+
new_creds = {**credentials}
|
|
65
|
+
new_creds["asr_config"] = new_creds.pop("asr", None)
|
|
66
|
+
new_creds["tts_config"] = new_creds.pop("tts", None)
|
|
67
|
+
return new_creds
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import asyncio
|
|
2
4
|
import copy
|
|
5
|
+
import time
|
|
3
6
|
from dataclasses import asdict, dataclass
|
|
4
7
|
from typing import Any, AsyncIterator, Awaitable, Callable, Dict, List, Optional, Tuple
|
|
5
8
|
|
|
@@ -189,7 +192,7 @@ class VoiceOutputChannel(OutputChannel):
|
|
|
189
192
|
def update_silence_timeout(self) -> None:
|
|
190
193
|
"""Updates the silence timeout for the session."""
|
|
191
194
|
if self.tracker_state:
|
|
192
|
-
call_state.silence_timeout = self.tracker_state["slots"][
|
|
195
|
+
call_state.silence_timeout = self.tracker_state["slots"][
|
|
193
196
|
SILENCE_TIMEOUT_SLOT
|
|
194
197
|
]
|
|
195
198
|
logger.debug(
|
|
@@ -207,22 +210,63 @@ class VoiceOutputChannel(OutputChannel):
|
|
|
207
210
|
"""Uses the concise button output format for voice channels."""
|
|
208
211
|
await self.send_text_with_buttons_concise(recipient_id, text, buttons, **kwargs)
|
|
209
212
|
|
|
213
|
+
def _track_rasa_processing_latency(self) -> None:
|
|
214
|
+
"""Track and log Rasa processing completion latency."""
|
|
215
|
+
if call_state.rasa_processing_start_time:
|
|
216
|
+
call_state.rasa_processing_latency_ms = (
|
|
217
|
+
time.time() - call_state.rasa_processing_start_time
|
|
218
|
+
) * 1000
|
|
219
|
+
logger.debug(
|
|
220
|
+
"voice_channel.rasa_processing_latency",
|
|
221
|
+
latency_ms=call_state.rasa_processing_latency_ms,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
def _track_tts_first_byte_latency(self) -> None:
|
|
225
|
+
"""Track and log TTS first byte latency."""
|
|
226
|
+
if call_state.tts_start_time:
|
|
227
|
+
call_state.tts_first_byte_latency_ms = (
|
|
228
|
+
time.time() - call_state.tts_start_time
|
|
229
|
+
) * 1000
|
|
230
|
+
logger.debug(
|
|
231
|
+
"voice_channel.tts_first_byte_latency",
|
|
232
|
+
latency_ms=call_state.tts_first_byte_latency_ms,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def _track_tts_complete_latency(self) -> None:
|
|
236
|
+
"""Track and log TTS completion latency."""
|
|
237
|
+
if call_state.tts_start_time:
|
|
238
|
+
call_state.tts_complete_latency_ms = (
|
|
239
|
+
time.time() - call_state.tts_start_time
|
|
240
|
+
) * 1000
|
|
241
|
+
logger.debug(
|
|
242
|
+
"voice_channel.tts_complete_latency",
|
|
243
|
+
latency_ms=call_state.tts_complete_latency_ms,
|
|
244
|
+
)
|
|
245
|
+
|
|
210
246
|
async def send_text_message(
|
|
211
247
|
self, recipient_id: str, text: str, **kwargs: Any
|
|
212
248
|
) -> None:
|
|
213
249
|
text = remove_emojis(text)
|
|
214
250
|
self.update_silence_timeout()
|
|
251
|
+
|
|
252
|
+
# Track Rasa processing completion
|
|
253
|
+
self._track_rasa_processing_latency()
|
|
254
|
+
|
|
255
|
+
# Track TTS start time
|
|
256
|
+
call_state.tts_start_time = time.time()
|
|
257
|
+
|
|
215
258
|
cached_audio_bytes = self.tts_cache.get(text)
|
|
216
259
|
collected_audio_bytes = RasaAudioBytes(b"")
|
|
217
260
|
seconds_marker = -1
|
|
218
261
|
last_sent_offset = 0
|
|
262
|
+
first_audio_sent = False
|
|
219
263
|
logger.debug("voice_channel.sending_audio", text=text)
|
|
220
264
|
|
|
221
265
|
# Send start marker before first chunk
|
|
222
266
|
try:
|
|
223
267
|
await self.send_start_marker(recipient_id)
|
|
224
268
|
except (WebsocketClosed, ServerError):
|
|
225
|
-
call_state.connection_failed = True
|
|
269
|
+
call_state.connection_failed = True
|
|
226
270
|
|
|
227
271
|
if cached_audio_bytes:
|
|
228
272
|
audio_stream = self.chunk_audio(cached_audio_bytes)
|
|
@@ -244,6 +288,11 @@ class VoiceOutputChannel(OutputChannel):
|
|
|
244
288
|
|
|
245
289
|
if should_send:
|
|
246
290
|
try:
|
|
291
|
+
# Track TTS first byte time
|
|
292
|
+
if not first_audio_sent:
|
|
293
|
+
self._track_tts_first_byte_latency()
|
|
294
|
+
first_audio_sent = True
|
|
295
|
+
|
|
247
296
|
# Send only the new bytes since last send
|
|
248
297
|
new_bytes = RasaAudioBytes(collected_audio_bytes[last_sent_offset:])
|
|
249
298
|
await self.send_audio_bytes(recipient_id, new_bytes)
|
|
@@ -256,24 +305,31 @@ class VoiceOutputChannel(OutputChannel):
|
|
|
256
305
|
|
|
257
306
|
except (WebsocketClosed, ServerError):
|
|
258
307
|
# ignore sending error, and keep collecting and caching audio bytes
|
|
259
|
-
call_state.connection_failed = True
|
|
308
|
+
call_state.connection_failed = True
|
|
260
309
|
|
|
261
310
|
# Send any remaining audio not yet sent
|
|
262
311
|
remaining_bytes = len(collected_audio_bytes) - last_sent_offset
|
|
263
312
|
if remaining_bytes > 0:
|
|
264
313
|
try:
|
|
314
|
+
# Track TTS first byte time if not already tracked
|
|
315
|
+
if not first_audio_sent:
|
|
316
|
+
self._track_tts_first_byte_latency()
|
|
317
|
+
|
|
265
318
|
new_bytes = RasaAudioBytes(collected_audio_bytes[last_sent_offset:])
|
|
266
319
|
await self.send_audio_bytes(recipient_id, new_bytes)
|
|
267
320
|
except (WebsocketClosed, ServerError):
|
|
268
321
|
# ignore sending error
|
|
269
|
-
call_state.connection_failed = True
|
|
322
|
+
call_state.connection_failed = True
|
|
323
|
+
|
|
324
|
+
# Track TTS completion time
|
|
325
|
+
self._track_tts_complete_latency()
|
|
270
326
|
|
|
271
327
|
try:
|
|
272
328
|
await self.send_end_marker(recipient_id)
|
|
273
329
|
except (WebsocketClosed, ServerError):
|
|
274
330
|
# ignore sending error
|
|
275
331
|
pass
|
|
276
|
-
call_state.latest_bot_audio_id = self.latest_message_id
|
|
332
|
+
call_state.latest_bot_audio_id = self.latest_message_id
|
|
277
333
|
|
|
278
334
|
if not cached_audio_bytes:
|
|
279
335
|
self.tts_cache.put(text, collected_audio_bytes)
|
|
@@ -298,7 +354,7 @@ class VoiceOutputChannel(OutputChannel):
|
|
|
298
354
|
return
|
|
299
355
|
|
|
300
356
|
async def hangup(self, recipient_id: str, **kwargs: Any) -> None:
|
|
301
|
-
call_state.should_hangup = True
|
|
357
|
+
call_state.should_hangup = True
|
|
302
358
|
|
|
303
359
|
|
|
304
360
|
class VoiceInputChannel(InputChannel):
|
|
@@ -345,32 +401,32 @@ class VoiceInputChannel(InputChannel):
|
|
|
345
401
|
if call_state.silence_timeout_watcher:
|
|
346
402
|
logger.debug("voice_channel.cancelling_current_timeout_watcher_task")
|
|
347
403
|
call_state.silence_timeout_watcher.cancel()
|
|
348
|
-
call_state.silence_timeout_watcher = None
|
|
404
|
+
call_state.silence_timeout_watcher = None
|
|
349
405
|
|
|
350
406
|
@classmethod
|
|
351
|
-
def
|
|
352
|
-
|
|
353
|
-
credentials: Optional[Dict[str, Any]],
|
|
354
|
-
) -> InputChannel:
|
|
407
|
+
def validate_basic_credentials(cls, credentials: Optional[Dict[str, Any]]) -> None:
|
|
408
|
+
"""Validate the basic credentials for the voice channel."""
|
|
355
409
|
if not credentials:
|
|
356
410
|
cls.raise_missing_credentials_exception()
|
|
357
|
-
|
|
358
|
-
if not credentials.get("server_url"):
|
|
359
|
-
raise InvalidConfigException("No server_url provided in credentials.")
|
|
360
|
-
if not credentials.get("asr"):
|
|
411
|
+
if not isinstance(credentials, dict):
|
|
361
412
|
raise InvalidConfigException(
|
|
362
|
-
"
|
|
413
|
+
"Credentials must be a dictionary for voice channel."
|
|
363
414
|
)
|
|
364
|
-
|
|
415
|
+
|
|
416
|
+
required_keys = {"server_url", "asr", "tts"}
|
|
417
|
+
credentials_keys = set(credentials.keys())
|
|
418
|
+
if not required_keys.issubset(credentials_keys):
|
|
419
|
+
missing_fields = required_keys - credentials_keys
|
|
365
420
|
raise InvalidConfigException(
|
|
366
|
-
"
|
|
421
|
+
f"Missing required fields in credentials: {', '.join(missing_fields)} "
|
|
422
|
+
f"for channel {cls.name()}"
|
|
367
423
|
)
|
|
368
424
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
425
|
+
@classmethod
|
|
426
|
+
def from_credentials(
|
|
427
|
+
cls, credentials: Optional[Dict[str, Any]]
|
|
428
|
+
) -> VoiceInputChannel:
|
|
429
|
+
raise NotImplementedError
|
|
374
430
|
|
|
375
431
|
def channel_bytes_to_rasa_audio_bytes(self, input_bytes: bytes) -> RasaAudioBytes:
|
|
376
432
|
raise NotImplementedError
|
|
@@ -439,10 +495,8 @@ class VoiceInputChannel(InputChannel):
|
|
|
439
495
|
if was_bot_speaking_before and not is_bot_speaking_after:
|
|
440
496
|
logger.debug("voice_channel.bot_stopped_speaking")
|
|
441
497
|
self._cancel_silence_timeout_watcher()
|
|
442
|
-
call_state.silence_timeout_watcher = (
|
|
443
|
-
|
|
444
|
-
self.monitor_silence_timeout(asr_event_queue)
|
|
445
|
-
)
|
|
498
|
+
call_state.silence_timeout_watcher = asyncio.create_task(
|
|
499
|
+
self.monitor_silence_timeout(asr_event_queue)
|
|
446
500
|
)
|
|
447
501
|
if isinstance(channel_action, NewAudioAction):
|
|
448
502
|
await asr_engine.send_audio_chunks(channel_action.audio_bytes)
|
|
@@ -498,6 +552,16 @@ class VoiceInputChannel(InputChannel):
|
|
|
498
552
|
"""Create a matching voice output channel for this voice input channel."""
|
|
499
553
|
raise NotImplementedError
|
|
500
554
|
|
|
555
|
+
def _track_asr_latency(self) -> None:
|
|
556
|
+
"""Track and log ASR processing latency."""
|
|
557
|
+
if call_state.user_speech_start_time:
|
|
558
|
+
call_state.asr_latency_ms = (
|
|
559
|
+
time.time() - call_state.user_speech_start_time
|
|
560
|
+
) * 1000
|
|
561
|
+
logger.debug(
|
|
562
|
+
"voice_channel.asr_latency", latency_ms=call_state.asr_latency_ms
|
|
563
|
+
)
|
|
564
|
+
|
|
501
565
|
async def handle_asr_event(
|
|
502
566
|
self,
|
|
503
567
|
e: ASREvent,
|
|
@@ -511,7 +575,12 @@ class VoiceInputChannel(InputChannel):
|
|
|
511
575
|
logger.debug(
|
|
512
576
|
"VoiceInputChannel.handle_asr_event.new_transcript", transcript=e.text
|
|
513
577
|
)
|
|
514
|
-
call_state.is_user_speaking = False
|
|
578
|
+
call_state.is_user_speaking = False
|
|
579
|
+
|
|
580
|
+
# Track ASR and Rasa latencies
|
|
581
|
+
self._track_asr_latency()
|
|
582
|
+
call_state.rasa_processing_start_time = time.time()
|
|
583
|
+
|
|
515
584
|
output_channel = self.create_output_channel(voice_websocket, tts_engine)
|
|
516
585
|
message = UserMessage(
|
|
517
586
|
text=e.text,
|
|
@@ -522,8 +591,11 @@ class VoiceInputChannel(InputChannel):
|
|
|
522
591
|
)
|
|
523
592
|
await on_new_message(message)
|
|
524
593
|
elif isinstance(e, UserIsSpeaking):
|
|
594
|
+
# Track when user starts speaking for ASR latency calculation
|
|
595
|
+
if not call_state.is_user_speaking:
|
|
596
|
+
call_state.user_speech_start_time = time.time()
|
|
525
597
|
self._cancel_silence_timeout_watcher()
|
|
526
|
-
call_state.is_user_speaking = True
|
|
598
|
+
call_state.is_user_speaking = True
|
|
527
599
|
elif isinstance(e, UserSilence):
|
|
528
600
|
output_channel = self.create_output_channel(voice_websocket, tts_engine)
|
|
529
601
|
message = UserMessage(
|
rasa/core/constants.py
CHANGED
|
@@ -31,6 +31,10 @@ BEARER_TOKEN_PREFIX = "Bearer "
|
|
|
31
31
|
# The lowest priority is intended to be used by machine learning policies.
|
|
32
32
|
DEFAULT_POLICY_PRIORITY = 1
|
|
33
33
|
|
|
34
|
+
DEFAULT_SUB_AGENTS = "sub_agents"
|
|
35
|
+
|
|
36
|
+
MCP_SERVERS_KEY = "mcp_servers"
|
|
37
|
+
|
|
34
38
|
# The priority of intent-prediction policies.
|
|
35
39
|
# This should be below all rule based policies but higher than ML
|
|
36
40
|
# based policies. This enables a loop inside ensemble where if none
|
|
@@ -225,8 +225,10 @@ class ContextualResponseRephraser(
|
|
|
225
225
|
|
|
226
226
|
@measure_llm_latency
|
|
227
227
|
async def _generate_llm_response(self, prompt: str) -> Optional[LLMResponse]:
|
|
228
|
-
"""Use LLM to generate a response
|
|
229
|
-
|
|
228
|
+
"""Use LLM to generate a response.
|
|
229
|
+
|
|
230
|
+
Returns an LLMResponse object containing both the generated text
|
|
231
|
+
(choices) and metadata.
|
|
230
232
|
|
|
231
233
|
Args:
|
|
232
234
|
prompt: The prompt to send to the LLM.
|
|
@@ -315,14 +317,18 @@ class ContextualResponseRephraser(
|
|
|
315
317
|
return response
|
|
316
318
|
|
|
317
319
|
prompt_template_text = self._template_for_response_rephrasing(response)
|
|
320
|
+
|
|
321
|
+
# Last user message (=current input) should always be in prompt if available
|
|
318
322
|
last_message_by_user = getattr(tracker.latest_message, "text", "")
|
|
319
323
|
current_input = (
|
|
320
324
|
f"{USER}: {last_message_by_user}" if last_message_by_user else ""
|
|
321
325
|
)
|
|
322
326
|
|
|
327
|
+
# Only summarise conversation history if flagged
|
|
323
328
|
if self.summarize_history:
|
|
324
329
|
history = await self._create_history(tracker)
|
|
325
330
|
else:
|
|
331
|
+
# Count multiple utterances by bot/user as single turn
|
|
326
332
|
turns_wrapper = (
|
|
327
333
|
_count_multiple_utterances_as_single_turn
|
|
328
334
|
if self.count_multiple_utterances_as_single_turn
|
|
@@ -365,6 +371,7 @@ class ContextualResponseRephraser(
|
|
|
365
371
|
)
|
|
366
372
|
|
|
367
373
|
if not (llm_response and llm_response.choices and llm_response.choices[0]):
|
|
374
|
+
# If the LLM fails to generate a response, return the original response.
|
|
368
375
|
return response
|
|
369
376
|
|
|
370
377
|
updated_text = llm_response.choices[0]
|
|
@@ -412,12 +419,9 @@ class ContextualResponseRephraser(
|
|
|
412
419
|
Returns:
|
|
413
420
|
The generated response.
|
|
414
421
|
"""
|
|
415
|
-
|
|
416
|
-
stack_context = tracker.stack.current_context()
|
|
417
|
-
templated_response = self.generate_from_slots(
|
|
422
|
+
templated_response = await super().generate(
|
|
418
423
|
utter_action=utter_action,
|
|
419
|
-
|
|
420
|
-
stack_context=stack_context,
|
|
424
|
+
tracker=tracker,
|
|
421
425
|
output_channel=output_channel,
|
|
422
426
|
**kwargs,
|
|
423
427
|
)
|
rasa/core/nlg/generator.py
CHANGED
|
@@ -6,6 +6,8 @@ from pypred import Predicate
|
|
|
6
6
|
|
|
7
7
|
import rasa.shared.utils.common
|
|
8
8
|
import rasa.shared.utils.io
|
|
9
|
+
from rasa.core.nlg.translate import has_translation
|
|
10
|
+
from rasa.engine.language import Language
|
|
9
11
|
from rasa.shared.constants import CHANNEL, RESPONSE_CONDITION
|
|
10
12
|
from rasa.shared.core.domain import Domain
|
|
11
13
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
@@ -131,11 +133,23 @@ class ResponseVariationFilter:
|
|
|
131
133
|
|
|
132
134
|
return True
|
|
133
135
|
|
|
136
|
+
def _filter_by_language(
|
|
137
|
+
self, responses: List[Dict[Text, Any]], language: Optional[Language] = None
|
|
138
|
+
) -> List[Dict[Text, Any]]:
|
|
139
|
+
if not language:
|
|
140
|
+
return responses
|
|
141
|
+
|
|
142
|
+
if filtered := [r for r in responses if has_translation(r, language)]:
|
|
143
|
+
return filtered
|
|
144
|
+
# if no translation is found, return the original response variations
|
|
145
|
+
return responses
|
|
146
|
+
|
|
134
147
|
def responses_for_utter_action(
|
|
135
148
|
self,
|
|
136
149
|
utter_action: Text,
|
|
137
150
|
output_channel: Text,
|
|
138
151
|
filled_slots: Dict[Text, Any],
|
|
152
|
+
language: Optional[Language] = None,
|
|
139
153
|
) -> List[Dict[Text, Any]]:
|
|
140
154
|
"""Returns array of responses that fit the channel, action and condition."""
|
|
141
155
|
# filter responses without a condition
|
|
@@ -176,16 +190,16 @@ class ResponseVariationFilter:
|
|
|
176
190
|
)
|
|
177
191
|
|
|
178
192
|
if conditional_channel:
|
|
179
|
-
return conditional_channel
|
|
193
|
+
return self._filter_by_language(conditional_channel, language)
|
|
180
194
|
|
|
181
195
|
if default_channel:
|
|
182
|
-
return default_channel
|
|
196
|
+
return self._filter_by_language(default_channel, language)
|
|
183
197
|
|
|
184
198
|
if conditional_no_channel:
|
|
185
|
-
return conditional_no_channel
|
|
199
|
+
return self._filter_by_language(conditional_no_channel, language)
|
|
186
200
|
|
|
187
201
|
if default_no_channel:
|
|
188
|
-
return default_no_channel
|
|
202
|
+
return self._filter_by_language(default_no_channel, language)
|
|
189
203
|
|
|
190
204
|
# if there is no response variation selected,
|
|
191
205
|
# return the internal error response to prevent
|
|
@@ -198,7 +212,9 @@ class ResponseVariationFilter:
|
|
|
198
212
|
f"a default variation and that all the conditions are valid. "
|
|
199
213
|
f"Returning the internal error response.",
|
|
200
214
|
)
|
|
201
|
-
return self.
|
|
215
|
+
return self._filter_by_language(
|
|
216
|
+
self.responses.get("utter_internal_error_rasa", []), language
|
|
217
|
+
)
|
|
202
218
|
|
|
203
219
|
def get_response_variation_id(
|
|
204
220
|
self,
|
rasa/core/nlg/response.py
CHANGED
|
@@ -5,8 +5,11 @@ from typing import Any, Dict, List, Optional, Text
|
|
|
5
5
|
from rasa.core.constants import DEFAULT_TEMPLATE_ENGINE, TEMPLATE_ENGINE_CONFIG_KEY
|
|
6
6
|
from rasa.core.nlg import interpolator
|
|
7
7
|
from rasa.core.nlg.generator import NaturalLanguageGenerator, ResponseVariationFilter
|
|
8
|
-
from rasa.
|
|
8
|
+
from rasa.core.nlg.translate import get_translated_buttons, get_translated_text
|
|
9
|
+
from rasa.engine.language import Language
|
|
10
|
+
from rasa.shared.constants import BUTTONS, RESPONSE_CONDITION, TEXT
|
|
9
11
|
from rasa.shared.core.domain import RESPONSE_KEYS_TO_INTERPOLATE
|
|
12
|
+
from rasa.shared.core.flows.constants import KEY_TRANSLATION
|
|
10
13
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
11
14
|
from rasa.shared.nlu.constants import METADATA
|
|
12
15
|
|
|
@@ -30,7 +33,11 @@ class TemplatedNaturalLanguageGenerator(NaturalLanguageGenerator):
|
|
|
30
33
|
|
|
31
34
|
# noinspection PyUnusedLocal
|
|
32
35
|
def _random_response_for(
|
|
33
|
-
self,
|
|
36
|
+
self,
|
|
37
|
+
utter_action: Text,
|
|
38
|
+
output_channel: Text,
|
|
39
|
+
filled_slots: Dict[Text, Any],
|
|
40
|
+
language: Optional[Language] = None,
|
|
34
41
|
) -> Optional[Dict[Text, Any]]:
|
|
35
42
|
"""Select random response for the utter action from available ones.
|
|
36
43
|
|
|
@@ -42,7 +49,7 @@ class TemplatedNaturalLanguageGenerator(NaturalLanguageGenerator):
|
|
|
42
49
|
if utter_action in self.responses:
|
|
43
50
|
response_filter = ResponseVariationFilter(self.responses)
|
|
44
51
|
suitable_responses = response_filter.responses_for_utter_action(
|
|
45
|
-
utter_action, output_channel, filled_slots
|
|
52
|
+
utter_action, output_channel, filled_slots, language
|
|
46
53
|
)
|
|
47
54
|
|
|
48
55
|
if suitable_responses:
|
|
@@ -75,9 +82,36 @@ class TemplatedNaturalLanguageGenerator(NaturalLanguageGenerator):
|
|
|
75
82
|
"""Generate a response for the requested utter action."""
|
|
76
83
|
filled_slots = tracker.current_slot_values()
|
|
77
84
|
stack_context = tracker.stack.current_context()
|
|
78
|
-
|
|
79
|
-
utter_action,
|
|
85
|
+
response = self.generate_from_slots(
|
|
86
|
+
utter_action,
|
|
87
|
+
filled_slots,
|
|
88
|
+
stack_context,
|
|
89
|
+
output_channel,
|
|
90
|
+
tracker.current_language,
|
|
91
|
+
**kwargs,
|
|
80
92
|
)
|
|
93
|
+
if response is not None:
|
|
94
|
+
return self.translate_response(response, tracker.current_language)
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
def translate_response(
|
|
98
|
+
self, response: Dict[Text, Any], language: Optional[Language] = None
|
|
99
|
+
) -> Dict[Text, Any]:
|
|
100
|
+
message_copy = copy.deepcopy(response)
|
|
101
|
+
|
|
102
|
+
text = get_translated_text(
|
|
103
|
+
text=message_copy.pop(TEXT, None),
|
|
104
|
+
translation=message_copy.pop(KEY_TRANSLATION, {}),
|
|
105
|
+
language=language,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
buttons = get_translated_buttons(
|
|
109
|
+
buttons=message_copy.pop(BUTTONS, None), language=language
|
|
110
|
+
)
|
|
111
|
+
message_copy[TEXT] = text
|
|
112
|
+
if buttons:
|
|
113
|
+
message_copy[BUTTONS] = buttons
|
|
114
|
+
return message_copy
|
|
81
115
|
|
|
82
116
|
def generate_from_slots(
|
|
83
117
|
self,
|
|
@@ -85,12 +119,15 @@ class TemplatedNaturalLanguageGenerator(NaturalLanguageGenerator):
|
|
|
85
119
|
filled_slots: Dict[Text, Any],
|
|
86
120
|
stack_context: Dict[Text, Any],
|
|
87
121
|
output_channel: Text,
|
|
122
|
+
language: Optional[Language] = None,
|
|
88
123
|
**kwargs: Any,
|
|
89
124
|
) -> Optional[Dict[Text, Any]]:
|
|
90
125
|
"""Generate a response for the requested utter action."""
|
|
91
126
|
# Fetching a random response for the passed utter action
|
|
92
127
|
r = copy.deepcopy(
|
|
93
|
-
self._random_response_for(
|
|
128
|
+
self._random_response_for(
|
|
129
|
+
utter_action, output_channel, filled_slots, language
|
|
130
|
+
)
|
|
94
131
|
)
|
|
95
132
|
# Filling the slots in the response with placeholders and returning the response
|
|
96
133
|
if r is not None:
|
rasa/core/nlg/translate.py
CHANGED
|
@@ -23,6 +23,14 @@ def get_translated_text(
|
|
|
23
23
|
return translation.get(language_code, text)
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def has_translation(
|
|
27
|
+
message: Dict[Text, Any], language: Optional[Language] = None
|
|
28
|
+
) -> bool:
|
|
29
|
+
"""Check if the message has a translation for the given language."""
|
|
30
|
+
language_code = language.code if language else None
|
|
31
|
+
return language_code in message.get(KEY_TRANSLATION, {})
|
|
32
|
+
|
|
33
|
+
|
|
26
34
|
def get_translated_buttons(
|
|
27
35
|
buttons: Optional[List[Dict[Text, Any]]], language: Optional[Language] = None
|
|
28
36
|
) -> Optional[List[Dict[Text, Any]]]:
|
|
@@ -63,6 +63,8 @@ from rasa.shared.constants import (
|
|
|
63
63
|
)
|
|
64
64
|
from rasa.shared.core.constants import (
|
|
65
65
|
ACTION_CANCEL_FLOW,
|
|
66
|
+
ACTION_METADATA_MESSAGE_KEY,
|
|
67
|
+
ACTION_METADATA_TEXT_KEY,
|
|
66
68
|
ACTION_SEND_TEXT_NAME,
|
|
67
69
|
DEFAULT_SLOT_NAMES,
|
|
68
70
|
)
|
|
@@ -585,8 +587,8 @@ class EnterpriseSearchPolicy(LLMHealthCheckMixin, EmbeddingsHealthCheckMixin, Po
|
|
|
585
587
|
return self._create_prediction_internal_error(domain, tracker)
|
|
586
588
|
|
|
587
589
|
action_metadata = {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
+
ACTION_METADATA_MESSAGE_KEY: {
|
|
591
|
+
ACTION_METADATA_TEXT_KEY: response,
|
|
590
592
|
SEARCH_RESULTS_METADATA_KEY: [
|
|
591
593
|
result.text for result in documents.results
|
|
592
594
|
],
|
|
@@ -137,7 +137,7 @@ class FlowPolicy(Policy):
|
|
|
137
137
|
|
|
138
138
|
# create executor and predict next action
|
|
139
139
|
try:
|
|
140
|
-
prediction = flow_executor.advance_flows(
|
|
140
|
+
prediction = await flow_executor.advance_flows(
|
|
141
141
|
tracker, domain.action_names_or_texts, flows
|
|
142
142
|
)
|
|
143
143
|
return self._create_prediction_result(
|
|
@@ -164,7 +164,7 @@ class FlowPolicy(Policy):
|
|
|
164
164
|
# we retry, with the internal error frame on the stack
|
|
165
165
|
events = tracker.create_stack_updated_events(updated_stack)
|
|
166
166
|
tracker.update_with_events(events)
|
|
167
|
-
prediction = flow_executor.advance_flows(
|
|
167
|
+
prediction = await flow_executor.advance_flows(
|
|
168
168
|
tracker, domain.action_names_or_texts, flows
|
|
169
169
|
)
|
|
170
170
|
collected_events = events + (prediction.events or [])
|