rasa-pro 3.14.0a1__py3-none-any.whl → 3.14.0a2__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 (65) hide show
  1. rasa/builder/copilot/copilot.py +1 -23
  2. rasa/builder/download.py +1 -6
  3. rasa/builder/main.py +43 -0
  4. rasa/builder/project_generator.py +17 -128
  5. rasa/builder/template_cache.py +244 -0
  6. rasa/cli/project_templates/basic/config.yml +3 -4
  7. rasa/cli/project_templates/basic/endpoints.yml +2 -12
  8. rasa/cli/project_templates/default/config.yml +3 -3
  9. rasa/cli/project_templates/default/endpoints.yml +1 -12
  10. rasa/cli/project_templates/finance/config.yml +3 -3
  11. rasa/cli/project_templates/finance/endpoints.yml +2 -12
  12. rasa/cli/project_templates/telco/config.yml +3 -3
  13. rasa/cli/project_templates/telco/endpoints.yml +2 -12
  14. rasa/core/channels/inspector/dist/assets/{arc-18042c22.js → arc-c24d8d79.js} +1 -1
  15. rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-fdd6bcfa.js → blockDiagram-38ab4fdb-1b6b9f26.js} +1 -1
  16. rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-f5ae6786.js → c4Diagram-3d4e48cf-da91d0f9.js} +1 -1
  17. rasa/core/channels/inspector/dist/assets/channel-d2444dfd.js +1 -0
  18. rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-81efba3e.js → classDiagram-70f12bd4-6067f302.js} +1 -1
  19. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-3b6b6a92.js → classDiagram-v2-f2320105-705d024a.js} +1 -1
  20. rasa/core/channels/inspector/dist/assets/clone-281a0990.js +1 -0
  21. rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-31422447.js → createText-2e5e7dd3-3751dffe.js} +1 -1
  22. rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-518a90db.js → edges-e0da2a9e-7b25b4af.js} +1 -1
  23. rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-a6d3c25a.js → erDiagram-9861fffd-eb7deea8.js} +1 -1
  24. rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-e048c2be.js → flowDb-956e92f1-67235ff6.js} +1 -1
  25. rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-c7474c91.js → flowDiagram-66a62f08-34c3a16a.js} +1 -1
  26. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-aa4cca3b.js +1 -0
  27. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-cb4d8723.js → flowchart-elk-definition-4a651766-f1a93631.js} +1 -1
  28. rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-346636a2.js → ganttDiagram-c361ad54-a68cbad1.js} +1 -1
  29. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-7c508874.js → gitGraphDiagram-72cf32ee-0b1e4a1d.js} +1 -1
  30. rasa/core/channels/inspector/dist/assets/{graph-14702d8a.js → graph-f3c1d212.js} +1 -1
  31. rasa/core/channels/inspector/dist/assets/{index-4d4bdf3a.js → index-051c5a6e.js} +131 -131
  32. rasa/core/channels/inspector/dist/assets/{index-3862675e-f18b534b.js → index-3862675e-34cbca30.js} +1 -1
  33. rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-64154b83.js → infoDiagram-f8f76790-e69960a1.js} +1 -1
  34. rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-833a5f95.js → journeyDiagram-49397b02-8dd3296a.js} +1 -1
  35. rasa/core/channels/inspector/dist/assets/{layout-5a3b2123.js → layout-e93126bc.js} +1 -1
  36. rasa/core/channels/inspector/dist/assets/{line-2272a8c7.js → line-15eb1e26.js} +1 -1
  37. rasa/core/channels/inspector/dist/assets/{linear-35bcf273.js → linear-fec95d33.js} +1 -1
  38. rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-92dcb0e9.js → mindmap-definition-fc14e90a-2557813e.js} +1 -1
  39. rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-94dbc900.js → pieDiagram-8a3498a8-40d756b1.js} +1 -1
  40. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-8b7a9c33.js → quadrantDiagram-120e2f19-a48cbdcd.js} +1 -1
  41. rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-6f7eab81.js → requirementDiagram-deff3bca-dc778150.js} +1 -1
  42. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-f43e581d.js → sankeyDiagram-04a897e0-10026b94.js} +1 -1
  43. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-0bcbefc3.js → sequenceDiagram-704730f1-3b2ed10a.js} +1 -1
  44. rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-b8a74083.js → stateDiagram-587899a1-c5f3b3fb.js} +1 -1
  45. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-2070218f.js → stateDiagram-v2-d93cdb3a-e503656b.js} +1 -1
  46. rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-f1d54e34.js → styles-6aaf32cf-a683ce56.js} +1 -1
  47. rasa/core/channels/inspector/dist/assets/{styles-9a916d00-980de489.js → styles-9a916d00-02bcdcee.js} +1 -1
  48. rasa/core/channels/inspector/dist/assets/{styles-c10674c1-3c03abde.js → styles-c10674c1-8e90dbb9.js} +1 -1
  49. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-46ba068f.js → svgDrawCommon-08f97a94-7c23fc1e.js} +1 -1
  50. rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-901f5e3d.js → timeline-definition-85554ec2-c42faec8.js} +1 -1
  51. rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-acbc628a.js → xychartDiagram-e933f94c-5e3bb0ea.js} +1 -1
  52. rasa/core/channels/inspector/dist/index.html +1 -1
  53. rasa/core/channels/inspector/src/App.tsx +5 -24
  54. rasa/core/channels/inspector/src/components/Chat.tsx +2 -3
  55. rasa/engine/graph.py +5 -1
  56. rasa/engine/storage/local_model_storage.py +41 -4
  57. rasa/version.py +1 -1
  58. {rasa_pro-3.14.0a1.dist-info → rasa_pro-3.14.0a2.dist-info}/METADATA +4 -4
  59. {rasa_pro-3.14.0a1.dist-info → rasa_pro-3.14.0a2.dist-info}/RECORD +62 -61
  60. rasa/core/channels/inspector/dist/assets/channel-b9b536fc.js +0 -1
  61. rasa/core/channels/inspector/dist/assets/clone-78d2ddcf.js +0 -1
  62. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-8b09c060.js +0 -1
  63. {rasa_pro-3.14.0a1.dist-info → rasa_pro-3.14.0a2.dist-info}/NOTICE +0 -0
  64. {rasa_pro-3.14.0a1.dist-info → rasa_pro-3.14.0a2.dist-info}/WHEEL +0 -0
  65. {rasa_pro-3.14.0a1.dist-info → rasa_pro-3.14.0a2.dist-info}/entry_points.txt +0 -0
