rasa-pro 3.14.0rc1__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 (84) 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/constants.py +4 -1
  6. rasa/builder/copilot/copilot.py +191 -79
  7. rasa/builder/copilot/models.py +306 -116
  8. rasa/builder/copilot/prompts/copilot_system_prompt.jinja2 +33 -12
  9. rasa/builder/copilot/prompts/copilot_training_error_handler_prompt.jinja2 +53 -0
  10. rasa/builder/copilot/prompts/latest_user_message_context_prompt.jinja2 +59 -29
  11. rasa/builder/copilot/telemetry.py +8 -0
  12. rasa/builder/guardrails/policy_checker.py +1 -1
  13. rasa/builder/jobs.py +182 -12
  14. rasa/builder/models.py +12 -3
  15. rasa/builder/service.py +16 -2
  16. rasa/cli/dialogue_understanding_test.py +1 -0
  17. rasa/cli/e2e_test.py +1 -0
  18. rasa/cli/inspect.py +1 -0
  19. rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/feedback.yml +46 -0
  20. rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/goodbye.yml +9 -0
  21. rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/help.yml +8 -0
  22. rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/human_handoff.yml +41 -0
  23. rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/patterns.yml +32 -0
  24. rasa/cli/project_templates/basic/tests/e2e_test_cases/without_stub/general/show_faqs.yml +8 -0
  25. rasa/cli/project_templates/finance/domain/general/help.yml +0 -0
  26. rasa/cli/project_templates/telco/data/network/flow_solve_internet_issue.yml +2 -2
  27. rasa/cli/project_templates/telco/domain/network/solve_internet_issue.yml +1 -2
  28. rasa/cli/project_templates/telco/tests/e2e_test_cases/with_stub/network/solve_internet_not_slow.yml +33 -0
  29. rasa/cli/project_templates/telco/tests/e2e_test_cases/with_stub/network/solve_internet_slow.yml +47 -0
  30. rasa/cli/project_templates/telco/tests/e2e_test_cases/without_stub/general/hello.yml +8 -0
  31. rasa/cli/run.py +1 -5
  32. rasa/cli/shell.py +1 -0
  33. rasa/cli/train.py +1 -0
  34. rasa/cli/validation/bot_config.py +7 -2
  35. rasa/core/available_agents.py +65 -55
  36. rasa/core/brokers/kafka.py +5 -1
  37. rasa/core/concurrent_lock_store.py +38 -21
  38. rasa/core/config/available_endpoints.py +0 -3
  39. rasa/core/config/configuration.py +36 -1
  40. rasa/core/constants.py +6 -0
  41. rasa/core/iam_credentials_providers/aws_iam_credentials_providers.py +69 -4
  42. rasa/core/iam_credentials_providers/credentials_provider_protocol.py +2 -1
  43. rasa/core/lock_store.py +4 -0
  44. rasa/core/policies/flows/agent_executor.py +16 -8
  45. rasa/core/redis_connection_factory.py +7 -2
  46. rasa/core/tracker_stores/redis_tracker_store.py +4 -0
  47. rasa/core/tracker_stores/sql_tracker_store.py +3 -1
  48. rasa/dialogue_understanding/commands/start_flow_command.py +10 -3
  49. rasa/dialogue_understanding/commands/utils.py +15 -4
  50. rasa/dialogue_understanding/generator/llm_based_command_generator.py +4 -2
  51. rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +4 -4
  52. rasa/dialogue_understanding/generator/single_step/search_ready_llm_command_generator.py +4 -4
  53. rasa/dialogue_understanding/generator/single_step/single_step_based_llm_command_generator.py +2 -2
  54. rasa/dialogue_understanding_test/du_test_runner.py +2 -2
  55. rasa/e2e_test/e2e_test_runner.py +2 -2
  56. rasa/shared/agents/auth/auth_strategy/oauth2_auth_strategy.py +10 -4
  57. rasa/shared/agents/auth/constants.py +1 -0
  58. rasa/shared/core/flows/steps/call.py +2 -2
  59. rasa/telemetry.py +3 -3
  60. rasa/validator.py +37 -0
  61. rasa/version.py +1 -1
  62. {rasa_pro-3.14.0rc1.dist-info → rasa_pro-3.14.0rc3.dist-info}/METADATA +14 -2
  63. {rasa_pro-3.14.0rc1.dist-info → rasa_pro-3.14.0rc3.dist-info}/RECORD +83 -73
  64. rasa/cli/project_templates/telco/tests/e2e_test_cases/network/solve_internet_issue.yml +0 -57
  65. /rasa/cli/project_templates/{finance/tests/e2e_test_cases → basic/tests/e2e_test_cases/without_stub}/general/hello.yml +0 -0
  66. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{accounts → without_stub/accounts}/check_balance.yml +0 -0
  67. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{accounts → without_stub/accounts}/download_statements.yml +0 -0
  68. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{cards → without_stub/cards}/block_card.yml +0 -0
  69. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{general → without_stub/general}/bot_challenge.yml +0 -0
  70. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{general → without_stub/general}/feedback.yml +0 -0
  71. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{general → without_stub/general}/goodbye.yml +0 -0
  72. /rasa/cli/project_templates/{telco/tests/e2e_test_cases → finance/tests/e2e_test_cases/without_stub}/general/hello.yml +0 -0
  73. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{general → without_stub/general}/human_handoff.yml +0 -0
  74. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{general → without_stub/general}/patterns.yml +0 -0
  75. /rasa/cli/project_templates/finance/tests/e2e_test_cases/{transfers → without_stub/transfers}/transfer_money.yml +0 -0
  76. /rasa/cli/project_templates/telco/tests/e2e_test_cases/{billing → without_stub/billing}/understand_bill.yml +0 -0
  77. /rasa/cli/project_templates/telco/tests/e2e_test_cases/{general → without_stub/general}/bot_challenge.yml +0 -0
  78. /rasa/cli/project_templates/telco/tests/e2e_test_cases/{general → without_stub/general}/feedback.yml +0 -0
  79. /rasa/cli/project_templates/telco/tests/e2e_test_cases/{general → without_stub/general}/goodbye.yml +0 -0
  80. /rasa/cli/project_templates/telco/tests/e2e_test_cases/{general → without_stub/general}/human_handoff.yml +0 -0
  81. /rasa/cli/project_templates/telco/tests/e2e_test_cases/{general → without_stub/general}/patterns.yml +0 -0
  82. {rasa_pro-3.14.0rc1.dist-info → rasa_pro-3.14.0rc3.dist-info}/NOTICE +0 -0
  83. {rasa_pro-3.14.0rc1.dist-info → rasa_pro-3.14.0rc3.dist-info}/WHEEL +0 -0
  84. {rasa_pro-3.14.0rc1.dist-info → rasa_pro-3.14.0rc3.dist-info}/entry_points.txt +0 -0
