rasa-pro 3.13.1a13__py3-none-any.whl → 3.13.1a15__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/builder/config.py +11 -1
- rasa/builder/llm_service.py +9 -6
- rasa/builder/main.py +95 -9
- rasa/builder/models.py +2 -2
- rasa/builder/project_generator.py +5 -5
- rasa/builder/scrape_rasa_docs.py +4 -4
- rasa/builder/service.py +621 -435
- rasa/builder/training_service.py +10 -3
- rasa/cli/inspect.py +7 -0
- rasa/cli/project_templates/telco/actions/__init__.py +0 -0
- rasa/cli/project_templates/telco/actions/actions_billing.py +6 -5
- rasa/cli/project_templates/telco/actions/actions_get_data_from_db.py +3 -2
- rasa/cli/shell.py +6 -1
- rasa/cli/train.py +4 -0
- rasa/core/tracker_stores/dynamo_tracker_store.py +30 -2
- rasa/model_manager/model_api.py +1 -2
- rasa/shared/core/trackers.py +17 -0
- rasa/shared/importers/utils.py +77 -1
- rasa/studio/upload.py +11 -45
- rasa/utils/json_utils.py +6 -1
- rasa/utils/openapi.py +144 -0
- rasa/utils/plotting.py +1 -1
- rasa/version.py +1 -1
- {rasa_pro-3.13.1a13.dist-info → rasa_pro-3.13.1a15.dist-info}/METADATA +10 -9
- {rasa_pro-3.13.1a13.dist-info → rasa_pro-3.13.1a15.dist-info}/RECORD +28 -26
- {rasa_pro-3.13.1a13.dist-info → rasa_pro-3.13.1a15.dist-info}/NOTICE +0 -0
- {rasa_pro-3.13.1a13.dist-info → rasa_pro-3.13.1a15.dist-info}/WHEEL +0 -0
- {rasa_pro-3.13.1a13.dist-info → rasa_pro-3.13.1a15.dist-info}/entry_points.txt +0 -0
rasa/builder/config.py
CHANGED
|
@@ -43,13 +43,18 @@ def get_default_config(assistant_id: str) -> Dict[str, Any]:
|
|
|
43
43
|
)
|
|
44
44
|
)
|
|
45
45
|
)
|
|
46
|
+
|
|
47
|
+
if not isinstance(base_config, dict):
|
|
48
|
+
raise ValueError("Base config is not a dictionary")
|
|
49
|
+
|
|
46
50
|
base_config["assistant_id"] = assistant_id
|
|
51
|
+
|
|
47
52
|
return base_config
|
|
48
53
|
|
|
49
54
|
|
|
50
55
|
def get_default_endpoints() -> Dict[str, Any]:
|
|
51
56
|
"""Get default endpoints configuration."""
|
|
52
|
-
|
|
57
|
+
endpoints_config = read_yaml_file(
|
|
53
58
|
str(
|
|
54
59
|
importlib_resources.files(PACKAGE_NAME).joinpath(
|
|
55
60
|
"cli/project_templates/default/endpoints.yml"
|
|
@@ -57,6 +62,11 @@ def get_default_endpoints() -> Dict[str, Any]:
|
|
|
57
62
|
)
|
|
58
63
|
)
|
|
59
64
|
|
|
65
|
+
if not isinstance(endpoints_config, dict):
|
|
66
|
+
raise ValueError("Endpoints config is not a dictionary")
|
|
67
|
+
|
|
68
|
+
return endpoints_config
|
|
69
|
+
|
|
60
70
|
|
|
61
71
|
def get_default_credentials() -> Dict[str, Any]:
|
|
62
72
|
"""Get default credentials configuration."""
|
rasa/builder/llm_service.py
CHANGED
|
@@ -5,7 +5,7 @@ import importlib
|
|
|
5
5
|
import json
|
|
6
6
|
from contextlib import asynccontextmanager
|
|
7
7
|
from copy import deepcopy
|
|
8
|
-
from typing import Any, Dict, List, Optional
|
|
8
|
+
from typing import Any, AsyncGenerator, Dict, List, Optional
|
|
9
9
|
|
|
10
10
|
import importlib_resources
|
|
11
11
|
import openai
|
|
@@ -28,14 +28,14 @@ structlogger = structlog.get_logger()
|
|
|
28
28
|
class LLMService:
|
|
29
29
|
"""Handles OpenAI LLM interactions with caching for efficiency."""
|
|
30
30
|
|
|
31
|
-
def __init__(self):
|
|
31
|
+
def __init__(self) -> None:
|
|
32
32
|
self._client: Optional[openai.AsyncOpenAI] = None
|
|
33
33
|
self._domain_schema: Optional[Dict[str, Any]] = None
|
|
34
34
|
self._flows_schema: Optional[Dict[str, Any]] = None
|
|
35
35
|
self._helper_schema: Optional[Dict[str, Any]] = None
|
|
36
36
|
|
|
37
37
|
@asynccontextmanager
|
|
38
|
-
async def _get_client(self):
|
|
38
|
+
async def _get_client(self) -> AsyncGenerator[openai.AsyncOpenAI, None]:
|
|
39
39
|
"""Get or create OpenAI client with proper resource management."""
|
|
40
40
|
if self._client is None:
|
|
41
41
|
self._client = openai.AsyncOpenAI(timeout=config.OPENAI_TIMEOUT)
|
|
@@ -46,7 +46,7 @@ class LLMService:
|
|
|
46
46
|
structlogger.error("llm.client_error", error=str(e))
|
|
47
47
|
raise
|
|
48
48
|
|
|
49
|
-
def _prepare_schemas(self):
|
|
49
|
+
def _prepare_schemas(self) -> None:
|
|
50
50
|
"""Prepare and cache schemas for LLM generation."""
|
|
51
51
|
if self._domain_schema is None:
|
|
52
52
|
self._domain_schema = _prepare_domain_schema()
|
|
@@ -192,7 +192,7 @@ class LLMService:
|
|
|
192
192
|
return []
|
|
193
193
|
|
|
194
194
|
@staticmethod
|
|
195
|
-
def _format_chat_dump(messages) -> str:
|
|
195
|
+
def _format_chat_dump(messages: List[Dict[str, Any]]) -> str:
|
|
196
196
|
"""Format chat messages for documentation search."""
|
|
197
197
|
result = ""
|
|
198
198
|
for message in messages:
|
|
@@ -213,7 +213,7 @@ class LLMService:
|
|
|
213
213
|
return result
|
|
214
214
|
|
|
215
215
|
@staticmethod
|
|
216
|
-
def _format_documentation_results(results) -> str:
|
|
216
|
+
def _format_documentation_results(results: List[Dict[str, Any]]) -> str:
|
|
217
217
|
"""Format documentation search results."""
|
|
218
218
|
if not results:
|
|
219
219
|
return "<sources>No relevant documentation found.</sources>"
|
|
@@ -233,6 +233,9 @@ def _prepare_domain_schema() -> Dict[str, Any]:
|
|
|
233
233
|
"""Prepare domain schema by removing unnecessary parts."""
|
|
234
234
|
domain_schema = deepcopy(read_schema_file(DOMAIN_SCHEMA_FILE, PACKAGE_NAME, False))
|
|
235
235
|
|
|
236
|
+
if not isinstance(domain_schema, dict):
|
|
237
|
+
raise ValueError("Domain schema is not a dictionary")
|
|
238
|
+
|
|
236
239
|
# Remove parts not needed for CALM bots
|
|
237
240
|
unnecessary_keys = ["intents", "entities", "forms", "config", "session_config"]
|
|
238
241
|
|
rasa/builder/main.py
CHANGED
|
@@ -5,13 +5,23 @@ import logging
|
|
|
5
5
|
import sys
|
|
6
6
|
from typing import Optional
|
|
7
7
|
|
|
8
|
+
import structlog
|
|
9
|
+
from sanic import HTTPResponse, Sanic
|
|
10
|
+
from sanic.request import Request
|
|
11
|
+
from sanic_openapi import openapi3_blueprint
|
|
12
|
+
|
|
8
13
|
import rasa.core.utils
|
|
14
|
+
from rasa.builder import config
|
|
9
15
|
from rasa.builder.logging_utils import collecting_logs_processor
|
|
10
|
-
from rasa.builder.service import
|
|
16
|
+
from rasa.builder.service import bp, setup_project_generator
|
|
17
|
+
from rasa.core.channels.studio_chat import StudioChatInput
|
|
18
|
+
from rasa.server import configure_cors
|
|
11
19
|
from rasa.utils.common import configure_logging_and_warnings
|
|
12
20
|
from rasa.utils.log_utils import configure_structlog
|
|
13
21
|
from rasa.utils.sanic_error_handler import register_custom_sanic_error_handler
|
|
14
22
|
|
|
23
|
+
structlogger = structlog.get_logger()
|
|
24
|
+
|
|
15
25
|
|
|
16
26
|
def setup_logging() -> None:
|
|
17
27
|
"""Setup logging configuration."""
|
|
@@ -31,22 +41,98 @@ def setup_logging() -> None:
|
|
|
31
41
|
)
|
|
32
42
|
|
|
33
43
|
|
|
44
|
+
def setup_input_channel() -> StudioChatInput:
|
|
45
|
+
"""Setup the input channel for chat interactions."""
|
|
46
|
+
studio_chat_credentials = config.get_default_credentials().get(
|
|
47
|
+
StudioChatInput.name()
|
|
48
|
+
)
|
|
49
|
+
return StudioChatInput.from_credentials(credentials=studio_chat_credentials)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def setup_middleware(app: Sanic) -> None:
|
|
53
|
+
"""Setup middleware for request/response processing."""
|
|
54
|
+
|
|
55
|
+
@app.middleware("request")
|
|
56
|
+
async def log_request(request: Request) -> None:
|
|
57
|
+
structlogger.info(
|
|
58
|
+
"request.received",
|
|
59
|
+
method=request.method,
|
|
60
|
+
path=request.path,
|
|
61
|
+
remote_addr=request.remote_addr or "unknown",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@app.middleware("response")
|
|
65
|
+
async def log_response(request: Request, response: HTTPResponse) -> None:
|
|
66
|
+
structlogger.info(
|
|
67
|
+
"request.completed",
|
|
68
|
+
method=request.method,
|
|
69
|
+
path=request.path,
|
|
70
|
+
status=response.status,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def create_app(project_folder: Optional[str] = None) -> Sanic:
|
|
75
|
+
"""Create and configure the Sanic app."""
|
|
76
|
+
app = Sanic("BotBuilderService")
|
|
77
|
+
|
|
78
|
+
# Basic app configuration
|
|
79
|
+
app.config.REQUEST_TIMEOUT = 60 # 1 minute timeout
|
|
80
|
+
app.ctx.agent = None
|
|
81
|
+
|
|
82
|
+
# Set up project generator and store in app context
|
|
83
|
+
app.ctx.project_generator = setup_project_generator(project_folder)
|
|
84
|
+
|
|
85
|
+
# Set up input channel and store in app context
|
|
86
|
+
app.ctx.input_channel = setup_input_channel()
|
|
87
|
+
|
|
88
|
+
# Register the blueprint
|
|
89
|
+
app.blueprint(bp)
|
|
90
|
+
|
|
91
|
+
# OpenAPI docs
|
|
92
|
+
app.blueprint(openapi3_blueprint)
|
|
93
|
+
app.config.API_TITLE = "Bot Builder API"
|
|
94
|
+
app.config.API_VERSION = rasa.__version__
|
|
95
|
+
app.config.API_DESCRIPTION = (
|
|
96
|
+
"API for building conversational AI bots from prompts and templates. "
|
|
97
|
+
"The API allows to change the assistant and retrain it with new data."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Setup middleware
|
|
101
|
+
setup_middleware(app)
|
|
102
|
+
|
|
103
|
+
configure_cors(app, cors_origins=config.CORS_ORIGINS)
|
|
104
|
+
|
|
105
|
+
# Register input channel webhooks
|
|
106
|
+
from rasa.core import channels
|
|
107
|
+
|
|
108
|
+
channels.channel.register([app.ctx.input_channel], app, route="/webhooks/")
|
|
109
|
+
|
|
110
|
+
return app
|
|
111
|
+
|
|
112
|
+
|
|
34
113
|
def main(project_folder: Optional[str] = None) -> None:
|
|
35
114
|
"""Main entry point."""
|
|
36
115
|
try:
|
|
37
116
|
# Setup logging
|
|
38
117
|
setup_logging()
|
|
39
118
|
|
|
40
|
-
# Create and configure
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
register_custom_sanic_error_handler(service.app)
|
|
44
|
-
|
|
45
|
-
# Log available routes
|
|
46
|
-
rasa.core.utils.list_routes(service.app)
|
|
119
|
+
# Create and configure app
|
|
120
|
+
app = create_app(project_folder)
|
|
121
|
+
register_custom_sanic_error_handler(app)
|
|
47
122
|
|
|
48
123
|
# Run the service
|
|
49
|
-
|
|
124
|
+
structlogger.info(
|
|
125
|
+
"service.starting",
|
|
126
|
+
host=config.BUILDER_SERVER_HOST,
|
|
127
|
+
port=config.BUILDER_SERVER_PORT,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
app.run(
|
|
131
|
+
host=config.BUILDER_SERVER_HOST,
|
|
132
|
+
port=config.BUILDER_SERVER_PORT,
|
|
133
|
+
legacy=True,
|
|
134
|
+
motd=False,
|
|
135
|
+
)
|
|
50
136
|
|
|
51
137
|
except KeyboardInterrupt:
|
|
52
138
|
print("\nService stopped by user")
|
rasa/builder/models.py
CHANGED
|
@@ -19,7 +19,7 @@ class PromptRequest(BaseModel):
|
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
@validator("prompt")
|
|
22
|
-
def validate_prompt(cls, v):
|
|
22
|
+
def validate_prompt(cls, v: str) -> str:
|
|
23
23
|
if not v.strip():
|
|
24
24
|
raise ValueError("Prompt cannot be empty or whitespace only")
|
|
25
25
|
return v.strip()
|
|
@@ -39,7 +39,7 @@ class TemplateRequest(BaseModel):
|
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
@validator("template_name")
|
|
42
|
-
def validate_template_name(cls, v):
|
|
42
|
+
def validate_template_name(cls, v: Any) -> Any:
|
|
43
43
|
if v not in ProjectTemplateName:
|
|
44
44
|
raise ValueError(
|
|
45
45
|
f"Template name must be one of {ProjectTemplateName.supported_values()}"
|
|
@@ -25,7 +25,7 @@ structlogger = structlog.get_logger()
|
|
|
25
25
|
class ProjectGenerator:
|
|
26
26
|
"""Service for generating Rasa projects from skill descriptions."""
|
|
27
27
|
|
|
28
|
-
def __init__(self, project_folder: str):
|
|
28
|
+
def __init__(self, project_folder: str) -> None:
|
|
29
29
|
"""Initialize the project generator with a folder for file persistence.
|
|
30
30
|
|
|
31
31
|
Args:
|
|
@@ -34,7 +34,7 @@ class ProjectGenerator:
|
|
|
34
34
|
self.project_folder = Path(project_folder)
|
|
35
35
|
self.project_folder.mkdir(parents=True, exist_ok=True)
|
|
36
36
|
|
|
37
|
-
def init_from_template(self, template: ProjectTemplateName):
|
|
37
|
+
def init_from_template(self, template: ProjectTemplateName) -> None:
|
|
38
38
|
"""Create the initial project files."""
|
|
39
39
|
self.cleanup()
|
|
40
40
|
create_initial_project(self.project_folder.as_posix(), template)
|
|
@@ -150,7 +150,7 @@ class ProjectGenerator:
|
|
|
150
150
|
|
|
151
151
|
return await _generate_with_retry(initial_messages, max_retries)
|
|
152
152
|
|
|
153
|
-
async def _validate_generated_project(self):
|
|
153
|
+
async def _validate_generated_project(self) -> None:
|
|
154
154
|
"""Validate the generated project using the validation service."""
|
|
155
155
|
importer = self._create_importer()
|
|
156
156
|
validation_error = await validate_project(importer)
|
|
@@ -180,7 +180,7 @@ class ProjectGenerator:
|
|
|
180
180
|
|
|
181
181
|
def get_bot_files(self) -> Dict[str, Optional[str]]:
|
|
182
182
|
"""Get the current bot files by reading from disk."""
|
|
183
|
-
bot_files = {}
|
|
183
|
+
bot_files: Dict[str, Optional[str]] = {}
|
|
184
184
|
|
|
185
185
|
for file in self.project_folder.glob("**/*"):
|
|
186
186
|
# Skip directories
|
|
@@ -255,7 +255,7 @@ class ProjectGenerator:
|
|
|
255
255
|
shutil.rmtree(flows_folder)
|
|
256
256
|
flows_folder.mkdir(parents=True, exist_ok=True)
|
|
257
257
|
|
|
258
|
-
def update_bot_files(self, files: Dict[str, str]) -> None:
|
|
258
|
+
def update_bot_files(self, files: Dict[str, Optional[str]]) -> None:
|
|
259
259
|
"""Update bot files with new content by writing to disk."""
|
|
260
260
|
for filename, content in files.items():
|
|
261
261
|
file_path = Path(subpath(self.project_folder, filename))
|
rasa/builder/scrape_rasa_docs.py
CHANGED
|
@@ -17,18 +17,18 @@ to_visit = [DOCS_ROOT]
|
|
|
17
17
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def is_valid_doc_url(url):
|
|
20
|
+
def is_valid_doc_url(url: str) -> bool:
|
|
21
21
|
return url.startswith(DOCS_ROOT) and not any(
|
|
22
22
|
[url.endswith(".pdf"), "#" in url, "mailto:" in url]
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
def slugify_url(url):
|
|
26
|
+
def slugify_url(url: str) -> str:
|
|
27
27
|
path = urlparse(url).path.strip("/").replace("/", "_")
|
|
28
28
|
return path if path else "index"
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
def clean_text(html):
|
|
31
|
+
def clean_text(html: str) -> str:
|
|
32
32
|
soup = BeautifulSoup(html, "html.parser")
|
|
33
33
|
|
|
34
34
|
# Remove navs, footers, and code tabs (customize if needed)
|
|
@@ -47,7 +47,7 @@ def clean_text(html):
|
|
|
47
47
|
return text
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
def save_as_markdown(text) -> str:
|
|
50
|
+
def save_as_markdown(text: str, url: str) -> str:
|
|
51
51
|
slug = slugify_url(url)
|
|
52
52
|
file_name = f"{slug}.md"
|
|
53
53
|
md_path = Path(OUTPUT_DIR) / file_name
|