letschatty 0.4.280__py3-none-any.whl → 0.4.343__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.
Files changed (92) hide show
  1. letschatty/models/ai_microservices/__init__.py +3 -3
  2. letschatty/models/ai_microservices/expected_output.py +35 -1
  3. letschatty/models/ai_microservices/lambda_events.py +85 -45
  4. letschatty/models/ai_microservices/lambda_invokation_types.py +6 -3
  5. letschatty/models/analytics/events/__init__.py +2 -3
  6. letschatty/models/analytics/events/chat_based_events/chat_funnel.py +69 -13
  7. letschatty/models/analytics/events/company_based_events/asset_events.py +9 -2
  8. letschatty/models/analytics/events/event_type_to_classes.py +6 -3
  9. letschatty/models/analytics/events/event_types.py +13 -50
  10. letschatty/models/chat/chat.py +14 -2
  11. letschatty/models/chat/chat_with_assets.py +6 -1
  12. letschatty/models/chat/client.py +0 -2
  13. letschatty/models/chat/continuous_conversation.py +1 -1
  14. letschatty/models/company/CRM/funnel.py +365 -33
  15. letschatty/models/company/__init__.py +3 -1
  16. letschatty/models/company/assets/ai_agents_v2/ai_agent_message_draft.py +58 -0
  17. letschatty/models/company/assets/ai_agents_v2/ai_agents_decision_output.py +1 -1
  18. letschatty/models/company/assets/ai_agents_v2/chain_of_thought_in_chat.py +5 -3
  19. letschatty/models/company/assets/ai_agents_v2/chatty_ai_agent.py +46 -2
  20. letschatty/models/company/assets/ai_agents_v2/chatty_ai_agent_in_chat.py +93 -1
  21. letschatty/models/company/assets/ai_agents_v2/pre_qualify_config.py +111 -0
  22. letschatty/models/company/assets/ai_agents_v2/statuses.py +33 -0
  23. letschatty/models/company/assets/assignment/__init__.py +14 -0
  24. letschatty/models/company/assets/assignment/assignment_assets.py +75 -0
  25. letschatty/models/company/assets/automation.py +10 -19
  26. letschatty/models/company/assets/chat_assets.py +12 -2
  27. letschatty/models/company/assets/company_assets.py +3 -0
  28. letschatty/models/company/assets/launch/__init__.py +12 -0
  29. letschatty/models/company/assets/launch/launch.py +128 -0
  30. letschatty/models/company/assets/launch/scheduled_communication.py +44 -0
  31. letschatty/models/company/assets/launch/subscription.py +63 -0
  32. letschatty/models/company/assets/sale.py +3 -3
  33. letschatty/models/company/assets/users/user.py +5 -1
  34. letschatty/models/company/company_messaging_settgins.py +2 -1
  35. letschatty/models/company/form_field.py +182 -12
  36. letschatty/models/data_base/collection_interface.py +29 -101
  37. letschatty/models/data_base/mongo_connection.py +9 -92
  38. letschatty/models/messages/chatty_messages/schema/chatty_content/content_document.py +4 -2
  39. letschatty/models/messages/chatty_messages/schema/chatty_content/content_media.py +4 -3
  40. letschatty/models/utils/custom_exceptions/custom_exceptions.py +24 -13
  41. letschatty/services/ai_agents/smart_follow_up_service_v2.py +10 -0
  42. letschatty/services/chat/chat_service.py +79 -14
  43. letschatty/services/chatty_assets/__init__.py +0 -12
  44. letschatty/services/chatty_assets/asset_service.py +13 -190
  45. letschatty/services/chatty_assets/base_container.py +2 -3
  46. letschatty/services/chatty_assets/base_container_with_collection.py +26 -35
  47. letschatty/services/continuous_conversation_service/continuous_conversation_helper.py +0 -11
  48. letschatty/services/events/events_manager.py +1 -218
  49. letschatty/services/factories/analytics/events_factory.py +6 -66
  50. letschatty/services/factories/lambda_ai_orchestrartor/lambda_events_factory.py +8 -23
  51. letschatty/services/users/user_factory.py +14 -8
  52. letschatty/services/validators/analytics_validator.py +11 -0
  53. {letschatty-0.4.280.dist-info → letschatty-0.4.343.dist-info}/METADATA +1 -1
  54. {letschatty-0.4.280.dist-info → letschatty-0.4.343.dist-info}/RECORD +56 -83
  55. {letschatty-0.4.280.dist-info → letschatty-0.4.343.dist-info}/WHEEL +1 -1
  56. letschatty/models/analytics/events/chat_based_events/ai_agent_execution_event.py +0 -71
  57. letschatty/services/chatty_assets/assets_collections.py +0 -137
  58. letschatty/services/chatty_assets/collections/__init__.py +0 -38
  59. letschatty/services/chatty_assets/collections/ai_agent_collection.py +0 -19
  60. letschatty/services/chatty_assets/collections/ai_agent_in_chat_collection.py +0 -32
  61. letschatty/services/chatty_assets/collections/ai_component_collection.py +0 -21
  62. letschatty/services/chatty_assets/collections/chain_of_thought_collection.py +0 -30
  63. letschatty/services/chatty_assets/collections/chat_collection.py +0 -21
  64. letschatty/services/chatty_assets/collections/contact_point_collection.py +0 -21
  65. letschatty/services/chatty_assets/collections/fast_answer_collection.py +0 -21
  66. letschatty/services/chatty_assets/collections/filter_criteria_collection.py +0 -18
  67. letschatty/services/chatty_assets/collections/flow_collection.py +0 -20
  68. letschatty/services/chatty_assets/collections/product_collection.py +0 -20
  69. letschatty/services/chatty_assets/collections/sale_collection.py +0 -20
  70. letschatty/services/chatty_assets/collections/source_collection.py +0 -21
  71. letschatty/services/chatty_assets/collections/tag_collection.py +0 -19
  72. letschatty/services/chatty_assets/collections/topic_collection.py +0 -21
  73. letschatty/services/chatty_assets/collections/user_collection.py +0 -20
  74. letschatty/services/chatty_assets/example_usage.py +0 -44
  75. letschatty/services/chatty_assets/services/__init__.py +0 -37
  76. letschatty/services/chatty_assets/services/ai_agent_in_chat_service.py +0 -73
  77. letschatty/services/chatty_assets/services/ai_agent_service.py +0 -23
  78. letschatty/services/chatty_assets/services/chain_of_thought_service.py +0 -70
  79. letschatty/services/chatty_assets/services/chat_service.py +0 -25
  80. letschatty/services/chatty_assets/services/contact_point_service.py +0 -29
  81. letschatty/services/chatty_assets/services/fast_answer_service.py +0 -32
  82. letschatty/services/chatty_assets/services/filter_criteria_service.py +0 -30
  83. letschatty/services/chatty_assets/services/flow_service.py +0 -25
  84. letschatty/services/chatty_assets/services/product_service.py +0 -30
  85. letschatty/services/chatty_assets/services/sale_service.py +0 -25
  86. letschatty/services/chatty_assets/services/source_service.py +0 -28
  87. letschatty/services/chatty_assets/services/tag_service.py +0 -32
  88. letschatty/services/chatty_assets/services/topic_service.py +0 -31
  89. letschatty/services/chatty_assets/services/user_service.py +0 -32
  90. letschatty/services/events/__init__.py +0 -6
  91. letschatty/services/factories/analytics/ai_agent_event_factory.py +0 -161
  92. {letschatty-0.4.280.dist-info → letschatty-0.4.343.dist-info}/LICENSE +0 -0