@@ -1,16 +1,23 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from enum import Enum
3
- from typing import Any, Dict, List, Literal, Optional, Union
3
+ from typing import Any, Dict, List, Literal, Optional, Type, TypeVar, Union
4
4
 
5
5
  import structlog
6
6
  from openai.types.chat.chat_completion_chunk import ChatCompletionChunk
7
- from pydantic import BaseModel, Field, field_serializer, model_validator
7
+ from pydantic import (
8
+ BaseModel,
9
+ Field,
10
+ field_serializer,
11
+ field_validator,
12
+ model_validator,
13
+ )
8
14
  from typing_extensions import Annotated
9
15
 
10
16
  from rasa.builder.copilot.constants import (
11
17
  ROLE_ASSISTANT,
12
18
  ROLE_COPILOT,
13
19
  ROLE_COPILOT_INTERNAL,
20
+ ROLE_SYSTEM,
14
21
  ROLE_USER,
15
22
  )
16
23
  from rasa.builder.document_retrieval.models import Document
@@ -49,7 +56,8 @@ class ResponseCategory(Enum):
49
56
  # When Copilot analyzes error logs and provides suggestions
50
57
  TRAINING_ERROR_LOG_ANALYSIS = "training_error_log_analysis"
51
58
  E2E_TESTING_ERROR_LOG_ANALYSIS = "e2e_testing_error_log_analysis"
