rasa-pro 3.13.0.dev20250613__py3-none-any.whl → 3.13.0rc2__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/cli/e2e_test.py +0 -7
- rasa/cli/export.py +2 -0
- rasa/cli/project_templates/tutorial/config.yml +1 -1
- rasa/cli/project_templates/tutorial/endpoints.yml +1 -1
- rasa/cli/studio/download.py +1 -23
- rasa/cli/studio/link.py +0 -17
- rasa/cli/studio/pull.py +3 -2
- rasa/cli/studio/push.py +1 -1
- rasa/cli/studio/train.py +1 -5
- rasa/cli/studio/upload.py +1 -1
- rasa/core/agent.py +6 -0
- rasa/core/channels/__init__.py +3 -0
- rasa/core/channels/development_inspector.py +1 -1
- rasa/core/channels/facebook.py +1 -4
- rasa/core/channels/inspector/README.md +3 -3
- rasa/core/channels/inspector/dist/assets/{arc-c4b064fc.js → arc-371401b1.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-215b5026.js → blockDiagram-38ab4fdb-3f126156.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-2b54a0a3.js → c4Diagram-3d4e48cf-12f22eb7.js} +1 -1
- rasa/core/channels/inspector/dist/assets/channel-f1efda17.js +1 -0
- rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-daacea5f.js → classDiagram-70f12bd4-03b1d386.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-930d4dc2.js → classDiagram-v2-f2320105-84f69d63.js} +1 -1
- rasa/core/channels/inspector/dist/assets/clone-fdf164e2.js +1 -0
- rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-83c206ba.js → createText-2e5e7dd3-ca47fd38.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-b0eb01d0.js → edges-e0da2a9e-f837ca8a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-17586500.js → erDiagram-9861fffd-8717ac54.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-be2a1776.js → flowDb-956e92f1-94f38b83.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-c2120ebd.js → flowDiagram-66a62f08-b616f9fb.js} +1 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-7d7a1629.js +1 -0
- rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-a6ab5c48.js → flowchart-elk-definition-4a651766-f5d24bb8.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-ef613457.js → ganttDiagram-c361ad54-b43ba8d9.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-d59185b3.js → gitGraphDiagram-72cf32ee-c3aafaa5.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{graph-0f155405.js → graph-0d0a2c10.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-3862675e-d5f1d1b7.js → index-3862675e-58ea0305.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-47737d3a.js → index-cce6f8a1.js} +3 -3
- rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-b07d141f.js → infoDiagram-f8f76790-b8f60461.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-1936d429.js → journeyDiagram-49397b02-95be5545.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{layout-dde8d0f3.js → layout-da885b9b.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{line-0c2c7ee0.js → line-f1c817d3.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{linear-35dd89a4.js → linear-d42801e6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-56192851.js → mindmap-definition-fc14e90a-a38923a6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-fc21ed78.js → pieDiagram-8a3498a8-ca6e71e9.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-25e98518.js → quadrantDiagram-120e2f19-b290dae9.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-546ff1f5.js → requirementDiagram-deff3bca-03f02ceb.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-02d8b82d.js → sankeyDiagram-04a897e0-c49eee40.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-3ca5a92e.js → sequenceDiagram-704730f1-b2cd6a3d.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-128ea07c.js → stateDiagram-587899a1-e53a2028.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-95f290af.js → stateDiagram-v2-d93cdb3a-e1982a03.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-4984898a.js → styles-6aaf32cf-d0226ca5.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-9a916d00-1bf266ba.js → styles-9a916d00-0e21dc00.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-c10674c1-60521c63.js → styles-c10674c1-9588494e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-a25b6e12.js → svgDrawCommon-08f97a94-be478d4f.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-0fc086bf.js → timeline-definition-85554ec2-74631749.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-44ee592e.js → xychartDiagram-e933f94c-a043552f.js} +1 -1
- rasa/core/channels/inspector/dist/index.html +1 -1
- rasa/core/channels/inspector/src/components/RecruitmentPanel.tsx +1 -1
- rasa/core/channels/socketio.py +56 -41
- rasa/core/channels/studio_chat.py +311 -8
- rasa/core/channels/voice_ready/audiocodes.py +1 -1
- rasa/core/channels/voice_ready/jambonz.py +5 -6
- rasa/core/channels/voice_ready/twilio_voice.py +13 -12
- rasa/core/channels/voice_ready/utils.py +22 -0
- rasa/core/channels/voice_stream/asr/azure.py +9 -0
- rasa/core/channels/voice_stream/audiocodes.py +5 -11
- rasa/core/channels/voice_stream/browser_audio.py +1 -1
- rasa/core/channels/voice_stream/genesys.py +35 -16
- rasa/core/channels/voice_stream/jambonz.py +232 -0
- rasa/core/channels/voice_stream/tts/__init__.py +8 -0
- rasa/core/channels/voice_stream/twilio_media_streams.py +12 -7
- rasa/core/channels/voice_stream/voice_channel.py +53 -15
- rasa/core/exporter.py +36 -0
- rasa/core/information_retrieval/faiss.py +18 -11
- rasa/core/information_retrieval/ingestion/faq_parser.py +158 -0
- rasa/core/nlg/contextual_response_rephraser.py +10 -1
- rasa/core/policies/enterprise_search_policy.py +189 -263
- rasa/core/policies/enterprise_search_policy_config.py +241 -0
- rasa/core/policies/enterprise_search_prompt_with_relevancy_check_and_citation_template.jinja2 +6 -5
- rasa/core/policies/intentless_policy.py +47 -10
- rasa/core/processor.py +6 -0
- rasa/core/utils.py +11 -2
- rasa/dialogue_understanding/coexistence/llm_based_router.py +13 -11
- rasa/dialogue_understanding/commands/__init__.py +4 -0
- rasa/dialogue_understanding/commands/cancel_flow_command.py +4 -2
- rasa/dialogue_understanding/commands/clarify_command.py +2 -2
- rasa/dialogue_understanding/commands/correct_slots_command.py +5 -6
- rasa/dialogue_understanding/commands/error_command.py +1 -1
- rasa/dialogue_understanding/commands/human_handoff_command.py +1 -3
- rasa/dialogue_understanding/commands/set_slot_command.py +4 -4
- rasa/dialogue_understanding/commands/skip_question_command.py +1 -3
- rasa/dialogue_understanding/commands/start_flow_command.py +3 -3
- rasa/dialogue_understanding/generator/command_generator.py +11 -1
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +3 -2
- rasa/dialogue_understanding/generator/nlu_command_adapter.py +2 -2
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_template.jinja2 +0 -2
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +1 -0
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_gpt_4o_2024_11_20_template.jinja2 +1 -0
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v3_claude_3_5_sonnet_20240620_template.jinja2 +79 -0
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v3_gpt_4o_2024_11_20_template.jinja2 +1 -0
- rasa/dialogue_understanding/generator/single_step/search_ready_llm_command_generator.py +2 -2
- rasa/dialogue_understanding/generator/single_step/single_step_based_llm_command_generator.py +2 -18
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +17 -11
- rasa/dialogue_understanding/patterns/cancel.py +1 -2
- rasa/dialogue_understanding/patterns/clarify.py +1 -1
- rasa/dialogue_understanding/patterns/correction.py +2 -2
- rasa/dialogue_understanding/processor/command_processor.py +11 -12
- rasa/dialogue_understanding/stack/utils.py +3 -1
- rasa/e2e_test/constants.py +1 -1
- rasa/e2e_test/e2e_test_coverage_report.py +1 -1
- rasa/engine/graph.py +2 -2
- rasa/llm_fine_tuning/paraphrasing/conversation_rephraser.py +2 -6
- rasa/model_manager/runner_service.py +20 -4
- rasa/model_manager/trainer_service.py +6 -0
- rasa/privacy/privacy_manager.py +26 -11
- rasa/shared/constants.py +14 -0
- rasa/shared/core/command_payload_reader.py +1 -5
- rasa/shared/core/events.py +1 -3
- rasa/shared/core/flows/constants.py +2 -0
- rasa/shared/core/flows/flow.py +126 -12
- rasa/shared/core/flows/flows_list.py +18 -1
- rasa/shared/core/flows/steps/link.py +7 -2
- rasa/shared/core/flows/validation.py +25 -5
- rasa/shared/core/training_data/story_reader/yaml_story_reader.py +1 -4
- rasa/shared/providers/_configs/azure_openai_client_config.py +2 -2
- rasa/shared/providers/_configs/default_litellm_client_config.py +1 -1
- rasa/shared/providers/_configs/huggingface_local_embedding_client_config.py +1 -1
- rasa/shared/providers/_configs/openai_client_config.py +1 -1
- rasa/shared/providers/_configs/rasa_llm_client_config.py +1 -1
- rasa/shared/providers/_configs/self_hosted_llm_client_config.py +1 -1
- rasa/shared/providers/_configs/utils.py +0 -99
- rasa/shared/utils/common.py +1 -1
- rasa/shared/utils/configs.py +110 -0
- rasa/shared/utils/constants.py +0 -3
- rasa/shared/utils/llm.py +123 -8
- rasa/shared/utils/pykwalify_extensions.py +0 -9
- rasa/studio/constants.py +1 -0
- rasa/studio/data_handler.py +30 -9
- rasa/studio/download.py +171 -0
- rasa/studio/link.py +13 -2
- rasa/studio/prompts.py +221 -0
- rasa/studio/pull/__init__.py +0 -0
- rasa/studio/{download/flows.py → pull/data.py} +2 -131
- rasa/studio/{download → pull}/domains.py +1 -1
- rasa/studio/pull/pull.py +239 -0
- rasa/studio/push.py +7 -0
- rasa/studio/train.py +1 -1
- rasa/studio/upload.py +61 -5
- rasa/studio/utils.py +33 -0
- rasa/tracing/instrumentation/attribute_extractors.py +21 -7
- rasa/utils/common.py +11 -0
- rasa/version.py +1 -1
- {rasa_pro-3.13.0.dev20250613.dist-info → rasa_pro-3.13.0rc2.dist-info}/METADATA +4 -4
- {rasa_pro-3.13.0.dev20250613.dist-info → rasa_pro-3.13.0rc2.dist-info}/RECORD +155 -147
- rasa/core/channels/inspector/dist/assets/channel-3730f5fd.js +0 -1
- rasa/core/channels/inspector/dist/assets/clone-e847561e.js +0 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-efbbfe00.js +0 -1
- rasa/studio/download/download.py +0 -416
- rasa/studio/pull.py +0 -94
- /rasa/{studio/download → core/information_retrieval/ingestion}/__init__.py +0 -0
- {rasa_pro-3.13.0.dev20250613.dist-info → rasa_pro-3.13.0rc2.dist-info}/NOTICE +0 -0
- {rasa_pro-3.13.0.dev20250613.dist-info → rasa_pro-3.13.0rc2.dist-info}/WHEEL +0 -0
- {rasa_pro-3.13.0.dev20250613.dist-info → rasa_pro-3.13.0rc2.dist-info}/entry_points.txt +0 -0
|
@@ -29,6 +29,7 @@ from rasa.core.channels.voice_stream.voice_channel import (
|
|
|
29
29
|
VoiceInputChannel,
|
|
30
30
|
VoiceOutputChannel,
|
|
31
31
|
)
|
|
32
|
+
from rasa.shared.exceptions import InvalidConfigException
|
|
32
33
|
|
|
33
34
|
"""
|
|
34
35
|
Genesys throws a rate limit error with too many audio messages.
|
|
@@ -91,9 +92,14 @@ class GenesysInputChannel(VoiceInputChannel):
|
|
|
91
92
|
return "genesys"
|
|
92
93
|
|
|
93
94
|
def __init__(
|
|
94
|
-
self,
|
|
95
|
+
self,
|
|
96
|
+
server_url: str,
|
|
97
|
+
asr_config: Dict,
|
|
98
|
+
tts_config: Dict,
|
|
99
|
+
api_key: Optional[Text] = None,
|
|
100
|
+
client_secret: Optional[Text] = None,
|
|
95
101
|
) -> None:
|
|
96
|
-
super().__init__(
|
|
102
|
+
super().__init__(server_url, asr_config, tts_config)
|
|
97
103
|
self.api_key = api_key
|
|
98
104
|
self.client_secret = client_secret
|
|
99
105
|
|
|
@@ -101,20 +107,33 @@ class GenesysInputChannel(VoiceInputChannel):
|
|
|
101
107
|
def from_credentials(
|
|
102
108
|
cls,
|
|
103
109
|
credentials: Optional[Dict[str, Any]],
|
|
104
|
-
) ->
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
110
|
+
) -> "GenesysInputChannel":
|
|
111
|
+
"""Create a channel from credentials dictionary.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
credentials: Dictionary containing the required credentials:
|
|
115
|
+
- server_url: URL where the server is hosted
|
|
116
|
+
- asr: ASR engine configuration
|
|
117
|
+
- tts: TTS engine configuration
|
|
118
|
+
- api_key: Required API key for Genesys authentication
|
|
119
|
+
- client_secret: Optional client secret for signature verification
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
GenesysInputChannel instance
|
|
123
|
+
"""
|
|
124
|
+
channel = super().from_credentials(credentials)
|
|
125
|
+
|
|
126
|
+
# Check required Genesys-specific credentials
|
|
127
|
+
if not credentials.get("api_key"): # type: ignore[union-attr]
|
|
128
|
+
raise InvalidConfigException(
|
|
129
|
+
"No API key given for Genesys voice channel (api_key)."
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Update channel with Genesys-specific credentials
|
|
133
|
+
channel.api_key = credentials["api_key"] # type: ignore[index,attr-defined]
|
|
134
|
+
channel.client_secret = credentials.get("client_secret") # type: ignore[union-attr,attr-defined]
|
|
135
|
+
|
|
136
|
+
return channel # type: ignore[return-value]
|
|
118
137
|
|
|
119
138
|
def _ensure_channel_data_initialized(self) -> None:
|
|
120
139
|
"""Initialize Genesys-specific channel data if not already present.
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import audioop
|
|
2
|
+
import json
|
|
3
|
+
import uuid
|
|
4
|
+
from typing import Any, Awaitable, Callable, Dict, Optional, Text, Tuple
|
|
5
|
+
|
|
6
|
+
import structlog
|
|
7
|
+
from sanic import ( # type: ignore[attr-defined]
|
|
8
|
+
Blueprint,
|
|
9
|
+
HTTPResponse,
|
|
10
|
+
Request,
|
|
11
|
+
Websocket,
|
|
12
|
+
response,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from rasa.core.channels import UserMessage, requires_basic_auth
|
|
16
|
+
from rasa.core.channels.voice_ready.utils import (
|
|
17
|
+
CallParameters,
|
|
18
|
+
validate_username_password_credentials,
|
|
19
|
+
)
|
|
20
|
+
from rasa.core.channels.voice_stream.audio_bytes import RasaAudioBytes
|
|
21
|
+
from rasa.core.channels.voice_stream.call_state import call_state
|
|
22
|
+
from rasa.core.channels.voice_stream.tts.tts_engine import TTSEngine
|
|
23
|
+
from rasa.core.channels.voice_stream.voice_channel import (
|
|
24
|
+
ContinueConversationAction,
|
|
25
|
+
EndConversationAction,
|
|
26
|
+
NewAudioAction,
|
|
27
|
+
VoiceChannelAction,
|
|
28
|
+
VoiceInputChannel,
|
|
29
|
+
VoiceOutputChannel,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
logger = structlog.get_logger()
|
|
33
|
+
|
|
34
|
+
JAMBONZ_STREAMS_WEBSOCKET_PATH = "webhooks/jambonz_streams/websocket"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def map_call_params(data: Dict[Text, str]) -> CallParameters:
|
|
38
|
+
"""Map the twilio stream parameters to the CallParameters dataclass."""
|
|
39
|
+
call_sid = data.get("callSid", "None")
|
|
40
|
+
from_number = data.get("from", "Unknown")
|
|
41
|
+
to_number = data.get("to")
|
|
42
|
+
return CallParameters(
|
|
43
|
+
call_id=call_sid,
|
|
44
|
+
user_phone=from_number,
|
|
45
|
+
bot_phone=to_number,
|
|
46
|
+
stream_id=call_sid,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class JambonzStreamOutputChannel(VoiceOutputChannel):
|
|
51
|
+
@classmethod
|
|
52
|
+
def name(cls) -> str:
|
|
53
|
+
return "jambonz_stream"
|
|
54
|
+
|
|
55
|
+
async def send_audio_bytes(
|
|
56
|
+
self, recipient_id: str, audio_bytes: RasaAudioBytes
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Overridden to send binary websocket messages for Jambonz.
|
|
59
|
+
|
|
60
|
+
Converts 8kHz μ-law to 8kHz L16 PCM for Jambonz streaming.
|
|
61
|
+
"""
|
|
62
|
+
pcm = audioop.ulaw2lin(audio_bytes, 2)
|
|
63
|
+
await self.voice_websocket.send(pcm)
|
|
64
|
+
|
|
65
|
+
def create_marker_message(self, recipient_id: str) -> Tuple[str, str]:
|
|
66
|
+
"""Create a marker message to track audio stream position."""
|
|
67
|
+
marker_id = uuid.uuid4().hex
|
|
68
|
+
return json.dumps({"type": "mark", "data": {"name": marker_id}}), marker_id
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class JambonzStreamInputChannel(VoiceInputChannel):
|
|
72
|
+
@classmethod
|
|
73
|
+
def name(cls) -> str:
|
|
74
|
+
return "jambonz_stream"
|
|
75
|
+
|
|
76
|
+
def __init__(
|
|
77
|
+
self,
|
|
78
|
+
server_url: str,
|
|
79
|
+
asr_config: Dict,
|
|
80
|
+
tts_config: Dict,
|
|
81
|
+
username: Optional[Text] = None,
|
|
82
|
+
password: Optional[Text] = None,
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Initialize the channel.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
username: Optional username for basic auth
|
|
88
|
+
password: Optional password for basic auth
|
|
89
|
+
"""
|
|
90
|
+
super().__init__(server_url, asr_config, tts_config)
|
|
91
|
+
self.username = username
|
|
92
|
+
self.password = password
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def from_credentials(
|
|
96
|
+
cls, credentials: Optional[Dict[Text, Any]]
|
|
97
|
+
) -> "JambonzStreamInputChannel":
|
|
98
|
+
"""Create a channel from credentials dictionary.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
credentials: Dictionary containing the required credentials:
|
|
102
|
+
- server_url: URL where the server is hosted
|
|
103
|
+
- asr: ASR engine configuration
|
|
104
|
+
- tts: TTS engine configuration
|
|
105
|
+
- username: Optional username for basic auth
|
|
106
|
+
- password: Optional password for basic auth
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
JambonzStreamInputChannel instance
|
|
110
|
+
"""
|
|
111
|
+
# Get common credentials from parent
|
|
112
|
+
channel = super().from_credentials(credentials)
|
|
113
|
+
|
|
114
|
+
# Check optional basic auth credentials
|
|
115
|
+
username = credentials.get("username") # type: ignore[union-attr]
|
|
116
|
+
password = credentials.get("password") # type: ignore[union-attr]
|
|
117
|
+
validate_username_password_credentials(username, password, "Jambonz Stream")
|
|
118
|
+
|
|
119
|
+
# Update channel with auth credentials
|
|
120
|
+
channel.username = username # type: ignore[attr-defined]
|
|
121
|
+
channel.password = password # type: ignore[attr-defined]
|
|
122
|
+
|
|
123
|
+
return channel # type: ignore[return-value]
|
|
124
|
+
|
|
125
|
+
def _websocket_stream_url(self) -> str:
|
|
126
|
+
"""Returns the websocket stream URL."""
|
|
127
|
+
# depending on the config value, the url might contain http as a
|
|
128
|
+
# protocol or not - we'll make sure both work
|
|
129
|
+
if self.server_url.startswith("http"):
|
|
130
|
+
base_url = self.server_url.replace("http", "ws")
|
|
131
|
+
else:
|
|
132
|
+
base_url = f"wss://{self.server_url}"
|
|
133
|
+
return f"{base_url}/{JAMBONZ_STREAMS_WEBSOCKET_PATH}"
|
|
134
|
+
|
|
135
|
+
def channel_bytes_to_rasa_audio_bytes(self, input_bytes: bytes) -> RasaAudioBytes:
|
|
136
|
+
"""Convert Jambonz audio bytes (L16 PCM) to Rasa audio bytes (μ-law)."""
|
|
137
|
+
ulaw = audioop.lin2ulaw(input_bytes, 2)
|
|
138
|
+
return RasaAudioBytes(ulaw)
|
|
139
|
+
|
|
140
|
+
async def collect_call_parameters(
|
|
141
|
+
self, channel_websocket: Websocket
|
|
142
|
+
) -> Optional[CallParameters]:
|
|
143
|
+
# Wait for initial metadata message
|
|
144
|
+
message = await channel_websocket.recv()
|
|
145
|
+
logger.debug("jambonz.collect_call_parameters", message=message)
|
|
146
|
+
metadata = json.loads(message)
|
|
147
|
+
return map_call_params(metadata)
|
|
148
|
+
|
|
149
|
+
def map_input_message(self, message: Any, ws: Websocket) -> VoiceChannelAction:
|
|
150
|
+
# Handle binary audio frames
|
|
151
|
+
if isinstance(message, bytes):
|
|
152
|
+
channel_bytes = message
|
|
153
|
+
audio_bytes = self.channel_bytes_to_rasa_audio_bytes(channel_bytes)
|
|
154
|
+
return NewAudioAction(audio_bytes)
|
|
155
|
+
|
|
156
|
+
# Handle JSON messages
|
|
157
|
+
data = json.loads(message)
|
|
158
|
+
if data["type"] == "mark":
|
|
159
|
+
if data["data"]["name"] == call_state.latest_bot_audio_id:
|
|
160
|
+
# Just finished streaming last audio bytes
|
|
161
|
+
call_state.is_bot_speaking = False # type: ignore[attr-defined]
|
|
162
|
+
if call_state.should_hangup:
|
|
163
|
+
logger.debug(
|
|
164
|
+
"jambonz.hangup", marker=call_state.latest_bot_audio_id
|
|
165
|
+
)
|
|
166
|
+
return EndConversationAction()
|
|
167
|
+
else:
|
|
168
|
+
call_state.is_bot_speaking = True # type: ignore[attr-defined]
|
|
169
|
+
elif data["event"] == "dtmf":
|
|
170
|
+
# TODO: handle DTMF input
|
|
171
|
+
logger.debug("jambonz.dtmf.received", dtmf=data["dtmf"])
|
|
172
|
+
else:
|
|
173
|
+
logger.warning("jambonz.unexpected_message", message=data)
|
|
174
|
+
|
|
175
|
+
return ContinueConversationAction()
|
|
176
|
+
|
|
177
|
+
def create_output_channel(
|
|
178
|
+
self, voice_websocket: Websocket, tts_engine: TTSEngine
|
|
179
|
+
) -> VoiceOutputChannel:
|
|
180
|
+
return JambonzStreamOutputChannel(
|
|
181
|
+
voice_websocket,
|
|
182
|
+
tts_engine,
|
|
183
|
+
self.tts_cache,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def blueprint(
|
|
187
|
+
self, on_new_message: Callable[[UserMessage], Awaitable[Any]]
|
|
188
|
+
) -> Blueprint:
|
|
189
|
+
blueprint = Blueprint("jambonz_stream", __name__)
|
|
190
|
+
|
|
191
|
+
@blueprint.route("/", methods=["GET"])
|
|
192
|
+
async def health(_: Request) -> HTTPResponse:
|
|
193
|
+
return response.json({"status": "ok"})
|
|
194
|
+
|
|
195
|
+
@blueprint.route("/call_status", methods=["POST"])
|
|
196
|
+
@requires_basic_auth(self.username, self.password)
|
|
197
|
+
async def call_status(request: Request) -> HTTPResponse:
|
|
198
|
+
"""Handle call status updates from Jambonz."""
|
|
199
|
+
data = request.json
|
|
200
|
+
logger.debug("jambonz.call_status.received", data=data)
|
|
201
|
+
return response.json({"status": "ok"})
|
|
202
|
+
|
|
203
|
+
@blueprint.route("/webhook", methods=["POST"])
|
|
204
|
+
@requires_basic_auth(self.username, self.password)
|
|
205
|
+
async def webhook(request: Request) -> HTTPResponse:
|
|
206
|
+
"""Handle incoming webhook requests from Jambonz."""
|
|
207
|
+
data = request.json
|
|
208
|
+
logger.debug("jambonz.webhook.received", data=data)
|
|
209
|
+
return response.json(
|
|
210
|
+
[
|
|
211
|
+
{
|
|
212
|
+
"verb": "listen",
|
|
213
|
+
"url": self._websocket_stream_url(),
|
|
214
|
+
"sampleRate": 8000,
|
|
215
|
+
"passDtmf": True,
|
|
216
|
+
"bidirectionalAudio": {
|
|
217
|
+
"enabled": True,
|
|
218
|
+
"streaming": True,
|
|
219
|
+
"sampleRate": 8000,
|
|
220
|
+
},
|
|
221
|
+
}
|
|
222
|
+
]
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
@blueprint.websocket("/websocket", subprotocols=["audio.jambonz.org"]) # type: ignore[misc]
|
|
226
|
+
async def handle_message(request: Request, ws: Websocket) -> None:
|
|
227
|
+
try:
|
|
228
|
+
await self.run_audio_streaming(on_new_message, ws)
|
|
229
|
+
except Exception as e:
|
|
230
|
+
logger.error("jambonz.handle_message.error", error=e)
|
|
231
|
+
|
|
232
|
+
return blueprint
|
|
@@ -19,7 +19,10 @@ from rasa.core.channels.channel import (
|
|
|
19
19
|
create_auth_requested_response_provider,
|
|
20
20
|
requires_basic_auth,
|
|
21
21
|
)
|
|
22
|
-
from rasa.core.channels.voice_ready.utils import
|
|
22
|
+
from rasa.core.channels.voice_ready.utils import (
|
|
23
|
+
CallParameters,
|
|
24
|
+
validate_username_password_credentials,
|
|
25
|
+
)
|
|
23
26
|
from rasa.core.channels.voice_stream.audio_bytes import RasaAudioBytes
|
|
24
27
|
from rasa.core.channels.voice_stream.call_state import call_state
|
|
25
28
|
from rasa.core.channels.voice_stream.tts.tts_engine import TTSEngine
|
|
@@ -31,7 +34,6 @@ from rasa.core.channels.voice_stream.voice_channel import (
|
|
|
31
34
|
VoiceInputChannel,
|
|
32
35
|
VoiceOutputChannel,
|
|
33
36
|
)
|
|
34
|
-
from rasa.shared.exceptions import RasaException
|
|
35
37
|
|
|
36
38
|
if TYPE_CHECKING:
|
|
37
39
|
from twilio.twiml.voice_response import VoiceResponse
|
|
@@ -122,11 +124,7 @@ class TwilioMediaStreamsInputChannel(VoiceInputChannel):
|
|
|
122
124
|
|
|
123
125
|
username = credentials.get("username")
|
|
124
126
|
password = credentials.get("password")
|
|
125
|
-
|
|
126
|
-
raise RasaException(
|
|
127
|
-
"In TwilioMediaStreams channel, either both username and password "
|
|
128
|
-
"or neither should be provided. "
|
|
129
|
-
)
|
|
127
|
+
validate_username_password_credentials(username, password, "TwilioMediaStreams")
|
|
130
128
|
|
|
131
129
|
return cls(
|
|
132
130
|
credentials["server_url"],
|
|
@@ -140,6 +138,13 @@ class TwilioMediaStreamsInputChannel(VoiceInputChannel):
|
|
|
140
138
|
def name(cls) -> str:
|
|
141
139
|
return "twilio_media_streams"
|
|
142
140
|
|
|
141
|
+
def get_sender_id(self, call_parameters: CallParameters) -> str:
|
|
142
|
+
"""Get the sender ID for the channel.
|
|
143
|
+
|
|
144
|
+
Twilio Media Streams uses the Stream ID as Sender ID because
|
|
145
|
+
it is required in OutputChannel.send_text_message to send messages."""
|
|
146
|
+
return call_parameters.stream_id # type: ignore[return-value]
|
|
147
|
+
|
|
143
148
|
def channel_bytes_to_rasa_audio_bytes(self, input_bytes: bytes) -> RasaAudioBytes:
|
|
144
149
|
return RasaAudioBytes(base64.b64decode(input_bytes))
|
|
145
150
|
|
|
@@ -35,7 +35,7 @@ from rasa.core.channels.voice_stream.util import (
|
|
|
35
35
|
generate_silence,
|
|
36
36
|
)
|
|
37
37
|
from rasa.shared.core.constants import SILENCE_TIMEOUT_SLOT
|
|
38
|
-
from rasa.shared.
|
|
38
|
+
from rasa.shared.exceptions import InvalidConfigException
|
|
39
39
|
from rasa.shared.utils.common import (
|
|
40
40
|
class_from_module_path,
|
|
41
41
|
mark_as_beta_feature,
|
|
@@ -71,6 +71,14 @@ class ContinueConversationAction(VoiceChannelAction):
|
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
def asr_engine_from_config(asr_config: Dict) -> ASREngine:
|
|
74
|
+
if not asr_config:
|
|
75
|
+
raise ValueError("ASR configuration dictionary cannot be empty")
|
|
76
|
+
|
|
77
|
+
if "name" not in asr_config:
|
|
78
|
+
raise ValueError(
|
|
79
|
+
"ASR configuration must contain 'name' key specifying the engine type"
|
|
80
|
+
)
|
|
81
|
+
|
|
74
82
|
name = str(asr_config["name"])
|
|
75
83
|
asr_config = copy.copy(asr_config)
|
|
76
84
|
asr_config.pop("name")
|
|
@@ -84,12 +92,12 @@ def asr_engine_from_config(asr_config: Dict) -> ASREngine:
|
|
|
84
92
|
asr_engine_class = class_from_module_path(name)
|
|
85
93
|
return asr_engine_class.from_config_dict(asr_config)
|
|
86
94
|
except NameError:
|
|
87
|
-
|
|
95
|
+
raise InvalidConfigException(
|
|
88
96
|
f"Failed to initialize ASR Engine with type '{name}'. "
|
|
89
97
|
f"Please make sure the method `from_config_dict`is implemented."
|
|
90
98
|
)
|
|
91
99
|
except TypeError as e:
|
|
92
|
-
|
|
100
|
+
raise InvalidConfigException(
|
|
93
101
|
f"Failed to initialize ASR Engine with type '{name}'. "
|
|
94
102
|
f"Invalid configuration provided. "
|
|
95
103
|
f"Error: {e}"
|
|
@@ -97,6 +105,14 @@ def asr_engine_from_config(asr_config: Dict) -> ASREngine:
|
|
|
97
105
|
|
|
98
106
|
|
|
99
107
|
def tts_engine_from_config(tts_config: Dict) -> TTSEngine:
|
|
108
|
+
if not tts_config:
|
|
109
|
+
raise ValueError("TTS configuration dictionary cannot be empty")
|
|
110
|
+
|
|
111
|
+
if "name" not in tts_config:
|
|
112
|
+
raise ValueError(
|
|
113
|
+
"TTS configuration must contain 'name' key specifying the engine type"
|
|
114
|
+
)
|
|
115
|
+
|
|
100
116
|
name = str(tts_config["name"])
|
|
101
117
|
tts_config = copy.copy(tts_config)
|
|
102
118
|
tts_config.pop("name")
|
|
@@ -110,13 +126,13 @@ def tts_engine_from_config(tts_config: Dict) -> TTSEngine:
|
|
|
110
126
|
tts_engine_class = class_from_module_path(name)
|
|
111
127
|
return tts_engine_class.from_config_dict(tts_config)
|
|
112
128
|
except NameError:
|
|
113
|
-
|
|
129
|
+
raise InvalidConfigException(
|
|
114
130
|
f"Failed to initialize TTS Engine with type '{name}'. "
|
|
115
131
|
f"Please make sure the method `from_config_dict`is implemented."
|
|
116
132
|
)
|
|
117
133
|
except TypeError as e:
|
|
118
|
-
|
|
119
|
-
f"Failed to initialize
|
|
134
|
+
raise InvalidConfigException(
|
|
135
|
+
f"Failed to initialize TTS Engine with type '{name}'. "
|
|
120
136
|
f"Invalid configuration provided. "
|
|
121
137
|
f"Error: {e}"
|
|
122
138
|
)
|
|
@@ -286,13 +302,18 @@ class VoiceOutputChannel(OutputChannel):
|
|
|
286
302
|
|
|
287
303
|
|
|
288
304
|
class VoiceInputChannel(InputChannel):
|
|
305
|
+
# All children of this class require a voice license to be used.
|
|
306
|
+
requires_voice_license = True
|
|
307
|
+
|
|
289
308
|
def __init__(
|
|
290
309
|
self,
|
|
291
310
|
server_url: str,
|
|
292
311
|
asr_config: Dict,
|
|
293
312
|
tts_config: Dict,
|
|
294
313
|
):
|
|
295
|
-
|
|
314
|
+
if self.requires_voice_license:
|
|
315
|
+
validate_voice_license_scope()
|
|
316
|
+
|
|
296
317
|
self.server_url = server_url
|
|
297
318
|
self.asr_config = asr_config
|
|
298
319
|
self.tts_config = tts_config
|
|
@@ -305,6 +326,10 @@ class VoiceInputChannel(InputChannel):
|
|
|
305
326
|
tts_config=self.tts_config,
|
|
306
327
|
)
|
|
307
328
|
|
|
329
|
+
def get_sender_id(self, call_parameters: CallParameters) -> str:
|
|
330
|
+
"""Get the sender ID for the channel."""
|
|
331
|
+
return call_parameters.call_id
|
|
332
|
+
|
|
308
333
|
async def monitor_silence_timeout(self, asr_event_queue: asyncio.Queue) -> None:
|
|
309
334
|
timeout = call_state.silence_timeout
|
|
310
335
|
if not timeout:
|
|
@@ -327,11 +352,24 @@ class VoiceInputChannel(InputChannel):
|
|
|
327
352
|
cls,
|
|
328
353
|
credentials: Optional[Dict[str, Any]],
|
|
329
354
|
) -> InputChannel:
|
|
330
|
-
|
|
355
|
+
if not credentials:
|
|
356
|
+
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"):
|
|
361
|
+
raise InvalidConfigException(
|
|
362
|
+
"No ASR configuration provided in credentials."
|
|
363
|
+
)
|
|
364
|
+
if not credentials.get("tts"):
|
|
365
|
+
raise InvalidConfigException(
|
|
366
|
+
"No TTS configuration provided in credentials."
|
|
367
|
+
)
|
|
368
|
+
|
|
331
369
|
return cls(
|
|
332
|
-
credentials["server_url"],
|
|
333
|
-
credentials["asr"],
|
|
334
|
-
credentials["tts"],
|
|
370
|
+
server_url=credentials["server_url"],
|
|
371
|
+
asr_config=credentials["asr"],
|
|
372
|
+
tts_config=credentials["tts"],
|
|
335
373
|
)
|
|
336
374
|
|
|
337
375
|
def channel_bytes_to_rasa_audio_bytes(self, input_bytes: bytes) -> RasaAudioBytes:
|
|
@@ -353,7 +391,7 @@ class VoiceInputChannel(InputChannel):
|
|
|
353
391
|
message = UserMessage(
|
|
354
392
|
text=USER_CONVERSATION_SESSION_START,
|
|
355
393
|
output_channel=output_channel,
|
|
356
|
-
sender_id=call_parameters
|
|
394
|
+
sender_id=self.get_sender_id(call_parameters),
|
|
357
395
|
input_channel=self.name(),
|
|
358
396
|
metadata=asdict(call_parameters),
|
|
359
397
|
)
|
|
@@ -471,7 +509,7 @@ class VoiceInputChannel(InputChannel):
|
|
|
471
509
|
message = UserMessage(
|
|
472
510
|
text=e.text,
|
|
473
511
|
output_channel=output_channel,
|
|
474
|
-
sender_id=call_parameters
|
|
512
|
+
sender_id=self.get_sender_id(call_parameters),
|
|
475
513
|
input_channel=self.name(),
|
|
476
514
|
metadata=asdict(call_parameters),
|
|
477
515
|
)
|
|
@@ -484,7 +522,7 @@ class VoiceInputChannel(InputChannel):
|
|
|
484
522
|
message = UserMessage(
|
|
485
523
|
text=USER_CONVERSATION_SILENCE_TIMEOUT,
|
|
486
524
|
output_channel=output_channel,
|
|
487
|
-
sender_id=call_parameters
|
|
525
|
+
sender_id=self.get_sender_id(call_parameters),
|
|
488
526
|
input_channel=self.name(),
|
|
489
527
|
metadata=asdict(call_parameters),
|
|
490
528
|
)
|
|
@@ -502,7 +540,7 @@ class VoiceInputChannel(InputChannel):
|
|
|
502
540
|
message = UserMessage(
|
|
503
541
|
text=USER_CONVERSATION_SESSION_END,
|
|
504
542
|
output_channel=output_channel,
|
|
505
|
-
sender_id=call_parameters
|
|
543
|
+
sender_id=self.get_sender_id(call_parameters),
|
|
506
544
|
input_channel=self.name(),
|
|
507
545
|
)
|
|
508
546
|
await on_new_message(message)
|
rasa/core/exporter.py
CHANGED
|
@@ -16,6 +16,11 @@ from rasa.exceptions import (
|
|
|
16
16
|
NoEventsToMigrateError,
|
|
17
17
|
PublishingError,
|
|
18
18
|
)
|
|
19
|
+
from rasa.shared.core.events import (
|
|
20
|
+
BotUttered,
|
|
21
|
+
SlotSet,
|
|
22
|
+
UserUttered,
|
|
23
|
+
)
|
|
19
24
|
from rasa.shared.core.trackers import EventVerbosity
|
|
20
25
|
|
|
21
26
|
logger = logging.getLogger(__name__)
|
|
@@ -43,6 +48,7 @@ class Exporter:
|
|
|
43
48
|
tracker_store: TrackerStore,
|
|
44
49
|
event_broker: EventBroker,
|
|
45
50
|
endpoints_path: Text,
|
|
51
|
+
is_pii_enabled: bool = False,
|
|
46
52
|
requested_conversation_ids: Optional[Text] = None,
|
|
47
53
|
minimum_timestamp: Optional[float] = None,
|
|
48
54
|
maximum_timestamp: Optional[float] = None,
|
|
@@ -52,6 +58,7 @@ class Exporter:
|
|
|
52
58
|
self.tracker_store = tracker_store
|
|
53
59
|
|
|
54
60
|
self.event_broker = event_broker
|
|
61
|
+
self.is_pii_enabled = is_pii_enabled
|
|
55
62
|
self.requested_conversation_ids = requested_conversation_ids
|
|
56
63
|
self.minimum_timestamp = minimum_timestamp
|
|
57
64
|
self.maximum_timestamp = maximum_timestamp
|
|
@@ -72,10 +79,12 @@ class Exporter:
|
|
|
72
79
|
current_timestamp = None
|
|
73
80
|
|
|
74
81
|
headers = self._get_message_headers()
|
|
82
|
+
warned_sender_ids: Set[Text] = set()
|
|
75
83
|
|
|
76
84
|
async for event in self._fetch_events_within_time_range():
|
|
77
85
|
# noinspection PyBroadException
|
|
78
86
|
try:
|
|
87
|
+
self._check_anonymization_status(event, warned_sender_ids)
|
|
79
88
|
self._publish_with_message_headers(event, headers)
|
|
80
89
|
published_events += 1
|
|
81
90
|
current_timestamp = event["timestamp"]
|
|
@@ -282,3 +291,30 @@ class Exporter:
|
|
|
282
291
|
events_with_conversation_id.append(event)
|
|
283
292
|
|
|
284
293
|
return events_with_conversation_id
|
|
294
|
+
|
|
295
|
+
def _check_anonymization_status(
|
|
296
|
+
self, event: Dict[Text, Any], warned_sender_ids: Set[Text]
|
|
297
|
+
) -> None:
|
|
298
|
+
"""Check if the tracker store contains unanonymized events.
|
|
299
|
+
|
|
300
|
+
If it does, print a warning that these events will be published as is.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
event: The event to check for anonymization status
|
|
304
|
+
warned_sender_ids: Set of sender IDs that have already been warned about
|
|
305
|
+
"""
|
|
306
|
+
sender_id = event["sender_id"]
|
|
307
|
+
if (
|
|
308
|
+
self.is_pii_enabled
|
|
309
|
+
and sender_id not in warned_sender_ids
|
|
310
|
+
and event["event"]
|
|
311
|
+
in (UserUttered.type_name, BotUttered.type_name, SlotSet.type_name)
|
|
312
|
+
and not event.get("anonymized_at", None)
|
|
313
|
+
):
|
|
314
|
+
rasa.shared.utils.cli.print_warning(
|
|
315
|
+
f"Retrieved un-anonymized event for sender_id {sender_id}. "
|
|
316
|
+
f"All events after this timestamp {event['timestamp']} "
|
|
317
|
+
"are not anonymized for this tracker. Proceeding with "
|
|
318
|
+
"publishing plaintext values for all events following this.",
|
|
319
|
+
)
|
|
320
|
+
warned_sender_ids.add(sender_id)
|
|
@@ -12,6 +12,7 @@ from rasa.core.information_retrieval import (
|
|
|
12
12
|
InformationRetrievalException,
|
|
13
13
|
SearchResultList,
|
|
14
14
|
)
|
|
15
|
+
from rasa.core.information_retrieval.ingestion.faq_parser import _format_faq_documents
|
|
15
16
|
from rasa.utils.endpoints import EndpointConfig
|
|
16
17
|
from rasa.utils.ml_utils import persist_faiss_vector_store
|
|
17
18
|
|
|
@@ -31,10 +32,12 @@ class FAISS_Store(InformationRetrieval):
|
|
|
31
32
|
index_path: str,
|
|
32
33
|
docs_folder: Optional[str],
|
|
33
34
|
create_index: Optional[bool] = False,
|
|
35
|
+
parse_as_faq_pairs: Optional[bool] = False,
|
|
34
36
|
):
|
|
35
37
|
"""Initializes the FAISS Store."""
|
|
36
38
|
self.chunk_size = 1000
|
|
37
39
|
self.chunk_overlap = 20
|
|
40
|
+
self.parse_as_faq_pairs = parse_as_faq_pairs
|
|
38
41
|
|
|
39
42
|
path = Path(index_path) / "documents_faiss"
|
|
40
43
|
if create_index:
|
|
@@ -86,21 +89,25 @@ class FAISS_Store(InformationRetrieval):
|
|
|
86
89
|
if not docs_folder:
|
|
87
90
|
raise ValueError("parameter `docs_folder` needs to be specified")
|
|
88
91
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
documents = self.load_documents(docs_folder)
|
|
93
|
+
|
|
94
|
+
if not self.parse_as_faq_pairs:
|
|
95
|
+
splitter = RecursiveCharacterTextSplitter(
|
|
96
|
+
chunk_size=self.chunk_size,
|
|
97
|
+
chunk_overlap=self.chunk_overlap,
|
|
98
|
+
length_function=len,
|
|
99
|
+
)
|
|
100
|
+
parsed_documents = splitter.split_documents(documents)
|
|
101
|
+
else:
|
|
102
|
+
parsed_documents = _format_faq_documents(documents)
|
|
96
103
|
|
|
97
104
|
logger.info(
|
|
98
105
|
"information_retrieval.faiss_store._create_document_index",
|
|
99
|
-
len_chunks=len(
|
|
106
|
+
len_chunks=len(parsed_documents),
|
|
100
107
|
)
|
|
101
|
-
if
|
|
102
|
-
texts = [
|
|
103
|
-
metadatas = [
|
|
108
|
+
if parsed_documents:
|
|
109
|
+
texts = [document.page_content for document in parsed_documents]
|
|
110
|
+
metadatas = [document.metadata for document in parsed_documents]
|
|
104
111
|
return FAISS.from_texts(texts, embedding, metadatas=metadatas, ids=None)
|
|
105
112
|
else:
|
|
106
113
|
raise ValueError(f"No documents found at '{docs_folder}'.")
|