@@ -1,8 +1,8 @@
1
1
  from .expected_output import ExpectedOutputQualityTest, ExpectedOutputSmartTag
2
2
  from .lambda_events import (
3
- IncomingMessageAIDecision, QualityTestCallbackEvent, SmartTaggingCallbackEvent,
4
- QualityTestInteractionCallbackEvent, QualityTestEvent,
5
- AllQualityTestEvent, SmartTaggingEvent, QualityTestEventData, AllQualityTestEventData,ChatData,
3
+ IncomingMessageCallbackEvent, QualityTestCallbackEvent, SmartTaggingCallbackEvent,
4
+ QualityTestInteractionCallbackEvent, IncomingMessageEvent, QualityTestEvent,
5
+ AllQualityTestEvent, FollowUpEvent, SmartTaggingEvent, QualityTestEventData, AllQualityTestEventData,ChatData,
6
6
  ComparisonAnalysisCallbackMetadata, InteractionCallbackMetadata, SmartTaggingCallbackMetadata,
7
7
  SmartTaggingPromptEvent
8
8
  )
@@ -27,6 +27,20 @@ class ExpectedOutputSmartTag(BaseModel):
27
27
  chain_of_thought: ChainOfThoughtInChatRequest = Field(
28
28
  description="REQUIRED: Your reasoning process and response decision explanation"
29
29
  )
30
+ # Unsubscribe intent detection (for launch agents)
31
+ unsubscribe_intent: bool = Field(
32
+ default=False,
33
+ description="True if user expressed intent to unsubscribe from communications (e.g., 'no more messages', 'unsubscribe', 'stop', 'rechazar mensajes')"
34
+ )
35
+ # Acceptance criteria evaluation (for agents with data collection + acceptance_criteria)
36
+ acceptance_criteria_met: Optional[bool] = Field(
37
+ default=None,
38
+ description="True if user meets acceptance criteria, False if not, None if cannot be determined yet or no criteria configured"
39
+ )
40
+ acceptance_criteria_reason: Optional[str] = Field(
41
+ default=None,
42
+ description="Reason explaining why acceptance criteria is met or not met"
43
+ )
30
44
 
31
45
  @staticmethod
32
46
  def get_json_schema() -> dict:
@@ -100,9 +114,21 @@ class ExpectedOutputSmartTag(BaseModel):
100
114
  },
101
115
  "required": ["trigger", "trigger_id", "chatty_ai_agent_id", "chain_of_thought", "title"],
102
116
  "additionalProperties": False
117
+ },
118
+ "unsubscribe_intent": {
119
+ "type": "boolean",
120
+ "description": "True if user expressed intent to unsubscribe from communications (e.g., 'no more messages', 'unsubscribe', 'stop', 'rechazar mensajes')"
121
+ },
122
+ "acceptance_criteria_met": {
123
+ "anyOf": [{"type": "boolean"}, {"type": "null"}],
124
+ "description": "True if user meets acceptance criteria, False if not, null if cannot be determined yet"
125
+ },
126
+ "acceptance_criteria_reason": {
127
+ "anyOf": [{"type": "string"}, {"type": "null"}],
128
+ "description": "Reason explaining why acceptance criteria is met or not met"
103
129
  }
104
130
  },
105
- "required": ["automation", "conversation_topics", "data_collection", "chain_of_thought"],
131
+ "required": ["automation", "conversation_topics", "data_collection", "chain_of_thought", "unsubscribe_intent", "acceptance_criteria_met", "acceptance_criteria_reason"],
106
132
  "additionalProperties": False
107
133
  }
108
134
 
@@ -143,6 +169,12 @@ class ExpectedOutputIncomingMessage(BaseModel):
143
169
  action: IncomingMessageDecisionAction
144
170
  messages: List[str] = Field(description="Array of message strings to send to the customer. Required for send/suggest actions, optional for escalate action, empty array for skip/remove actions.")
145
171
  chain_of_thought: ChainOfThoughtInChatRequest = Field(description="REQUIRED: Your reasoning process and response decision explanation")
172
+ confidence: Optional[int] = Field(
173
+ default=None,
174
+ ge=0,
175
+ le=100,
176
+ description="Confidence level 0-100"
177
+ )
146
178
 
147
179
  def to_incoming_message_decision_output(self) -> IncomingMessageAIDecision:
148
180
  messages_drafts = [
@@ -161,3 +193,5 @@ class ExpectedOutputIncomingMessage(BaseModel):
161
193
  return incoming_decision
162
194
 
163
195
 
196
+ class ExpectedOutputSmartFollowUp(BaseModel):
197
+ action: Optional[str] = None
@@ -1,18 +1,17 @@
1
- from letschatty.models.company.assets.ai_agents_v2.chatty_ai_agent_in_chat import HumanInterventionReason
2
1
  from letschatty.models.company.assets.chat_assets import ChainOfThoughtInChatTrigger
3
2
  from pydantic import BaseModel, Field, model_validator
4
- from typing import Dict, Any, List, Optional, TYPE_CHECKING
3
+ from typing import Dict, Any, List, Optional
5
4
 
6
5
  from letschatty.models.base_models.ai_agent_component import AiAgentComponentType
7
6
  from letschatty.models.utils.types.identifier import StrObjectId
8
7
  from .lambda_invokation_types import InvokationType, LambdaAiEvent
9
- from .expected_output import ExpectedOutputIncomingMessage, ExpectedOutputSmartTag, ExpectedOutputQualityTest, IncomingMessageAIDecision
10
- from ...models.company.assets.ai_agents_v2.ai_agents_decision_output import IncomingMessageDecisionAction, SmartFollowUpDecision
8
+ from .expected_output import ExpectedOutputIncomingMessage, ExpectedOutputSmartTag, ExpectedOutputQualityTest, ExpectedOutputSmartFollowUp, IncomingMessageAIDecision
9
+ from ...models.company.assets.ai_agents_v2.ai_agents_decision_output import IncomingMessageDecisionAction
10
+ from ...models.company.assets.ai_agents_v2.chatty_ai_agent_in_chat import HumanInterventionReason
11
11
 
12
12
  class SmartTaggingCallbackMetadata(BaseModel):
13
13
  chat_id: StrObjectId
14
14
  company_id: StrObjectId
15
- trigger: ChainOfThoughtInChatTrigger
16
15
 
17
16
  class ComparisonAnalysisCallbackMetadata(BaseModel):
18
17
  test_case_id: StrObjectId
@@ -24,7 +23,21 @@ class InteractionCallbackMetadata(BaseModel):
24
23
  ai_agent_id: StrObjectId
25
24
  company_id: StrObjectId
26
25
  interaction_index: int
27
- trigger: ChainOfThoughtInChatTrigger
26
+
27
+ class IncomingMessageCallbackMetadata(BaseModel):
28
+ chat_id: StrObjectId
29
+ company_id: StrObjectId
30
+ ai_agent_id: StrObjectId
31
+ cot_id: StrObjectId
32
+ trigger: str
33
+ # Trigger info is in: incoming_message_ids for user_message,
34
+ # triggered_by_user_id for manual_trigger, smart_follow_up_id for follow_up
35
+ triggered_by_user_id: Optional[StrObjectId] = None
36
+
37
+ class IncomingMessageCallbackEvent(LambdaAiEvent):
38
+ type: InvokationType = InvokationType.INCOMING_MESSAGE_CALLBACK
39
+ data: IncomingMessageAIDecision
40
+ callback_metadata: IncomingMessageCallbackMetadata
28
41
 
29
42
  class QualityTestCallbackEvent(LambdaAiEvent):
30
43
  type: InvokationType = InvokationType.SINGLE_QUALITY_TEST_CALLBACK
@@ -48,15 +61,41 @@ class SmartTaggingCallbackEvent(LambdaAiEvent):
48
61
  callback_metadata: SmartTaggingCallbackMetadata
49
62
 
50
63
  @model_validator(mode="before")
51
- def validate_data(cls, data):
52
- if isinstance(data, dict) and "data" in data and "chain_of_thought" in data["data"]:
53
- data["data"]["chain_of_thought"]["chatty_ai_agent_id"] = "000000000000000000000000"
54
- return data
64
+ def normalize_data(cls, values):
65
+ if not isinstance(values, dict):
66
+ return values
67
+
68
+ data = values.get("data")
69
+ if isinstance(data, str):
70
+ import json
71
+ try:
72
+ data = json.loads(data)
73
+ values["data"] = data
74
+ except (json.JSONDecodeError, TypeError):
75
+ return values
76
+
77
+ if isinstance(data, dict) and "chain_of_thought" in data:
78
+ data["chain_of_thought"]["chatty_ai_agent_id"] = "000000000000000000000000"
79
+
80
+ return values
55
81
 
56
82
  class ChatData(BaseModel):
57
83
  chat_id: StrObjectId
58
84
  company_id: StrObjectId
59
85
 
86
+ class IncomingMessageEvent(LambdaAiEvent):
87
+ type: InvokationType = InvokationType.INCOMING_MESSAGE
88
+ data: ChatData
89
+
90
+ class FollowUpEvent(LambdaAiEvent):
91
+ type: InvokationType = InvokationType.FOLLOW_UP
92
+ data: ChatData
93
+
94
+
95
+ class SmartFollowUpDecisionOutputEvent(LambdaAiEvent):
96
+ type: InvokationType = InvokationType.FOLLOW_UP
97
+ data: ExpectedOutputSmartFollowUp
98
+
60
99
  class QualityTestEventData(BaseModel):
61
100
  chat_example_id: StrObjectId
62
101
  company_id: StrObjectId
@@ -111,7 +150,7 @@ class DoubleCheckerCallbackMetadata(BaseModel):
111
150
  ai_agent_id: StrObjectId
112
151
  incoming_messages_ids: List[str]
113
152
  # Trigger info
114
- trigger: ChainOfThoughtInChatTrigger
153
+ trigger: str
115
154
  # Trigger info is in: incoming_messages_ids for user_message,
116
155
  # triggered_by_user_id for manual_trigger, smart_follow_up_id for follow_up
117
156
  triggered_by_user_id: Optional[StrObjectId] = None
@@ -131,27 +170,6 @@ class DoubleCheckerForIncomingMessagesAnswerCallbackMetadata(BaseModel):
131
170
  ai_agent_id: StrObjectId
132
171
 
133
172
 
134
- # Smart Follow-Up Decision Output Events
135
-
136
- class SmartFollowUpDecisionOutputData(BaseModel):
137
- """Data for smart follow-up decision output"""
138
- chat_id: StrObjectId
139
- company_id: StrObjectId
140
- ai_agent_id: StrObjectId
141
- smart_follow_up_decision: 'SmartFollowUpDecision'
142
- smart_follow_up_id: Optional[StrObjectId] = None
143
-
144
- class SmartFollowUpDecisionOutputEvent(LambdaAiEvent):
145
- """
146
- Event for smart follow-up decision output.
147
-
148
- Similar to incoming message decision but for follow-ups.
149
- Bypasses double checker and sends directly to API.
150
- """
151
- type: InvokationType = InvokationType.SMART_FOLLOW_UP_DECISION_OUTPUT
152
- data: SmartFollowUpDecisionOutputData
153
-
154
-
155
173
  # New AI Agent Context Building Events (for new architecture)
156
174
 
157
175
  class GetChatWithPromptForIncomingMessageEventData(BaseModel):
@@ -184,7 +202,7 @@ class GetChatWithPromptForFollowUpEventData(BaseModel):
184
202
  """Data for get chat with prompt for follow-up event"""
185
203
  chat_id: StrObjectId
186
204
  company_id: StrObjectId
187
- smart_follow_up_id: Optional[StrObjectId] = Field(default=None, description="Smart follow-up ID")
205
+ smart_follow_up_id: StrObjectId
188
206
  # Trigger information
189
207
  trigger: ChainOfThoughtInChatTrigger
190
208
  # trigger_id is derived from: smart_follow_up_id
@@ -304,8 +322,8 @@ class EscalateAIAgentInChatEventData(BaseModel):
304
322
  """Data for escalating an AI agent (requiring human intervention)"""
305
323
  chat_id: StrObjectId
306
324
  company_id: StrObjectId
307
- reason: HumanInterventionReason = Field(description="Reason for escalation", default=HumanInterventionReason.SOMETHING_WENT_WRONG)
308
- message: Optional[str] = Field(default="El agente de IA requiere de tu intervención", description="The message to display to the user if any")
325
+ reason: str = Field(description="Reason for escalation (e.g., 'Complex technical issue', 'Customer request')")
326
+
309
327
 
310
328
  class EscalateAIAgentInChatEvent(LambdaAiEvent):
311
329
  """
@@ -336,21 +354,43 @@ class UnescalateAIAgentInChatEvent(LambdaAiEvent):
336
354
  data: UnescalateAIAgentInChatEventData
337
355
 
338
356
 
357
+ # Launch Events
358
+
359
+ class LaunchCommunicationEventData(BaseModel):
360
+ """Data for sending a scheduled launch communication"""
361
+ chat_ids: List[StrObjectId] = Field(description="Batch of chat IDs to send communication to")
362
+ company_id: StrObjectId
363
+ launch_id: StrObjectId
364
+ communication_id: str = Field(description="ID of the communication within the launch")
365
+ communication_type: str = Field(description="Type: pre_launch, post_attended, post_missed, welcome_kit")
366
+ lambda_callback_url: str = Field(description="URL for Lambda to call back after N8N processing")
367
+
368
+
369
+ class LaunchCommunicationEvent(LambdaAiEvent):
370
+ """
371
+ Event to send a scheduled launch communication.
372
+
373
+ Triggered by Copilot when delta_minutes is reached. Lambda processes
374
+ the batch of chats, building context for each and sending to N8N
375
+ for AI adaptation (or sending literal messages directly).
376
+ """
377
+ type: InvokationType = InvokationType.LAUNCH_COMMUNICATION
378
+ data: LaunchCommunicationEventData
379
+
339
380
 
340
- class GetChainOfThoughtsByChatIdEventData(BaseModel):
341
- """Data for getting chain of thoughts by chat ID"""
381
+ class LaunchWelcomeKitEventData(BaseModel):
382
+ """Data for sending a welcome kit with AI adaptation"""
342
383
  chat_id: StrObjectId
343
384
  company_id: StrObjectId
344
- skip: int = Field(default=0, description="Number of results to skip for pagination")
345
- limit: int = Field(default=10, description="Number of results to return")
385
+ launch_id: StrObjectId
346
386
 
347
387
 
348
- class GetChainOfThoughtsByChatIdEvent(LambdaAiEvent):
388
+ class LaunchWelcomeKitEvent(LambdaAiEvent):
349
389
  """
350
- Event to get chain of thoughts for a chat.
390
+ Event to send a welcome kit with AI adaptation.
351
391
 
352
- Returns a list of chain of thoughts associated with the given chat ID,
353
- sorted by created_at (newest first), with pagination support via skip.
392
+ Triggered when a user subscribes to a launch and the welcome kit
393
+ requires AI adaptation. Lambda builds context and sends to N8N.
354
394
  """
355
- type: InvokationType = InvokationType.GET_CHAIN_OF_THOUGHTS_BY_CHAT_ID
356
- data: GetChainOfThoughtsByChatIdEventData
395
+ type: InvokationType = InvokationType.LAUNCH_WELCOME_KIT
396
+ data: LaunchWelcomeKitEventData
@@ -3,20 +3,21 @@ from enum import StrEnum
3
3
  from pydantic import BaseModel
4
4
 
5
5
  class InvokationType(StrEnum):
6
+ INCOMING_MESSAGE = "incoming_message"
7
+ FOLLOW_UP = "follow_up"
6
8
  SINGLE_QUALITY_TEST = "single_quality_test"
7
9
  ALL_QUALITY_TEST = "all_quality_test"
8
10
  SMART_TAGGING = "smart_tagging"
9
11
  SMART_TAGGING_PROMPT = "smart_tagging_prompt"
10
12
  QUALITY_TEST_INTERACTION = "quality_test_interaction"
11
13
  # Callback-specific types
14
+ INCOMING_MESSAGE_CALLBACK = "incoming_message_callback"
12
15
  SINGLE_QUALITY_TEST_CALLBACK = "single_quality_test_callback"
13
16
  SMART_TAGGING_CALLBACK = "smart_tagging_callback"
14
17
  QUALITY_TESTS_FOR_UPDATED_AI_COMPONENT = "quality_tests_for_updated_ai_component"
15
18
  FIX_BUGGED_AI_AGENTS_CALLS_IN_CHATS = "fix_bugged_ai_agents_calls_in_chats"
16
19
  DOUBLE_CHECKER_FOR_INCOMING_MESSAGES_ANSWER = "double_checker_for_incoming_messages_answer"
17
20
  DOUBLE_CHECKER_FOR_INCOMING_MESSAGES_ANSWER_CALLBACK = "double_checker_for_incoming_messages_answer_callback"
18
- # Decision output events
19
- SMART_FOLLOW_UP_DECISION_OUTPUT = "smart_follow_up_decision_output"
20
21
  # AI Agent context building events (new architecture)
21
22
  GET_CHAT_WITH_PROMPT_INCOMING_MESSAGE = "get_chat_with_prompt_incoming_message"
22
23
  GET_CHAT_WITH_PROMPT_FOLLOW_UP = "get_chat_with_prompt_follow_up"
@@ -29,7 +30,9 @@ class InvokationType(StrEnum):
29
30
  UPDATE_AI_AGENT_MODE_IN_CHAT = "update_ai_agent_mode_in_chat"
30
31
  ESCALATE_AI_AGENT_IN_CHAT = "escalate_ai_agent_in_chat"
31
32
  UNESCALATE_AI_AGENT_IN_CHAT = "unescalate_ai_agent_in_chat"
32
- GET_CHAIN_OF_THOUGHTS_BY_CHAT_ID = "get_chain_of_thoughts_by_chat_id"
33
+ # Launch events
34
+ LAUNCH_COMMUNICATION = "launch_communication"
35
+ LAUNCH_WELCOME_KIT = "launch_welcome_kit"
33
36
 
34
37
  class LambdaAiEvent(BaseModel):
35
38
  type: InvokationType
@@ -4,7 +4,7 @@ from .chat_based_events.business_area import ChatBusinessAreaEvent, BusinessArea
4
4
  from .chat_based_events.workflow import WorkflowEvent, WorkflowEventData
5
5
  from .chat_based_events.chat_status import ChatStatusEvent, ChatStatusEventData, ChatStatusModification, ChatCreatedFrom
6
6
  from .chat_based_events.chat_funnel import ChatFunnelEvent, FunnelEventData
7
- from ...company.CRM.funnel import ClientFunnel, StageTransition
7
+ from ...company.CRM.funnel import ChatFunnel, StageTransition, ActiveFunnel
8
8
  from .company_based_events.user_events import UserEvent, UserEventData
9
9
  from .chat_based_events.quality_scoring import QualityScoringEvent
10
10
  from .chat_based_events.message import MessageEvent, MessageData
@@ -28,5 +28,4 @@ from ...messages.chatty_messages import ChattyMessage
28
28
  from ...company.CRM.funnel import Funnel, FunnelStage
29
29
  from ...utils.types import Status
30
30
  from .chat_based_events.chat_based_event import CustomerEventData
31
- from .chat_based_events.ai_agent_chat import ChattyAIChatEvent, ChattyAIChatData
32
- from .chat_based_events.ai_agent_execution_event import AIAgentExecutionEvent, AIAgentExecutionEventData
31
+ from .chat_based_events.ai_agent_chat import ChattyAIChatEvent, ChattyAIChatData
@@ -4,37 +4,93 @@ from ..base import Event
4
4
  from ..event_types import EventType
5
5
  from .chat_based_event import CustomerEventData
6
6
  from ....utils.types.identifier import StrObjectId
7
- from ....company.CRM.funnel import ClientFunnel, StageTransition
7
+ from ....company.CRM.funnel import StageTransition
8
8
  import json
9
9
 
10
+
10
11
  class FunnelEventData(CustomerEventData):
11
- funnel_id: StrObjectId
12
- funnel_stage_transition: StageTransition
13
- time_in_funnel_seconds: Optional[int] = None
12
+ """
13
+ Event data for chat funnel events.
14
+
15
+ Fields required per event type:
16
+ - STARTED: funnel_id, chat_funnel_id, stage_transition
17
+ - STAGE_CHANGED: funnel_id, chat_funnel_id, stage_transition (with time_in_previous_stage_seconds)
18
+ - COMPLETED: funnel_id, chat_funnel_id, time_in_funnel_seconds, time_in_last_stage_seconds
19
+ - ABANDONED: funnel_id, chat_funnel_id, time_in_funnel_seconds, time_in_last_stage_seconds
20
+ """
21
+ funnel_id: StrObjectId = Field(description="The funnel the chat is in")
22
+ chat_funnel_id: StrObjectId = Field(description="Reference to the ChatFunnel record")
23
+
24
+ # For STARTED and STAGE_CHANGED events
25
+ stage_transition: Optional[StageTransition] = Field(
26
+ default=None,
27
+ description="The stage transition details (for STARTED and STAGE_CHANGED)"
28
+ )
29
+
30
+ # For COMPLETED and ABANDONED events
31
+ time_in_funnel_seconds: Optional[int] = Field(
32
+ default=None,
33
+ description="Total time spent in the funnel (for COMPLETED and ABANDONED)"
34
+ )
35
+ time_in_last_stage_seconds: Optional[int] = Field(
36
+ default=None,
37
+ description="Time spent in the last stage before completion/abandonment"
38
+ )
39
+
14
40
 
15
41
  class ChatFunnelEvent(Event):
16
- """Event for tracking chat funnel stage transitions"""
42
+ """
43
+ Event for tracking chat funnel lifecycle.
44
+
45
+ Events:
46
+ - CHAT_FUNNEL_STARTED: Chat entered a funnel
47
+ - CHAT_FUNNEL_STAGE_CHANGED: Chat moved between stages
48
+ - CHAT_FUNNEL_COMPLETED: Chat completed the funnel
49
+ - CHAT_FUNNEL_ABANDONED: Chat abandoned the funnel
50
+ """
17
51
  data: FunnelEventData
18
52
 
19
- # Define valid event types for this event class
20
53
  VALID_TYPES: ClassVar[set] = {
21
- EventType.CHAT_FUNNEL_UPDATED,
22
54
  EventType.CHAT_FUNNEL_STARTED,
55
+ EventType.CHAT_FUNNEL_STAGE_CHANGED,
23
56
  EventType.CHAT_FUNNEL_COMPLETED,
24
57
  EventType.CHAT_FUNNEL_ABANDONED
25
58
  }
26
59
 
27
-
28
60
  @field_validator('data')
29
61
  def validate_data_fields(cls, v: FunnelEventData, info: ValidationInfo):
30
62
  """Validate that appropriate fields are set based on event type"""
31
- if info.data.get('type') == EventType.CHAT_FUNNEL_UPDATED and v.time_in_funnel_seconds is None:
32
- raise ValueError("time_in_funnel_seconds must be set for CHAT_FUNNEL_UPDATED events")
33
- if info.data.get('type') == EventType.CHAT_FUNNEL_UPDATED and v.funnel_stage_transition.time_in_previous_stage_seconds is None:
34
- raise ValueError("time_in_previous_stage_seconds must be set for CHAT_FUNNEL_UPDATED events")
63
+ event_type = info.data.get('type')
64
+
65
+ # STARTED: requires stage_transition
66
+ if event_type == EventType.CHAT_FUNNEL_STARTED:
67
+ if v.stage_transition is None:
68
+ raise ValueError("stage_transition must be set for CHAT_FUNNEL_STARTED events")
69
+
70
+ # STAGE_CHANGED: requires stage_transition with time_in_previous_stage_seconds
71
+ elif event_type == EventType.CHAT_FUNNEL_STAGE_CHANGED:
72
+ if v.stage_transition is None:
73
+ raise ValueError("stage_transition must be set for CHAT_FUNNEL_STAGE_CHANGED events")
74
+ if v.stage_transition.time_in_previous_stage_seconds is None:
75
+ raise ValueError("time_in_previous_stage_seconds must be set in stage_transition for CHAT_FUNNEL_STAGE_CHANGED events")
76
+
77
+ # COMPLETED: requires time metrics
78
+ elif event_type == EventType.CHAT_FUNNEL_COMPLETED:
79
+ if v.time_in_funnel_seconds is None:
80
+ raise ValueError("time_in_funnel_seconds must be set for CHAT_FUNNEL_COMPLETED events")
81
+ if v.time_in_last_stage_seconds is None:
82
+ raise ValueError("time_in_last_stage_seconds must be set for CHAT_FUNNEL_COMPLETED events")
83
+
84
+ # ABANDONED: requires time metrics
85
+ elif event_type == EventType.CHAT_FUNNEL_ABANDONED:
86
+ if v.time_in_funnel_seconds is None:
87
+ raise ValueError("time_in_funnel_seconds must be set for CHAT_FUNNEL_ABANDONED events")
88
+ if v.time_in_last_stage_seconds is None:
89
+ raise ValueError("time_in_last_stage_seconds must be set for CHAT_FUNNEL_ABANDONED events")
90
+
35
91
  return v
36
92
 
37
93
  def model_dump_json(self, *args, **kwargs):
38
94
  dump = json.loads(super().model_dump_json(*args, **kwargs))
39
95
  dump['data'] = self.data.model_dump_json()
40
- return dump
96
+ return dump
@@ -58,6 +58,9 @@ class AssetEvent(Event):
58
58
  EventType.FUNNEL_STAGE_CREATED,
59
59
  EventType.FUNNEL_STAGE_UPDATED,
60
60
  EventType.FUNNEL_STAGE_DELETED,
61
+ EventType.FUNNEL_MEMBER_ADDED,
62
+ EventType.FUNNEL_MEMBER_UPDATED,
63
+ EventType.FUNNEL_MEMBER_REMOVED,
61
64
  EventType.WORKFLOW_CREATED,
62
65
  EventType.WORKFLOW_UPDATED,
63
66
  EventType.WORKFLOW_DELETED,
@@ -70,6 +73,9 @@ class AssetEvent(Event):
70
73
  EventType.FILTER_CRITERIA_CREATED,
71
74
  EventType.FILTER_CRITERIA_UPDATED,
72
75
  EventType.FILTER_CRITERIA_DELETED,
76
+ EventType.FORM_FIELD_CREATED,
77
+ EventType.FORM_FIELD_UPDATED,
78
+ EventType.FORM_FIELD_DELETED,
73
79
  EventType.COMPANY_CREATED,
74
80
  EventType.COMPANY_UPDATED,
75
81
  EventType.COMPANY_DELETED
@@ -77,8 +83,9 @@ class AssetEvent(Event):
77
83
 
78
84
  @model_validator(mode='after')
79
85
  def validate_data_fields(self):
80
- if "deleted" not in self.type.value and not self.data.asset:
81
- raise ValueError("asset must be set for all events except DELETED")
86
+ # Asset is not required for deleted/removed events
87
+ if "deleted" not in self.type.value and "removed" not in self.type.value and not self.data.asset:
88
+ raise ValueError("asset must be set for all events except DELETED/REMOVED")
82
89
  return self
83
90
 
84
91
  def model_dump_json(self, *args, **kwargs):
@@ -41,10 +41,9 @@ EVENT_TO_TYPE_CLASSES = {
41
41
  #CONTINUOUS CONVERSATION
42
42
  EventType.CONTINUOUS_CONVERSATION_CREATED : ContinuousConversationEvent,
43
43
  EventType.CONTINUOUS_CONVERSATION_UPDATED : ContinuousConversationEvent,
44
- #FUNNEL STAGES
45
- # Funnel-level events
44
+ #CHAT FUNNEL EVENTS
46
45
  EventType.CHAT_FUNNEL_STARTED : ChatFunnelEvent,
47
- EventType.CHAT_FUNNEL_UPDATED : ChatFunnelEvent,
46
+ EventType.CHAT_FUNNEL_STAGE_CHANGED : ChatFunnelEvent,
48
47
  EventType.CHAT_FUNNEL_COMPLETED : ChatFunnelEvent,
49
48
  EventType.CHAT_FUNNEL_ABANDONED : ChatFunnelEvent,
50
49
 
@@ -86,6 +85,10 @@ EVENT_TO_TYPE_CLASSES = {
86
85
  EventType.WORKFLOW_CREATED : AssetEvent,
87
86
  EventType.WORKFLOW_UPDATED : AssetEvent,
88
87
  EventType.WORKFLOW_DELETED : AssetEvent,
88
+ #FORM FIELDS
89
+ EventType.FORM_FIELD_CREATED : AssetEvent,
90
+ EventType.FORM_FIELD_UPDATED : AssetEvent,
91
+ EventType.FORM_FIELD_DELETED : AssetEvent,
89
92
  #AGENTS
90
93
  EventType.USER_CREATED : UserEvent,
91
94
  EventType.USER_UPDATED : UserEvent,
@@ -12,51 +12,6 @@ class EventType(StrEnum):
12
12
  AI_AGENT_ASSIGNED_TO_CHAT = "chat.chatty_ai_agent.assigned_to_chat"
13
13
  AI_AGENT_REMOVED_FROM_CHAT = "chat.chatty_ai_agent.removed_from_chat"
14
14
  AI_AGENT_UPDATED_ON_CHAT = "chat.chatty_ai_agent.updated_on_chat"
15
-
16
- #CHATTY AI AGENT EXECUTION EVENTS - 3-level hierarchy for execution tracking
17
- # Pattern: chatty_ai_agent_in_chat.{operation}.{detail}
18
- # Note: Execution events are already chat-scoped via CustomerEventData
19
-
20
- # TRIGGER EVENTS - What initiates AI agent processing
21
- CHATTY_AI_AGENT_IN_CHAT_TRIGGER_USER_MESSAGE = "chatty_ai_agent_in_chat.trigger.user_message"
22
- CHATTY_AI_AGENT_IN_CHAT_TRIGGER_FOLLOW_UP = "chatty_ai_agent_in_chat.trigger.follow_up"
23
- CHATTY_AI_AGENT_IN_CHAT_TRIGGER_MANUAL = "chatty_ai_agent_in_chat.trigger.manual"
24
- CHATTY_AI_AGENT_IN_CHAT_TRIGGER_RETRY = "chatty_ai_agent_in_chat.trigger.retry"
25
-
26
- # STATE EVENTS - AI agent state changes
27
- CHATTY_AI_AGENT_IN_CHAT_STATE_PROCESSING_STARTED = "chatty_ai_agent_in_chat.state.processing_started"
28
- CHATTY_AI_AGENT_IN_CHAT_STATE_CALL_STARTED = "chatty_ai_agent_in_chat.state.call_started"
29
- CHATTY_AI_AGENT_IN_CHAT_STATE_ESCALATED = "chatty_ai_agent_in_chat.state.escalated"
30
- CHATTY_AI_AGENT_IN_CHAT_STATE_UNESCALATED = "chatty_ai_agent_in_chat.state.unescalated"
31
-
32
- # CALL EVENTS - Outbound calls to services
33
- CHATTY_AI_AGENT_IN_CHAT_CALL_GET_CHAT_WITH_PROMPT = "chatty_ai_agent_in_chat.call.get_chat_with_prompt"
34
- CHATTY_AI_AGENT_IN_CHAT_CALL_TAGGER = "chatty_ai_agent_in_chat.call.tagger"
35
- CHATTY_AI_AGENT_IN_CHAT_CALL_DOUBLE_CHECKER = "chatty_ai_agent_in_chat.call.double_checker"
36
- CHATTY_AI_AGENT_IN_CHAT_CALL_DEBUGGER = "chatty_ai_agent_in_chat.call.debugger"
37
-
38
- # CALLBACK EVENTS - Responses received from services
39
- CHATTY_AI_AGENT_IN_CHAT_CALLBACK_GET_CHAT_WITH_PROMPT = "chatty_ai_agent_in_chat.callback.get_chat_with_prompt"
40
- CHATTY_AI_AGENT_IN_CHAT_CALLBACK_TAGGER = "chatty_ai_agent_in_chat.callback.tagger"
41
- CHATTY_AI_AGENT_IN_CHAT_CALLBACK_DOUBLE_CHECKER = "chatty_ai_agent_in_chat.callback.double_checker"
42
- CHATTY_AI_AGENT_IN_CHAT_CALLBACK_OUTPUT_RECEIVED = "chatty_ai_agent_in_chat.callback.output_received"
43
-
44
- # DECISION EVENTS - AI agent decisions and actions
45
- CHATTY_AI_AGENT_IN_CHAT_DECISION_SEND = "chatty_ai_agent_in_chat.decision.send"
46
- CHATTY_AI_AGENT_IN_CHAT_DECISION_SUGGEST = "chatty_ai_agent_in_chat.decision.suggest"
47
- CHATTY_AI_AGENT_IN_CHAT_DECISION_ESCALATE = "chatty_ai_agent_in_chat.decision.escalate"
48
- CHATTY_AI_AGENT_IN_CHAT_DECISION_SKIP = "chatty_ai_agent_in_chat.decision.skip"
49
- CHATTY_AI_AGENT_IN_CHAT_DECISION_SENT_TO_API = "chatty_ai_agent_in_chat.decision.sent_to_api"
50
- CHATTY_AI_AGENT_IN_CHAT_DECISION_COMPLETED = "chatty_ai_agent_in_chat.decision.completed"
51
-
52
- # ERROR EVENTS - Failures and cancellations
53
- CHATTY_AI_AGENT_IN_CHAT_ERROR_CALL_FAILED = "chatty_ai_agent_in_chat.error.call_failed"
54
- CHATTY_AI_AGENT_IN_CHAT_ERROR_CALL_CANCELLED = "chatty_ai_agent_in_chat.error.call_cancelled"
55
- CHATTY_AI_AGENT_IN_CHAT_ERROR_VALIDATION_FAILED = "chatty_ai_agent_in_chat.error.validation_failed"
56
-
57
- # RATING EVENTS - User feedback
58
- CHATTY_AI_AGENT_IN_CHAT_RATING_RECEIVED = "chatty_ai_agent_in_chat.rating.received"
59
-
60
15
  #PRODUCTS
61
16
  PRODUCT_ASSIGNED = "chat.product.assigned"
62
17
  PRODUCT_REMOVED = "chat.product.removed"
@@ -87,10 +42,9 @@ class EventType(StrEnum):
87
42
  #CONTINUOUS CONVERSATION
88
43
  CONTINUOUS_CONVERSATION_CREATED = "chat.continuous_conversation.created"
89
44
  CONTINUOUS_CONVERSATION_UPDATED = "chat.continuous_conversation.updated"
90
- #FUNNEL STAGES
91
- # Funnel-level events
92
- CHAT_FUNNEL_STARTED = "chat.funnel.started" # New
93
- CHAT_FUNNEL_UPDATED = "chat.funnel.updated"
45
+ #CHAT FUNNEL EVENTS
46
+ CHAT_FUNNEL_STARTED = "chat.funnel.started"
47
+ CHAT_FUNNEL_STAGE_CHANGED = "chat.funnel.stage_changed"
94
48
  CHAT_FUNNEL_COMPLETED = "chat.funnel.completed"
95
49
  CHAT_FUNNEL_ABANDONED = "chat.funnel.abandoned"
96
50
 
@@ -129,13 +83,18 @@ class EventType(StrEnum):
129
83
  FAST_ANSWER_CREATED = "company.fast_answer.created"
130
84
  FAST_ANSWER_UPDATED = "company.fast_answer.updated"
131
85
  FAST_ANSWER_DELETED = "company.fast_answer.deleted"
132
- #FUNNEL STAGES
86
+ #FUNNELS
133
87
  FUNNEL_CREATED = "company.funnel.created"
134
88
  FUNNEL_UPDATED = "company.funnel.updated"
135
89
  FUNNEL_DELETED = "company.funnel.deleted"
90
+ #FUNNEL STAGES
136
91
  FUNNEL_STAGE_CREATED = "company.funnel_stage.created"
137
92
  FUNNEL_STAGE_UPDATED = "company.funnel_stage.updated"
138
93
  FUNNEL_STAGE_DELETED = "company.funnel_stage.deleted"
94
+ #FUNNEL MEMBERS
95
+ FUNNEL_MEMBER_ADDED = "company.funnel_member.added"
96
+ FUNNEL_MEMBER_UPDATED = "company.funnel_member.updated"
97
+ FUNNEL_MEMBER_REMOVED = "company.funnel_member.removed"
139
98
  #BUSINESS AREA
140
99
  BUSINESS_AREA_CREATED = "company.business_area.created"
141
100
  BUSINESS_AREA_UPDATED = "company.business_area.updated"
@@ -163,3 +122,7 @@ class EventType(StrEnum):
163
122
  FILTER_CRITERIA_CREATED = "company.filter_criteria.created"
164
123
  FILTER_CRITERIA_UPDATED = "company.filter_criteria.updated"
165
124
  FILTER_CRITERIA_DELETED = "company.filter_criteria.deleted"
125
+ #FORM FIELDS
126
+ FORM_FIELD_CREATED = "company.form_field.created"
127
+ FORM_FIELD_UPDATED = "company.form_field.updated"
128
+ FORM_FIELD_DELETED = "company.form_field.deleted"
@@ -19,6 +19,7 @@ from ..utils.custom_exceptions.custom_exceptions import MissingAIAgentForSmartFo
19
19
  from .time_left import TimeLeft
20
20
  from ..company.conversation_topic import TopicTimelineEntry
21
21
  from ..company.form_field import CollectedData
22
+ from ..company.CRM.funnel import ActiveFunnel
22
23
  import json
23
24
  import logging
24
25
  logger = logging.getLogger(__name__)
@@ -47,6 +48,7 @@ class Chat(CompanyAssetModel):
47
48
  chatty_ai_agent: Optional[ChattyAIAgentAssignedToChat] = Field(default=None, description="The id of the chatty ai agent that might or might not be assigned to the chat")
48
49
  suggested_messages: List[MessageDraft] = Field(default_factory=list)
49
50
  topics_timeline: List[TopicTimelineEntry] = Field(default_factory=list, description="Timeline of conversation topics throughout the conversation")
51
+ active_funnel: Optional[ActiveFunnel] = Field(default=None, description="Current active funnel for this chat")
50
52
  model_config = ConfigDict(extra="ignore")
51
53
 
52
54
  @property
@@ -139,6 +141,7 @@ class Chat(CompanyAssetModel):
139
141
  @property
140
142
  def assigned_chatty_ai_agent(self) -> ChattyAIAgentAssignedToChat:
141
143
  """Get the assigned chatty ai agent"""
144
+ raise DeprecationWarning("This method is not implemented anymore")
142
145
  if self.chatty_ai_agent is None:
143
146
  raise MissingAIAgentForSmartFollowUp(f"Chat {self.id} has no chatty ai agent assigned to it")
144
147
  return self.chatty_ai_agent
@@ -266,7 +269,17 @@ class Chat(CompanyAssetModel):
266
269
  @property
267
270
  def bought_product_ids(self) -> List[StrObjectId]:
268
271
  """Get all sale ids in the chat"""
269
- return [sale.product_id for sale in self.sales]
272
+ product_ids: List[StrObjectId] = []
273
+ for sale in self.sales:
274
+ sale_product_ids = getattr(sale, "product_ids", None)
275
+ if sale_product_ids:
276
+ for product_id in sale_product_ids:
277
+ if product_id not in product_ids:
278
+ product_ids.append(product_id)
279
+ elif sale.product_id:
280
+ if sale.product_id not in product_ids:
281
+ product_ids.append(sale.product_id)
282
+ return product_ids
270
283
 
271
284
  @property
272
285
  def products(self) -> List[AssignedAssetToChat]:
@@ -363,4 +376,3 @@ class Chat(CompanyAssetModel):
363
376
  dump["last_message"] = self.last_message.model_dump_json(serializer=SerializerType.DATABASE) if self.last_message else None
364
377
  dump["flow_states"] = [flow_state.model_dump_json(serializer=SerializerType.DATABASE) for flow_state in self.flow_states]
365
378
  return dump
366
-
@@ -1,6 +1,6 @@
1
-
2
1
  from .chat import Chat
3
2
  from letschatty.models.company.assets import TagPreview, FlowPreview, ContactPoint, Sale, ChattyAIAgentPreview, ProductPreview
3
+ from letschatty.models.company.CRM.funnel import ActiveFunnel
4
4
 
5
5
  from letschatty.models.utils.types.serializer_type import SerializerType
6
6
  from pydantic import BaseModel
@@ -15,3 +15,8 @@ class ChatWithAssets(BaseModel):
15
15
  contact_points: List[ContactPoint]
16
16
  flows_links_states: List[FlowPreview]
17
17
  chatty_ai_agent: Optional[ChattyAIAgentPreview]
18
+
19
+ @property
20
+ def active_funnel(self) -> Optional[ActiveFunnel]:
21
+ """Convenience property to access the chat's active funnel"""
22
+ return self.chat.active_funnel