@@ -55,29 +55,7 @@ class Copilot:
55
55
  @asynccontextmanager
56
56
  async def _get_client(self) -> AsyncGenerator[openai.AsyncOpenAI, None]:
57
57
  """Create a fresh OpenAI client, yield it, and always close it."""
58
- kwargs: Dict[str, Any] = {"timeout": config.OPENAI_TIMEOUT}
59
- if config.HELLO_LLM_PROXY_BASE_URL:
60
- structlogger.debug(
61
- "copilot.using_llm_proxy", base_url=config.HELLO_LLM_PROXY_BASE_URL
62
- )
63
- if not config.RASA_PRO_LICENSE:
64
- structlogger.error(
65
- "copilot.proxy_missing_license",
66
- event_info=(
67
- "HELLO_LLM_PROXY_BASE_URL is set "
68
- "but RASA_PRO_LICENSE is missing."
69
- ),
70
- )
71
- raise CopilotStreamError(
72
- "HELLO_LLM_PROXY_BASE_URL is set but RASA_PRO_LICENSE is missing. "
73
- "Provide a valid license token for proxy authentication."
74
- )
75
-
76
- kwargs["base_url"] = config.HELLO_LLM_PROXY_BASE_URL
77
- kwargs["api_key"] = config.RASA_PRO_LICENSE
78
-
79
- client = openai.AsyncOpenAI(**kwargs)
80
-
58
+ client = openai.AsyncOpenAI(timeout=config.OPENAI_TIMEOUT)
81
59
  try:
82
60
  yield client
83
61
  except Exception as e:
rasa/builder/download.py CHANGED
@@ -20,11 +20,6 @@ def _get_python_version_content() -> str:
20
20
  return f"{sys.version_info.major}.{sys.version_info.minor}\n"
21
21
 
22
22
 