52
-
59
+ TRAINING_ERROR_LOG = "training_error_log"
60
+ E2E_TESTING_ERROR_LOG = "e2e_testing_error_log"
53
61
  # Conversation history signature
54
62
  SIGNATURE = "signature"
55
63
 
@@ -90,7 +98,7 @@ class LogContent(BaseContent):
90
98
  type: Literal["log"]
91
99
  content: str = Field(..., description="Logs, error messages, stack traces, etc.")
92
100
  context: Optional[str] = Field(
93
- None,
101
+ default=None,
94
102
  description=(
95
103
  "Additional, optional context description for the logs "
96
104
  "(e.g., 'training session', 'e2e testing run', 'deployment process')"
@@ -102,7 +110,7 @@ class LogContent(BaseContent):
102
110
  )
103
111
 
104
112
 
105
- class EventContent(BaseModel):
113
+ class EventContent(BaseContent):
106
114
  type: Literal["event"]
107
115
  event: str = Field(..., description="The event's type_name")
108
116
 
@@ -144,125 +152,220 @@ ContentBlock = Annotated[
144
152
  ),
145
153
  ]
146
154
 
155
+ TContentBlock = TypeVar("TContentBlock", bound=BaseContent)
147
156
 
148
- class CopilotChatMessage(BaseModel):
149
- """Model for a single chat messages between the user and the copilot."""
150
157
 
151
- role: str = Field(
152
- ...,
153
- pattern=f"^({ROLE_USER}|{ROLE_COPILOT}|{ROLE_COPILOT_INTERNAL})$",
154
- description="The role of the message sender.",
155
- )
158
+ class BaseCopilotChatMessage(BaseModel, ABC):
159
+ role: str
160
+ response_category: Optional[ResponseCategory] = Field(default=None)
161
+
162
+ @abstractmethod
163
+ def build_openai_message(self, *args, **kwargs) -> Dict[str, Any]: # type: ignore[no-untyped-def]
164
+ pass
165
+
166
+ @field_serializer("response_category", when_used="always")
167
+ def _serialize_response_category(
168
+ self, v: Optional[ResponseCategory]
169
+ ) -> Optional[str]:
170
+ """Serializing CopilotChatMessage, response_category should be a string."""
171
+ return None if v is None else v.value
172
+
173
+
174
+ class BaseContentBlockCopilotChatMessage(BaseCopilotChatMessage, ABC):
175
+ """Base class for messages that contain ContentBlock lists."""
176
+
156
177
  content: List[ContentBlock]
