rasa-pro 3.13.0.dev2__py3-none-any.whl → 3.13.0.dev3__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.

Files changed (48) hide show
  1. rasa/cli/run.py +10 -6
  2. rasa/cli/utils.py +7 -0
  3. rasa/core/channels/channel.py +30 -0
  4. rasa/core/channels/voice_ready/jambonz.py +25 -5
  5. rasa/core/channels/voice_ready/jambonz_protocol.py +4 -0
  6. rasa/core/information_retrieval/faiss.py +7 -68
  7. rasa/core/information_retrieval/information_retrieval.py +2 -40
  8. rasa/core/information_retrieval/milvus.py +2 -7
  9. rasa/core/information_retrieval/qdrant.py +2 -7
  10. rasa/core/nlg/contextual_response_rephraser.py +3 -0
  11. rasa/core/policies/enterprise_search_policy.py +310 -61
  12. rasa/core/policies/intentless_policy.py +3 -0
  13. rasa/dialogue_understanding/coexistence/llm_based_router.py +8 -0
  14. rasa/dialogue_understanding/commands/knowledge_answer_command.py +2 -2
  15. rasa/dialogue_understanding/generator/command_parser.py +1 -1
  16. rasa/dialogue_understanding/generator/flow_retrieval.py +1 -4
  17. rasa/dialogue_understanding/generator/llm_based_command_generator.py +1 -2
  18. rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +13 -0
  19. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_template.jinja2 +1 -1
  20. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +1 -1
  21. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_gpt_4o_2024_11_20_template.jinja2 +2 -24
  22. rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +22 -17
  23. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +27 -12
  24. rasa/dialogue_understanding_test/io.py +8 -13
  25. rasa/e2e_test/utils/validation.py +3 -3
  26. rasa/engine/recipes/default_components.py +0 -2
  27. rasa/llm_fine_tuning/paraphrasing/conversation_rephraser.py +3 -0
  28. rasa/shared/utils/constants.py +3 -0
  29. rasa/shared/utils/llm.py +70 -24
  30. rasa/tracing/instrumentation/attribute_extractors.py +7 -10
  31. rasa/tracing/instrumentation/instrumentation.py +12 -12
  32. rasa/version.py +1 -1
  33. {rasa_pro-3.13.0.dev2.dist-info → rasa_pro-3.13.0.dev3.dist-info}/METADATA +2 -2
  34. {rasa_pro-3.13.0.dev2.dist-info → rasa_pro-3.13.0.dev3.dist-info}/RECORD +37 -48
  35. rasa/document_retrieval/__init__.py +0 -0
  36. rasa/document_retrieval/constants.py +0 -32
  37. rasa/document_retrieval/document_post_processor.py +0 -351
  38. rasa/document_retrieval/document_post_processor_prompt_template.jinja2 +0 -0
  39. rasa/document_retrieval/document_retriever.py +0 -333
  40. rasa/document_retrieval/knowledge_base_connectors/__init__.py +0 -0
  41. rasa/document_retrieval/knowledge_base_connectors/api_connector.py +0 -39
  42. rasa/document_retrieval/knowledge_base_connectors/knowledge_base_connector.py +0 -34
  43. rasa/document_retrieval/knowledge_base_connectors/vector_store_connector.py +0 -226
  44. rasa/document_retrieval/query_rewriter.py +0 -234
  45. rasa/document_retrieval/query_rewriter_prompt_template.jinja2 +0 -8
  46. {rasa_pro-3.13.0.dev2.dist-info → rasa_pro-3.13.0.dev3.dist-info}/NOTICE +0 -0
  47. {rasa_pro-3.13.0.dev2.dist-info → rasa_pro-3.13.0.dev3.dist-info}/WHEEL +0 -0
  48. {rasa_pro-3.13.0.dev2.dist-info → rasa_pro-3.13.0.dev3.dist-info}/entry_points.txt +0 -0
rasa/cli/run.py CHANGED
@@ -64,12 +64,16 @@ def run_actions(args: argparse.Namespace) -> None:
64
64
 
65
65
  def _validate_model_path(model_path: Text, parameter: Text, default: Text) -> Text:
66
66
  if model_path is not None and not os.path.exists(model_path):
67
- reason_str = f"'{model_path}' not found."
68
- if model_path is None:
69
- reason_str = f"Parameter '{parameter}' not set."
70
-
71
- logger.debug(f"{reason_str} Using default location '{default}' instead.")
72
-
67
+ raise ModelNotFound(
68
+ f"The provided model path '{model_path}' could not be found. "
69
+ "Provide an existing model path."
70
+ )
71
+
72
+ if model_path is None:
73
+ logger.debug(
74
+ f"Parameter '{parameter}' not set. "
75
+ "Using default location '{default}' instead."
76
+ )
73
77
  os.makedirs(default, exist_ok=True)
