rasa-pro 3.14.0a15__py3-none-any.whl → 3.14.0a16__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 +1 -0
- rasa/builder/copilot/constants.py +3 -0
- rasa/builder/copilot/copilot.py +127 -31
- rasa/builder/copilot/models.py +34 -0
- rasa/builder/copilot/prompts/copilot_system_prompt.jinja2 +28 -84
- rasa/builder/copilot/prompts/latest_user_message_context_prompt.jinja2 +61 -0
- rasa/builder/copilot/telemetry.py +16 -6
- rasa/builder/document_retrieval/models.py +3 -3
- rasa/builder/main.py +14 -5
- rasa/builder/project_generator.py +1 -3
- rasa/builder/service.py +8 -9
- rasa/builder/template_cache.py +183 -9
- rasa/cli/project_templates/telco/data/general/human_handoff.yml +1 -1
- rasa/cli/project_templates/telco/domain/general/human_handoff.yml +3 -6
- rasa/cli/project_templates/telco/tests/e2e_test_cases/billing/understand_bill.yml +67 -0
- rasa/cli/project_templates/telco/tests/e2e_test_cases/general/bot_challenge.yml +8 -0
- rasa/cli/project_templates/telco/tests/e2e_test_cases/general/feedback.yml +46 -0
- rasa/cli/project_templates/telco/tests/e2e_test_cases/general/goodbye.yml +9 -0
- rasa/cli/project_templates/telco/tests/e2e_test_cases/general/hello.yml +8 -0
- rasa/cli/project_templates/telco/tests/e2e_test_cases/general/human_handoff.yml +35 -0
- rasa/cli/project_templates/telco/tests/e2e_test_cases/general/patterns.yml +23 -0
- rasa/cli/project_templates/telco/tests/e2e_test_cases/network/solve_internet_issue.yml +57 -0
- rasa/core/channels/development_inspector.py +1 -21
- rasa/core/channels/hangouts.py +2 -2
- rasa/core/channels/inspector/dist/assets/{arc-c24d8d79.js → arc-460861ce.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-1b6b9f26.js → blockDiagram-38ab4fdb-16c993e0.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-da91d0f9.js → c4Diagram-3d4e48cf-488337d7.js} +1 -1
- rasa/core/channels/inspector/dist/assets/channel-b560a3d4.js +1 -0
- rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-6067f302.js → classDiagram-70f12bd4-b08e53a8.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-705d024a.js → classDiagram-v2-f2320105-b73f5a83.js} +1 -1
- rasa/core/channels/inspector/dist/assets/clone-67015557.js +1 -0
- rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-3751dffe.js → createText-2e5e7dd3-0210a219.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-7b25b4af.js → edges-e0da2a9e-28df7099.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-eb7deea8.js → erDiagram-9861fffd-9fbf4a58.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-67235ff6.js → flowDb-956e92f1-fa691f62.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-34c3a16a.js → flowDiagram-66a62f08-ca907b67.js} +1 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-4a070961.js +1 -0
- rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-f1a93631.js → flowchart-elk-definition-4a651766-c10945f2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-a68cbad1.js → ganttDiagram-c361ad54-9d49a75a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-0b1e4a1d.js → gitGraphDiagram-72cf32ee-9aa698ac.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{graph-f3c1d212.js → graph-3ab38d50.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-3862675e-34cbca30.js → index-3862675e-6edac98f.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-051c5a6e.js → index-61128091.js} +41 -40
- rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-e69960a1.js → infoDiagram-f8f76790-21baff85.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-8dd3296a.js → journeyDiagram-49397b02-4a6c7e98.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{layout-e93126bc.js → layout-4beae36e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{line-15eb1e26.js → line-633b638e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{linear-fec95d33.js → linear-22d77d65.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-2557813e.js → mindmap-definition-fc14e90a-f219ef43.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-40d756b1.js → pieDiagram-8a3498a8-c7e1cafb.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-a48cbdcd.js → quadrantDiagram-120e2f19-045e49b4.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-dc778150.js → requirementDiagram-deff3bca-22485cb9.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-10026b94.js → sankeyDiagram-04a897e0-281c3da2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-3b2ed10a.js → sequenceDiagram-704730f1-a3dd10e0.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-c5f3b3fb.js → stateDiagram-587899a1-61bd6eb2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-e503656b.js → stateDiagram-v2-d93cdb3a-deead491.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-a683ce56.js → styles-6aaf32cf-1b10e104.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-9a916d00-02bcdcee.js → styles-9a916d00-b1e18e58.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-c10674c1-8e90dbb9.js → styles-c10674c1-956c3492.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-7c23fc1e.js → svgDrawCommon-08f97a94-e13f753d.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-c42faec8.js → timeline-definition-85554ec2-e568acd2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-5e3bb0ea.js → xychartDiagram-e933f94c-8b7e27fc.js} +1 -1
- rasa/core/channels/inspector/dist/index.html +1 -1
- rasa/core/channels/inspector/src/App.tsx +0 -7
- rasa/core/channels/inspector/src/components/DialogueInformation.tsx +9 -1
- rasa/core/channels/inspector/src/components/LatencyDisplay.tsx +63 -35
- rasa/core/channels/inspector/src/types.ts +32 -7
- rasa/core/channels/studio_chat.py +14 -40
- rasa/core/constants.py +6 -0
- rasa/core/iam_credentials_providers/__init__.py +0 -0
- rasa/core/iam_credentials_providers/aws_iam_credentials_providers.py +66 -0
- rasa/core/iam_credentials_providers/credentials_provider_protocol.py +89 -0
- rasa/core/processor.py +32 -0
- rasa/core/redis_connection_factory.py +411 -0
- rasa/core/tracker_stores/redis_tracker_store.py +32 -14
- rasa/core/tracker_stores/sql_tracker_store.py +57 -1
- rasa/model_manager/socket_bridge.py +1 -2
- rasa/shared/core/constants.py +1 -0
- rasa/shared/core/events.py +2 -0
- rasa/version.py +1 -1
- {rasa_pro-3.14.0a15.dist-info → rasa_pro-3.14.0a16.dist-info}/METADATA +11 -12
- {rasa_pro-3.14.0a15.dist-info → rasa_pro-3.14.0a16.dist-info}/RECORD +90 -77
- rasa/core/channels/inspector/dist/assets/channel-d2444dfd.js +0 -1
- rasa/core/channels/inspector/dist/assets/clone-281a0990.js +0 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-aa4cca3b.js +0 -1
- /rasa/cli/project_templates/telco/domain/billing/{domain_undertand_bill.yml → understand_bill.yml} +0 -0
- /rasa/cli/project_templates/telco/domain/network/{domain_reboot_router.yml → reboot_router.yml} +0 -0
- /rasa/cli/project_templates/telco/domain/network/{domain_reset_router.yml → reset_router.yml} +0 -0
- /rasa/cli/project_templates/telco/domain/network/{domain_run_speed_test.yml → run_speed_test.yml} +0 -0
- /rasa/cli/project_templates/telco/domain/network/{domain_solve_internet_issue.yml → solve_internet_issue.yml} +0 -0
- {rasa_pro-3.14.0a15.dist-info → rasa_pro-3.14.0a16.dist-info}/NOTICE +0 -0
- {rasa_pro-3.14.0a15.dist-info → rasa_pro-3.14.0a16.dist-info}/WHEEL +0 -0
- {rasa_pro-3.14.0a15.dist-info → rasa_pro-3.14.0a16.dist-info}/entry_points.txt +0 -0
rasa/builder/service.py
CHANGED
|
@@ -1122,11 +1122,9 @@ async def copilot(request: Request) -> None:
|
|
|
1122
1122
|
# copilot response handler
|
|
1123
1123
|
start_timestamp = time.perf_counter()
|
|
1124
1124
|
copilot_client = llm_service.instantiate_copilot()
|
|
1125
|
-
(
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
system_prompt,
|
|
1129
|
-
) = await copilot_client.generate_response(context)
|
|
1125
|
+
(original_stream, generation_context) = await copilot_client.generate_response(
|
|
1126
|
+
context
|
|
1127
|
+
)
|
|
1130
1128
|
|
|
1131
1129
|
copilot_response_handler = llm_service.instantiate_handler(
|
|
1132
1130
|
COPILOT_HANDLER_ROLLING_BUFFER_SIZE
|
|
@@ -1142,18 +1140,19 @@ async def copilot(request: Request) -> None:
|
|
|
1142
1140
|
asyncio.to_thread(
|
|
1143
1141
|
telemetry.log_copilot_from_handler,
|
|
1144
1142
|
handler=copilot_response_handler,
|
|
1145
|
-
used_documents=
|
|
1143
|
+
used_documents=generation_context.relevant_documents,
|
|
1146
1144
|
latency_ms=int((time.perf_counter() - start_timestamp) * 1000),
|
|
1147
|
-
|
|
1145
|
+
system_message=generation_context.system_message,
|
|
1146
|
+
chat_history=generation_context.chat_history,
|
|
1148
1147
|
**copilot_client.usage_statistics.model_dump(),
|
|
1149
1148
|
)
|
|
1150
1149
|
)
|
|
1151
1150
|
|
|
1152
1151
|
# 9. Once the stream is over, extract and send references
|
|
1153
1152
|
# if any documents were used
|
|
1154
|
-
if
|
|
1153
|
+
if generation_context.relevant_documents:
|
|
1155
1154
|
reference_section = copilot_response_handler.extract_references(
|
|
1156
|
-
|
|
1155
|
+
generation_context.relevant_documents
|
|
1157
1156
|
)
|
|
1158
1157
|
await sse.send(reference_section.to_sse_event().format())
|
|
1159
1158
|
|
rasa/builder/template_cache.py
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import os
|
|
3
|
+
import shutil
|
|
4
|
+
import tarfile
|
|
5
|
+
import tempfile
|
|
2
6
|
from pathlib import Path
|
|
7
|
+
from typing import Generator
|
|
3
8
|
|
|
4
9
|
import aiofiles
|
|
5
|
-
import
|
|
6
|
-
import aioshutil
|
|
10
|
+
import aiohttp
|
|
7
11
|
import structlog
|
|
12
|
+
from sanic import Sanic
|
|
8
13
|
|
|
14
|
+
import rasa.version
|
|
15
|
+
from rasa.builder.logging_utils import capture_exception_with_context
|
|
9
16
|
from rasa.cli.scaffold import ProjectTemplateName
|
|
10
17
|
|
|
11
18
|
structlogger = structlog.get_logger()
|
|
@@ -13,15 +20,64 @@ structlogger = structlog.get_logger()
|
|
|
13
20
|
CACHE_BUCKET_URL = "https://trained-templates.s3.us-east-1.amazonaws.com"
|
|
14
21
|
|
|
15
22
|
# Root directory for storing downloaded template caches on disk.
|
|
16
|
-
_CACHE_ROOT_DIR = Path(
|
|
23
|
+
_CACHE_ROOT_DIR = Path(
|
|
24
|
+
os.getenv(
|
|
25
|
+
"RASA_TEMPLATE_CACHE_DIR",
|
|
26
|
+
Path.home().joinpath(".rasa", "template-cache").as_posix(),
|
|
27
|
+
)
|
|
28
|
+
)
|
|
17
29
|
|
|
18
30
|
|
|
19
31
|
def _template_cache_dir(template: ProjectTemplateName) -> Path:
|
|
20
32
|
"""Return the local cache directory for a given template and version."""
|
|
21
|
-
return _CACHE_ROOT_DIR / template.value
|
|
33
|
+
return _CACHE_ROOT_DIR / rasa.version.__version__ / template.value
|
|
22
34
|
|
|
23
35
|
|
|
24
|
-
|
|
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:
|
|
25
81
|
"""Copy directory tree from src to dst, merging into dst.
|
|
26
82
|
|
|
27
83
|
Existing files are overwritten. Hidden files and directories are included, as
|
|
@@ -31,14 +87,132 @@ async def _copytree(src: Path, dst: Path) -> None:
|
|
|
31
87
|
for root, dirs, files in os.walk(src):
|
|
32
88
|
rel_path = Path(root).relative_to(src)
|
|
33
89
|
target_dir = dst / rel_path
|
|
34
|
-
|
|
90
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
35
91
|
for filename in files:
|
|
36
92
|
src_file = Path(root) / filename
|
|
37
93
|
dst_file = target_dir / filename
|
|
38
|
-
|
|
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
|
+
)
|
|
39
213
|
|
|
40
214
|
|
|
41
|
-
|
|
215
|
+
def copy_cache_for_template_if_available(
|
|
42
216
|
template: ProjectTemplateName, project_folder: Path
|
|
43
217
|
) -> None:
|
|
44
218
|
"""Copy a previously downloaded cache for `template` into `project_folder`.
|
|
@@ -48,7 +222,7 @@ async def copy_cache_for_template_if_available(
|
|
|
48
222
|
try:
|
|
49
223
|
cache_dir = _template_cache_dir(template)
|
|
50
224
|
if cache_dir.exists() and any(cache_dir.iterdir()):
|
|
51
|
-
|
|
225
|
+
_copytree(cache_dir, project_folder)
|
|
52
226
|
structlogger.info(
|
|
53
227
|
"project_generator.copy_cache_for_template.success",
|
|
54
228
|
template=template,
|
|
@@ -4,20 +4,17 @@ actions:
|
|
|
4
4
|
|
|
5
5
|
slots:
|
|
6
6
|
confirm_human_handoff:
|
|
7
|
-
type:
|
|
7
|
+
type: bool
|
|
8
8
|
mappings:
|
|
9
9
|
- type: "from_llm"
|
|
10
|
-
values:
|
|
11
|
-
- "Yes"
|
|
12
|
-
- "No"
|
|
13
10
|
|
|
14
11
|
responses:
|
|
15
12
|
utter_ask_confirm_human_handoff:
|
|
16
13
|
- text: "Do you want to be connected to a human agent?"
|
|
17
14
|
buttons:
|
|
18
|
-
- payload: "/SetSlots(confirm_human_handoff=
|
|
15
|
+
- payload: "/SetSlots(confirm_human_handoff=True)"
|
|
19
16
|
title: "Yes"
|
|
20
|
-
- payload: "/SetSlots(confirm_human_handoff=
|
|
17
|
+
- payload: "/SetSlots(confirm_human_handoff=False)"
|
|
21
18
|
title: "No"
|
|
22
19
|
|
|
23
20
|
utter_transferring_to_human:
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
fixtures:
|
|
2
|
+
- customer_information: # name of the fixture must be provided and be unique
|
|
3
|
+
- customer_id: "123" # every fixture can contain multiple slot key-value pairs
|
|
4
|
+
|
|
5
|
+
test_cases:
|
|
6
|
+
- test_case: billing happy path
|
|
7
|
+
fixtures:
|
|
8
|
+
- customer_information
|
|
9
|
+
steps:
|
|
10
|
+
- user: "I would like to check my bill"
|
|
11
|
+
assertions:
|
|
12
|
+
- flow_started: "understand_bill"
|
|
13
|
+
- bot_uttered:
|
|
14
|
+
utter_name: utter_ask_bill_month
|
|
15
|
+
- user: "April"
|
|
16
|
+
assertions:
|
|
17
|
+
- slot_was_set:
|
|
18
|
+
- name: "bill_month"
|
|
19
|
+
value: "April"
|
|
20
|
+
- action_executed: action_verify_bill_by_date
|
|
21
|
+
- bot_uttered:
|
|
22
|
+
utter_name: utter_ask_breakdown_cost
|
|
23
|
+
text_matches: Would you like to see the breakdown of the costs?
|
|
24
|
+
buttons:
|
|
25
|
+
- title: "Yes, please"
|
|
26
|
+
payload: "/SetSlots(breakdown_cost=True)"
|
|
27
|
+
- title: "No, all good"
|
|
28
|
+
payload: "/SetSlots(breakdown_cost=False)"
|
|
29
|
+
- user: "/SetSlots(breakdown_cost=True)"
|
|
30
|
+
assertions:
|
|
31
|
+
- action_executed: action_recap_bill
|
|
32
|
+
- bot_uttered:
|
|
33
|
+
utter_name: utter_ask_more_bill_details
|
|
34
|
+
- user: "/SetSlots(more_bill_details=True)"
|
|
35
|
+
assertions:
|
|
36
|
+
- bot_uttered:
|
|
37
|
+
utter_name: utter_provide_bill_sources
|
|
38
|
+
# ====================================================>
|
|
39
|
+
- test_case: billing unhappy path
|
|
40
|
+
steps:
|
|
41
|
+
- user: "I would like to check my bill"
|
|
42
|
+
assertions:
|
|
43
|
+
- flow_started: "understand_bill"
|
|
44
|
+
- bot_uttered:
|
|
45
|
+
utter_name: utter_ask_bill_month
|
|
46
|
+
- user: "February"
|
|
47
|
+
assertions:
|
|
48
|
+
- slot_was_set:
|
|
49
|
+
- name: "bill_month"
|
|
50
|
+
value: "February"
|
|
51
|
+
- action_executed: action_verify_bill_by_date
|
|
52
|
+
- bot_uttered:
|
|
53
|
+
utter_name: utter_ask_breakdown_cost
|
|
54
|
+
text_matches: Would you like to see the breakdown of the costs?
|
|
55
|
+
buttons:
|
|
56
|
+
- title: "Yes, please"
|
|
57
|
+
payload: "/SetSlots(breakdown_cost=True)"
|
|
58
|
+
- title: "No, all good"
|
|
59
|
+
payload: "/SetSlots(breakdown_cost=False)"
|
|
60
|
+
- user: "/SetSlots(breakdown_cost=False)"
|
|
61
|
+
assertions:
|
|
62
|
+
- bot_uttered:
|
|
63
|
+
utter_name: utter_ask_issue_solved
|
|
64
|
+
- user: "/SetSlots(issue_solved=True)"
|
|
65
|
+
assertions:
|
|
66
|
+
- bot_uttered:
|
|
67
|
+
utter_name: utter_end_conversation
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
test_cases:
|
|
2
|
+
- test_case: positive feedback
|
|
3
|
+
steps:
|
|
4
|
+
- user: "goodbye"
|
|
5
|
+
assertions:
|
|
6
|
+
- flow_started: "goodbye"
|
|
7
|
+
- bot_uttered:
|
|
8
|
+
utter_name: "utter_goodbye"
|
|
9
|
+
- flow_started: "leave_feedback"
|
|
10
|
+
- bot_uttered:
|
|
11
|
+
utter_name: "utter_ask_feedback_rating"
|
|
12
|
+
buttons:
|
|
13
|
+
- title: "👍 Great"
|
|
14
|
+
payload: "/SetSlots(feedback_rating=thumbs_up)"
|
|
15
|
+
- title: "👎 Could be better"
|
|
16
|
+
payload: "/SetSlots(feedback_rating=thumbs_down)"
|
|
17
|
+
- user: "/SetSlots(feedback_rating=thumbs_up)"
|
|
18
|
+
assertions:
|
|
19
|
+
- slot_was_set:
|
|
20
|
+
- name: "feedback_rating"
|
|
21
|
+
value: "thumbs_up"
|
|
22
|
+
- bot_uttered:
|
|
23
|
+
utter_name: "utter_thankyou_positive"
|
|
24
|
+
#
|
|
25
|
+
- test_case: negative feedback
|
|
26
|
+
steps:
|
|
27
|
+
- user: "goodbye"
|
|
28
|
+
assertions:
|
|
29
|
+
- flow_started: "goodbye"
|
|
30
|
+
- bot_uttered:
|
|
31
|
+
utter_name: "utter_goodbye"
|
|
32
|
+
- flow_started: "leave_feedback"
|
|
33
|
+
- bot_uttered:
|
|
34
|
+
utter_name: "utter_ask_feedback_rating"
|
|
35
|
+
buttons:
|
|
36
|
+
- title: "👍 Great"
|
|
37
|
+
payload: "/SetSlots(feedback_rating=thumbs_up)"
|
|
38
|
+
- title: "👎 Could be better"
|
|
39
|
+
payload: "/SetSlots(feedback_rating=thumbs_down)"
|
|
40
|
+
- user: "/SetSlots(feedback_rating=thumbs_down)"
|
|
41
|
+
assertions:
|
|
42
|
+
- slot_was_set:
|
|
43
|
+
- name: "feedback_rating"
|
|
44
|
+
value: "thumbs_down"
|
|
45
|
+
- bot_uttered:
|
|
46
|
+
utter_name: "utter_thankyou_negative"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
test_cases:
|
|
2
|
+
- test_case: human handoff happy path
|
|
3
|
+
steps:
|
|
4
|
+
- user: "I would like to speak with a human"
|
|
5
|
+
assertions:
|
|
6
|
+
- flow_started: "human_handoff"
|
|
7
|
+
- bot_uttered:
|
|
8
|
+
utter_name: "utter_ask_confirm_human_handoff"
|
|
9
|
+
buttons:
|
|
10
|
+
- payload: "/SetSlots(confirm_human_handoff=True)"
|
|
11
|
+
title: "Yes"
|
|
12
|
+
- payload: "/SetSlots(confirm_human_handoff=False)"
|
|
13
|
+
title: "No"
|
|
14
|
+
- user: "Yes"
|
|
15
|
+
assertions:
|
|
16
|
+
- bot_uttered:
|
|
17
|
+
utter_name: "utter_transferring_to_human"
|
|
18
|
+
- action_executed: action_human_handoff
|
|
19
|
+
# ====================================================>
|
|
20
|
+
- test_case: human handoff unhappy path
|
|
21
|
+
steps:
|
|
22
|
+
- user: "I would like to speak with a human"
|
|
23
|
+
assertions:
|
|
24
|
+
- flow_started: "human_handoff"
|
|
25
|
+
- bot_uttered:
|
|
26
|
+
utter_name: "utter_ask_confirm_human_handoff"
|
|
27
|
+
buttons:
|
|
28
|
+
- payload: "/SetSlots(confirm_human_handoff=True)"
|
|
29
|
+
title: "Yes"
|
|
30
|
+
- payload: "/SetSlots(confirm_human_handoff=False)"
|
|
31
|
+
title: "No"
|
|
32
|
+
- user: "No"
|
|
33
|
+
assertions:
|
|
34
|
+
- bot_uttered:
|
|
35
|
+
utter_name: "utter_human_handoff_cancelled"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
test_cases:
|
|
2
|
+
- test_case: test pattern search
|
|
3
|
+
steps:
|
|
4
|
+
- user: "how do you run a network speed test?"
|
|
5
|
+
assertions:
|
|
6
|
+
- generative_response_is_grounded:
|
|
7
|
+
threshold: 0.90
|
|
8
|
+
ground_truth: "You can run a network speed test using special software such as \"Fast.com\", \"Speedtest.net\", or Google's \"Internet speed test\". Alternatively, if you have our app, you can go to the Home menu, select WLAN speed test and run it. These tests measure your internet connection's performance by evaluating download and upload speeds, ping, and jitter."
|
|
9
|
+
- user: "what is the difference between restart and reset a router"
|
|
10
|
+
assertions:
|
|
11
|
+
- flow_completed:
|
|
12
|
+
flow_id: "pattern_search"
|
|
13
|
+
- generative_response_is_grounded:
|
|
14
|
+
threshold: 0.90
|
|
15
|
+
ground_truth: "Restarting or rebooting a router involves turning it off and back on without changing any settings, which can help resolve temporary issues like slow speeds or dropped connections. Resetting a router, however, restores it to factory settings, erasing all custom configurations such as the Wi-Fi name, password, and advanced settings. Resetting should only be done as a last resort when troubleshooting persistent problems."
|
|
16
|
+
#
|
|
17
|
+
- test_case: test pattern session start
|
|
18
|
+
steps:
|
|
19
|
+
- user: "/session_start"
|
|
20
|
+
assertions:
|
|
21
|
+
- flow_started: "pattern_session_start"
|
|
22
|
+
- bot_uttered:
|
|
23
|
+
utter_name: "utter_greeting"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
test_cases:
|
|
2
|
+
# one of these test cases will fail since the speed test will be randomly selected
|
|
3
|
+
- test_case: Solving Internet slow issue
|
|
4
|
+
steps:
|
|
5
|
+
- user: "Hey, my internet is very slow. What's going on?"
|
|
6
|
+
assertions:
|
|
7
|
+
- flow_started: "fix_internet_slow"
|
|
8
|
+
- bot_uttered:
|
|
9
|
+
utter_name: "utter_acknowledge_issue"
|
|
10
|
+
- action_executed: action_sleep_few_sec
|
|
11
|
+
- flow_started: "run_diagnostics"
|
|
12
|
+
- bot_uttered:
|
|
13
|
+
utter_name: "utter_communicate_run_diagnosticss"
|
|
14
|
+
- action_executed: "actions_run_speed_test"
|
|
15
|
+
- bot_uttered:
|
|
16
|
+
utter_name: "utter_acknowledge_speed_test_not_good"
|
|
17
|
+
- action_executed: "action_sleep_few_sec"
|
|
18
|
+
- bot_uttered:
|
|
19
|
+
utter_name: "utter_propose_reboot_router_solution"
|
|
20
|
+
- action_executed: "action_sleep_few_sec"
|
|
21
|
+
- flow_started: "reboot_router"
|
|
22
|
+
- bot_uttered:
|
|
23
|
+
utter_name: "utter_explain_reboot_router"
|
|
24
|
+
- bot_uttered:
|
|
25
|
+
utter_name: "utter_ask_reboot_router"
|
|
26
|
+
buttons:
|
|
27
|
+
- title: "Done"
|
|
28
|
+
payload: "/SetSlots(reboot_router=True)"
|
|
29
|
+
- title: "Facing a problem"
|
|
30
|
+
payload: "/SetSlots(reboot_router=False)"
|
|
31
|
+
- user: "/SetSlots(reboot_router=False)"
|
|
32
|
+
assertions:
|
|
33
|
+
- slot_was_set:
|
|
34
|
+
- name: "reboot_router"
|
|
35
|
+
value: False
|
|
36
|
+
- flow_started: "human_handoff"
|
|
37
|
+
#
|
|
38
|
+
- test_case: Solving Internet issue not slow
|
|
39
|
+
steps:
|
|
40
|
+
- user: "Hey, my internet is very slow. What's going on?"
|
|
41
|
+
assertions:
|
|
42
|
+
- flow_started: "fix_internet_slow"
|
|
43
|
+
- bot_uttered:
|
|
44
|
+
utter_name: "utter_acknowledge_issue"
|
|
45
|
+
- action_executed: action_sleep_few_sec
|
|
46
|
+
- flow_started: "run_diagnostics"
|
|
47
|
+
- bot_uttered:
|
|
48
|
+
utter_name: "utter_speed_network_not_issue"
|
|
49
|
+
- action_executed: "actions_run_speed_test"
|
|
50
|
+
- bot_uttered:
|
|
51
|
+
utter_name: "utter_propose_other_solutions"
|
|
52
|
+
- action_executed: "action_sleep_few_sec"
|
|
53
|
+
- bot_uttered:
|
|
54
|
+
utter_name: "utter_ask_more_help_needed"
|
|
55
|
+
- user: "/SetSlots(more_help_needed=True)"
|
|
56
|
+
assertions:
|
|
57
|
+
- flow_started: "human_handoff"
|
|
@@ -145,15 +145,10 @@ class DevelopmentInspectProxy(InputChannel):
|
|
|
145
145
|
state = tracker.current_state(EventVerbosity.AFTER_RESTART)
|
|
146
146
|
return orjson.dumps(state, option=orjson.OPT_SERIALIZE_NUMPY).decode("utf-8")
|
|
147
147
|
|
|
148
|
-
async def on_tracker_updated(
|
|
149
|
-
self, tracker: DialogueStateTracker, latency: Optional[float] = None
|
|
150
|
-
) -> None:
|
|
148
|
+
async def on_tracker_updated(self, tracker: DialogueStateTracker) -> None:
|
|
151
149
|
"""Notifies all clients about tracker updates in real-time."""
|
|
152
150
|
if self.tracker_stream and tracker.sender_id:
|
|
153
151
|
state = tracker.current_state(EventVerbosity.AFTER_RESTART)
|
|
154
|
-
if latency is not None:
|
|
155
|
-
state["latency"] = {"rasa_processing_latency_ms": latency}
|
|
156
|
-
|
|
157
152
|
tracker_dump = orjson.dumps(
|
|
158
153
|
state, option=orjson.OPT_SERIALIZE_NUMPY
|
|
159
154
|
).decode("utf-8")
|
|
@@ -163,28 +158,13 @@ class DevelopmentInspectProxy(InputChannel):
|
|
|
163
158
|
"""Records the start time of a new turn."""
|
|
164
159
|
self._turn_start_times[sender_id] = time.time()
|
|
165
160
|
|
|
166
|
-
async def _broadcast_latency(self, sender_id: Text) -> None:
|
|
167
|
-
"""Broadcasts the tracker with latency of the current turn to all clients."""
|
|
168
|
-
if sender_id not in self._turn_start_times:
|
|
169
|
-
return None
|
|
170
|
-
|
|
171
|
-
latency = (time.time() - self._turn_start_times[sender_id]) * 1000
|
|
172
|
-
# The turn is over, so we can remove the start time
|
|
173
|
-
del self._turn_start_times[sender_id]
|
|
174
|
-
|
|
175
|
-
# broadcast tracker update with latency
|
|
176
|
-
tracker = await self._get_tracker(sender_id)
|
|
177
|
-
await self.on_tracker_updated(tracker, latency)
|
|
178
|
-
|
|
179
161
|
async def on_message_proxy(
|
|
180
162
|
self,
|
|
181
163
|
on_new_message: Callable[["UserMessage"], Awaitable[Any]],
|
|
182
164
|
message: "UserMessage",
|
|
183
165
|
) -> None:
|
|
184
166
|
"""Proxies the on_new_message call to the underlying channel."""
|
|
185
|
-
self._record_turn_start_time(message.sender_id)
|
|
186
167
|
await on_new_message(message)
|
|
187
|
-
await self._broadcast_latency(message.sender_id)
|
|
188
168
|
|
|
189
169
|
@classmethod
|
|
190
170
|
async def serve_inspect_html(cls) -> HTTPResponse:
|
rasa/core/channels/hangouts.py
CHANGED
|
@@ -213,7 +213,7 @@ class HangoutsInput(InputChannel):
|
|
|
213
213
|
# every message. Actual caching depends on response headers.
|
|
214
214
|
# see: https://github.com/googleapis/google-auth-library-python/blob/main/google/oauth2/id_token.py#L15 # noqa: E501
|
|
215
215
|
cached_session = cachecontrol.CacheControl(requests.session())
|
|
216
|
-
self.google_request = google.auth.transport.requests.Request(
|
|
216
|
+
self.google_request = google.auth.transport.requests.Request( # type: ignore[no-untyped-call]
|
|
217
217
|
session=cached_session
|
|
218
218
|
)
|
|
219
219
|
|
|
@@ -267,7 +267,7 @@ class HangoutsInput(InputChannel):
|
|
|
267
267
|
# see https://developers.google.com/chat/how-tos/bots-develop#verifying_bot_authenticity # noqa: E501
|
|
268
268
|
# and https://google-auth.readthedocs.io/en/latest/user-guide.html#identity-tokens # noqa: E501
|
|
269
269
|
try:
|
|
270
|
-
decoded_token = id_token.verify_token(
|
|
270
|
+
decoded_token = id_token.verify_token( # type: ignore[no-untyped-call]
|
|
271
271
|
bot_token,
|
|
272
272
|
self.google_request,
|
|
273
273
|
audience=self.project_id,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{w as ln,c as H}from"./path-53f90ab3.js";import{aw as an,ax as V,ay as D,az as rn,aA as y,V as on,aB as K,aC as _,aD as un,aE as t,aF as sn,aG as tn,aH as fn}from"./index-
|
|
1
|
+
import{w as ln,c as H}from"./path-53f90ab3.js";import{aw as an,ax as V,ay as D,az as rn,aA as y,V as on,aB as K,aC as _,aD as un,aE as t,aF as sn,aG as tn,aH as fn}from"./index-61128091.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function mn(l){return l.endAngle}function pn(l){return l&&l.padAngle}function xn(l,h,z,E,v,A,I,a){var B=z-l,i=E-h,n=I-v,m=a-A,r=m*B-n*i;if(!(r*r<y))return r=(n*(h-A)-m*(l-v))/r,[l+r*B,h+r*i]}function W(l,h,z,E,v,A,I){var a=l-z,B=h-E,i=(I?A:-A)/K(a*a+B*B),n=i*B,m=-i*a,r=l+n,s=h+m,f=z+n,c=E+m,O=(r+f)/2,o=(s+c)/2,p=f-r,g=c-s,R=p*p+g*g,T=v-A,w=r*c-f*s,C=(g<0?-1:1)*K(fn(0,T*T*R-w*w)),F=(w*g-p*C)/R,G=(-w*p-g*C)/R,P=(w*g+p*C)/R,x=(-w*p+g*C)/R,d=F-O,e=G-o,u=P-O,S=x-o;return d*d+e*e>u*u+S*S&&(F=P,G=x),{cx:F,cy:G,x01:-n,y01:-m,x11:F*(v/T-1),y11:G*(v/T-1)}}function vn(){var l=cn,h=yn,z=H(0),E=null,v=gn,A=mn,I=pn,a=null,B=ln(i);function i(){var n,m,r=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-rn,c=A.apply(this,arguments)-rn,O=un(c-f),o=c>f;if(a||(a=n=B()),s<r&&(m=s,s=r,r=m),!(s>y))a.moveTo(0,0);else if(O>on-y)a.moveTo(s*V(f),s*D(f)),a.arc(0,0,s,f,c,!o),r>y&&(a.moveTo(r*V(c),r*D(c)),a.arc(0,0,r,c,f,o));else{var p=f,g=c,R=f,T=c,w=O,C=O,F=I.apply(this,arguments)/2,G=F>y&&(E?+E.apply(this,arguments):K(r*r+s*s)),P=_(un(s-r)/2,+z.apply(this,arguments)),x=P,d=P,e,u;if(G>y){var S=sn(G/r*D(F)),L=sn(G/s*D(F));(w-=S*2)>y?(S*=o?1:-1,R+=S,T-=S):(w=0,R=T=(f+c)/2),(C-=L*2)>y?(L*=o?1:-1,p+=L,g-=L):(C=0,p=g=(f+c)/2)}var j=s*V(p),J=s*D(p),M=r*V(T),N=r*D(T);if(P>y){var Q=s*V(g),U=s*D(g),X=r*V(R),Y=r*D(R),q;if(O<an)if(q=xn(j,J,X,Y,Q,U,M,N)){var Z=j-q[0],$=J-q[1],k=Q-q[0],b=U-q[1],nn=1/D(tn((Z*k+$*b)/(K(Z*Z+$*$)*K(k*k+b*b)))/2),en=K(q[0]*q[0]+q[1]*q[1]);x=_(P,(r-en)/(nn-1)),d=_(P,(s-en)/(nn+1))}else x=d=0}C>y?d>y?(e=W(X,Y,j,J,s,d,o),u=W(Q,U,M,N,s,d,o),a.moveTo(e.cx+e.x01,e.cy+e.y01),d<P?a.arc(e.cx,e.cy,d,t(e.y01,e.x01),t(u.y01,u.x01),!o):(a.arc(e.cx,e.cy,d,t(e.y01,e.x01),t(e.y11,e.x11),!o),a.arc(0,0,s,t(e.cy+e.y11,e.cx+e.x11),t(u.cy+u.y11,u.cx+u.x11),!o),a.arc(u.cx,u.cy,d,t(u.y11,u.x11),t(u.y01,u.x01),!o))):(a.moveTo(j,J),a.arc(0,0,s,p,g,!o)):a.moveTo(j,J),!(r>y)||!(w>y)?a.lineTo(M,N):x>y?(e=W(M,N,Q,U,r,-x,o),u=W(j,J,X,Y,r,-x,o),a.lineTo(e.cx+e.x01,e.cy+e.y01),x<P?a.arc(e.cx,e.cy,x,t(e.y01,e.x01),t(u.y01,u.x01),!o):(a.arc(e.cx,e.cy,x,t(e.y01,e.x01),t(e.y11,e.x11),!o),a.arc(0,0,r,t(e.cy+e.y11,e.cx+e.x11),t(u.cy+u.y11,u.cx+u.x11),o),a.arc(u.cx,u.cy,x,t(u.y11,u.x11),t(u.y01,u.x01),!o))):a.arc(0,0,r,T,R,o)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,m=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[V(m)*n,D(m)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:H(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:H(+n),i):h},i.cornerRadius=function(n){return arguments.length?(z=typeof n=="function"?n:H(+n),i):z},i.padRadius=function(n){return arguments.length?(E=n==null?null:typeof n=="function"?n:H(+n),i):E},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:H(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:H(+n),i):A},i.padAngle=function(n){return arguments.length?(I=typeof n=="function"?n:H(+n),i):I},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{vn as a};
|