157
- response_category: Optional[ResponseCategory] = Field(
158
- None,
159
- description=(
160
- "The category/source of this message. For user role messages, only `None` "
161
- "or `GUARDRAILS_POLICY_VIOLATION` are allowed. For copilot role messages, "
162
- "any category is permitted."
163
- ),
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
+
206
+ class CopilotSystemMessage(BaseCopilotChatMessage):
207
+ role: Literal["system"] = Field(
208
+ default=ROLE_SYSTEM,
209
+ pattern=f"^{ROLE_SYSTEM}",
210
+ description="The system message that sets the system instructions for the LLM.",
164
211
  )
165
212
 
213
+ def build_openai_message(self, prompt: str, *args, **kwargs) -> Dict[str, Any]: # type: ignore[no-untyped-def]
214
+ """Render the system message template and return OpenAI format."""
215
+ return {"role": ROLE_SYSTEM, "content": prompt}
216
+
217
+
218
+ class UserChatMessage(BaseContentBlockCopilotChatMessage):
219
+ role: Literal["user"] = Field(
220
+ default=ROLE_USER,
221
+ pattern=f"^{ROLE_USER}",
222
+ description="The user who sent the message.",
223
+ )
224
+
225
+ @classmethod
226
+ @field_validator("content")
227
+ def must_have_at_least_one_text(cls, v: List[ContentBlock]) -> List[ContentBlock]:
228
+ if not any(isinstance(content_block, TextContent) for content_block in v):
229
+ message = "User role messages must have at least one `TextContent` block."
230
+ structlogger.error(
231
+ "user_chat_message.missing_text_content",
232
+ event_info=message,
233
+ content=v,
234
+ )
235
+ raise ValueError(
236
+ "UserChatMessage must contain at least one TextContent block."
237
+ )
238
+ return v
239
+
166
240
  @model_validator(mode="after")
167
- def validate_response_category_for_role(self) -> "CopilotChatMessage":
168
- """Validate value of response_category for the role of the message.
241
+ def validate_response_category(self) -> "UserChatMessage":
242
+ """Validate value of response_category for user message.
169
243
 
170
244
  For 'user' role messages, only None or GUARDRAILS_POLICY_VIOLATION are allowed.
171
- For 'copilot' role messages, any category is permitted.
172
- For 'rasa_internal' role messages, any category is permitted.
173
245
  """
246
+ allowed_response_categories = [ResponseCategory.GUARDRAILS_POLICY_VIOLATION]
174
247
  if (
175
- self.role == ROLE_USER
176
- and self.response_category is not None
177
- and self.response_category != ResponseCategory.GUARDRAILS_POLICY_VIOLATION
248
+ self.response_category is not None
249
+ and self.response_category not in allowed_response_categories
178
250
  ):
179
251
  message = (
180
252
  f"User role messages can only have response_category of `None` or "
181
- f"`{ResponseCategory.GUARDRAILS_POLICY_VIOLATION}`, "
182
- f"got `{self.response_category}`."
253
+ f"{', '.join(category.value for category in allowed_response_categories)}." # noqa: E501
254
+ f"Got `{self.response_category}`."
183
255
  )
184
256
  structlogger.error(
185
- "copilot_chat_message.validate_response_category_for_role"
257
+ "user_chat_message.validate_response_category"
186
258
  ".invalid_response_category",
187
259
  event_info=message,
188
260
  response_category=self.response_category,
261
+ allowed_response_categories=allowed_response_categories,
189
262
  role=self.role,
190
263
  )
191
264
  raise ValueError(message)
192
265
 
193
266
  return self
194
267
 
195
- @field_serializer("response_category", when_used="always")
196
- def _serialize_response_category(
197
- self, v: Optional[ResponseCategory]
198
- ) -> Optional[str]:
199
- """Serializing CopilotChatMessage, response_category should be a string."""
200
- return None if v is None else v.value
201
-
202
- def get_text_content(self) -> str:
203
- """Concatenate all 'text' content blocks into a single string."""
204
- return "\n".join(
205
- content_block.text
206
- for content_block in self.content
207
- if isinstance(content_block, TextContent)
208
- )
209
-
210
- def get_log_content(self) -> str:
211
- """Concatenate all 'log' content blocks into a single string."""
212
- return "\n".join(
213
- content_block.content
214
- for content_block in self.content
215
- if isinstance(content_block, LogContent)
216
- )
268
+ def build_openai_message( # type: ignore[no-untyped-def]
269
+ self, prompt: Optional[str] = None, *args, **kwargs
270
+ ) -> Dict[str, Any]:
271
+ # If a prompt is provided, add it to the message content as additional
272
+ # instructions
273
+ if prompt:
274
+ return {
275
+ "role": ROLE_USER,
276
+ "content": [
277
+ {"type": "text", "text": prompt},
278
+ {"type": "text", "text": self.get_flattened_text_content()},
279
+ ],
280
+ }
281
+ # Return simple text content (useful for showing the history)
282
+ else:
283
+ return {"role": ROLE_USER, "content": self.get_flattened_text_content()}
217
284
 
218
- def to_openai_format(self) -> Dict[str, Any]:
219
- """Convert to OpenAI message format for API calls."""
220
- role_to_openai_format = {
221
- ROLE_USER: self._user_message_to_openai_format,
222
- ROLE_COPILOT: self._copilot_message_to_openai_format,
223
- ROLE_COPILOT_INTERNAL: self._copilot_message_to_openai_format,
224
- }
225
- return role_to_openai_format[self.role]()
226
285
 
227
- def _user_message_to_openai_format(self) -> Dict[str, Any]:
228
- role = self._map_role_to_openai()
229
- content = self.get_text_content()
230
- return {"role": role, "content": content}
286
+ class CopilotChatMessage(BaseContentBlockCopilotChatMessage):
287
+ role: Literal["copilot"]
231
288
 
232
- def _copilot_message_to_openai_format(self) -> Dict[str, Any]:
233
- role = self._map_role_to_openai()
289
+ def build_openai_message(self, *args, **kwargs) -> Dict[str, Any]: # type: ignore[no-untyped-def]
234
290
  # For now the Copilot responds only with the text content and all the content
235
291
  # is formatted as a markdown.
236
- # TODO: Once we start predicting the files, and expecting other content blocks
237
- # we should update this.
238
- content = self.get_text_content()
239
- return {"role": role, "content": content}
240
-
241
- def _map_role_to_openai(self) -> str:
242
- """Map internal roles to OpenAI-compatible roles."""
243
- role_mapping = {
244
- ROLE_USER: ROLE_USER,
245
- ROLE_COPILOT: ROLE_ASSISTANT,
246
- ROLE_COPILOT_INTERNAL: ROLE_USER,
247
- }
248
- if self.role not in role_mapping.keys():
292
+ return {"role": ROLE_ASSISTANT, "content": self.get_flattened_text_content()}
293
+
294
+
295
+ class InternalCopilotRequestChatMessage(BaseContentBlockCopilotChatMessage):
296
+ role: Literal["internal_copilot_request"]
297
+
298
+ @model_validator(mode="after")
299
+ def validate_response_category(self) -> "InternalCopilotRequestChatMessage":
300
+ """Validate value of response_category for internal copilot request message.
301
+
302
+ For 'internal_copilot_request' role messages, only `TRAINING_ERROR_LOG_ANALYSIS`
303
+ and `E2E_TESTING_ERROR_LOG_ANALYSIS` response categories are allowed.
304
+ """
305
+ allowed_response_categories = [
306
+ ResponseCategory.TRAINING_ERROR_LOG_ANALYSIS,
307
+ ResponseCategory.E2E_TESTING_ERROR_LOG_ANALYSIS,
308
+ ]
309
+ if self.response_category not in allowed_response_categories:
310
+ message = (
311
+ f"Copilot Internal Roles request messages can only have of "
312
+ f"{', '.join(category.value for category in allowed_response_categories)}. " # noqa: E501
313
+ f"Got `{self.response_category}`."
314
+ )
249
315
  structlogger.error(
250
- "copilot_chat_message.to_openai_format.invalid_role",
251
- event_info=(
252
- f"Invalid role: `{self.role}`. "
253
- f"Only {', '.join(role_mapping.keys())} roles are supported."
254
- ),
316
+ "internal_copilot_request_chat_message.validate_response_category"
317
+ ".invalid_response_category",
318
+ event_info=message,
319
+ response_category=self.response_category,
320
+ allowed_response_categories=allowed_response_categories,
255
321
  role=self.role,
256
322
  )
257
- raise ValueError(f"Invalid role: {self.role}")
323
+ raise ValueError(message)
324
+
325
+ return self
326
+
327
+ def build_openai_message(self, prompt: str, *args, **kwargs) -> Dict[str, Any]: # type: ignore[no-untyped-def]
328
+ """Build OpenAI message with pre-rendered prompt.
329
+
330
+ The prompt should be rendered externally using the content from this message
331
+ (logs, files, any additional context outside of this message, etc.) before
332
+ being passed to this method.
333
+ """
334
+ return {"role": ROLE_USER, "content": prompt}
335
+
336
+
337
+ # Union type for all possible chat message types
338
+ ChatMessage = Union[
339
+ CopilotSystemMessage,
340
+ UserChatMessage,
341
+ CopilotChatMessage,
342
+ InternalCopilotRequestChatMessage,
343
+ ]
344
+
345
+
346
+ class CopilotContext(BaseModel):
347
+ """Model containing the context used by the copilot to generate a response."""
348
+
349
+ assistant_logs: str = Field(default="")
350
+ assistant_files: Dict[str, str] = Field(
351
+ default_factory=dict,
352
+ description=(
353
+ "The assistant files. Key is the file path, value is the file content."
354
+ ),
355
+ )
356
+ copilot_chat_history: List[ChatMessage] = Field(default_factory=list)
357
+ tracker_context: Optional[TrackerContext] = Field(default=None)
358
+
359
+ class Config:
360
+ """Config for LLMBuilderContext."""
258
361
 
259
- return role_mapping[self.role]
362
+ arbitrary_types_allowed = True
260
363
 
261
364
 
262
365
  class CopilotRequest(BaseModel):
263
366
  """Request model for the copilot endpoint."""
264
367
 
265
- copilot_chat_history: List[CopilotChatMessage] = Field(
368
+ copilot_chat_history: List[ChatMessage] = Field(
266
369
  ...,
267
370
  description=(
268
371
  "The chat history between the user and the copilot. "
@@ -285,8 +388,43 @@ class CopilotRequest(BaseModel):
285
388
  description='Signature scheme version (e.g. "v1").',
286
389
  )
287
390
 
391
+ @field_validator("copilot_chat_history", mode="before")
392
+ @classmethod
393
+ def parse_chat_history(cls, v: List[Dict[str, Any]]) -> List[ChatMessage]:
394
+ """Manually parse chat history messages based on role field."""
395
+ parsed_messages: List[ChatMessage] = []
396
+ available_roles = [ROLE_USER, ROLE_COPILOT, ROLE_COPILOT_INTERNAL]
397
+ for message_data in v:
398
+ role = message_data.get("role")
399
+
400
+ if role == ROLE_USER:
401
+ parsed_messages.append(UserChatMessage(**message_data))
402
+
403
+ elif role == ROLE_COPILOT:
404
+ parsed_messages.append(CopilotChatMessage(**message_data))
405
+
406
+ elif role == ROLE_COPILOT_INTERNAL:
407
+ parsed_messages.append(
408
+ InternalCopilotRequestChatMessage(**message_data)
409
+ )
410
+
411
+ else:
412
+ message = (
413
+ f"Unknown role '{role}' in chat message. "
414
+ f"Available roles are: {', '.join(available_roles)}."
415
+ )
416
+ structlogger.error(
417
+ "copilot_request.parse_chat_history.unknown_role",
418
+ event_info=message,
419
+ role=role,
420
+ available_roles=available_roles,
421
+ )
422
+ raise ValueError(message)
423
+
424
+ return parsed_messages
425
+
288
426
  @property
289
- def last_message(self) -> Optional[CopilotChatMessage]:
427
+ def last_message(self) -> Optional[ChatMessage]:
290
428
  """Get the last message from the copilot chat history."""
291
429
  if not self.copilot_chat_history:
292
430
  return None
@@ -315,6 +453,12 @@ class CopilotOutput(BaseModel, ABC):
315
453
  """Convert to SSE event format."""
316
454
  pass
317
455
 
456
+ @property
457
+ @abstractmethod
458
+ def sse_data(self) -> Dict[str, Any]:
459
+ """Extract the SSE data payload."""
460
+ pass
461
+
318
462
 
319
463
  class GeneratedContent(CopilotOutput):
320
464
  """Represents generated content from the LLM to be streamed."""
@@ -327,13 +471,18 @@ class GeneratedContent(CopilotOutput):
327
471
  """Convert to SSE event format."""
328
472
  return ServerSentEvent(
329
473
  event="copilot_response",
330
- data={
331
- "content": self.content,
332
- "response_category": self.response_category.value,
333
- "completeness": self.response_completeness.value,
334
- },
474
+ data=self.sse_data,
335
475
  )
336
476
 
477
+ @property
478
+ def sse_data(self) -> Dict[str, Any]:
479
+ """Extract the SSE data payload."""
480
+ return {
481
+ "content": self.content,
482
+ "response_category": self.response_category.value,
483
+ "completeness": self.response_completeness.value,
484
+ }
485
+
337
486
 
338
487
  class ReferenceEntry(CopilotOutput):
339
488
  """Represents a reference entry with title and url."""
@@ -361,15 +510,20 @@ class ReferenceEntry(CopilotOutput):
361
510
  """Convert to SSE event format."""
362
511
  return ServerSentEvent(
363
512
  event="copilot_response",
364
- data={
365
- "index": self.index,
366
- "title": self.title,
367
- "url": self.url,
368
- "response_category": self.response_category.value,
369
- "completeness": self.response_completeness.value,
370
- },
513
+ data=self.sse_data,
371
514
  )
372
515
 
516
+ @property
517
+ def sse_data(self) -> Dict[str, Any]:
518
+ """Extract the SSE data payload."""
519
+ return {
520
+ "index": self.index,
521
+ "title": self.title,
522
+ "url": self.url,
523
+ "response_category": self.response_category.value,
524
+ "completeness": self.response_completeness.value,
525
+ }
526
+
373
527
 
374
528
  class ReferenceSection(CopilotOutput):
375
529
  """Represents a reference section with documentation links."""
@@ -395,16 +549,21 @@ class ReferenceSection(CopilotOutput):
395
549
  """Convert to SSE event format."""
396
550
  return ServerSentEvent(
397
551
  event="copilot_response",
398
- data={
399
- "references": [
400
- reference.model_dump(include={"index", "title", "url"})
401
- for reference in self.references
402
- ],
403
- "response_category": self.response_category.value,
404
- "completeness": self.response_completeness.value,
405
- },
552
+ data=self.sse_data,
406
553
  )
407
554
 
555
+ @property
556
+ def sse_data(self) -> Dict[str, Any]:
557
+ """Extract the SSE data payload."""
558
+ return {
559
+ "references": [
560
+ reference.model_dump(include={"index", "title", "url"})
561
+ for reference in self.references
562
+ ],
563
+ "response_category": self.response_category.value,
564
+ "completeness": self.response_completeness.value,
565
+ }
566
+
408
567
  def sort_references(self) -> None:
409
568
  """Sort references by index value."""
410
569
  sorted_references = sorted(
@@ -414,18 +573,42 @@ class ReferenceSection(CopilotOutput):
414
573
  self.references = sorted_references
415
574
 
416
575
 
417
- class CopilotContext(BaseModel):
418
- """Model containing the context used by the copilot to generate a response."""
576
+ class TrainingErrorLog(CopilotOutput):
577
+ """Represents an error log."""
419
578
 
420
- assistant_logs: str = Field("")
421
- assistant_files: Dict[str, str] = Field({})
422
- copilot_chat_history: List["CopilotChatMessage"] = Field([])
423
- tracker_context: Optional[TrackerContext] = Field(None)
579
+ logs: List[LogContent]
580
+ response_category: ResponseCategory = Field(
581
+ default=ResponseCategory.TRAINING_ERROR_LOG,
582
+ frozen=True,
583
+ )
584
+ response_completeness: ResponseCompleteness = ResponseCompleteness.COMPLETE
424
585
 
425
- class Config:
426
- """Config for LLMBuilderContext."""
586
+ @model_validator(mode="after")
587
+ def validate_response_category(self) -> "TrainingErrorLog":
588
+ """Validate that response_category has the correct default value."""
589
+ if self.response_category != ResponseCategory.TRAINING_ERROR_LOG:
590
+ raise ValueError(
591
+ f"TrainingErrorLog response_category must be "
592
+ f"{ResponseCategory.TRAINING_ERROR_LOG}, "
593
+ f"got `{self.response_category}`."
594
+ )
595
+ return self
427
596
 
428
- arbitrary_types_allowed = True
597
+ def to_sse_event(self) -> ServerSentEvent:
598
+ """Convert to SSE event format."""
599
+ return ServerSentEvent(
600
+ event="copilot_response",
601
+ data=self.sse_data,
602
+ )
603
+
604
+ @property
605
+ def sse_data(self) -> Dict[str, Any]:
606
+ """Extract the SSE data payload."""
607
+ return {
608
+ "logs": [log.model_dump() for log in self.logs],
609
+ "response_category": self.response_category.value,
610
+ "completeness": self.response_completeness.value,
611
+ }
429
612
 
430
613
 
431
614
  class UsageStatistics(BaseModel):
@@ -493,6 +676,13 @@ class CopilotGenerationContext(BaseModel):
493
676
  last_user_message: Optional[Dict[str, Any]] = Field(
494
677
  None, description="The last user message with context that was processed."
495
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
+ )
496
686
 
497
687
  class Config:
498
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