74
78
  model_path = default
75
79
 
rasa/cli/utils.py CHANGED
@@ -14,6 +14,7 @@ import structlog
14
14
  import rasa.shared.utils.cli
15
15
  import rasa.shared.utils.io
16
16
  from rasa import telemetry
17
+ from rasa.exceptions import ModelNotFound
17
18
  from rasa.shared.constants import (
18
19
  ASSISTANT_ID_DEFAULT_VALUE,
19
20
  ASSISTANT_ID_KEY,
@@ -77,6 +78,12 @@ def get_validated_path(
77
78
  if current and os.path.exists(current):
78
79
  return current
79
80
 
81
+ if parameter == "model":
82
+ raise ModelNotFound(
83
+ f"The provided model path '{current}' could not be found. "
84
+ "Provide an existing model path."
85
+ )
86
+
80
87
  # try to find a valid option among the defaults
81
88
  if isinstance(default, str) or isinstance(default, Path):
82
89
  default_options = [str(default)]
@@ -1,6 +1,9 @@
1
+ import hmac
1
2
  import json
2
3
  import logging
3
4
  import uuid
5
+ from base64 import b64encode
6
+ from functools import wraps
4
7
  from typing import (
5
8
  Any,
6
9
  Awaitable,
@@ -15,6 +18,7 @@ from typing import (
15
18
 
16
19
  import jwt
17
20
  from sanic import Blueprint, Sanic
21
+ from sanic.exceptions import Unauthorized
18
22
  from sanic.request import Request
19
23
 
20
24
  from rasa.cli import utils as cli_utils
@@ -454,3 +458,29 @@ class CollectingOutputChannel(OutputChannel):
454
458
  self, recipient_id: Text, json_message: Dict[Text, Any], **kwargs: Any
455
459
  ) -> None:
456
460
  await self._persist_message(self._message(recipient_id, custom=json_message))
461
+
462
+
463
+ def requires_basic_auth(username: Optional[Text], password: Optional[Text]) -> Callable:
464
+ """Decorator to require basic auth for a route."""
465
+
466
+ def decorator(func: Callable) -> Callable:
467
+ @wraps(func)
468
+ async def wrapper(request: Request, *args: Any, **kwargs: Any) -> Any:
469
+ if not username or not password:
470
+ return await func(request, *args, **kwargs)
471
+
472
+ auth = request.headers.get("Authorization")
473
+ if not auth or not auth.startswith("Basic "):
474
+ logger.error("Missing or invalid authorization header.")
475
+ raise Unauthorized("Missing or invalid authorization header.") # type: ignore[no-untyped-call]
476
+
477
+ encoded = b64encode(f"{username}:{password}".encode()).decode()
478
+ if not hmac.compare_digest(auth[6:], encoded):
479
+ logger.error("Invalid username or password.")
480
+ raise Unauthorized("Invalid username or password.") # type: ignore[no-untyped-call]
481
+
482
+ return await func(request, *args, **kwargs)
483
+
484
+ return wrapper
485
+
486
+ return decorator
@@ -5,8 +5,14 @@ from sanic import Blueprint, Websocket, response # type: ignore[attr-defined]
5
5
  from sanic.request import Request
6
6
  from sanic.response import HTTPResponse
7
7
 
8
- from rasa.core.channels.channel import InputChannel, OutputChannel, UserMessage
8
+ from rasa.core.channels.channel import (
9
+ InputChannel,
10
+ OutputChannel,
11
+ UserMessage,
12
+ requires_basic_auth,
13
+ )
9
14
  from rasa.core.channels.voice_ready.jambonz_protocol import (
15
+ CHANNEL_NAME,
10
16
  send_ws_hangup_message,
11
17
  send_ws_text_message,
12
18
  websocket_message_handler,
@@ -18,8 +24,6 @@ from rasa.utils.io import remove_emojis
18
24
 
19
25
  structlogger = structlog.get_logger()
20
26
 
21
- CHANNEL_NAME = "jambonz"
22
-
23
27
  DEFAULT_HANGUP_DELAY_SECONDS = 1
24
28
 
25
29
 
@@ -32,12 +36,27 @@ class JambonzVoiceReadyInput(InputChannel):
32
36
 
33
37
  @classmethod
34
38
  def from_credentials(cls, credentials: Optional[Dict[Text, Any]]) -> InputChannel:
35
- return cls()
39
+ if not credentials:
40
+ return cls()
41
+
42
+ username = credentials.get("username")
43
+ password = credentials.get("password")
44
+ if (username is None) != (password is None):
45
+ raise RasaException(
46
+ "In Jambonz channel, either both username and password "
47
+ "or neither should be provided. "
48
+ )
36
49
 
37
- def __init__(self) -> None:
50
+ return cls(username, password)
51
+
52
+ def __init__(
53
+ self, username: Optional[Text] = None, password: Optional[Text] = None
54
+ ) -> None:
38
55
  """Initializes the JambonzVoiceReadyInput channel."""
39
56
  mark_as_beta_feature("Jambonz Channel")
40
57
  validate_voice_license_scope()
58
+ self.username = username
59
+ self.password = password
41
60
 
42
61
  def blueprint(
43
62
  self, on_new_message: Callable[[UserMessage], Awaitable[Any]]
@@ -50,6 +69,7 @@ class JambonzVoiceReadyInput(InputChannel):
50
69
  return response.json({"status": "ok"})
51
70
 
52
71
  @jambonz_webhook.websocket("/websocket", subprotocols=["ws.jambonz.org"]) # type: ignore
72
+ @requires_basic_auth(self.username, self.password)
53
73
  async def websocket(request: Request, ws: Websocket) -> None:
54
74
  """Triggered on new websocket connection."""
55
75
  async for message in ws:
@@ -10,6 +10,7 @@ from rasa.core.channels.channel import UserMessage
10
10
  from rasa.core.channels.voice_ready.utils import CallParameters
11
11
 
12
12
  structlogger = structlog.get_logger()
13
+ CHANNEL_NAME = "jambonz"
13
14
 
14
15
 
15
16
  @dataclass
@@ -206,6 +207,7 @@ async def handle_new_session(
206
207
  output_channel=output_channel,
207
208
  sender_id=message.call_sid,
208
209
  metadata=asdict(message.call_params),
210
+ input_channel=CHANNEL_NAME,
209
211
  )
210
212
  await send_config_ack(message.message_id, ws)
211
213
  await on_new_message(user_msg)
@@ -238,6 +240,7 @@ async def handle_gather_completed(
238
240
  output_channel = JambonzWebsocketOutput(ws, transcript_result.call_sid)
239
241
  user_msg = UserMessage(
240
242
  text=most_likely_transcript.text,
243
+ input_channel=CHANNEL_NAME,
241
244
  output_channel=output_channel,
242
245
  sender_id=transcript_result.call_sid,
243
246
  metadata={},
@@ -288,6 +291,7 @@ async def handle_call_status(
288
291
  output_channel = JambonzWebsocketOutput(ws, call_status.call_sid)
289
292
  user_msg = UserMessage(
290
293
  text="/session_end",
294
+ input_channel=CHANNEL_NAME,
291
295
  output_channel=output_channel,
292
296
  sender_id=call_status.call_sid,
293
297
  metadata={},
@@ -31,12 +31,10 @@ class FAISS_Store(InformationRetrieval):
31
31
  index_path: str,
32
32
  docs_folder: Optional[str],
33
33
  create_index: Optional[bool] = False,
34
- use_llm: bool = False,
35
34
  ):
36
35
  """Initializes the FAISS Store."""
37
36
  self.chunk_size = 1000
38
37
  self.chunk_overlap = 20
39
- self.use_llm = use_llm
40
38
 
41
39
  path = Path(index_path) / "documents_faiss"
42
40
  if create_index:
@@ -73,57 +71,6 @@ class FAISS_Store(InformationRetrieval):
73
71
 
74
72
  return loader.load()
75
73
 
76
- def _format_faqs(self, docs: List["Document"]) -> List["Document"]:
77
- """Splits each loaded file into individual FAQs.
78
-
79
- Args:
80
- docs: Documents representing whole files containing FAQs.
81
-
82
- Returns:
83
- List of Document objects, each containing a separate FAQ.
84
-
85
- Examples:
86
- An example of a file containing FAQs:
87
-
88
- Q: Who is Finley?
89
- A: Finley is your smart assistant for the FinX App. You can add him to your
90
- favorite messenger and tell him what you need help with.
91
-
92
- Q: How does Finley work?
93
- A: Finley is powered by the latest chatbot technology leveraging a unique
94
- interplay of large language models and secure logic.
95
-
96
- More details in documentation: https://rasa.com/docs/reference/config/policies/extractive-search/
97
- """
98
- structured_faqs = []
99
- from langchain.schema import Document
100
-
101
- for doc in docs:
102
- faq_chunks = doc.page_content.strip().split("\n\n")
103
-
104
- for chunk in faq_chunks:
105
- lines = chunk.strip().split("\n")
106
- if len(lines) < 2:
107
- continue # Skip if something unexpected
108
-
109
- question_line = lines[0].strip()
110
- answer_line = lines[1].strip()
111
-
112
- question = question_line.replace("Q: ", "").strip()
113
- answer = answer_line.replace("A: ", "").strip()
114
-
115
- doc_obj = Document(
116
- page_content=question,
117
- metadata={
118
- "title": question.lower().replace(" ", "_")[:-1],
119
- "type": "faq",
120
- "answer": answer,
121
- },
122
- )
123
-
124
- structured_faqs.append(doc_obj)
125
- return structured_faqs
126
-
127
74
  def _create_document_index(
128
75
  self, docs_folder: Optional[str], embedding: "Embeddings"
129
76
  ) -> FAISS:
@@ -140,15 +87,12 @@ class FAISS_Store(InformationRetrieval):
140
87
  raise ValueError("parameter `docs_folder` needs to be specified")
141
88
 
142
89
  docs = self.load_documents(docs_folder)
143
- if self.use_llm:
144
- splitter = RecursiveCharacterTextSplitter(
145
- chunk_size=self.chunk_size,
146
- chunk_overlap=self.chunk_overlap,
147
- length_function=len,
148
- )
149
- doc_chunks = splitter.split_documents(docs)
150
- else:
151
- doc_chunks = self._format_faqs(docs)
90
+ splitter = RecursiveCharacterTextSplitter(
91
+ chunk_size=self.chunk_size,
92
+ chunk_overlap=self.chunk_overlap,
93
+ length_function=len,
94
+ )
95
+ doc_chunks = splitter.split_documents(docs)
152
96
 
153
97
  logger.info(
154
98
  "information_retrieval.faiss_store._create_document_index",
@@ -169,15 +113,10 @@ class FAISS_Store(InformationRetrieval):
169
113
  pass
170
114
 
171
115
  async def search(
172
- self,
173
- query: Text,
174
- tracker_state: Dict[str, Any],
175
- threshold: float = 0.0,
176
- k: int = 1,
116
+ self, query: Text, tracker_state: Dict[str, Any], threshold: float = 0.0
177
117
  ) -> SearchResultList:
178
118
  logger.debug("information_retrieval.faiss_store.search", query=query)
179
119
  try:
180
- # TODO: make use of k
181
120
  documents = await self.index.as_retriever().ainvoke(query)
182
121
  except Exception as exc:
183
122
  raise InformationRetrievalException from exc
@@ -36,19 +36,6 @@ class SearchResult:
36
36
  """Construct a SearchResult object from Langchain Document object."""
37
37
  return cls(text=document.page_content, metadata=document.metadata)
38
38
 
39
- @classmethod
40
- def from_dict(cls, data: dict[str, Any]) -> "SearchResult":
41
- """Construct a SearchResult object from a JSON object."""
42
- return cls(text=data["text"], metadata=data["metadata"], score=data["score"])
43
-
44
- def to_dict(self) -> dict[str, Any]:
45
- """Convert the SearchResult object to a dictionary."""
46
- return {
47
- "text": self.text,
48
- "metadata": self.metadata,
49
- "score": self.score,
50
- }
51
-
52
39
 
53
40
  @dataclass
54
41
  class SearchResultList:
@@ -57,7 +44,8 @@ class SearchResultList:
57
44
 
58
45
  @classmethod
59
46
  def from_document_list(cls, documents: List["Document"]) -> "SearchResultList":
60
- """Convert a list of Langchain Documents to a SearchResultList object.
47
+ """
48
+ Convert a list of Langchain Documents to a SearchResultList object.
61
49
 
62
50
  Args:
63
51
  documents: List of Langchain Documents.
@@ -70,31 +58,6 @@ class SearchResultList:
70
58
  metadata={"total_results": len(documents)},
71
59
  )
72
60
 
73
- @classmethod
74
- def from_dict(cls, data: dict[str, Any]) -> "SearchResultList":
75
- """Convert a JSON object to a SearchResultList object.
76
-
77
- Args:
78
- data: JSON object.
79
-
80
- Returns:
81
- SearchResultList object.
82
- """
83
- if not data:
84
- return cls(results=[], metadata={})
85
-
86
- return cls(
87
- results=[SearchResult.from_dict(result) for result in data["results"]],
88
- metadata=data["metadata"],
89
- )
90
-
91
- def to_dict(self) -> dict[str, Any]:
92
- """Convert the SearchResultList object to a dictionary."""
93
- return {
94
- "results": [result.to_dict() for result in self.results],
95
- "metadata": self.metadata,
96
- }
97
-
98
61
 
99
62
  class InformationRetrievalException(RasaException):
100
63
  """Base class for exceptions raised by InformationRetrieval operations."""
@@ -126,7 +89,6 @@ class InformationRetrieval:
126
89
  query: Text,
127
90
  tracker_state: dict[str, Any],
128
91
  threshold: float = 0.0,
129
- k: int = 1,
130
92
  ) -> SearchResultList:
131
93
  """Search for a document in the InformationRetrieval system."""
132
94
  raise NotImplementedError(
@@ -31,25 +31,20 @@ class Milvus_Store(InformationRetrieval):
31
31
  )
32
32
 
33
33
  async def search(
34
- self,
35
- query: Text,
36
- tracker_state: Dict[str, Any],
37
- threshold: float = 0.0,
38
- k: int = 1,
34
+ self, query: Text, tracker_state: Dict[str, Any], threshold: float = 0.0
39
35
  ) -> SearchResultList:
40
36
  """Search for documents in the Milvus store.
41
37
 
42
38
  Args:
43
39
  query: The query to search for.
44
40
  threshold: minimum similarity score to consider a document a match.
45
- k: number of results to return.
46
41
 
47
42
  Returns:
48
43
  A list of documents that match the query.
49
44
  """
50
45
  logger.debug("information_retrieval.milvus_store.search", query=query)
51
46
  try:
52
- hits = await self.client.asimilarity_search_with_score(query, k=k)
47
+ hits = await self.client.asimilarity_search_with_score(query, k=4)
53
48
  except Exception as exc:
54
49
  raise InformationRetrievalException from exc
55
50
 
@@ -66,18 +66,13 @@ class Qdrant_Store(InformationRetrieval):
66
66
  )
67
67
 
68
68
  async def search(
69
- self,
70
- query: Text,
71
- tracker_state: Dict[str, Any],
72
- threshold: float = 0.0,
73
- k: int = 1,
69
+ self, query: Text, tracker_state: Dict[str, Any], threshold: float = 0.0
74
70
  ) -> SearchResultList:
75
71
  """Search for a document in the Qdrant vector store.
76
72
 
77
73
  Args:
78
74
  query: The query to search for.
79
75
  threshold: minimum similarity score to consider a document a match.
80
- k: number of results to return.
81
76
 
82
77
  Returns:
83
78
  A list of documents that match the query.
@@ -85,7 +80,7 @@ class Qdrant_Store(InformationRetrieval):
85
80
  logger.debug("information_retrieval.qdrant_store.search", query=query)
86
81
  try:
87
82
  hits = await self.client.asimilarity_search(
88
- query, k=k, score_threshold=threshold
83
+ query, k=4, score_threshold=threshold
89
84
  )
90
85
  except ValidationError as e:
91
86
  raise PayloadNotFoundException(
@@ -27,6 +27,7 @@ from rasa.shared.nlu.constants import (
27
27
  PROMPTS,
28
28
  )
29
29
  from rasa.shared.providers.llm.llm_response import LLMResponse, measure_llm_latency
30
+ from rasa.shared.utils.constants import LOG_COMPONENT_SOURCE_METHOD_INIT
30
31
  from rasa.shared.utils.health_check.llm_health_check_mixin import LLMHealthCheckMixin
31
32
  from rasa.shared.utils.llm import (
32
33
  DEFAULT_OPENAI_GENERATE_MODEL_NAME,
@@ -105,6 +106,8 @@ class ContextualResponseRephraser(
105
106
  self.prompt_template = get_prompt_template(
106
107
  self.nlg_endpoint.kwargs.get(PROMPT_CONFIG_KEY),
107
108
  DEFAULT_RESPONSE_VARIATION_PROMPT_TEMPLATE,
109
+ log_source_component=ContextualResponseRephraser.__name__,
110
+ log_source_method=LOG_COMPONENT_SOURCE_METHOD_INIT,
108
111
  )
109
112
  self.rephrase_all = self.nlg_endpoint.kwargs.get(
110
113
  "rephrase_all", DEFAULT_REPHRASE_ALL