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.

Files changed (69) hide show
  1. rasa/agents/protocol/a2a/a2a_agent.py +50 -42
  2. rasa/agents/utils.py +27 -5
  3. rasa/agents/validation.py +7 -9
  4. rasa/api.py +1 -2
  5. rasa/builder/copilot/copilot.py +37 -1
  6. rasa/builder/copilot/models.py +43 -49
  7. rasa/builder/copilot/prompts/copilot_system_prompt.jinja2 +33 -12
  8. rasa/builder/copilot/prompts/latest_user_message_context_prompt.jinja2 +59 -29
  9. rasa/builder/copilot/telemetry.py +8 -0
  10. rasa/builder/service.py +1 -0
  11. rasa/cli/dialogue_understanding_test.py +1 -0
  12. rasa/cli/e2e_test.py +1 -0
  13. rasa/cli/inspect.py +1 -0
  14. rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/feedback.yml +46 -0
  15. rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/goodbye.yml +9 -0
  16. rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/help.yml +8 -0
  17. rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/human_handoff.yml +41 -0
  18. rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/patterns.yml +32 -0
  19. rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/show_faqs.yml +8 -0
  20. rasa/cli/project_templates/telco/data/network/flow_solve_internet_issue.yml +2 -2
  21. rasa/cli/project_templates/telco/domain/network/solve_internet_issue.yml +1 -2
  22. rasa/cli/project_templates/telco/tests/e2e_test_cases/with_stub/network/solve_internet_not_slow.yml +33 -0
  23. rasa/cli/project_templates/telco/tests/e2e_test_cases/with_stub/network/solve_internet_slow.yml +47 -0
  24. rasa/cli/project_templates/telco/tests/e2e_test_cases/without_stub/general/hello.yml +8 -0
  25. rasa/cli/run.py +1 -5
  26. rasa/cli/shell.py +1 -0
  27. rasa/cli/train.py +1 -0
  28. rasa/cli/validation/bot_config.py +7 -2
  29. rasa/core/available_agents.py +65 -55
  30. rasa/core/config/available_endpoints.py +0 -3
  31. rasa/core/config/configuration.py +36 -1
  32. rasa/core/policies/flows/agent_executor.py +16 -8
  33. rasa/dialogue_understanding/commands/start_flow_command.py +10 -3
  34. rasa/dialogue_understanding/commands/utils.py +15 -4
  35. rasa/dialogue_understanding/generator/llm_based_command_generator.py +4 -2
  36. rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +4 -4
  37. rasa/dialogue_understanding/generator/single_step/search_ready_llm_command_generator.py +4 -4
  38. rasa/dialogue_understanding/generator/single_step/single_step_based_llm_command_generator.py +2 -2
  39. rasa/dialogue_understanding_test/du_test_runner.py +2 -2
  40. rasa/e2e_test/e2e_test_runner.py +2 -2
  41. rasa/shared/agents/auth/auth_strategy/oauth2_auth_strategy.py +10 -4
  42. rasa/shared/agents/auth/constants.py +1 -0
  43. rasa/shared/core/flows/steps/call.py +2 -2
  44. rasa/telemetry.py +3 -3
  45. rasa/validator.py +37 -0
  46. rasa/version.py +1 -1
  47. {rasa_pro-3.14.0rc2.dist-info → rasa_pro-3.14.0rc3.dist-info}/METADATA +13 -2
  48. {rasa_pro-3.14.0rc2.dist-info → rasa_pro-3.14.0rc3.dist-info}/RECORD +68 -60
  49. rasa/cli/project_templates/telco/tests/e2e_test_cases/network/solve_internet_issue.yml +0 -57
  50. /rasa/cli/project_templates/{finance/tests/e2e_test_cases → basic/tests/e2e_test_cases/without_stub}/general/hello.yml +0 -0
  51. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{accounts → without_stub/accounts}/check_balance.yml +0 -0
  52. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{accounts → without_stub/accounts}/download_statements.yml +0 -0
  53. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{cards → without_stub/cards}/block_card.yml +0 -0
  54. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{general → without_stub/general}/bot_challenge.yml +0 -0
  55. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{general → without_stub/general}/feedback.yml +0 -0
  56. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{general → without_stub/general}/goodbye.yml +0 -0
  57. /rasa/cli/project_templates/{telco/tests/e2e_test_cases → finance/tests/e2e_test_cases/without_stub}/general/hello.yml +0 -0
  58. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{general → without_stub/general}/human_handoff.yml +0 -0
  59. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{general → without_stub/general}/patterns.yml +0 -0
  60. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{transfers → without_stub/transfers}/transfer_money.yml +0 -0
  61. /rasa/cli/project_templates/telco/tests/e2e_test_cases/{billing → without_stub/billing}/understand_bill.yml +0 -0
  62. /rasa/cli/project_templates/telco/tests/e2e_test_cases/{general → without_stub/general}/bot_challenge.yml +0 -0
  63. /rasa/cli/project_templates/telco/tests/e2e_test_cases/{general → without_stub/general}/feedback.yml +0 -0
  64. /rasa/cli/project_templates/telco/tests/e2e_test_cases/{general → without_stub/general}/goodbye.yml +0 -0
  65. /rasa/cli/project_templates/telco/tests/e2e_test_cases/{general → without_stub/general}/human_handoff.yml +0 -0
  66. /rasa/cli/project_templates/telco/tests/e2e_test_cases/{general → without_stub/general}/patterns.yml +0 -0
  67. {rasa_pro-3.14.0rc2.dist-info → rasa_pro-3.14.0rc3.dist-info}/NOTICE +0 -0
  68. {rasa_pro-3.14.0rc2.dist-info → rasa_pro-3.14.0rc3.dist-info}/WHEEL +0 -0
  69. {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
- async for event in self._client.send_message(message):
219
- events_received += 1
220
- agent_output = self._handle_send_message_response(agent_input, event)
221
- if agent_output is not None:
222
- return agent_output
223
- else:
224
- # Not a terminal response, save taskID (in case that's the only
225
- # event, and we need to pool) and continue waiting for next events
226
- if (
227
- isinstance(event, tuple)
228
- and len(event) == 2
229
- and isinstance(event[0], Task)
230
- ):
231
- task_id = event[0].id
232
- continue
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
- async for event in self._client.send_message(test_message):
837
- if (
838
- isinstance(event, Message)
839
- or isinstance(event, tuple)
840
- and len(event) == 2
841
- and isinstance(event[0], Task)
842
- ):
843
- # We got a valid response, health check succeeded
844
- return
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
- event_info = "Unexpected response type during health check"
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.unexpected_response",
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(f"{event_info}: {event}")
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 = AvailableAgents.get_agent_config(agent_id)
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 = AvailableAgents.get_agent_config(agent_id)
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
- agent_info = get_agent_info(event.agent_id)
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", "protocol", "description"]:
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
- with open(config_path, "r") as f:
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
- # Create the agent config using AvailableAgents
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 = AvailableAgents.get_instance(sub_agents)
43
+ _sub_agents = Configuration.get_instance().available_agents
45
44
 
46
45
  credentials = Configuration.get_instance().credentials
47
46
 
@@ -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)
@@ -110,7 +110,7 @@ class LogContent(BaseContent):
110
110
  )
111
111
 
112
112
 
113
- class EventContent(BaseModel):
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(BaseCopilotChatMessage):
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(BaseCopilotChatMessage):
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(BaseCopilotChatMessage):
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 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).
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 | Rasa CLI | Hello Rasa Action |
149
- |----------------------|-----------------------|---------------------------|
150
- | Train assistant | `rasa train` | Apply Changes |
151
- | Test conversation | `rasa shell` | Chat Mode |
152
- | Debug conversation | `rasa shell --debug` | Inspect Mode |
153
- | Run custom actions | `rasa run actions` | Code Mode + Apply Changes |
154
- | Export project files | — | Download button |
155
- | Edit project files | — | Code Mode |
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** 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.
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