23
- def _get_rasa_pro_minor_version() -> str:
24
- """Get the minor version of Rasa Pro."""
25
- return ".".join(rasa.__version__.split(".")[:2])
26
-
27
-
28
23
  def _get_pyproject_toml_content(project_id: str) -> str:
29
24
  """Generate pyproject.toml file content."""
30
25
  return dedent(
@@ -34,7 +29,7 @@ def _get_pyproject_toml_content(project_id: str) -> str:
34
29
  version = "0.1.0"
35
30
  description = "Add your description for your Rasa bot here"
36
31
  readme = "README.md"
37
- dependencies = ["rasa-pro>={_get_rasa_pro_minor_version()}"]
32
+ dependencies = ["rasa-pro>={rasa.__version__}"]
38
33
  requires-python = ">={sys.version_info.major}.{sys.version_info.minor}"
39
34
  """
40
35
  )
rasa/builder/main.py CHANGED
@@ -22,6 +22,9 @@ from rasa.builder.logging_utils import (
22
22
  log_request_start,
23
23
  )
24
24
  from rasa.builder.service import bp, setup_project_generator
25
+ from rasa.builder.template_cache import (
26
+ background_download_template_caches,
27
+ )
25
28
  from rasa.builder.training_service import try_load_existing_agent
26
29
  from rasa.core.channels.studio_chat import StudioChatInput
27
30
  from rasa.server import configure_cors
@@ -146,9 +149,46 @@ def create_app(project_folder: str) -> Sanic:
146
149
  except Exception as e:
147
150
  structlogger.warning("Failed to load agent on server startup", error=str(e))
148
151
 
152
+ if config.HELLO_RASA_PROJECT_ID and app.ctx.project_generator.is_empty():
153
+ app.register_listener(background_download_template_caches, "after_server_start")
154
+ else:
155
+ structlogger.debug(
156
+ "builder.main.background_cache_download.disabled",
157
+ event_info=(
158
+ "No hello rasa project id set; skipping background cache download"
159
+ ),
160
+ )
161
+
149
162
  return app
150
163
 
151
164
 
165
+ def _apply_llm_overrides_from_builder_env() -> None:
166
+ # Prefer a dedicated builder key, fall back to license if you proxy with it
167
+ if not config.HELLO_LLM_PROXY_BASE_URL:
168
+ return
169
+
170
+ structlogger.debug(
171
+ "builder.main.using_llm_proxy", base_url=config.HELLO_LLM_PROXY_BASE_URL
172
+ )
173
+
174
+ if not config.RASA_PRO_LICENSE:
175
+ structlogger.error(
176
+ "copilot.proxy_missing_license",
177
+ event_info=(
178
+ "HELLO_LLM_PROXY_BASE_URL is set but RASA_PRO_LICENSE is missing."
179
+ ),
180
+ )
181
+ return
182
+
183
+ if not os.getenv("OPENAI_API_BASE") and not os.getenv("OPENAI_API_KEY"):
184
+ base_url = config.HELLO_LLM_PROXY_BASE_URL.rstrip("/")
185
+ # needed for litellm client
186
+ os.environ["OPENAI_API_BASE"] = base_url
187
+ # needed for openai async client
188
+ os.environ["OPENAI_BASE_URL"] = base_url
189
+ os.environ["OPENAI_API_KEY"] = config.RASA_PRO_LICENSE
190
+
191
+
152
192
  def main(project_folder: Optional[str] = None) -> None:
153
193
  """Main entry point."""
154
194
  try:
@@ -159,6 +199,9 @@ def main(project_folder: Optional[str] = None) -> None:
159
199
  rasa.telemetry.initialize_telemetry()
160
200
  rasa.telemetry.initialize_error_reporting(private_mode=False)
161
201
 
202
+ # TODO: don't do this when running locally
203
+ _apply_llm_overrides_from_builder_env()
204
+
162
205
  # working directory needs to be the project folder, e.g.
163
206
  # for relative paths (./docs) in a projects config to work
164
207
  if not project_folder:
@@ -3,23 +3,18 @@
3
3
  import json
4
4
  import os
5
5
  import shutil
6
- import tarfile
7
- import tempfile
8
6
  from pathlib import Path
9
7
  from textwrap import dedent
10
- from typing import Any, Dict, Generator, List, Optional
8
+ from typing import Any, Dict, List, Optional
11
9
 
12
- import aiofiles
13
- import aiohttp
14
10
  import structlog
15
11
 
16
- import rasa.version
17
12
  from rasa.builder import config
18
13
  from rasa.builder.exceptions import ProjectGenerationError, ValidationError
19
14
  from rasa.builder.llm_service import get_skill_generation_messages, llm_service
20
- from rasa.builder.logging_utils import capture_exception_with_context
21
15
  from rasa.builder.models import BotFiles
22
16
  from rasa.builder.project_info import ProjectInfo, ensure_first_used, load_project_info
17
+ from rasa.builder.template_cache import copy_cache_for_template_if_available
23
18
  from rasa.builder.training_service import TrainingInput
24
19
  from rasa.builder.validation_service import validate_project
25
20
  from rasa.cli.scaffold import ProjectTemplateName, create_initial_project
@@ -48,12 +43,25 @@ class ProjectGenerator:
48
43
  """Get the project info."""
49
44
  return load_project_info(self.project_folder)
50
45
 
46
+ def is_empty(self) -> bool:
47
+ """Check if the project folder is empty.
48
+
49
+ Excluding hidden paths.
50
+ """
51
+ return not any(
52
+ file.is_file()
53
+ for file in self.project_folder.iterdir()
54
+ if not file.name.startswith(".")
55
+ )
56
+
51
57
  async def init_from_template(self, template: ProjectTemplateName) -> None:
52
58
  """Create the initial project files."""
53
59
  self.cleanup()
54
60
  create_initial_project(self.project_folder.as_posix(), template)
55
- await download_cache_for_template(template, self.project_folder.as_posix())
56
- # needs to happen after caching, as we download .rasa and that would
61
+ # If a local cache for this template exists, copy it into the project.
62
+ # We no longer download here to avoid blocking project creation.
63
+ copy_cache_for_template_if_available(template, self.project_folder)
64
+ # needs to happen after caching, as we download/copy .rasa and that would
57
65
  # overwrite the project info file in .rasa
58
66
  ensure_first_used(self.project_folder)
59
67
 
@@ -341,122 +349,3 @@ class ProjectGenerator:
341
349
  error=str(e),
342
350
  file_path=file_path,
343
351
  )
344
-
345
-
346
- CACHE_BUCKET_URL = "https://trained-templates.s3.us-east-1.amazonaws.com"
347
-
348
-
349
- def _safe_tar_members(
350
- tar: tarfile.TarFile, destination_directory: Path
351
- ) -> Generator[tarfile.TarInfo, None, None]:
352
- """Yield safe members for extraction to prevent path traversal and links.
353
-
354
- Args:
355
- tar: Open tar file handle
356
- destination_directory: Directory to which files will be extracted
357
-
358
- Yields:
359
- Members that are safe to extract within destination_directory
360
- """
361
- base_path = destination_directory.resolve()
362
-
363
- for member in tar.getmembers():
364
- name = member.name
365
- # Skip empty names and absolute paths
366
- if not name or name.startswith("/") or name.startswith("\\"):
367
- continue
368
-
369
- # Disallow symlinks and hardlinks
370
- if member.issym() or member.islnk():
371
- continue
372
-
373
- # Compute the final path and ensure it's within base_path
374
- target_path = (base_path / name).resolve()
375
- try:
376
- target_path.relative_to(base_path)
377
- except ValueError:
378
- # Member would escape the destination directory
379
- continue
380
-
381
- yield member
382
-
383
-
384
- async def download_cache_for_template(
385
- template: ProjectTemplateName, project_folder: str
386
- ) -> None:
387
- # get a temp path for the cache file download
388
- temporary_cache_file = tempfile.NamedTemporaryFile(suffix=".tar.gz", delete=False)
389
-
390
- try:
391
- url = f"{CACHE_BUCKET_URL}/{rasa.version.__version__}-{template.value}.tar.gz"
392
- async with aiohttp.ClientSession() as session:
393
- async with session.get(url) as response:
394
- response.raise_for_status()
395
- async with aiofiles.open(temporary_cache_file.name, "wb") as f:
396
- async for chunk in response.content.iter_chunked(1024 * 1024):
397
- await f.write(chunk)
398
-
399
- # extract the cache to the project folder using safe member filtering
400
- with tarfile.open(temporary_cache_file.name, "r:gz") as tar:
401
- destination = Path(project_folder)
402
- destination.mkdir(parents=True, exist_ok=True)
403
- tar.extractall(
404
- path=destination,
405
- members=_safe_tar_members(tar, destination),
406
- )
407
-
408
- structlogger.info(
409
- "project_generator.download_cache_for_template.success",
410
- template=template,
411
- event_info=(
412
- f"Downloaded cache for template, extracted to {project_folder}."
413
- ),
414
- )
415
- except aiohttp.ClientResponseError as e:
416
- if e.status == 403:
417
- structlogger.debug(
418
- "project_generator.download_cache_for_template.no_cache_found",
419
- template=template,
420
- event_info=("No cache found for template, continuing without it."),
421
- )
422
- else:
423
- structlogger.debug(
424
- "project_generator.download_cache_for_template.response_error",
425
- error=str(e),
426
- status=e.status,
427
- template=template,
428
- event_info=(
429
- "Failed to download cache for template, continuing without it."
430
- ),
431
- )
432
- capture_exception_with_context(
433
- e,
434
- "project_generator.download_cache_for_template.response_error",
435
- tags={"template": template.value, "status": str(e.status)},
436
- )
437
- except Exception as exc:
438
- structlogger.debug(
439
- "project_generator.download_cache_for_template.unexpected_error",
440
- error=str(exc),
441
- template=template,
442
- event_info=(
443
- "Unexpected error when downloading cache for template, "
444
- "continuing without it."
445
- ),
446
- )
447
- capture_exception_with_context(
448
- exc,
449
- "project_generator.download_cache_for_template.unexpected_error",
450
- tags={"template": template.value},
451
- )
452
- finally:
453
- # Clean up the temporary file
454
- try:
455
- Path(temporary_cache_file.name).unlink(missing_ok=True)
456
- except Exception as exc:
457
- structlogger.debug(
458
- "project_generator.download_cache_for_template.cleanup_error",
459
- error=str(exc),
460
- template=template,
461
- event_info=("Failed to cleanup cache for template, ignoring."),
462
- )
@@ -0,0 +1,244 @@
1
+ import asyncio
2
+ import os
3
+ import shutil
4
+ import tarfile
5
+ import tempfile
6
+ from pathlib import Path
7
+ from typing import Generator
8
+
9
+ import aiofiles
10
+ import aiohttp
11
+ import structlog
12
+ from sanic import Sanic
13
+
14
+ import rasa.version
15
+ from rasa.builder.logging_utils import capture_exception_with_context
16
+ from rasa.cli.scaffold import ProjectTemplateName
17
+
18
+ structlogger = structlog.get_logger()
19
+
20
+ CACHE_BUCKET_URL = "https://trained-templates.s3.us-east-1.amazonaws.com"
21
+
22
+ # Root directory for storing downloaded template caches on disk.
23
+ _CACHE_ROOT_DIR = Path(
24
+ os.getenv(
25
+ "RASA_TEMPLATE_CACHE_DIR",
26
+ Path.home().joinpath(".rasa", "template-cache").as_posix(),
27
+ )
28
+ )
29
+
30
+
31
+ def _template_cache_dir(template: ProjectTemplateName) -> Path:
32
+ """Return the local cache directory for a given template and version."""
33
+ return _CACHE_ROOT_DIR / rasa.version.__version__ / template.value
34
+
35
+
36
+ def _cache_root_dir() -> Path:
37
+ return Path(
38
+ os.getenv(
39
+ "RASA_TEMPLATE_CACHE_DIR",
40
+ Path.home().joinpath(".rasa", "template-cache").as_posix(),
41
+ )
42
+ )
43
+
44
+
45
+ def _safe_tar_members(
46
+ tar: tarfile.TarFile, destination_directory: Path
47
+ ) -> Generator[tarfile.TarInfo, None, None]:
48
+ """Yield safe members for extraction to prevent path traversal and links.
49
+
50
+ Args:
51
+ tar: Open tar file handle
52
+ destination_directory: Directory to which files will be extracted
53
+
54
+ Yields:
55
+ Members that are safe to extract within destination_directory
56
+ """
57
+ base_path = destination_directory.resolve()
58
+
59
+ for member in tar.getmembers():
60
+ name = member.name
61
+ # Skip empty names and absolute paths
62
+ if not name or name.startswith("/") or name.startswith("\\"):
63
+ continue
64
+
65
+ # Disallow symlinks and hardlinks
66
+ if member.issym() or member.islnk():
67
+ continue
68
+
69
+ # Compute the final path and ensure it's within base_path
70
+ target_path = (base_path / name).resolve()
71
+ try:
72
+ target_path.relative_to(base_path)
73
+ except ValueError:
74
+ # Member would escape the destination directory
75
+ continue
76
+
77
+ yield member
78
+
79
+
80
+ def _copytree(src: Path, dst: Path) -> None:
81
+ """Copy directory tree from src to dst, merging into dst.
82
+
83
+ Existing files are overwritten. Hidden files and directories are included, as
84
+ caches can contain `.rasa` metadata that should be applied before calling
85
+ `ensure_first_used`.
86
+ """
87
+ for root, dirs, files in os.walk(src):
88
+ rel_path = Path(root).relative_to(src)
89
+ target_dir = dst / rel_path
90
+ target_dir.mkdir(parents=True, exist_ok=True)
91
+ for filename in files:
92
+ src_file = Path(root) / filename
93
+ dst_file = target_dir / filename
94
+ shutil.copy2(src_file, dst_file)
95
+
96
+
97
+ async def download_cache_for_template(
98
+ template: ProjectTemplateName, target_dir: str
99
+ ) -> None:
100
+ # get a temp path for the cache file download
101
+ temporary_cache_file = tempfile.NamedTemporaryFile(suffix=".tar.gz", delete=False)
102
+
103
+ try:
104
+ url = f"{CACHE_BUCKET_URL}/{rasa.version.__version__}-{template.value}.tar.gz"
105
+ async with aiohttp.ClientSession() as session:
106
+ async with session.get(url) as response:
107
+ response.raise_for_status()
108
+ async with aiofiles.open(temporary_cache_file.name, "wb") as f:
109
+ async for chunk in response.content.iter_chunked(1024 * 1024):
110
+ await f.write(chunk)
111
+
112
+ # extract the cache to the project folder using safe member filtering
113
+ with tarfile.open(temporary_cache_file.name, "r:gz") as tar:
114
+ destination = Path(target_dir)
115
+ destination.mkdir(parents=True, exist_ok=True)
116
+ tar.extractall(
117
+ path=destination,
118
+ members=_safe_tar_members(tar, destination),
119
+ )
120
+
121
+ structlogger.info(
122
+ "project_generator.download_cache_for_template.success",
123
+ template=template,
124
+ event_info=(
125
+ "Downloaded cache for template, extracted to target directory."
126
+ ),
127
+ target_dir=target_dir,
128
+ )
129
+ except aiohttp.ClientResponseError as e:
130
+ if e.status == 403:
131
+ structlogger.debug(
132
+ "project_generator.download_cache_for_template.no_cache_found",
133
+ template=template,
134
+ event_info=("No cache found for template, continuing without it."),
135
+ target_dir=target_dir,
136
+ )
137
+ else:
138
+ capture_exception_with_context(
139
+ e,
140
+ "project_generator.download_cache_for_template.response_error",
141
+ extra={
142
+ "template": template.value,
143
+ "status": str(e.status),
144
+ "target_dir": target_dir,
145
+ },
146
+ )
147
+ except Exception as exc:
148
+ capture_exception_with_context(
149
+ exc,
150
+ "project_generator.download_cache_for_template.unexpected_error",
151
+ extra={"template": template.value, "target_dir": target_dir},
152
+ )
153
+ finally:
154
+ # Clean up the temporary file
155
+ try:
156
+ Path(temporary_cache_file.name).unlink(missing_ok=True)
157
+ except Exception as exc:
158
+ structlogger.debug(
159
+ "project_generator.download_cache_for_template.cleanup_error",
160
+ error=str(exc),
161
+ template=template,
162
+ event_info=("Failed to cleanup cache for template, ignoring."),
163
+ )
164
+
165
+
166
+ async def background_download_template_caches(
167
+ app: Sanic, loop: asyncio.AbstractEventLoop
168
+ ) -> None:
169
+ """Kick off background downloads of template caches if enabled."""
170
+ try:
171
+ structlogger.info(
172
+ "builder.main.background_cache_download.start",
173
+ event_info=(
174
+ "Starting background download of template caches for this " "version"
175
+ ),
176
+ )
177
+
178
+ # Ensure cache root exists
179
+ _cache_root_dir().mkdir(parents=True, exist_ok=True)
180
+
181
+ async def _download(template: ProjectTemplateName) -> None:
182
+ try:
183
+ target_dir = _template_cache_dir(template)
184
+ if target_dir.exists() and any(target_dir.iterdir()):
185
+ structlogger.debug(
186
+ "builder.main.background_cache_download.skipped",
187
+ template=template,
188
+ event_info=(
189
+ "Skipping download of template cache because it "
190
+ "already exists."
191
+ ),
192
+ target_dir=target_dir,
193
+ )
194
+ return
195
+
196
+ target_dir.mkdir(parents=True, exist_ok=True)
197
+ await download_cache_for_template(template, target_dir.as_posix())
198
+ except Exception as exc:
199
+ structlogger.debug(
200
+ "builder.main.background_cache_download.error",
201
+ template=template,
202
+ error=str(exc),
203
+ )
204
+
205
+ # schedule downloads concurrently without blocking startup
206
+ for template in ProjectTemplateName:
207
+ loop.create_task(_download(template))
208
+ except Exception as exc:
209
+ structlogger.debug(
210
+ "builder.main.background_cache_download.unexpected_error",
211
+ error=str(exc),
212
+ )
213
+
214
+
215
+ def copy_cache_for_template_if_available(
216
+ template: ProjectTemplateName, project_folder: Path
217
+ ) -> None:
218
+ """Copy a previously downloaded cache for `template` into `project_folder`.
219
+
220
+ If the cache does not exist, this function is a no-op.
221
+ """
222
+ try:
223
+ cache_dir = _template_cache_dir(template)
224
+ if cache_dir.exists() and any(cache_dir.iterdir()):
225
+ _copytree(cache_dir, project_folder)
226
+ structlogger.info(
227
+ "project_generator.copy_cache_for_template.success",
228
+ template=template,
229
+ event_info=(
230
+ "Copied cached template files from cache to project folder."
231
+ ),
232
+ )
233
+ else:
234
+ structlogger.debug(
235
+ "project_generator.copy_cache_for_template.missing",
236
+ template=template,
237
+ event_info=("No local cache found for template; skipping copy."),
238
+ )
239
+ except Exception as exc:
240
+ structlogger.warning(
241
+ "project_generator.copy_cache_for_template.error",
242
+ error=str(exc),
243
+ template=template,
244
+ )
@@ -11,7 +11,7 @@ assistant_id: placeholder_default
11
11
  pipeline:
12
12
  - name: SearchReadyLLMCommandGenerator
13
13
  llm:
14
- model_group: rasa-llm-proxy-openai-llm
14
+ model_group: openai-gpt-4o
15
15
  flow_retrieval:
16
16
  active: false
17
17
 
@@ -23,8 +23,7 @@ policies:
23
23
  type: "faiss"
24
24
  source: "./docs"
25
25
  llm:
26
- model_group: rasa-llm-proxy-openai-llm
26
+ model_group: openai-gpt-4o
27
27
  embeddings:
28
- model_group: rasa-llm-proxy-openai-embeddings
29
-
28
+ model_group: openai-embeddings
30
29
  check_relevancy: true
@@ -49,7 +49,7 @@ action_endpoint:
49
49
  nlg:
50
50
  type: rephrase
51
51
  llm:
52
- model_group: rasa-llm-proxy-openai-llm
52
+ model_group: openai-gpt-4o
53
53
  prompt_template: prompts/rephraser_demo_personality_prompt.jinja2
54
54
  rephrase_all: False
55
55
  summarize_history: False
@@ -61,17 +61,7 @@ model_groups:
61
61
  model: gpt-4o-2024-11-20
62
62
  timeout: 7
63
63
  max_tokens: 256
64
- - id: rasa-llm-proxy-openai-llm
65
- models:
66
- - provider: openai
67
- api_base: https://hello-llm-proxy.rasa-e2e.workers.dev
68
- api_key: ${RASA_PRO_LICENSE}
69
- model: gpt-4o-2024-11-20
70
- timeout: 7
71
- max_tokens: 256
72
- - id: rasa-llm-proxy-openai-embeddings
64
+ - id: openai-embeddings
73
65
  models:
74
66
  - provider: openai
75
67
  model: text-embedding-3-large
76
- api_base: https://hello-llm-proxy.rasa-e2e.workers.dev/embeddings
77
- api_key: ${RASA_PRO_LICENSE}
@@ -9,13 +9,13 @@ language: en
9
9
  pipeline:
10
10
  - name: CompactLLMCommandGenerator
11
11
  llm:
12
- model_group: rasa-llm-proxy-openai-llm
12
+ model_group: openai-gpt-4o
13
13
 
14
14
  # Configuration for Rasa Core.
15
15
  policies:
16
16
  - name: FlowPolicy
17
17
  - name: IntentlessPolicy
18
18
  llm:
19
- model_group: rasa-llm-proxy-openai-llm
19
+ model_group: openai-gpt-4o
20
20
  embeddings:
21
- model_group: rasa-llm-proxy-openai-embeddings
21
+ model_group: openai-embeddings
@@ -56,18 +56,7 @@ model_groups:
56
56
  model: gpt-4o-2024-11-20
57
57
  request_timeout: 7
58
58
  max_tokens: 256
59
- - id: rasa-llm-proxy-openai-llm
60
- models:
61
- - provider: openai
62
- api_base: https://hello-llm-proxy.rasa-e2e.workers.dev
63
- api_key: ${RASA_PRO_LICENSE}
64
- model: gpt-4o-2024-11-20
65
- timeout: 7
66
- max_tokens: 256
67
- - id: rasa-llm-proxy-openai-embeddings
59
+ - id: openai-embeddings
68
60
  models:
69
61
  - provider: openai
70
62
  model: text-embedding-3-large
71
- api_base: https://hello-llm-proxy.rasa-e2e.workers.dev/embeddings
72
- api_key: ${RASA_PRO_LICENSE}
73
-
@@ -5,7 +5,7 @@ assistant_id: placeholder_default
5
5
  pipeline:
6
6
  - name: SearchReadyLLMCommandGenerator
7
7
  llm:
8
- model_group: rasa-llm-proxy-openai-llm
8
+ model_group: openai-gpt-4o
9
9
  flow_retrieval:
10
10
  active: false
11
11
 
@@ -17,7 +17,7 @@ policies:
17
17
  type: "faiss"
18
18
  source: "./docs"
19
19
  llm:
20
- model_group: rasa-llm-proxy-openai-llm
20
+ model_group: openai-gpt-4o
21
21
  embeddings:
22
- model_group: rasa-llm-proxy-openai-embeddings
22
+ model_group: openai-embeddings
23
23
  check_relevancy: true
@@ -49,7 +49,7 @@ action_endpoint:
49
49
  nlg:
50
50
  type: rephrase
51
51
  llm:
52
- model_group: rasa-llm-proxy-openai-llm
52
+ model_group: openai-gpt-4o
53
53
  prompt: prompts/rephraser_demo_personality_prompt.jinja2
54
54
  rephrase_all: False
55
55
 
@@ -60,17 +60,7 @@ model_groups:
60
60
  model: gpt-4o-2024-11-20
61
61
  request_timeout: 7
62
62
  max_tokens: 256
63
- - id: rasa-llm-proxy-openai-llm
64
- models:
65
- - provider: openai
66
- api_base: https://hello-llm-proxy.rasa-e2e.workers.dev
67
- api_key: ${RASA_PRO_LICENSE}
68
- model: gpt-4o-2024-11-20
69
- timeout: 7
70
- max_tokens: 256
71
- - id: rasa-llm-proxy-openai-embeddings
63
+ - id: openai-embeddings
72
64
  models:
73
65
  - provider: openai
74
66
  model: text-embedding-3-large
75
- api_base: https://hello-llm-proxy.rasa-e2e.workers.dev/embeddings
76
- api_key: ${RASA_PRO_LICENSE}