rasa-pro 3.13.0a1.dev6__py3-none-any.whl → 3.13.0a1.dev7__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/README.md +120 -0
- rasa/builder/config.py +69 -0
- rasa/builder/create_openai_vector_store.py +204 -45
- rasa/builder/exceptions.py +49 -0
- rasa/builder/llm_service.py +327 -0
- rasa/builder/logging_utils.py +51 -0
- rasa/builder/main.py +61 -0
- rasa/builder/models.py +174 -0
- rasa/builder/project_generator.py +264 -0
- rasa/builder/service.py +447 -0
- rasa/builder/skill_to_bot_prompt.jinja2 +6 -1
- rasa/builder/training_service.py +123 -0
- rasa/builder/validation_service.py +79 -0
- rasa/cli/project_templates/finance/config.yml +17 -0
- rasa/cli/project_templates/finance/credentials.yml +33 -0
- rasa/cli/project_templates/finance/data/flows/transfer_money.yml +5 -0
- rasa/cli/project_templates/finance/data/patterns/pattern_session_start.yml +7 -0
- rasa/cli/project_templates/finance/domain.yml +7 -0
- rasa/cli/project_templates/finance/endpoints.yml +58 -0
- rasa/cli/project_templates/plain/config.yml +17 -0
- rasa/cli/project_templates/plain/credentials.yml +33 -0
- rasa/cli/project_templates/plain/data/patterns/pattern_session_start.yml +7 -0
- rasa/cli/project_templates/plain/domain.yml +5 -0
- rasa/cli/project_templates/plain/endpoints.yml +58 -0
- rasa/cli/project_templates/telecom/config.yml +17 -0
- rasa/cli/project_templates/telecom/credentials.yml +33 -0
- rasa/cli/project_templates/telecom/data/flows/upgrade_contract.yml +5 -0
- rasa/cli/project_templates/telecom/data/patterns/pattern_session_start.yml +7 -0
- rasa/cli/project_templates/telecom/domain.yml +7 -0
- rasa/cli/project_templates/telecom/endpoints.yml +58 -0
- rasa/cli/scaffold.py +19 -3
- rasa/core/actions/action.py +5 -3
- rasa/model_manager/model_api.py +1 -1
- rasa/model_manager/runner_service.py +1 -1
- rasa/model_manager/trainer_service.py +1 -1
- rasa/model_manager/utils.py +1 -29
- rasa/shared/core/domain.py +62 -15
- rasa/shared/core/flows/yaml_flows_io.py +16 -8
- rasa/telemetry.py +2 -1
- rasa/utils/io.py +27 -9
- rasa/version.py +1 -1
- {rasa_pro-3.13.0a1.dev6.dist-info → rasa_pro-3.13.0a1.dev7.dist-info}/METADATA +1 -1
- {rasa_pro-3.13.0a1.dev6.dist-info → rasa_pro-3.13.0a1.dev7.dist-info}/RECORD +46 -19
- rasa/builder/prompt_to_bot.py +0 -650
- {rasa_pro-3.13.0a1.dev6.dist-info → rasa_pro-3.13.0a1.dev7.dist-info}/NOTICE +0 -0
- {rasa_pro-3.13.0a1.dev6.dist-info → rasa_pro-3.13.0a1.dev7.dist-info}/WHEEL +0 -0
- {rasa_pro-3.13.0a1.dev6.dist-info → rasa_pro-3.13.0a1.dev7.dist-info}/entry_points.txt +0 -0
rasa/builder/prompt_to_bot.py
DELETED
|
@@ -1,650 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import collections
|
|
3
|
-
import importlib
|
|
4
|
-
import json
|
|
5
|
-
import logging
|
|
6
|
-
import sys
|
|
7
|
-
import tempfile
|
|
8
|
-
import traceback
|
|
9
|
-
from copy import deepcopy
|
|
10
|
-
from textwrap import dedent
|
|
11
|
-
from typing import Any, Callable, Dict, List, Optional
|
|
12
|
-
|
|
13
|
-
import importlib_resources
|
|
14
|
-
import openai
|
|
15
|
-
import structlog
|
|
16
|
-
from jinja2 import Template
|
|
17
|
-
from pydantic import BaseModel
|
|
18
|
-
from sanic import Sanic, response
|
|
19
|
-
from structlog.testing import capture_logs
|
|
20
|
-
|
|
21
|
-
import rasa.core.utils
|
|
22
|
-
from rasa.builder.llm_context import tracker_as_llm_context
|
|
23
|
-
from rasa.cli.utils import validate_files
|
|
24
|
-
from rasa.constants import PACKAGE_NAME
|
|
25
|
-
from rasa.core import agent, channels
|
|
26
|
-
from rasa.core.channels.channel import InputChannel
|
|
27
|
-
from rasa.core.channels.studio_chat import StudioChatInput
|
|
28
|
-
from rasa.core.utils import AvailableEndpoints, read_endpoints_from_path
|
|
29
|
-
from rasa.model_training import train
|
|
30
|
-
from rasa.server import configure_cors
|
|
31
|
-
from rasa.shared.constants import DOMAIN_SCHEMA_FILE, RESPONSES_SCHEMA_FILE
|
|
32
|
-
from rasa.shared.core.domain import Domain
|
|
33
|
-
from rasa.shared.core.flows.yaml_flows_io import FLOWS_SCHEMA_FILE, YAMLFlowsReader
|
|
34
|
-
from rasa.shared.core.trackers import DialogueStateTracker
|
|
35
|
-
from rasa.shared.importers.importer import TrainingDataImporter
|
|
36
|
-
from rasa.shared.importers.static import StaticTrainingDataImporter
|
|
37
|
-
from rasa.shared.utils.io import read_json_file
|
|
38
|
-
from rasa.shared.utils.yaml import (
|
|
39
|
-
dump_obj_as_yaml_to_string,
|
|
40
|
-
read_schema_file,
|
|
41
|
-
read_yaml,
|
|
42
|
-
read_yaml_file,
|
|
43
|
-
)
|
|
44
|
-
from rasa.utils.common import configure_logging_and_warnings
|
|
45
|
-
from rasa.utils.log_utils import configure_structlog
|
|
46
|
-
from rasa.utils.sanic_error_handler import register_custom_sanic_error_handler
|
|
47
|
-
|
|
48
|
-
structlogger = structlog.get_logger()
|
|
49
|
-
|
|
50
|
-
DEFAULT_SKILL_GENERATION_SYSTEM_PROMPT = importlib.resources.read_text(
|
|
51
|
-
"rasa.builder",
|
|
52
|
-
"skill_to_bot_prompt.jinja2",
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
DEFAULT_LLM_HELPER_SYSTEM_PROMPT = importlib.resources.read_text(
|
|
56
|
-
"rasa.builder",
|
|
57
|
-
"llm_helper_prompt.jinja2",
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
VECTOR_STORE_ID = "vs_685123376e288191a005b6b144d3026f"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
default_credentials_yaml = """
|
|
64
|
-
studio_chat:
|
|
65
|
-
user_message_evt: "user_message"
|
|
66
|
-
bot_message_evt: "bot_message"
|
|
67
|
-
session_persistence: true
|
|
68
|
-
"""
|
|
69
|
-
|
|
70
|
-
# create a dict where we collect most recent logs. only collect the last 30 log lines
|
|
71
|
-
# use a builtin type for this
|
|
72
|
-
recent_logs = collections.deque(maxlen=30)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def collecting_logs_processor(logger, log_level, event_dict):
|
|
76
|
-
if log_level != logging.getLevelName(logging.DEBUG).lower():
|
|
77
|
-
event_message = event_dict.get("event_info") or event_dict.get("event", "")
|
|
78
|
-
recent_logs.append(f"[{log_level}] {event_message}")
|
|
79
|
-
|
|
80
|
-
return event_dict
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
class PromptRequest(BaseModel):
|
|
84
|
-
prompt: str
|
|
85
|
-
client_id: Optional[str] = None
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def default_credentials() -> Dict[str, Any]:
|
|
89
|
-
return read_yaml(default_credentials_yaml)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def default_endpoints() -> Dict[str, Any]:
|
|
93
|
-
return read_yaml_file(
|
|
94
|
-
str(
|
|
95
|
-
importlib_resources.files(PACKAGE_NAME).joinpath(
|
|
96
|
-
"cli/project_templates/default/endpoints.yml"
|
|
97
|
-
)
|
|
98
|
-
)
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def default_config(assistant_id: str) -> Dict[str, Any]:
|
|
103
|
-
base_config = read_yaml_file(
|
|
104
|
-
str(
|
|
105
|
-
importlib_resources.files(PACKAGE_NAME).joinpath(
|
|
106
|
-
"cli/project_templates/default/config.yml"
|
|
107
|
-
)
|
|
108
|
-
)
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
base_config["assistant_id"] = assistant_id
|
|
112
|
-
|
|
113
|
-
return base_config
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
async def continuously_run_task(task: Callable, name: str) -> None:
|
|
117
|
-
"""Regularly run a task."""
|
|
118
|
-
structlogger.debug("prompt_to_bot.continuously_run_task.started", name=name)
|
|
119
|
-
|
|
120
|
-
while True:
|
|
121
|
-
try:
|
|
122
|
-
if asyncio.iscoroutinefunction(task):
|
|
123
|
-
await task()
|
|
124
|
-
else:
|
|
125
|
-
task()
|
|
126
|
-
except asyncio.exceptions.CancelledError:
|
|
127
|
-
structlogger.debug(
|
|
128
|
-
"prompt_to_bot.continuously_run_task.cancelled", name=name
|
|
129
|
-
)
|
|
130
|
-
break
|
|
131
|
-
except Exception as e:
|
|
132
|
-
structlogger.error(
|
|
133
|
-
"prompt_to_bot.continuously_run_task.error", name=name, error=str(e)
|
|
134
|
-
)
|
|
135
|
-
finally:
|
|
136
|
-
await asyncio.sleep(0.1)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
class PromptToBotService:
|
|
140
|
-
def __init__(self):
|
|
141
|
-
self.app = Sanic("PromptToBotService")
|
|
142
|
-
self.app.ctx.agent = None
|
|
143
|
-
self.input_channel = self.setup_input_channel()
|
|
144
|
-
self.setup_routes(self.input_channel)
|
|
145
|
-
self.max_retries = 5
|
|
146
|
-
self.bot_files = {}
|
|
147
|
-
|
|
148
|
-
configure_cors(self.app, cors_origins=["*"])
|
|
149
|
-
|
|
150
|
-
def setup_input_channel(self) -> StudioChatInput:
|
|
151
|
-
studio_chat_credentials = default_credentials().get(StudioChatInput.name())
|
|
152
|
-
return StudioChatInput.from_credentials(credentials=studio_chat_credentials)
|
|
153
|
-
|
|
154
|
-
def setup_routes(self, input_channel: InputChannel):
|
|
155
|
-
self.app.add_route(
|
|
156
|
-
self.handle_prompt_to_bot, "/api/prompt-to-bot", methods=["POST"]
|
|
157
|
-
)
|
|
158
|
-
self.app.add_route(self.get_bot_data, "/api/bot-data", methods=["GET"])
|
|
159
|
-
|
|
160
|
-
self.app.add_route(self.update_bot_data, "/api/bot-data", methods=["PUT"])
|
|
161
|
-
|
|
162
|
-
self.app.add_route(self.llm_builder, "/api/llm-builder", methods=["POST"])
|
|
163
|
-
|
|
164
|
-
self.app.add_route(self.health, "/", methods=["GET"])
|
|
165
|
-
|
|
166
|
-
input_channels = [input_channel]
|
|
167
|
-
channels.channel.register(input_channels, self.app, route="/webhooks/")
|
|
168
|
-
|
|
169
|
-
def health(self, request):
|
|
170
|
-
return response.json({"status": "ok"})
|
|
171
|
-
|
|
172
|
-
def importer_for_data(self) -> TrainingDataImporter:
|
|
173
|
-
return TrainingDataImporter.wrap_in_builtins(
|
|
174
|
-
[
|
|
175
|
-
StaticTrainingDataImporter(
|
|
176
|
-
domain=Domain.from_dict(
|
|
177
|
-
read_yaml(self.bot_files.get("domain.yml", ""))
|
|
178
|
-
),
|
|
179
|
-
flows=YAMLFlowsReader.read_from_string(
|
|
180
|
-
self.bot_files.get("flows.yml", "")
|
|
181
|
-
),
|
|
182
|
-
config=self.config_from_bot_data(),
|
|
183
|
-
)
|
|
184
|
-
]
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
async def validate_rasa_project(self) -> Optional[str]:
|
|
188
|
-
"""Validate the Rasa project data."""
|
|
189
|
-
was_sys_exit_called = {"value": False}
|
|
190
|
-
|
|
191
|
-
def sys_exit_mock(code: int = 0):
|
|
192
|
-
was_sys_exit_called["value"] = True
|
|
193
|
-
|
|
194
|
-
# prevent sys.exit from being called
|
|
195
|
-
original_exit = sys.exit
|
|
196
|
-
# TODO: avoid sys exit in the validation functions in the first place
|
|
197
|
-
sys.exit = sys_exit_mock
|
|
198
|
-
try:
|
|
199
|
-
training_data_importer = self.importer_for_data()
|
|
200
|
-
|
|
201
|
-
with capture_logs() as cap_logs:
|
|
202
|
-
validate_files(
|
|
203
|
-
fail_on_warnings=False,
|
|
204
|
-
max_history=None,
|
|
205
|
-
importer=training_data_importer,
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
if was_sys_exit_called["value"]:
|
|
209
|
-
structlogger.error(
|
|
210
|
-
"prompt_to_bot.validate_rasa_project.failed.sys_exit",
|
|
211
|
-
error_logs=cap_logs,
|
|
212
|
-
)
|
|
213
|
-
return json.dumps(
|
|
214
|
-
[x for x in cap_logs if x.get("log_level") != "debug"]
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
return None
|
|
218
|
-
except Exception as e:
|
|
219
|
-
structlogger.error(
|
|
220
|
-
"prompt_to_bot.validate_rasa_project.failed.exception",
|
|
221
|
-
error=str(e),
|
|
222
|
-
traceback=traceback.format_exc(),
|
|
223
|
-
)
|
|
224
|
-
return str(e)
|
|
225
|
-
finally:
|
|
226
|
-
sys.exit = original_exit
|
|
227
|
-
|
|
228
|
-
async def handle_prompt_to_bot(self, request):
|
|
229
|
-
try:
|
|
230
|
-
prompt_data = PromptRequest(**request.json)
|
|
231
|
-
config = default_config(prompt_data.client_id)
|
|
232
|
-
# Generate Rasa project data with retries
|
|
233
|
-
await self.generate_rasa_project_with_retries(
|
|
234
|
-
prompt_data.prompt,
|
|
235
|
-
config,
|
|
236
|
-
self.max_retries,
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
self.app.ctx.agent = await self.train_and_load_agent()
|
|
240
|
-
|
|
241
|
-
return response.json(
|
|
242
|
-
{
|
|
243
|
-
"bot_data": self.bot_files,
|
|
244
|
-
"status": "success",
|
|
245
|
-
}
|
|
246
|
-
)
|
|
247
|
-
|
|
248
|
-
except Exception as e:
|
|
249
|
-
structlogger.error("prompt_to_bot.error", error=str(e))
|
|
250
|
-
return response.json({"error": str(e)}, status=500)
|
|
251
|
-
|
|
252
|
-
async def get_bot_data(self, request):
|
|
253
|
-
return response.json(self.bot_files)
|
|
254
|
-
|
|
255
|
-
async def update_bot_data(self, request):
|
|
256
|
-
response = await request.respond(content_type="text/event-stream")
|
|
257
|
-
|
|
258
|
-
def sse_event(event, data):
|
|
259
|
-
return f"event: {event}\ndata: {json.dumps(data)}\n\n"
|
|
260
|
-
|
|
261
|
-
# 1. Received
|
|
262
|
-
await response.send(sse_event("received", {"status": "received"}))
|
|
263
|
-
|
|
264
|
-
bot_data = request.json
|
|
265
|
-
for file_name, file_content in bot_data.items():
|
|
266
|
-
self.bot_files[file_name] = file_content
|
|
267
|
-
|
|
268
|
-
# 2. Validating
|
|
269
|
-
await response.send(sse_event("validating", {"status": "validating"}))
|
|
270
|
-
try:
|
|
271
|
-
await self.validate_rasa_project()
|
|
272
|
-
await response.send(
|
|
273
|
-
sse_event("validation_success", {"status": "validation_success"})
|
|
274
|
-
)
|
|
275
|
-
except Exception as e:
|
|
276
|
-
structlogger.error(
|
|
277
|
-
"prompt_to_bot.update_bot_data.validation_error",
|
|
278
|
-
error=str(e),
|
|
279
|
-
event_info="Failed to validate the Rasa project. Error: " + str(e),
|
|
280
|
-
)
|
|
281
|
-
await response.send(
|
|
282
|
-
sse_event(
|
|
283
|
-
"validation_error",
|
|
284
|
-
{"status": "validation_error", "error": str(e)},
|
|
285
|
-
)
|
|
286
|
-
)
|
|
287
|
-
await response.eof()
|
|
288
|
-
return
|
|
289
|
-
|
|
290
|
-
# 3. Training
|
|
291
|
-
await response.send(sse_event("training", {"status": "training"}))
|
|
292
|
-
try:
|
|
293
|
-
self.app.ctx.agent = await self.train_and_load_agent()
|
|
294
|
-
await response.send(sse_event("train_success", {"status": "train_success"}))
|
|
295
|
-
except Exception as e:
|
|
296
|
-
structlogger.error(
|
|
297
|
-
"prompt_to_bot.update_bot_data.train_error",
|
|
298
|
-
error=str(e),
|
|
299
|
-
event_info="Failed to train the agent. Error: " + str(e),
|
|
300
|
-
)
|
|
301
|
-
await response.send(
|
|
302
|
-
sse_event("train_error", {"status": "train_error", "error": str(e)})
|
|
303
|
-
)
|
|
304
|
-
await response.eof()
|
|
305
|
-
return
|
|
306
|
-
|
|
307
|
-
# 4. Done
|
|
308
|
-
await response.send(
|
|
309
|
-
sse_event("done", {"status": "done", "bot_data": self.bot_files})
|
|
310
|
-
)
|
|
311
|
-
await response.eof()
|
|
312
|
-
|
|
313
|
-
def config_from_bot_data(self) -> Dict[str, Any]:
|
|
314
|
-
return read_yaml(self.bot_files.get("config.yml", ""))
|
|
315
|
-
|
|
316
|
-
def update_stored_bot_data(self, bot_data: Dict[str, Any], config: Dict[str, Any]):
|
|
317
|
-
self.bot_files = {
|
|
318
|
-
"domain.yml": dump_obj_as_yaml_to_string(bot_data["domain"]),
|
|
319
|
-
"flows.yml": dump_obj_as_yaml_to_string(bot_data["flows"]),
|
|
320
|
-
"config.yml": dump_obj_as_yaml_to_string(config),
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
async def generate_rasa_project_with_retries(
|
|
324
|
-
self, skill_description: str, config: Dict[str, Any], max_retry_count: int = 5
|
|
325
|
-
) -> Dict[str, Any]:
|
|
326
|
-
"""Generate Rasa project data with retry logic."""
|
|
327
|
-
initial_messages = self.prompt_messages(skill_description)
|
|
328
|
-
|
|
329
|
-
async def _generate(messages: List[Dict[str, Any]], tries_left: int):
|
|
330
|
-
rasa_project_data = await self.generate_rasa_project(messages)
|
|
331
|
-
self.update_stored_bot_data(rasa_project_data, config)
|
|
332
|
-
|
|
333
|
-
structlogger.info(
|
|
334
|
-
"prompt_to_bot.generate_rasa_project_with_retries.generated_project",
|
|
335
|
-
tries_left=tries_left,
|
|
336
|
-
)
|
|
337
|
-
|
|
338
|
-
try:
|
|
339
|
-
validation_error = await self.validate_rasa_project()
|
|
340
|
-
|
|
341
|
-
if validation_error:
|
|
342
|
-
structlogger.error(
|
|
343
|
-
"prompt_to_bot.generate_rasa_project_with_retries.validation_error",
|
|
344
|
-
validation_error=validation_error,
|
|
345
|
-
)
|
|
346
|
-
raise Exception(validation_error)
|
|
347
|
-
|
|
348
|
-
structlogger.info(
|
|
349
|
-
"prompt_to_bot.generate_rasa_project_with_retries.validation_success",
|
|
350
|
-
tries_left=tries_left,
|
|
351
|
-
)
|
|
352
|
-
|
|
353
|
-
return rasa_project_data
|
|
354
|
-
except Exception as e:
|
|
355
|
-
structlogger.error(
|
|
356
|
-
"prompt_to_bot.generate_rasa_project_with_retries.error",
|
|
357
|
-
error=str(e),
|
|
358
|
-
tries_left=tries_left,
|
|
359
|
-
)
|
|
360
|
-
|
|
361
|
-
if tries_left <= 0:
|
|
362
|
-
raise Exception(
|
|
363
|
-
f"Failed to generate valid Rasa project after "
|
|
364
|
-
f"{max_retry_count} attempts"
|
|
365
|
-
)
|
|
366
|
-
|
|
367
|
-
# Use error to improve the prompt
|
|
368
|
-
messages = messages + [
|
|
369
|
-
{
|
|
370
|
-
"role": "assistant",
|
|
371
|
-
"content": json.dumps(rasa_project_data),
|
|
372
|
-
},
|
|
373
|
-
{
|
|
374
|
-
"role": "user",
|
|
375
|
-
"content": dedent(f"""
|
|
376
|
-
Previous attempt failed with error: {e!s}
|
|
377
|
-
|
|
378
|
-
Please fix the issues and generate a valid Rasa project.
|
|
379
|
-
"""),
|
|
380
|
-
},
|
|
381
|
-
]
|
|
382
|
-
|
|
383
|
-
return await _generate(messages, tries_left - 1)
|
|
384
|
-
|
|
385
|
-
return await _generate(initial_messages, max_retry_count)
|
|
386
|
-
|
|
387
|
-
def prompt_messages(self, skill_description: str) -> List[Dict[str, Any]]:
|
|
388
|
-
system_prompt = Template(DEFAULT_SKILL_GENERATION_SYSTEM_PROMPT).render(
|
|
389
|
-
skill_description=skill_description
|
|
390
|
-
)
|
|
391
|
-
|
|
392
|
-
return [
|
|
393
|
-
{"role": "system", "content": system_prompt},
|
|
394
|
-
]
|
|
395
|
-
|
|
396
|
-
async def generate_rasa_project(
|
|
397
|
-
self, messages: List[Dict[str, Any]]
|
|
398
|
-
) -> Dict[str, Any]:
|
|
399
|
-
"""Generate Rasa project data using LLM."""
|
|
400
|
-
schema_file = str(
|
|
401
|
-
importlib_resources.files(PACKAGE_NAME).joinpath(FLOWS_SCHEMA_FILE)
|
|
402
|
-
)
|
|
403
|
-
|
|
404
|
-
# TODO: clean up the schema
|
|
405
|
-
flows_schema = deepcopy(read_json_file(schema_file))
|
|
406
|
-
|
|
407
|
-
del flows_schema["$defs"]["flow"]["properties"]["nlu_trigger"]
|
|
408
|
-
|
|
409
|
-
# TODO: restrict the domain schema to only the properties that are
|
|
410
|
-
# needed for the CALM bot
|
|
411
|
-
domain_schema = deepcopy(
|
|
412
|
-
read_schema_file(DOMAIN_SCHEMA_FILE, PACKAGE_NAME, False)
|
|
413
|
-
)
|
|
414
|
-
|
|
415
|
-
# not needed in calm
|
|
416
|
-
del domain_schema["mapping"]["intents"]
|
|
417
|
-
del domain_schema["mapping"]["entities"]
|
|
418
|
-
del domain_schema["mapping"]["forms"]
|
|
419
|
-
|
|
420
|
-
# don't think the llm needs to configure these
|
|
421
|
-
del domain_schema["mapping"]["config"]
|
|
422
|
-
del domain_schema["mapping"]["session_config"]
|
|
423
|
-
|
|
424
|
-
# don't work, llm tends to pick from_intent or something like that
|
|
425
|
-
del domain_schema["mapping"]["slots"]["mapping"]["regex;([A-Za-z]+)"][
|
|
426
|
-
"mapping"
|
|
427
|
-
]["mappings"]
|
|
428
|
-
# also creates issues...
|
|
429
|
-
del domain_schema["mapping"]["slots"]["mapping"]["regex;([A-Za-z]+)"][
|
|
430
|
-
"mapping"
|
|
431
|
-
]["validation"]
|
|
432
|
-
|
|
433
|
-
# pull in the responses schema
|
|
434
|
-
domain_schema["mapping"]["responses"] = read_schema_file(
|
|
435
|
-
RESPONSES_SCHEMA_FILE, PACKAGE_NAME, False
|
|
436
|
-
)["schema;responses"]
|
|
437
|
-
|
|
438
|
-
client = openai.AsyncOpenAI()
|
|
439
|
-
response = await client.chat.completions.create(
|
|
440
|
-
model="gpt-4.1-2025-04-14",
|
|
441
|
-
messages=messages,
|
|
442
|
-
temperature=0.7,
|
|
443
|
-
response_format={
|
|
444
|
-
"type": "json_schema",
|
|
445
|
-
"json_schema": {
|
|
446
|
-
"name": "rasa_project",
|
|
447
|
-
"schema": {
|
|
448
|
-
"type": "object",
|
|
449
|
-
"properties": {
|
|
450
|
-
"domain": domain_schema,
|
|
451
|
-
"flows": flows_schema,
|
|
452
|
-
},
|
|
453
|
-
"required": ["domain", "flows"],
|
|
454
|
-
},
|
|
455
|
-
},
|
|
456
|
-
},
|
|
457
|
-
)
|
|
458
|
-
|
|
459
|
-
try:
|
|
460
|
-
return json.loads(response.choices[0].message.content)
|
|
461
|
-
except json.JSONDecodeError:
|
|
462
|
-
raise Exception("LLM response was not valid JSON")
|
|
463
|
-
|
|
464
|
-
async def generate_chat_bot_context(self) -> str:
|
|
465
|
-
"""Generate a chat bot context."""
|
|
466
|
-
if self.app.ctx.agent and self.input_channel.latest_tracker_session_id:
|
|
467
|
-
tracker: Optional[
|
|
468
|
-
DialogueStateTracker
|
|
469
|
-
] = await self.app.ctx.agent.tracker_store.retrieve(
|
|
470
|
-
self.input_channel.latest_tracker_session_id
|
|
471
|
-
)
|
|
472
|
-
return tracker_as_llm_context(tracker)
|
|
473
|
-
else:
|
|
474
|
-
return tracker_as_llm_context(None)
|
|
475
|
-
|
|
476
|
-
def format_chat_dump(self, user_chat_history: List[Dict[str, Any]]) -> str:
|
|
477
|
-
"""Format the chat dump for the LLM."""
|
|
478
|
-
result = ""
|
|
479
|
-
for message in user_chat_history:
|
|
480
|
-
if message.get("type") == "user":
|
|
481
|
-
result += f"User: {message.get('content')}\n"
|
|
482
|
-
else:
|
|
483
|
-
for part in message.get("content", []):
|
|
484
|
-
if part.get("type") == "text":
|
|
485
|
-
result += f"Assistant: {part.get('text')}\n"
|
|
486
|
-
return result
|
|
487
|
-
|
|
488
|
-
def llm_helper_prompt_messages(
|
|
489
|
-
self,
|
|
490
|
-
current_conversation: str,
|
|
491
|
-
bot_logs: str,
|
|
492
|
-
chat_bot_files: Dict[str, str],
|
|
493
|
-
documentation_results: str,
|
|
494
|
-
) -> List[Dict[str, Any]]:
|
|
495
|
-
system_prompt = Template(DEFAULT_LLM_HELPER_SYSTEM_PROMPT).render(
|
|
496
|
-
current_conversation=current_conversation,
|
|
497
|
-
bot_logs=bot_logs,
|
|
498
|
-
chat_bot_files=chat_bot_files,
|
|
499
|
-
documentation_results=documentation_results,
|
|
500
|
-
)
|
|
501
|
-
|
|
502
|
-
return [
|
|
503
|
-
{"role": "system", "content": system_prompt},
|
|
504
|
-
]
|
|
505
|
-
|
|
506
|
-
async def llm_builder(self, request):
|
|
507
|
-
current_conversation = await self.generate_chat_bot_context()
|
|
508
|
-
bot_logs = "\n".join(recent_logs)
|
|
509
|
-
chat_bot_files = self.bot_files
|
|
510
|
-
user_chat_history = request.json.get("messages", [])
|
|
511
|
-
chat_dump = self.format_chat_dump(user_chat_history)
|
|
512
|
-
|
|
513
|
-
client = openai.AsyncOpenAI()
|
|
514
|
-
|
|
515
|
-
results = await client.vector_stores.search(
|
|
516
|
-
vector_store_id=VECTOR_STORE_ID,
|
|
517
|
-
query=chat_dump,
|
|
518
|
-
max_num_results=10,
|
|
519
|
-
rewrite_query=True,
|
|
520
|
-
)
|
|
521
|
-
|
|
522
|
-
documentation_results = self.format_results(results.data)
|
|
523
|
-
|
|
524
|
-
messages = self.llm_helper_prompt_messages(
|
|
525
|
-
current_conversation,
|
|
526
|
-
bot_logs,
|
|
527
|
-
chat_bot_files,
|
|
528
|
-
documentation_results,
|
|
529
|
-
)
|
|
530
|
-
|
|
531
|
-
for message in user_chat_history:
|
|
532
|
-
messages.append(
|
|
533
|
-
{
|
|
534
|
-
"role": "user" if message.get("type") == "user" else "assistant",
|
|
535
|
-
"content": json.dumps(message.get("content")),
|
|
536
|
-
}
|
|
537
|
-
)
|
|
538
|
-
|
|
539
|
-
llm_helper_schema = read_json_file(
|
|
540
|
-
importlib_resources.files(PACKAGE_NAME).joinpath(
|
|
541
|
-
"builder/llm-helper-schema.json"
|
|
542
|
-
)
|
|
543
|
-
)
|
|
544
|
-
|
|
545
|
-
openai_response = await client.chat.completions.create(
|
|
546
|
-
model="gpt-4.1-2025-04-14",
|
|
547
|
-
messages=messages,
|
|
548
|
-
response_format={
|
|
549
|
-
"type": "json_schema",
|
|
550
|
-
"json_schema": {
|
|
551
|
-
"name": "llm_helper",
|
|
552
|
-
"schema": llm_helper_schema,
|
|
553
|
-
},
|
|
554
|
-
},
|
|
555
|
-
)
|
|
556
|
-
|
|
557
|
-
return response.json(json.loads(openai_response.choices[0].message.content))
|
|
558
|
-
|
|
559
|
-
@staticmethod
|
|
560
|
-
def format_results(results):
|
|
561
|
-
formatted_results = ""
|
|
562
|
-
for result in results:
|
|
563
|
-
formatted_result = f"<result url='{result.attributes.get('url')}'>"
|
|
564
|
-
for part in result.content:
|
|
565
|
-
formatted_result += f"<content>{part.text}</content>"
|
|
566
|
-
formatted_results += formatted_result + "</result>"
|
|
567
|
-
return f"<sources>{formatted_results}</sources>"
|
|
568
|
-
|
|
569
|
-
async def train_and_load_agent(self):
|
|
570
|
-
file_importer = self.importer_for_data()
|
|
571
|
-
# this is used inside the training validation. validation assumes
|
|
572
|
-
# that the endpoints are either stored in the default location or
|
|
573
|
-
# that they have been loaded before - so that is what we do here.
|
|
574
|
-
with tempfile.NamedTemporaryFile() as temp_file:
|
|
575
|
-
temp_file.write(
|
|
576
|
-
dump_obj_as_yaml_to_string(default_endpoints()).encode("utf-8")
|
|
577
|
-
)
|
|
578
|
-
temp_file.flush()
|
|
579
|
-
AvailableEndpoints.reset_instance()
|
|
580
|
-
read_endpoints_from_path(temp_file.name)
|
|
581
|
-
|
|
582
|
-
available_endpoints = AvailableEndpoints.get_instance()
|
|
583
|
-
assert available_endpoints is not None
|
|
584
|
-
|
|
585
|
-
training_result = await train(
|
|
586
|
-
domain="",
|
|
587
|
-
config="",
|
|
588
|
-
training_files=None,
|
|
589
|
-
file_importer=file_importer,
|
|
590
|
-
)
|
|
591
|
-
|
|
592
|
-
structlogger.info(
|
|
593
|
-
"prompt_to_bot.train_and_load_agent.training_result",
|
|
594
|
-
training_result=training_result,
|
|
595
|
-
)
|
|
596
|
-
|
|
597
|
-
agent_instance = await agent.load_agent(
|
|
598
|
-
model_path=training_result.model,
|
|
599
|
-
remote_storage=None,
|
|
600
|
-
endpoints=available_endpoints,
|
|
601
|
-
loop=self.app.loop,
|
|
602
|
-
)
|
|
603
|
-
|
|
604
|
-
structlogger.info(
|
|
605
|
-
"prompt_to_bot.train_and_load_agent.agent_instance",
|
|
606
|
-
agent_instance=agent_instance,
|
|
607
|
-
)
|
|
608
|
-
|
|
609
|
-
if not agent_instance.is_ready():
|
|
610
|
-
raise Exception(
|
|
611
|
-
"Generation of the chatbot failed with an error (model failed "
|
|
612
|
-
"to load). Please try again."
|
|
613
|
-
)
|
|
614
|
-
|
|
615
|
-
structlogger.info(
|
|
616
|
-
"prompt_to_bot.train_and_load_agent.agent_ready",
|
|
617
|
-
agent_instance=agent_instance,
|
|
618
|
-
)
|
|
619
|
-
|
|
620
|
-
self.input_channel.agent = agent_instance
|
|
621
|
-
return agent_instance
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
def main():
|
|
625
|
-
"""Start the Prompt to Bot service."""
|
|
626
|
-
log_level = logging.DEBUG
|
|
627
|
-
configure_logging_and_warnings(
|
|
628
|
-
log_level=log_level,
|
|
629
|
-
logging_config_file=None,
|
|
630
|
-
warn_only_once=True,
|
|
631
|
-
filter_repeated_logs=True,
|
|
632
|
-
)
|
|
633
|
-
configure_structlog(
|
|
634
|
-
log_level,
|
|
635
|
-
include_time=True,
|
|
636
|
-
additional_processors=[
|
|
637
|
-
collecting_logs_processor,
|
|
638
|
-
],
|
|
639
|
-
)
|
|
640
|
-
|
|
641
|
-
service = PromptToBotService()
|
|
642
|
-
register_custom_sanic_error_handler(service.app)
|
|
643
|
-
|
|
644
|
-
rasa.core.utils.list_routes(service.app)
|
|
645
|
-
|
|
646
|
-
service.app.run(host="0.0.0.0", port=5005, legacy=True, motd=False)
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
if __name__ == "__main__":
|
|
650
|
-
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|