rasa-pro 3.14.0rc2__py3-none-any.whl → 3.14.0rc3__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/agents/protocol/a2a/a2a_agent.py +50 -42
- rasa/agents/utils.py +27 -5
- rasa/agents/validation.py +7 -9
- rasa/api.py +1 -2
- rasa/builder/copilot/copilot.py +37 -1
- rasa/builder/copilot/models.py +43 -49
- rasa/builder/copilot/prompts/copilot_system_prompt.jinja2 +33 -12
- rasa/builder/copilot/prompts/latest_user_message_context_prompt.jinja2 +59 -29
- rasa/builder/copilot/telemetry.py +8 -0
- rasa/builder/service.py +1 -0
- rasa/cli/dialogue_understanding_test.py +1 -0
- rasa/cli/e2e_test.py +1 -0
- rasa/cli/inspect.py +1 -0
- rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/feedback.yml +46 -0
- rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/goodbye.yml +9 -0
- rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/help.yml +8 -0
- rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/human_handoff.yml +41 -0
- rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/patterns.yml +32 -0
- rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/show_faqs.yml +8 -0
- rasa/cli/project_templates/telco/data/network/flow_solve_internet_issue.yml +2 -2
- rasa/cli/project_templates/telco/domain/network/solve_internet_issue.yml +1 -2
- rasa/cli/project_templates/telco/tests/e2e_test_cases/with_stub/network/solve_internet_not_slow.yml +33 -0
- rasa/cli/project_templates/telco/tests/e2e_test_cases/with_stub/network/solve_internet_slow.yml +47 -0
- rasa/cli/project_templates/telco/tests/e2e_test_cases/without_stub/general/hello.yml +8 -0
- rasa/cli/run.py +1 -5
- rasa/cli/shell.py +1 -0
- rasa/cli/train.py +1 -0
- rasa/cli/validation/bot_config.py +7 -2
- rasa/core/available_agents.py +65 -55
- rasa/core/config/available_endpoints.py +0 -3
- rasa/core/config/configuration.py +36 -1
- rasa/core/policies/flows/agent_executor.py +16 -8
- rasa/dialogue_understanding/commands/start_flow_command.py +10 -3
- rasa/dialogue_understanding/commands/utils.py +15 -4
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +4 -2
- rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +4 -4
- rasa/dialogue_understanding/generator/single_step/search_ready_llm_command_generator.py +4 -4
- rasa/dialogue_understanding/generator/single_step/single_step_based_llm_command_generator.py +2 -2
- rasa/dialogue_understanding_test/du_test_runner.py +2 -2
- rasa/e2e_test/e2e_test_runner.py +2 -2
- rasa/shared/agents/auth/auth_strategy/oauth2_auth_strategy.py +10 -4
- rasa/shared/agents/auth/constants.py +1 -0
- rasa/shared/core/flows/steps/call.py +2 -2
- rasa/telemetry.py +3 -3
- rasa/validator.py +37 -0
- rasa/version.py +1 -1
- {rasa_pro-3.14.0rc2.dist-info → rasa_pro-3.14.0rc3.dist-info}/METADATA +13 -2
- {rasa_pro-3.14.0rc2.dist-info → rasa_pro-3.14.0rc3.dist-info}/RECORD +68 -60
- rasa/cli/project_templates/telco/tests/e2e_test_cases/network/solve_internet_issue.yml +0 -57
- /rasa/cli/project_templates/{finance/tests/e2e_test_cases → basic/tests/e2e_test_cases/without_stub}/general/hello.yml +0 -0
- /rasa/cli/project_templates/finance/tests/e2e_test_cases/{accounts → without_stub/accounts}/check_balance.yml +0 -0
- /rasa/cli/project_templates/finance/tests/e2e_test_cases/{accounts → without_stub/accounts}/download_statements.yml +0 -0
- /rasa/cli/project_templates/finance/tests/e2e_test_cases/{cards → without_stub/cards}/block_card.yml +0 -0
- /rasa/cli/project_templates/finance/tests/e2e_test_cases/{general → without_stub/general}/bot_challenge.yml +0 -0
- /rasa/cli/project_templates/finance/tests/e2e_test_cases/{general → without_stub/general}/feedback.yml +0 -0
- /rasa/cli/project_templates/finance/tests/e2e_test_cases/{general → without_stub/general}/goodbye.yml +0 -0
- /rasa/cli/project_templates/{telco/tests/e2e_test_cases → finance/tests/e2e_test_cases/without_stub}/general/hello.yml +0 -0
- /rasa/cli/project_templates/finance/tests/e2e_test_cases/{general → without_stub/general}/human_handoff.yml +0 -0
- /rasa/cli/project_templates/finance/tests/e2e_test_cases/{general → without_stub/general}/patterns.yml +0 -0
- /rasa/cli/project_templates/finance/tests/e2e_test_cases/{transfers → without_stub/transfers}/transfer_money.yml +0 -0
- /rasa/cli/project_templates/telco/tests/e2e_test_cases/{billing → without_stub/billing}/understand_bill.yml +0 -0
- /rasa/cli/project_templates/telco/tests/e2e_test_cases/{general → without_stub/general}/bot_challenge.yml +0 -0
- /rasa/cli/project_templates/telco/tests/e2e_test_cases/{general → without_stub/general}/feedback.yml +0 -0
- /rasa/cli/project_templates/telco/tests/e2e_test_cases/{general → without_stub/general}/goodbye.yml +0 -0
- /rasa/cli/project_templates/telco/tests/e2e_test_cases/{general → without_stub/general}/human_handoff.yml +0 -0
- /rasa/cli/project_templates/telco/tests/e2e_test_cases/{general → without_stub/general}/patterns.yml +0 -0
- {rasa_pro-3.14.0rc2.dist-info → rasa_pro-3.14.0rc3.dist-info}/NOTICE +0 -0
- {rasa_pro-3.14.0rc2.dist-info → rasa_pro-3.14.0rc3.dist-info}/WHEEL +0 -0
- {rasa_pro-3.14.0rc2.dist-info → rasa_pro-3.14.0rc3.dist-info}/entry_points.txt +0 -0
|
@@ -3,6 +3,7 @@ import json
|
|
|
3
3
|
import os
|
|
4
4
|
import time
|
|
5
5
|
import uuid
|
|
6
|
+
from contextlib import aclosing
|
|
6
7
|
from typing import Any, ClassVar, Dict, List, Optional
|
|
7
8
|
from urllib.parse import urlparse
|
|
8
9
|
|
|
@@ -168,8 +169,7 @@ class A2AAgent(AgentProtocol):
|
|
|
168
169
|
error=str(exception),
|
|
169
170
|
)
|
|
170
171
|
raise AgentInitializationException(
|
|
171
|
-
f"Failed to initialize A2A client for agent "
|
|
172
|
-
f"'{self._name}': {exception}"
|
|
172
|
+
f"Failed to initialize A2A client for agent '{self._name}': {exception}"
|
|
173
173
|
) from exception
|
|
174
174
|
|
|
175
175
|
await self._perform_health_check()
|
|
@@ -215,21 +215,26 @@ class A2AAgent(AgentProtocol):
|
|
|
215
215
|
task_id: Optional[str] = None
|
|
216
216
|
events_received = 0
|
|
217
217
|
try:
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
218
|
+
# Use aclosing to ensure proper cleanup of the async generator
|
|
219
|
+
stream = self._client.send_message(message)
|
|
220
|
+
async with aclosing(stream) as stream: # type: ignore[type-var]
|
|
221
|
+
async for event in stream:
|
|
222
|
+
events_received += 1
|
|
223
|
+
agent_output = self._handle_send_message_response(
|
|
224
|
+
agent_input, event
|
|
225
|
+
)
|
|
226
|
+
if agent_output is not None:
|
|
227
|
+
return agent_output
|
|
228
|
+
else:
|
|
229
|
+
# Not a terminal response, save taskID (in case that's the only
|
|
230
|
+
# event, and we need to pool) and continue waiting for events
|
|
231
|
+
if (
|
|
232
|
+
isinstance(event, tuple)
|
|
233
|
+
and len(event) == 2
|
|
234
|
+
and isinstance(event[0], Task)
|
|
235
|
+
):
|
|
236
|
+
task_id = event[0].id
|
|
237
|
+
continue
|
|
233
238
|
except A2AClientJSONRPCError as e:
|
|
234
239
|
return self._handle_json_rpc_error_response(agent_input, e.error)
|
|
235
240
|
except A2AClientError as exception:
|
|
@@ -833,37 +838,40 @@ class A2AAgent(AgentProtocol):
|
|
|
833
838
|
parts=[Part(root=TextPart(text="hello"))],
|
|
834
839
|
message_id=str(uuid.uuid4()),
|
|
835
840
|
)
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
841
|
+
# Use aclosing to ensure proper cleanup of the async generator
|
|
842
|
+
stream = self._client.send_message(test_message)
|
|
843
|
+
async with aclosing(stream) as stream: # type: ignore[type-var]
|
|
844
|
+
async for event in stream:
|
|
845
|
+
if (
|
|
846
|
+
isinstance(event, Message)
|
|
847
|
+
or isinstance(event, tuple)
|
|
848
|
+
and len(event) == 2
|
|
849
|
+
and isinstance(event[0], Task)
|
|
850
|
+
):
|
|
851
|
+
# We got a valid response, health check succeeded
|
|
852
|
+
return
|
|
845
853
|
|
|
846
|
-
|
|
854
|
+
event_info = "Unexpected response type during health check"
|
|
855
|
+
structlogger.error(
|
|
856
|
+
"a2a_agent.health_check.unexpected_response",
|
|
857
|
+
event_info=event_info,
|
|
858
|
+
agent_name=self._name,
|
|
859
|
+
response=event,
|
|
860
|
+
url=str(self.agent_card.url),
|
|
861
|
+
)
|
|
862
|
+
raise AgentInitializationException(f"{event_info}: {event}")
|
|
863
|
+
# If the loop completes with no return, no events were received
|
|
864
|
+
event_info = (
|
|
865
|
+
f"Health check failed for A2A agent '{self._name}' "
|
|
866
|
+
f"at {self.agent_card.url}: no events received"
|
|
867
|
+
)
|
|
847
868
|
structlogger.error(
|
|
848
|
-
"a2a_agent.health_check.
|
|
869
|
+
"a2a_agent.health_check.no_events",
|
|
849
870
|
event_info=event_info,
|
|
850
871
|
agent_name=self._name,
|
|
851
|
-
response=event,
|
|
852
872
|
url=str(self.agent_card.url),
|
|
853
873
|
)
|
|
854
|
-
raise AgentInitializationException(
|
|
855
|
-
# If the loop completes with no return, no events were received
|
|
856
|
-
event_info = (
|
|
857
|
-
f"Health check failed for A2A agent '{self._name}' "
|
|
858
|
-
f"at {self.agent_card.url}: no events received"
|
|
859
|
-
)
|
|
860
|
-
structlogger.error(
|
|
861
|
-
"a2a_agent.health_check.no_events",
|
|
862
|
-
event_info=event_info,
|
|
863
|
-
agent_name=self._name,
|
|
864
|
-
url=str(self.agent_card.url),
|
|
865
|
-
)
|
|
866
|
-
raise AgentInitializationException(event_info)
|
|
874
|
+
raise AgentInitializationException(event_info)
|
|
867
875
|
except Exception as exception:
|
|
868
876
|
event_info = (
|
|
869
877
|
f"Health check failed for A2A agent '{self._name}' at "
|
rasa/agents/utils.py
CHANGED
|
@@ -127,7 +127,9 @@ def is_agent_valid(agent_id: str) -> bool:
|
|
|
127
127
|
Returns:
|
|
128
128
|
True if the agent exists, False otherwise.
|
|
129
129
|
"""
|
|
130
|
-
agent_config =
|
|
130
|
+
agent_config = Configuration.get_instance().available_agents.get_agent_config(
|
|
131
|
+
agent_id
|
|
132
|
+
)
|
|
131
133
|
return agent_config is not None
|
|
132
134
|
|
|
133
135
|
|
|
@@ -159,7 +161,9 @@ def get_agent_info(agent_id: str) -> Optional[Dict[str, str]]:
|
|
|
159
161
|
Returns:
|
|
160
162
|
Dictionary with agent name and description if found, None otherwise.
|
|
161
163
|
"""
|
|
162
|
-
agent_config =
|
|
164
|
+
agent_config = Configuration.get_instance().available_agents.get_agent_config(
|
|
165
|
+
agent_id
|
|
166
|
+
)
|
|
163
167
|
if agent_config is None:
|
|
164
168
|
return None
|
|
165
169
|
|
|
@@ -170,7 +174,7 @@ def get_agent_info(agent_id: str) -> Optional[Dict[str, str]]:
|
|
|
170
174
|
|
|
171
175
|
|
|
172
176
|
def get_completed_agents_info(tracker: DialogueStateTracker) -> List[Dict[str, str]]:
|
|
173
|
-
"""Get information for all completed agents.
|
|
177
|
+
"""Get information for all completed agents in the currently active flow.
|
|
174
178
|
|
|
175
179
|
Args:
|
|
176
180
|
tracker: The dialogue state tracker.
|
|
@@ -178,12 +182,30 @@ def get_completed_agents_info(tracker: DialogueStateTracker) -> List[Dict[str, s
|
|
|
178
182
|
Returns:
|
|
179
183
|
List of dictionaries containing agent information for completed agents.
|
|
180
184
|
"""
|
|
185
|
+
from rasa.dialogue_understanding.stack.utils import top_user_flow_frame
|
|
186
|
+
|
|
187
|
+
# Get the currently active flow
|
|
188
|
+
top_flow_frame = top_user_flow_frame(tracker.stack)
|
|
189
|
+
if not top_flow_frame:
|
|
190
|
+
# No active flow, return empty list
|
|
191
|
+
return []
|
|
192
|
+
|
|
193
|
+
current_flow_id = top_flow_frame.flow_id
|
|
181
194
|
completed_agents = []
|
|
195
|
+
|
|
196
|
+
# Get all agents that completed in the current flow
|
|
197
|
+
agents_completed_in_current_flow = set()
|
|
182
198
|
for event in reversed(tracker.events):
|
|
183
|
-
if isinstance(event, AgentCompleted):
|
|
184
|
-
|
|
199
|
+
if isinstance(event, AgentCompleted) and event.flow_id == current_flow_id:
|
|
200
|
+
agents_completed_in_current_flow.add(event.agent_id)
|
|
201
|
+
|
|
202
|
+
# Only include agents that are completed (not currently running)
|
|
203
|
+
for agent_id in agents_completed_in_current_flow:
|
|
204
|
+
if is_agent_completed(tracker, agent_id):
|
|
205
|
+
agent_info = get_agent_info(agent_id)
|
|
185
206
|
if agent_info:
|
|
186
207
|
completed_agents.append(agent_info)
|
|
208
|
+
|
|
187
209
|
return completed_agents
|
|
188
210
|
|
|
189
211
|
|
rasa/agents/validation.py
CHANGED
|
@@ -6,7 +6,6 @@ from collections import Counter
|
|
|
6
6
|
from typing import Any, Dict, List, NoReturn, Set
|
|
7
7
|
|
|
8
8
|
from pydantic import ValidationError as PydanticValidationError
|
|
9
|
-
from ruamel import yaml
|
|
10
9
|
|
|
11
10
|
from rasa.agents.exceptions import (
|
|
12
11
|
AgentNameFlowConflictException,
|
|
@@ -18,11 +17,13 @@ from rasa.core.available_agents import (
|
|
|
18
17
|
AgentConfiguration,
|
|
19
18
|
AgentConnections,
|
|
20
19
|
AgentInfo,
|
|
20
|
+
AvailableAgents,
|
|
21
21
|
ProtocolConfig,
|
|
22
22
|
)
|
|
23
23
|
from rasa.core.config.available_endpoints import AvailableEndpoints
|
|
24
24
|
from rasa.core.config.configuration import Configuration
|
|
25
25
|
from rasa.exceptions import ValidationError
|
|
26
|
+
from rasa.shared.utils.yaml import read_config_file
|
|
26
27
|
|
|
27
28
|
# Centralized allowed keys configuration to eliminate duplication
|
|
28
29
|
ALLOWED_KEYS = {
|
|
@@ -34,6 +35,7 @@ ALLOWED_KEYS = {
|
|
|
34
35
|
"timeout",
|
|
35
36
|
"max_retries",
|
|
36
37
|
"agent_card",
|
|
38
|
+
"auth",
|
|
37
39
|
},
|
|
38
40
|
"connections": {"mcp_servers"},
|
|
39
41
|
}
|
|
@@ -348,9 +350,9 @@ def _validate_mandatory_fields(data: Dict[str, Any], agent_name: str) -> None:
|
|
|
348
350
|
),
|
|
349
351
|
)
|
|
350
352
|
|
|
351
|
-
# Check for required fields
|
|
353
|
+
# Check for required fields (protocol is optional due to default in model)
|
|
352
354
|
missing_fields = []
|
|
353
|
-
for field in ["name", "
|
|
355
|
+
for field in ["name", "description"]:
|
|
354
356
|
if field not in agent_data or not agent_data[field]:
|
|
355
357
|
missing_fields.append(field)
|
|
356
358
|
|
|
@@ -433,8 +435,7 @@ def validate_agent_folder(agent_folder: str = DEFAULT_AGENTS_CONFIG_FOLDER) -> N
|
|
|
433
435
|
# Read and validate the config content
|
|
434
436
|
try:
|
|
435
437
|
# First read the raw YAML data to validate structure
|
|
436
|
-
|
|
437
|
-
data = yaml.safe_load(f)
|
|
438
|
+
data = read_config_file(config_path)
|
|
438
439
|
|
|
439
440
|
# Validate no additional keys
|
|
440
441
|
_validate_no_additional_keys_raw_data(data)
|
|
@@ -442,10 +443,7 @@ def validate_agent_folder(agent_folder: str = DEFAULT_AGENTS_CONFIG_FOLDER) -> N
|
|
|
442
443
|
# Validate mandatory fields before creating Pydantic models
|
|
443
444
|
_validate_mandatory_fields(data, agent_folder_name)
|
|
444
445
|
|
|
445
|
-
|
|
446
|
-
from rasa.core.available_agents import AvailableAgents
|
|
447
|
-
|
|
448
|
-
agent_config = AvailableAgents._read_agent_config(config_path)
|
|
446
|
+
agent_config = AvailableAgents.from_dict(data)
|
|
449
447
|
|
|
450
448
|
# Validate the agent config (protocol-specific and endpoint references)
|
|
451
449
|
validate_agent_config(agent_config)
|
rasa/api.py
CHANGED
|
@@ -37,11 +37,10 @@ def run(
|
|
|
37
37
|
"""
|
|
38
38
|
import rasa.core.run
|
|
39
39
|
import rasa.shared.utils.common
|
|
40
|
-
from rasa.core.available_agents import AvailableAgents
|
|
41
40
|
from rasa.shared.constants import DOCS_BASE_URL
|
|
42
41
|
from rasa.shared.utils.cli import print_warning
|
|
43
42
|
|
|
44
|
-
_sub_agents =
|
|
43
|
+
_sub_agents = Configuration.get_instance().available_agents
|
|
45
44
|
|
|
46
45
|
credentials = Configuration.get_instance().credentials
|
|
47
46
|
|
rasa/builder/copilot/copilot.py
CHANGED
|
@@ -22,10 +22,12 @@ from rasa.builder.copilot.constants import (
|
|
|
22
22
|
)
|
|
23
23
|
from rasa.builder.copilot.exceptions import CopilotStreamError
|
|
24
24
|
from rasa.builder.copilot.models import (
|
|
25
|
+
ChatMessage,
|
|
25
26
|
CopilotChatMessage,
|
|
26
27
|
CopilotContext,
|
|
27
28
|
CopilotGenerationContext,
|
|
28
29
|
CopilotSystemMessage,
|
|
30
|
+
EventContent,
|
|
29
31
|
FileContent,
|
|
30
32
|
InternalCopilotRequestChatMessage,
|
|
31
33
|
ResponseCategory,
|
|
@@ -144,12 +146,16 @@ class Copilot:
|
|
|
144
146
|
"""
|
|
145
147
|
relevant_documents = await self.search_rasa_documentation(context)
|
|
146
148
|
messages = await self._build_messages(context, relevant_documents)
|
|
149
|
+
tracker_event_attachments = self._extract_tracker_event_attachments(
|
|
150
|
+
context.copilot_chat_history[-1]
|
|
151
|
+
)
|
|
147
152
|
|
|
148
153
|
support_evidence = CopilotGenerationContext(
|
|
149
154
|
relevant_documents=relevant_documents,
|
|
150
155
|
system_message=messages[0],
|
|
151
156
|
chat_history=messages[1:-1],
|
|
152
157
|
last_user_message=messages[-1],
|
|
158
|
+
tracker_event_attachments=tracker_event_attachments,
|
|
153
159
|
)
|
|
154
160
|
|
|
155
161
|
return (
|
|
@@ -272,8 +278,11 @@ class Copilot:
|
|
|
272
278
|
ValueError: If the message type is not supported.
|
|
273
279
|
"""
|
|
274
280
|
if isinstance(latest_message, UserChatMessage):
|
|
281
|
+
tracker_event_attachments = latest_message.get_content_blocks_by_type(
|
|
282
|
+
EventContent
|
|
283
|
+
)
|
|
275
284
|
rendered_prompt = self._render_last_user_message_context_prompt(
|
|
276
|
-
context, relevant_documents
|
|
285
|
+
context, relevant_documents, tracker_event_attachments
|
|
277
286
|
)
|
|
278
287
|
return latest_message.build_openai_message(prompt=rendered_prompt)
|
|
279
288
|
|
|
@@ -299,6 +308,7 @@ class Copilot:
|
|
|
299
308
|
self,
|
|
300
309
|
context: CopilotContext,
|
|
301
310
|
relevant_documents: List[Document],
|
|
311
|
+
tracker_event_attachments: List[EventContent],
|
|
302
312
|
) -> str:
|
|
303
313
|
# Format relevant documentation
|
|
304
314
|
documents = [doc.model_dump() for doc in relevant_documents]
|
|
@@ -306,6 +316,8 @@ class Copilot:
|
|
|
306
316
|
conversation = self._format_conversation_history(context.tracker_context)
|
|
307
317
|
# Format current state
|
|
308
318
|
current_state = self._format_current_state(context.tracker_context)
|
|
319
|
+
# Format tracker events
|
|
320
|
+
attachments = self._format_tracker_event_attachments(tracker_event_attachments)
|
|
309
321
|
|
|
310
322
|
rendered_prompt = self._last_user_message_context_prompt_template.render(
|
|
311
323
|
current_conversation=conversation,
|
|
@@ -313,6 +325,7 @@ class Copilot:
|
|
|
313
325
|
assistant_logs=context.assistant_logs,
|
|
314
326
|
assistant_files=context.assistant_files,
|
|
315
327
|
documentation_results=documents,
|
|
328
|
+
attachments=attachments,
|
|
316
329
|
)
|
|
317
330
|
return rendered_prompt
|
|
318
331
|
|
|
@@ -390,6 +403,8 @@ class Copilot:
|
|
|
390
403
|
@staticmethod
|
|
391
404
|
def _format_documents(results: List[Document]) -> Optional[str]:
|
|
392
405
|
"""Format documentation search results as JSON dump to be used in the prompt."""
|
|
406
|
+
# We want the special message that indicates no relevant documentation source
|
|
407
|
+
# found if there are no results.
|
|
393
408
|
if not results:
|
|
394
409
|
return None
|
|
395
410
|
|
|
@@ -524,3 +539,24 @@ class Copilot:
|
|
|
524
539
|
return f"Logs: {log_content}"
|
|
525
540
|
else:
|
|
526
541
|
return ""
|
|
542
|
+
|
|
543
|
+
@staticmethod
|
|
544
|
+
def _format_tracker_event_attachments(events: List[EventContent]) -> Optional[str]:
|
|
545
|
+
"""Format tracker events as JSON dump to be used in the prompt."""
|
|
546
|
+
# We don't want to display the attachment sectin in the last user message
|
|
547
|
+
# context prompt if there are no attachments.
|
|
548
|
+
if not events:
|
|
549
|
+
return None
|
|
550
|
+
# If there are attachments, return the formatted JSON dump.
|
|
551
|
+
return json.dumps(
|
|
552
|
+
[event_content.model_dump() for event_content in events],
|
|
553
|
+
ensure_ascii=False,
|
|
554
|
+
indent=2,
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
@staticmethod
|
|
558
|
+
def _extract_tracker_event_attachments(message: ChatMessage) -> List[EventContent]:
|
|
559
|
+
"""Extract the tracker event attachments from the message."""
|
|
560
|
+
if not isinstance(message, UserChatMessage):
|
|
561
|
+
return []
|
|
562
|
+
return message.get_content_blocks_by_type(EventContent)
|
rasa/builder/copilot/models.py
CHANGED
|
@@ -110,7 +110,7 @@ class LogContent(BaseContent):
|
|
|
110
110
|
)
|
|
111
111
|
|
|
112
112
|
|
|
113
|
-
class EventContent(
|
|
113
|
+
class EventContent(BaseContent):
|
|
114
114
|
type: Literal["event"]
|
|
115
115
|
event: str = Field(..., description="The event's type_name")
|
|
116
116
|
|
|
@@ -171,6 +171,38 @@ class BaseCopilotChatMessage(BaseModel, ABC):
|
|
|
171
171
|
return None if v is None else v.value
|
|
172
172
|
|
|
173
173
|
|
|
174
|
+
class BaseContentBlockCopilotChatMessage(BaseCopilotChatMessage, ABC):
|
|
175
|
+
"""Base class for messages that contain ContentBlock lists."""
|
|
176
|
+
|
|
177
|
+
content: List[ContentBlock]
|
|
178
|
+
|
|
179
|
+
def get_flattened_text_content(self) -> str:
|
|
180
|
+
"""Get the text content from the message."""
|
|
181
|
+
return "\n".join(
|
|
182
|
+
content_block.text
|
|
183
|
+
for content_block in self.content
|
|
184
|
+
if isinstance(content_block, TextContent)
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
def get_flattened_log_content(self) -> str:
|
|
188
|
+
"""Get the log content from the message."""
|
|
189
|
+
return "\n".join(
|
|
190
|
+
content_block.content
|
|
191
|
+
for content_block in self.content
|
|
192
|
+
if isinstance(content_block, LogContent)
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def get_content_blocks_by_type(
|
|
196
|
+
self, content_type: Type[TContentBlock]
|
|
197
|
+
) -> List[TContentBlock]:
|
|
198
|
+
"""Get the content blocks from the message by type."""
|
|
199
|
+
return [
|
|
200
|
+
content_block
|
|
201
|
+
for content_block in self.content
|
|
202
|
+
if isinstance(content_block, content_type)
|
|
203
|
+
]
|
|
204
|
+
|
|
205
|
+
|
|
174
206
|
class CopilotSystemMessage(BaseCopilotChatMessage):
|
|
175
207
|
role: Literal["system"] = Field(
|
|
176
208
|
default=ROLE_SYSTEM,
|
|
@@ -183,13 +215,12 @@ class CopilotSystemMessage(BaseCopilotChatMessage):
|
|
|
183
215
|
return {"role": ROLE_SYSTEM, "content": prompt}
|
|
184
216
|
|
|
185
217
|
|
|
186
|
-
class UserChatMessage(
|
|
218
|
+
class UserChatMessage(BaseContentBlockCopilotChatMessage):
|
|
187
219
|
role: Literal["user"] = Field(
|
|
188
220
|
default=ROLE_USER,
|
|
189
221
|
pattern=f"^{ROLE_USER}",
|
|
190
222
|
description="The user who sent the message.",
|
|
191
223
|
)
|
|
192
|
-
content: List[ContentBlock]
|
|
193
224
|
|
|
194
225
|
@classmethod
|
|
195
226
|
@field_validator("content")
|
|
@@ -234,14 +265,6 @@ class UserChatMessage(BaseCopilotChatMessage):
|
|
|
234
265
|
|
|
235
266
|
return self
|
|
236
267
|
|
|
237
|
-
def get_flattened_text_content(self) -> str:
|
|
238
|
-
"""Get the text content from the message."""
|
|
239
|
-
return "\n".join(
|
|
240
|
-
content_block.text
|
|
241
|
-
for content_block in self.content
|
|
242
|
-
if isinstance(content_block, TextContent)
|
|
243
|
-
)
|
|
244
|
-
|
|
245
268
|
def build_openai_message( # type: ignore[no-untyped-def]
|
|
246
269
|
self, prompt: Optional[str] = None, *args, **kwargs
|
|
247
270
|
) -> Dict[str, Any]:
|
|
@@ -260,17 +283,8 @@ class UserChatMessage(BaseCopilotChatMessage):
|
|
|
260
283
|
return {"role": ROLE_USER, "content": self.get_flattened_text_content()}
|
|
261
284
|
|
|
262
285
|
|
|
263
|
-
class CopilotChatMessage(
|
|
286
|
+
class CopilotChatMessage(BaseContentBlockCopilotChatMessage):
|
|
264
287
|
role: Literal["copilot"]
|
|
265
|
-
content: List[ContentBlock]
|
|
266
|
-
|
|
267
|
-
def get_flattened_text_content(self) -> str:
|
|
268
|
-
"""Get the text content from the message."""
|
|
269
|
-
return "\n".join(
|
|
270
|
-
content_block.text
|
|
271
|
-
for content_block in self.content
|
|
272
|
-
if isinstance(content_block, TextContent)
|
|
273
|
-
)
|
|
274
288
|
|
|
275
289
|
def build_openai_message(self, *args, **kwargs) -> Dict[str, Any]: # type: ignore[no-untyped-def]
|
|
276
290
|
# For now the Copilot responds only with the text content and all the content
|
|
@@ -278,9 +292,8 @@ class CopilotChatMessage(BaseCopilotChatMessage):
|
|
|
278
292
|
return {"role": ROLE_ASSISTANT, "content": self.get_flattened_text_content()}
|
|
279
293
|
|
|
280
294
|
|
|
281
|
-
class InternalCopilotRequestChatMessage(
|
|
295
|
+
class InternalCopilotRequestChatMessage(BaseContentBlockCopilotChatMessage):
|
|
282
296
|
role: Literal["internal_copilot_request"]
|
|
283
|
-
content: List[ContentBlock]
|
|
284
297
|
|
|
285
298
|
@model_validator(mode="after")
|
|
286
299
|
def validate_response_category(self) -> "InternalCopilotRequestChatMessage":
|
|
@@ -311,32 +324,6 @@ class InternalCopilotRequestChatMessage(BaseCopilotChatMessage):
|
|
|
311
324
|
|
|
312
325
|
return self
|
|
313
326
|
|
|
314
|
-
def get_flattened_text_content(self) -> str:
|
|
315
|
-
"""Get the text content from the message."""
|
|
316
|
-
return "\n".join(
|
|
317
|
-
content_block.text
|
|
318
|
-
for content_block in self.content
|
|
319
|
-
if isinstance(content_block, TextContent)
|
|
320
|
-
)
|
|
321
|
-
|
|
322
|
-
def get_flattened_log_content(self) -> str:
|
|
323
|
-
"""Get the text content from the message."""
|
|
324
|
-
return "\n".join(
|
|
325
|
-
content_block.content
|
|
326
|
-
for content_block in self.content
|
|
327
|
-
if isinstance(content_block, LogContent)
|
|
328
|
-
)
|
|
329
|
-
|
|
330
|
-
def get_content_blocks_by_type(
|
|
331
|
-
self, content_type: Type[TContentBlock]
|
|
332
|
-
) -> List[TContentBlock]:
|
|
333
|
-
"""Get the content blocks from the message by type."""
|
|
334
|
-
return [
|
|
335
|
-
content_block
|
|
336
|
-
for content_block in self.content
|
|
337
|
-
if isinstance(content_block, content_type)
|
|
338
|
-
]
|
|
339
|
-
|
|
340
327
|
def build_openai_message(self, prompt: str, *args, **kwargs) -> Dict[str, Any]: # type: ignore[no-untyped-def]
|
|
341
328
|
"""Build OpenAI message with pre-rendered prompt.
|
|
342
329
|
|
|
@@ -689,6 +676,13 @@ class CopilotGenerationContext(BaseModel):
|
|
|
689
676
|
last_user_message: Optional[Dict[str, Any]] = Field(
|
|
690
677
|
None, description="The last user message with context that was processed."
|
|
691
678
|
)
|
|
679
|
+
tracker_event_attachments: List[EventContent] = Field(
|
|
680
|
+
...,
|
|
681
|
+
description=(
|
|
682
|
+
"The tracker event attachments passed with the user message used as "
|
|
683
|
+
"an additional context."
|
|
684
|
+
),
|
|
685
|
+
)
|
|
692
686
|
|
|
693
687
|
class Config:
|
|
694
688
|
"""Config for CopilotGenerationContext."""
|
|
@@ -86,9 +86,9 @@ and panels.
|
|
|
86
86
|
***
|
|
87
87
|
|
|
88
88
|
## Layout
|
|
89
|
-
- **Left Panel
|
|
90
|
-
- **Center Panel
|
|
91
|
-
- **Right Panel
|
|
89
|
+
- **Left Panel - Copilot Chat:** Where the user asks you for help, guidance, or troubleshooting.
|
|
90
|
+
- **Center Panel - Playground Preview:** Main workspace with Chat Mode (default) or Inspect Mode.
|
|
91
|
+
- **Right Panel - Inspector Visualization:** Real-time diagram of conversation logic (only in Inspect Mode).
|
|
92
92
|
|
|
93
93
|
***
|
|
94
94
|
|
|
@@ -140,19 +140,40 @@ response.
|
|
|
140
140
|
|
|
141
141
|
***
|
|
142
142
|
|
|
143
|
+
### 5. Sharing Attachments
|
|
144
|
+
|
|
145
|
+
Users can share additional context with Copilot by clicking the "Ask Copilot" button
|
|
146
|
+
while "Inspect Mode" is open. This sends selected conversation state as an attachment
|
|
147
|
+
together with their question.
|
|
148
|
+
|
|
149
|
+
The attachments are typically tracker events, which can be:
|
|
150
|
+
- User messages - what the user typed or said.
|
|
151
|
+
- Assistant messages - what the assistant responded with.
|
|
152
|
+
- Actions - operations the assistant executed, including how they were chosen.
|
|
153
|
+
- Slots - what slots were set or updated during the exchange.
|
|
154
|
+
- Flows - when a flow starts, is interrupted, resumes, or completes.
|
|
155
|
+
- Sessions - the beginning or end of a conversation session.
|
|
156
|
+
|
|
157
|
+
**Tip:** Encourage users to use attachments to get to know the Rasa workings better. If
|
|
158
|
+
user is facing issues, these attachments will give Copilot a ground-truth trace of what
|
|
159
|
+
actually happened in the assistant, making attachments a powerful tool for debugging.
|
|
160
|
+
|
|
161
|
+
***
|
|
162
|
+
|
|
143
163
|
## Rasa CLI to Hello Rasa UI Mapping
|
|
144
164
|
|
|
145
165
|
Map available features to **Rasa CLI** to the **Hello Rasa Action**, so users see
|
|
146
166
|
continuity.
|
|
147
167
|
|
|
148
|
-
| Feature
|
|
149
|
-
|
|
150
|
-
| Train assistant
|
|
151
|
-
| Test conversation
|
|
152
|
-
| Debug conversation
|
|
153
|
-
| Run custom actions
|
|
154
|
-
| Export project files
|
|
155
|
-
| Edit project files
|
|
168
|
+
| Feature | Rasa CLI | Hello Rasa Action |
|
|
169
|
+
|--------------------------|-----------------------|------------------------------------|
|
|
170
|
+
| Train assistant | `rasa train` | Apply Changes |
|
|
171
|
+
| Test conversation | `rasa shell` | Chat Mode |
|
|
172
|
+
| Debug conversation | `rasa shell --debug` | Inspect Mode |
|
|
173
|
+
| Run custom actions | `rasa run actions` | Code Mode + Apply Changes |
|
|
174
|
+
| Export project files | — | Download button |
|
|
175
|
+
| Edit project files | — | Code Mode |
|
|
176
|
+
| Share conversation trace | `rasa shell --debug` | Ask Copilot button in Inspect Mode |
|
|
156
177
|
|
|
157
178
|
**Note:** Ignore any references to *Rasa Studio*.
|
|
158
179
|
|
|
@@ -181,7 +202,7 @@ code change, or reference). It should be:
|
|
|
181
202
|
- **Friendly, but Focused**: Use a warm and conversational style, but stay precise and technically correct.
|
|
182
203
|
- **Confident & Trustworthy**: Present guidance as clear and reliable; avoid hedging unless there's genuine uncertainty (in which case, ask clarifying questions).
|
|
183
204
|
- **Brand-Positive**: Highlight the strengths of **Rasa** and **Hello Rasa**, when appropriate, framing them as powerful and easy to use.
|
|
184
|
-
- **Code-style references
|
|
205
|
+
- **Code-style references**: You MUST wrap all flow names, slot names, variables, and any part of the user's code in backticks (e.g., `slot_name`, `flow_name`, `variable_name`). This is mandatory formatting.
|
|
185
206
|
|
|
186
207
|
***
|
|
187
208
|
|