letschatty 0.4.349__py3-none-any.whl → 0.4.351__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 letschatty might be problematic. Click here for more details.

Files changed (89) hide show
  1. letschatty/models/ai_microservices/__init__.py +4 -4
  2. letschatty/models/ai_microservices/expected_output.py +29 -2
  3. letschatty/models/ai_microservices/lambda_events.py +155 -28
  4. letschatty/models/ai_microservices/lambda_invokation_types.py +4 -1
  5. letschatty/models/ai_microservices/n8n_ai_agents_payload.py +3 -1
  6. letschatty/models/analytics/events/__init__.py +3 -3
  7. letschatty/models/analytics/events/chat_based_events/ai_agent_execution_event.py +71 -0
  8. letschatty/models/analytics/events/chat_based_events/chat_funnel.py +13 -69
  9. letschatty/models/analytics/events/company_based_events/asset_events.py +2 -9
  10. letschatty/models/analytics/events/event_type_to_classes.py +3 -7
  11. letschatty/models/analytics/events/event_types.py +50 -11
  12. letschatty/models/chat/chat.py +2 -13
  13. letschatty/models/chat/chat_with_assets.py +1 -6
  14. letschatty/models/chat/client.py +2 -0
  15. letschatty/models/chat/continuous_conversation.py +1 -1
  16. letschatty/models/company/CRM/funnel.py +33 -365
  17. letschatty/models/company/__init__.py +1 -7
  18. letschatty/models/company/assets/ai_agents_v2/ai_agents_decision_output.py +1 -1
  19. letschatty/models/company/assets/ai_agents_v2/chatty_ai_agent_in_chat.py +4 -0
  20. letschatty/models/company/assets/ai_agents_v2/chatty_ai_mode.py +2 -2
  21. letschatty/models/company/assets/ai_agents_v2/get_chat_with_prompt_response.py +1 -0
  22. letschatty/models/company/assets/ai_agents_v2/pre_qualify_config.py +28 -1
  23. letschatty/models/company/assets/automation.py +19 -10
  24. letschatty/models/company/assets/chat_assets.py +2 -3
  25. letschatty/models/company/assets/company_assets.py +0 -2
  26. letschatty/models/company/assets/sale.py +3 -3
  27. letschatty/models/company/empresa.py +1 -2
  28. letschatty/models/data_base/collection_interface.py +101 -29
  29. letschatty/models/data_base/mongo_connection.py +92 -9
  30. letschatty/models/messages/chatty_messages/schema/chatty_content/content_document.py +2 -4
  31. letschatty/models/messages/chatty_messages/schema/chatty_content/content_media.py +3 -4
  32. letschatty/models/utils/custom_exceptions/custom_exceptions.py +14 -1
  33. letschatty/services/ai_agents/smart_follow_up_context_builder_v2.py +5 -2
  34. letschatty/services/chat/chat_service.py +11 -47
  35. letschatty/services/chatty_assets/__init__.py +12 -0
  36. letschatty/services/chatty_assets/asset_service.py +190 -13
  37. letschatty/services/chatty_assets/assets_collections.py +137 -0
  38. letschatty/services/chatty_assets/base_container.py +3 -2
  39. letschatty/services/chatty_assets/base_container_with_collection.py +35 -26
  40. letschatty/services/chatty_assets/collections/__init__.py +38 -0
  41. letschatty/services/chatty_assets/collections/ai_agent_collection.py +19 -0
  42. letschatty/services/chatty_assets/collections/ai_agent_in_chat_collection.py +32 -0
  43. letschatty/services/chatty_assets/collections/ai_component_collection.py +21 -0
  44. letschatty/services/chatty_assets/collections/chain_of_thought_collection.py +30 -0
  45. letschatty/services/chatty_assets/collections/chat_collection.py +21 -0
  46. letschatty/services/chatty_assets/collections/contact_point_collection.py +21 -0
  47. letschatty/services/chatty_assets/collections/fast_answer_collection.py +21 -0
  48. letschatty/services/chatty_assets/collections/filter_criteria_collection.py +18 -0
  49. letschatty/services/chatty_assets/collections/flow_collection.py +20 -0
  50. letschatty/services/chatty_assets/collections/product_collection.py +20 -0
  51. letschatty/services/chatty_assets/collections/sale_collection.py +20 -0
  52. letschatty/services/chatty_assets/collections/source_collection.py +21 -0
  53. letschatty/services/chatty_assets/collections/tag_collection.py +19 -0
  54. letschatty/services/chatty_assets/collections/topic_collection.py +21 -0
  55. letschatty/services/chatty_assets/collections/user_collection.py +20 -0
  56. letschatty/services/chatty_assets/example_usage.py +44 -0
  57. letschatty/services/chatty_assets/services/__init__.py +37 -0
  58. letschatty/services/chatty_assets/services/ai_agent_in_chat_service.py +73 -0
  59. letschatty/services/chatty_assets/services/ai_agent_service.py +23 -0
  60. letschatty/services/chatty_assets/services/chain_of_thought_service.py +70 -0
  61. letschatty/services/chatty_assets/services/chat_service.py +25 -0
  62. letschatty/services/chatty_assets/services/contact_point_service.py +29 -0
  63. letschatty/services/chatty_assets/services/fast_answer_service.py +32 -0
  64. letschatty/services/chatty_assets/services/filter_criteria_service.py +30 -0
  65. letschatty/services/chatty_assets/services/flow_service.py +25 -0
  66. letschatty/services/chatty_assets/services/product_service.py +30 -0
  67. letschatty/services/chatty_assets/services/sale_service.py +25 -0
  68. letschatty/services/chatty_assets/services/source_service.py +28 -0
  69. letschatty/services/chatty_assets/services/tag_service.py +32 -0
  70. letschatty/services/chatty_assets/services/topic_service.py +31 -0
  71. letschatty/services/chatty_assets/services/user_service.py +32 -0
  72. letschatty/services/continuous_conversation_service/continuous_conversation_helper.py +11 -0
  73. letschatty/services/events/__init__.py +6 -0
  74. letschatty/services/events/events_manager.py +218 -1
  75. letschatty/services/factories/analytics/ai_agent_event_factory.py +161 -0
  76. letschatty/services/factories/analytics/events_factory.py +66 -30
  77. letschatty/services/factories/lambda_ai_orchestrartor/lambda_events_factory.py +46 -8
  78. letschatty/services/messages_helpers/get_caption_or_body_or_preview.py +6 -4
  79. letschatty/services/validators/analytics_validator.py +0 -11
  80. {letschatty-0.4.349.dist-info → letschatty-0.4.351.dist-info}/METADATA +1 -1
  81. {letschatty-0.4.349.dist-info → letschatty-0.4.351.dist-info}/RECORD +83 -53
  82. letschatty/models/analytics/events/chat_based_events/chat_client.py +0 -19
  83. letschatty/models/company/integrations/product_sync_status.py +0 -28
  84. letschatty/models/company/integrations/shopify/company_shopify_integration.py +0 -62
  85. letschatty/models/company/integrations/shopify/shopify_product_sync_status.py +0 -18
  86. letschatty/models/company/integrations/shopify/shopify_webhook_topics.py +0 -40
  87. letschatty/models/company/integrations/sync_status_enum.py +0 -9
  88. {letschatty-0.4.349.dist-info → letschatty-0.4.351.dist-info}/LICENSE +0 -0
  89. {letschatty-0.4.349.dist-info → letschatty-0.4.351.dist-info}/WHEEL +0 -0
@@ -1,2 +1,219 @@
1
- from letschatty.models.base_models.singleton import SingletonMeta
1
+ """
2
+ Events Manager - Handles queuing and publishing events to EventBridge
2
3
 
4
+ This is a generic implementation that can be configured for different environments.
5
+ """
6
+ from ...models.base_models.singleton import SingletonMeta
7
+ from ...models.analytics.events.base import Event, EventType
8
+ from typing import List, Optional, Callable
9
+ import logging
10
+ import boto3
11
+ import queue
12
+ import threading
13
+ import time
14
+ from datetime import datetime
15
+ from zoneinfo import ZoneInfo
16
+ import os
17
+ import json
18
+
19
+ logger = logging.getLogger("EventsManager")
20
+
21
+
22
+ class EventsManager(metaclass=SingletonMeta):
23
+ """
24
+ Manages event queuing and publishing to AWS EventBridge.
25
+
26
+ Can be configured via environment variables or init parameters.
27
+ """
28
+
29
+ def __init__(self,
30
+ event_bus_name: Optional[str] = None,
31
+ source: Optional[str] = None,
32
+ publish_events: Optional[bool] = None,
33
+ failed_events_callback: Optional[Callable] = None):
34
+ """
35
+ Initialize EventsManager.
36
+
37
+ Args:
38
+ event_bus_name: AWS EventBridge event bus name (or uses env var)
39
+ source: Source identifier for events (or uses env var)
40
+ publish_events: Whether to publish events (or uses env var)
41
+ failed_events_callback: Optional callback for handling failed events
42
+ """
43
+ self.events_queue: queue.Queue[Event] = queue.Queue()
44
+ self.eventbridge_client = boto3.client('events', region_name='us-east-1')
45
+
46
+ # Configuration - prefer parameters, fall back to env vars
47
+ self.event_bus_name = event_bus_name or os.getenv('CHATTY_EVENT_BUS_NAME', 'chatty-events')
48
+ self.source = source or os.getenv('CHATTY_EVENT_SOURCE')
49
+ if not self.source:
50
+ raise ValueError("Source must be provided either as a parameter or through the CHATTY_EVENT_SOURCE environment variable.")
51
+ self.publish_events = publish_events if publish_events is not None else os.getenv('PUBLISH_EVENTS_TO_EVENTBRIDGE', 'true').lower() == 'true'
52
+
53
+ self.max_retries = 3
54
+ self.thread_lock = threading.Lock()
55
+ self.thread_running = False
56
+ self.max_thread_runtime = 300
57
+ self.failed_events_callback = failed_events_callback
58
+
59
+ logger.debug(f"EventsManager initialized: bus={self.event_bus_name}, source={self.source}, publish={self.publish_events}")
60
+
61
+ def queue_events(self, events: List[Event]):
62
+ """Queue events and spawn a thread to publish them if one isn't already running"""
63
+ if not self.publish_events:
64
+ logger.debug("Event publishing disabled, skipping")
65
+ return
66
+
67
+ for event in events:
68
+ logger.debug(f"Queueing event: {event.type.value} {event.company_id}")
69
+ logger.debug(f"Event: {event.model_dump_json()}")
70
+ self.events_queue.put(event)
71
+
72
+ logger.debug(f"Queued {len(events)} events")
73
+ if events:
74
+ logger.debug(f"1° event: {events[0].model_dump_json()}")
75
+
76
+ # Only start a new thread if one isn't already running
77
+ with self.thread_lock:
78
+ if not self.thread_running:
79
+ logger.debug("Starting publisher thread")
80
+ self.thread_running = True
81
+ thread = threading.Thread(
82
+ target=self._process_queue,
83
+ daemon=True,
84
+ name="EventBridge-Publisher"
85
+ )
86
+ thread.start()
87
+ logger.debug("Started publisher thread")
88
+ else:
89
+ logger.debug("Publisher thread already running, using existing thread")
90
+
91
+ def _process_queue(self):
92
+ """Process all events in the queue and then terminate"""
93
+ try:
94
+ start_time = time.time()
95
+ while not self.events_queue.empty():
96
+ logger.debug("Processing queue")
97
+ events_batch = []
98
+ if time.time() - start_time > self.max_thread_runtime:
99
+ logger.warning(f"Thread ran for more than {self.max_thread_runtime}s - terminating")
100
+ break
101
+
102
+ # Collect up to 10 events (EventBridge limit)
103
+ for _ in range(10):
104
+ try:
105
+ event = self.events_queue.get(timeout=0.5)
106
+ events_batch.append(event)
107
+ self.events_queue.task_done()
108
+ except queue.Empty:
109
+ logger.debug("Queue is empty")
110
+ break
111
+
112
+ # Publish this batch
113
+ if events_batch:
114
+ self._publish_batch(events_batch)
115
+
116
+ except Exception as e:
117
+ logger.exception(f"Error in publisher thread: {str(e)}")
118
+
119
+ finally:
120
+ # Mark thread as completed
121
+ with self.thread_lock:
122
+ self.thread_running = False
123
+
124
+ def _publish_batch(self, events: List[Event]):
125
+ """Send a batch of events to EventBridge with retries"""
126
+ if not events:
127
+ return
128
+
129
+ entries = []
130
+ for event in events:
131
+ entry = {
132
+ 'Source': self.source,
133
+ 'DetailType': event.type.value,
134
+ 'Detail': json.dumps(event.model_dump_json()),
135
+ 'EventBusName': self.event_bus_name
136
+ }
137
+ logger.debug(f"Appending event: {event.type.value}")
138
+ entries.append(entry)
139
+
140
+ for retry in range(self.max_retries):
141
+ try:
142
+ logger.debug(f"Sending {len(entries)} events to EventBridge")
143
+ logger.debug(f"Entries: {entries}")
144
+ response = self.eventbridge_client.put_events(Entries=entries)
145
+ logger.debug(f"Response: {response}")
146
+
147
+ if response.get('FailedEntryCount', 0) == 0:
148
+ logger.info(f"Successfully published {len(events)} events")
149
+ return
150
+
151
+ # Handle partial failures
152
+ failed_entries: List[dict] = []
153
+ failed_events: List[Event] = []
154
+
155
+ for i, result in enumerate(response.get('Entries', [])):
156
+ if 'ErrorCode' in result:
157
+ failed_entries.append(entries[i])
158
+ failed_events.append(events[i])
159
+ logger.error(f"Failed to publish event: {events[i].type.value}")
160
+
161
+ if retry < self.max_retries - 1 and failed_entries:
162
+ logger.info(f"Retrying {len(failed_entries)} events")
163
+ entries = failed_entries
164
+ events = failed_events
165
+ else:
166
+ # Store failed events via callback if provided
167
+ if self.failed_events_callback and failed_events:
168
+ failed_events_with_errors = []
169
+ for i, event in enumerate(failed_events):
170
+ result = response.get('Entries', [])[i]
171
+ failed_event_data = {
172
+ "event": event.model_dump_json(),
173
+ "error_code": result.get('ErrorCode'),
174
+ "error_message": result.get('ErrorMessage'),
175
+ "retry_count": self.max_retries,
176
+ "timestamp": datetime.now(ZoneInfo("UTC"))
177
+ }
178
+ failed_events_with_errors.append(failed_event_data)
179
+
180
+ try:
181
+ self.failed_events_callback(failed_events_with_errors)
182
+ except Exception as e:
183
+ logger.error(f"Error calling failed_events_callback: {e}")
184
+
185
+ logger.error(f"Gave up on {len(failed_entries)} events after {self.max_retries} attempts")
186
+ return
187
+
188
+ except Exception as e:
189
+ if retry < self.max_retries - 1:
190
+ logger.warning(f"Error publishing events (attempt {retry+1}/{self.max_retries}): {str(e)}")
191
+ time.sleep(0.5 * (2 ** retry)) # Exponential backoff
192
+ else:
193
+ logger.exception(f"Failed to publish events after {self.max_retries} attempts")
194
+ return
195
+
196
+ def flush(self):
197
+ """Wait for all queued events to be processed"""
198
+ # If no thread is running but we have events, start one
199
+ with self.thread_lock:
200
+ if not self.thread_running and not self.events_queue.empty():
201
+ self.thread_running = True
202
+ thread = threading.Thread(
203
+ target=self._process_queue,
204
+ daemon=True,
205
+ name="EventBridge-Publisher"
206
+ )
207
+ thread.start()
208
+
209
+ # Wait for queue to be empty
210
+ try:
211
+ self.events_queue.join()
212
+ return True
213
+ except Exception:
214
+ logger.warning("Error waiting for events queue to complete")
215
+ return False
216
+
217
+
218
+ # Singleton instance
219
+ events_manager = EventsManager()
@@ -0,0 +1,161 @@
1
+ """
2
+ AI Agent Event Factory - Helper for creating AI agent execution events
3
+
4
+ This factory simplifies event creation by providing a consistent interface
5
+ for generating both full analytics events and simplified UI events.
6
+ """
7
+
8
+ from letschatty.models.analytics.events.chat_based_events.ai_agent_execution_event import (
9
+ AIAgentExecutionEvent,
10
+ AIAgentExecutionEventData
11
+ )
12
+ from letschatty.models.analytics.events.event_types import EventType
13
+ from datetime import datetime
14
+ from zoneinfo import ZoneInfo
15
+ from typing import Optional, Dict, Any
16
+ from letschatty.models.utils.types.identifier import StrObjectId
17
+ import logging
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class AIAgentEventFactory:
23
+ """
24
+ Factory for creating AI agent execution events with proper context.
25
+
26
+ Provides a simplified API for event creation while ensuring all required
27
+ fields are properly populated for analytics and monitoring.
28
+ """
29
+
30
+ @staticmethod
31
+ def create_event(
32
+ event_type: EventType,
33
+ chat_id: StrObjectId,
34
+ company_id: StrObjectId,
35
+ frozen_company_name: str,
36
+ ai_agent_id: StrObjectId,
37
+ chain_of_thought_id: StrObjectId,
38
+ trigger: str,
39
+ source: str = "chatty.api",
40
+ decision_type: Optional[str] = None,
41
+ error_message: Optional[str] = None,
42
+ duration_ms: Optional[int] = None,
43
+ user_rating: Optional[int] = None,
44
+ metadata: Optional[Dict[str, Any]] = None,
45
+ trace_id: Optional[str] = None
46
+ ) -> AIAgentExecutionEvent:
47
+ """
48
+ Create a full AI agent execution event for EventBridge.
49
+
50
+ Args:
51
+ event_type: The type of event (from EventType enum)
52
+ chat_id: ID of the chat where the event occurred
53
+ company_id: ID of the company
54
+ frozen_company_name: Company name snapshot for analytics
55
+ ai_agent_id: ID of the AI agent asset
56
+ chain_of_thought_id: ID of the chain of thought execution
57
+ trigger: What triggered the execution (USER_MESSAGE, FOLLOW_UP, etc.)
58
+ source: Event source (e.g., 'chatty.api', 'chatty.lambda')
59
+ decision_type: Type of decision if applicable (send, suggest, escalate, skip)
60
+ error_message: Error message if this is an error event
61
+ duration_ms: Duration of the operation in milliseconds
62
+ user_rating: User rating (1-5 stars) if applicable
63
+ metadata: Additional event-specific data
64
+ trace_id: Trace ID for tracking event flows across systems
65
+
66
+ Returns:
67
+ AIAgentExecutionEvent ready to be queued to EventBridge
68
+ """
69
+ return AIAgentExecutionEvent(
70
+ type=event_type,
71
+ time=datetime.now(ZoneInfo("UTC")),
72
+ source=source,
73
+ company_id=company_id,
74
+ frozen_company_name=frozen_company_name,
75
+ specversion="1.0",
76
+ trace_id=trace_id,
77
+ data=AIAgentExecutionEventData(
78
+ chat_id=chat_id,
79
+ ai_agent_id=ai_agent_id,
80
+ chain_of_thought_id=chain_of_thought_id,
81
+ trigger=trigger,
82
+ decision_type=decision_type,
83
+ error_message=error_message,
84
+ duration_ms=duration_ms,
85
+ user_rating=user_rating,
86
+ metadata=metadata
87
+ )
88
+ )
89
+
90
+ @staticmethod
91
+ def get_simplified_event_type(event_type: EventType) -> str:
92
+ """
93
+ Convert full EventType to simplified event type string for UI.
94
+
95
+ Transforms 'chatty_ai_agent_in_chat.trigger.user_message' -> 'trigger.user_message'
96
+ This provides a cleaner format for embedded events in COT documents.
97
+
98
+ Args:
99
+ event_type: Full EventType enum value
100
+
101
+ Returns:
102
+ Simplified event type string (e.g., 'trigger.user_message')
103
+ """
104
+ # Extract the last two parts after 'chatty_ai_agent_in_chat.'
105
+ parts = event_type.value.split('.')
106
+ if len(parts) >= 3 and parts[0] == 'chatty_ai_agent_in_chat':
107
+ return '.'.join(parts[1:]) # e.g., 'trigger.user_message'
108
+ return event_type.value # Fallback to full type if format doesn't match
109
+
110
+ @staticmethod
111
+ def get_user_friendly_message(event_type: EventType, **context) -> str:
112
+ """
113
+ Generate a user-friendly message for an event type.
114
+
115
+ This provides human-readable descriptions for events that will be
116
+ displayed in the UI as part of the chain of thought timeline.
117
+
118
+ Args:
119
+ event_type: The type of event
120
+ **context: Additional context for message formatting (e.g., decision_type, error_message)
121
+
122
+ Returns:
123
+ User-friendly message string
124
+ """
125
+ messages = {
126
+ EventType.CHATTY_AI_AGENT_IN_CHAT_TRIGGER_USER_MESSAGE: "Triggered by user message",
127
+ EventType.CHATTY_AI_AGENT_IN_CHAT_TRIGGER_FOLLOW_UP: "Triggered by smart follow-up",
128
+ EventType.CHATTY_AI_AGENT_IN_CHAT_TRIGGER_MANUAL: "Manually triggered",
129
+ EventType.CHATTY_AI_AGENT_IN_CHAT_TRIGGER_RETRY: "Retry triggered",
130
+
131
+ EventType.CHATTY_AI_AGENT_IN_CHAT_STATE_PROCESSING_STARTED: "Processing started",
132
+ EventType.CHATTY_AI_AGENT_IN_CHAT_STATE_CALL_STARTED: "AI agent call started",
133
+ EventType.CHATTY_AI_AGENT_IN_CHAT_STATE_ESCALATED: "Escalated to human agent",
134
+ EventType.CHATTY_AI_AGENT_IN_CHAT_STATE_UNESCALATED: "Returned to AI agent",
135
+
136
+ EventType.CHATTY_AI_AGENT_IN_CHAT_CALL_GET_CHAT_WITH_PROMPT: "Requesting chat context",
137
+ EventType.CHATTY_AI_AGENT_IN_CHAT_CALL_TAGGER: "Calling tagger service",
138
+ EventType.CHATTY_AI_AGENT_IN_CHAT_CALL_DOUBLE_CHECKER: "Calling double checker",
139
+ EventType.CHATTY_AI_AGENT_IN_CHAT_CALL_DEBUGGER: "Running debugger",
140
+
141
+ EventType.CHATTY_AI_AGENT_IN_CHAT_CALLBACK_GET_CHAT_WITH_PROMPT: "Chat context received",
142
+ EventType.CHATTY_AI_AGENT_IN_CHAT_CALLBACK_TAGGER: "Tagger response received",
143
+ EventType.CHATTY_AI_AGENT_IN_CHAT_CALLBACK_DOUBLE_CHECKER: "Double checker validation complete",
144
+ EventType.CHATTY_AI_AGENT_IN_CHAT_CALLBACK_OUTPUT_RECEIVED: "AI agent output received",
145
+
146
+ EventType.CHATTY_AI_AGENT_IN_CHAT_DECISION_SEND: "Decision: Send message",
147
+ EventType.CHATTY_AI_AGENT_IN_CHAT_DECISION_SUGGEST: "Decision: Suggest message",
148
+ EventType.CHATTY_AI_AGENT_IN_CHAT_DECISION_ESCALATE: "Decision: Escalate to human",
149
+ EventType.CHATTY_AI_AGENT_IN_CHAT_DECISION_SKIP: "Decision: Skip message",
150
+ EventType.CHATTY_AI_AGENT_IN_CHAT_DECISION_SENT_TO_API: "Decision sent to API",
151
+ EventType.CHATTY_AI_AGENT_IN_CHAT_DECISION_COMPLETED: "Decision completed successfully",
152
+
153
+ EventType.CHATTY_AI_AGENT_IN_CHAT_ERROR_CALL_FAILED: f"Call failed: {context.get('error_message', 'Unknown error')}",
154
+ EventType.CHATTY_AI_AGENT_IN_CHAT_ERROR_CALL_CANCELLED: "Call cancelled",
155
+ EventType.CHATTY_AI_AGENT_IN_CHAT_ERROR_VALIDATION_FAILED: f"Validation failed: {context.get('error_message', 'Invalid data')}",
156
+
157
+ EventType.CHATTY_AI_AGENT_IN_CHAT_RATING_RECEIVED: f"User rated: {context.get('user_rating', '?')}/5 stars",
158
+ }
159
+
160
+ return messages.get(event_type, str(event_type))
161
+
@@ -1,6 +1,7 @@
1
1
  from typing import Optional, Dict, Any
2
2
  from datetime import datetime
3
3
  from ....models.analytics.events import *
4
+ from ....models.analytics.events.chat_based_events.ai_agent_execution_event import AIAgentExecutionEvent
4
5
  from ....models.base_models.chatty_asset_model import ChattyAssetModel
5
6
  from ....models.company.assets.chat_assets import AssignedAssetToChat
6
7
  from ....models.company.empresa import EmpresaModel
@@ -321,7 +322,7 @@ class EventFactory:
321
322
  return events
322
323
 
323
324
  @staticmethod
324
- def funnel_stage_events(chat_with_assets: ChatWithAssets, company_info: EmpresaModel, trace_id: str,executor_type: ExecutorType, executor_id: StrObjectId, chat_funnel: ChatFunnel, funnel_transition: StageTransition, event_type: EventType, time: datetime, company_snapshot: Optional[CompanyChatsSnapshot] = None, agent_snapshot: Optional[AgentChatsSnapshot] = None) -> List[Event]:
325
+ def funnel_stage_events(chat_with_assets: ChatWithAssets, company_info: EmpresaModel, trace_id: str,executor_type: ExecutorType, executor_id: StrObjectId, chat_funnel: ClientFunnel, funnel_transition: StageTransition, event_type: EventType, time: datetime, company_snapshot: Optional[CompanyChatsSnapshot] = None, agent_snapshot: Optional[AgentChatsSnapshot] = None) -> List[Event]:
325
326
  events = []
326
327
  base_data = EventFactory._create_base_customer_event_data(
327
328
  chat_with_assets=chat_with_assets,
@@ -334,10 +335,8 @@ class EventFactory:
334
335
  funnel_data = FunnelEventData(
335
336
  **base_data.model_dump(),
336
337
  funnel_id=chat_funnel.funnel_id,
337
- chat_funnel_id=chat_funnel.id,
338
- stage_transition=funnel_transition,
339
- time_in_funnel_seconds=chat_funnel.time_in_funnel_seconds,
340
- time_in_last_stage_seconds=chat_funnel.time_in_current_stage_seconds
338
+ funnel_stage_transition=funnel_transition,
339
+ time_in_funnel_seconds=chat_funnel.time_in_funnel_seconds
341
340
  )
342
341
  event = ChatFunnelEvent(
343
342
  specversion=EventFactory.package_version(),
@@ -415,30 +414,6 @@ class EventFactory:
415
414
  events.append(event)
416
415
  return events
417
416
 
418
- @staticmethod
419
- def chat_client_updated_events(chat_with_assets: ChatWithAssets, company_info: EmpresaModel, trace_id: str, executor_type: ExecutorType, executor_id: StrObjectId, event_type: EventType, time: datetime, company_snapshot: Optional[CompanyChatsSnapshot] = None, agent_snapshot: Optional[AgentChatsSnapshot] = None) -> List[Event]:
420
- events = []
421
- base_data = EventFactory._create_base_customer_event_data(
422
- chat_with_assets=chat_with_assets,
423
- company_info=company_info,
424
- executor_type=executor_type,
425
- executor_id=executor_id,
426
- company_snapshot=company_snapshot,
427
- agent_snapshot=agent_snapshot
428
- )
429
- event = ChatClientEvent(
430
- specversion=EventFactory.package_version(),
431
- type=event_type,
432
- time=time,
433
- data=base_data,
434
- source="chatty_api.webapp",
435
- company_id=company_info.id,
436
- frozen_company_name=company_info.frozen_name,
437
- trace_id=trace_id
438
- )
439
- events.append(event)
440
- return events
441
-
442
417
  @staticmethod
443
418
  def chat_created_events(chat_with_assets: ChatWithAssets, company_info: EmpresaModel, trace_id: str,executor_type: ExecutorType, executor_id: StrObjectId, event_type: EventType, time: datetime, contact_point_id: Optional[StrObjectId], created_from: ChatCreatedFrom, company_snapshot: Optional[CompanyChatsSnapshot] = None, agent_snapshot: Optional[AgentChatsSnapshot] = None) -> List[Event]:
444
419
  events = []
@@ -637,4 +612,65 @@ class EventFactory:
637
612
  trace_id=trace_id
638
613
  )
639
614
  events.append(event)
640
- return events
615
+ return events
616
+
617
+ @staticmethod
618
+ def ai_agent_execution_event(
619
+ event_type: EventType,
620
+ chat_id: StrObjectId,
621
+ company_id: StrObjectId,
622
+ frozen_company_name: str,
623
+ ai_agent_id: StrObjectId,
624
+ chain_of_thought_id: StrObjectId,
625
+ trigger: str,
626
+ source: str = "chatty.api",
627
+ decision_type: Optional[str] = None,
628
+ error_message: Optional[str] = None,
629
+ duration_ms: Optional[int] = None,
630
+ user_rating: Optional[int] = None,
631
+ metadata: Optional[Dict[str, Any]] = None,
632
+ trace_id: Optional[str] = None
633
+ ) -> AIAgentExecutionEvent:
634
+ """
635
+ Create an AI agent execution event using the modularized AIAgentEventFactory.
636
+
637
+ This method delegates to AIAgentEventFactory to maintain modularity while
638
+ keeping event creation centralized through EventsFactory.
639
+
640
+ Args:
641
+ event_type: The type of event (from EventType enum)
642
+ chat_id: ID of the chat where the event occurred
643
+ company_id: ID of the company
644
+ frozen_company_name: Company name snapshot for analytics
645
+ ai_agent_id: ID of the AI agent asset
646
+ chain_of_thought_id: ID of the chain of thought execution
647
+ trigger: What triggered the execution (USER_MESSAGE, FOLLOW_UP, etc.)
648
+ source: Event source (e.g., 'chatty.api', 'chatty.lambda')
649
+ decision_type: Type of decision if applicable
650
+ error_message: Error message if this is an error event
651
+ duration_ms: Duration of the operation in milliseconds
652
+ user_rating: User rating (1-5 stars) if applicable
653
+ metadata: Additional event-specific data
654
+ trace_id: Trace ID for tracking event flows
655
+
656
+ Returns:
657
+ AIAgentExecutionEvent ready to be queued to EventBridge
658
+ """
659
+ from .ai_agent_event_factory import AIAgentEventFactory
660
+
661
+ return AIAgentEventFactory.create_event(
662
+ event_type=event_type,
663
+ chat_id=chat_id,
664
+ company_id=company_id,
665
+ frozen_company_name=frozen_company_name,
666
+ ai_agent_id=ai_agent_id,
667
+ chain_of_thought_id=chain_of_thought_id,
668
+ trigger=trigger,
669
+ source=source,
670
+ decision_type=decision_type,
671
+ error_message=error_message,
672
+ duration_ms=duration_ms,
673
+ user_rating=user_rating,
674
+ metadata=metadata,
675
+ trace_id=trace_id
676
+ )
@@ -1,8 +1,27 @@
1
1
 
2
2
  from letschatty.models import StrObjectId
3
- from letschatty.models.ai_microservices.lambda_events import DoubleCheckerForIncomingMessagesAnswerCallbackEvent, DoubleCheckerForIncomingMessagesAnswerEvent, FixBuggedAiAgentsCallsInChatsEvent, QualityTestEventData, QualityTestsForUpdatedAIComponentEvent, QualityTestsForUpdatedAIComponentEventData
3
+ from letschatty.models.ai_microservices.lambda_events import (
4
+ DoubleCheckerForIncomingMessagesAnswerCallbackEvent,
5
+ DoubleCheckerForIncomingMessagesAnswerEvent,
6
+ FixBuggedAiAgentsCallsInChatsEvent,
7
+ GetChainOfThoughtsByChatIdEvent,
8
+ QualityTestEventData,
9
+ QualityTestsForUpdatedAIComponentEvent,
10
+ QualityTestsForUpdatedAIComponentEventData,
11
+ CancelExecutionEvent,
12
+ ManualTriggerEvent,
13
+ AssignAIAgentToChatEvent,
14
+ RemoveAIAgentFromChatEvent,
15
+ SmartFollowUpDecisionOutputEvent,
16
+ UpdateAIAgentModeInChatEvent,
17
+ EscalateAIAgentInChatEvent,
18
+ UnescalateAIAgentInChatEvent,
19
+ GetChatWithPromptForIncomingMessageEvent,
20
+ GetChatWithPromptForFollowUpEvent,
21
+ UpdateAIAgentPrequalStatusInChatEvent,
22
+ )
4
23
  from letschatty.models.ai_microservices.lambda_invokation_types import InvokationType
5
- from letschatty.models.ai_microservices import AllQualityTestEvent, AllQualityTestEventData, FollowUpEvent, IncomingMessageEvent, QualityTestEvent, QualityTestInteractionCallbackEvent, SmartTaggingCallbackEvent, IncomingMessageCallbackEvent, QualityTestCallbackEvent, LambdaAiEvent, SmartTaggingEvent, SmartTaggingPromptEvent
24
+ from letschatty.models.ai_microservices import AllQualityTestEvent, AllQualityTestEventData, QualityTestEvent, QualityTestInteractionCallbackEvent, SmartTaggingCallbackEvent, QualityTestCallbackEvent, LambdaAiEvent, SmartTaggingEvent, SmartTaggingPromptEvent
6
25
  from letschatty.models.base_models.ai_agent_component import AiAgentComponent
7
26
  from letschatty.models.company.assets.ai_agents_v2.chat_example import ChatExample
8
27
  from letschatty.models.company.assets.ai_agents_v2.chat_example_test import ChatExampleTestCase
@@ -19,16 +38,10 @@ class LambdaEventFactory:
19
38
  return SmartTaggingEvent(**event_data)
20
39
  case InvokationType.SMART_TAGGING_CALLBACK:
21
40
  return SmartTaggingCallbackEvent(**event_data)
22
- case InvokationType.INCOMING_MESSAGE:
23
- return IncomingMessageEvent(**event_data)
24
41
  case InvokationType.SINGLE_QUALITY_TEST:
25
42
  return QualityTestEvent(**event_data)
26
43
  case InvokationType.ALL_QUALITY_TEST:
27
44
  return AllQualityTestEvent(**event_data)
28
- case InvokationType.INCOMING_MESSAGE_CALLBACK:
29
- return IncomingMessageCallbackEvent(**event_data)
30
- case InvokationType.FOLLOW_UP:
31
- return FollowUpEvent(**event_data)
32
45
  case InvokationType.SINGLE_QUALITY_TEST_CALLBACK:
33
46
  return QualityTestCallbackEvent(**event_data)
34
47
  case InvokationType.SMART_TAGGING_PROMPT:
@@ -41,6 +54,31 @@ class LambdaEventFactory:
41
54
  return DoubleCheckerForIncomingMessagesAnswerEvent(**event_data)
42
55
  case InvokationType.DOUBLE_CHECKER_FOR_INCOMING_MESSAGES_ANSWER_CALLBACK:
43
56
  return DoubleCheckerForIncomingMessagesAnswerCallbackEvent(**event_data)
57
+ case InvokationType.MANUAL_TRIGGER:
58
+ return ManualTriggerEvent(**event_data)
59
+ case InvokationType.CANCEL_EXECUTION:
60
+ return CancelExecutionEvent(**event_data)
61
+ case InvokationType.ASSIGN_AI_AGENT_TO_CHAT:
62
+ return AssignAIAgentToChatEvent(**event_data)
63
+ case InvokationType.REMOVE_AI_AGENT_FROM_CHAT:
64
+ return RemoveAIAgentFromChatEvent(**event_data)
65
+ case InvokationType.UPDATE_AI_AGENT_MODE_IN_CHAT:
66
+ return UpdateAIAgentModeInChatEvent(**event_data)
67
+ case InvokationType.UPDATE_AI_AGENT_PREQUAL_STATUS_IN_CHAT:
68
+ return UpdateAIAgentPrequalStatusInChatEvent(**event_data)
69
+ case InvokationType.ESCALATE_AI_AGENT_IN_CHAT:
70
+ return EscalateAIAgentInChatEvent(**event_data)
71
+ case InvokationType.UNESCALATE_AI_AGENT_IN_CHAT:
72
+ return UnescalateAIAgentInChatEvent(**event_data)
73
+ case InvokationType.GET_CHAIN_OF_THOUGHTS_BY_CHAT_ID:
74
+ return GetChainOfThoughtsByChatIdEvent(**event_data)
75
+ case InvokationType.SMART_FOLLOW_UP_DECISION_OUTPUT:
76
+ return SmartFollowUpDecisionOutputEvent(**event_data)
77
+
78
+ case InvokationType.GET_CHAT_WITH_PROMPT_INCOMING_MESSAGE:
79
+ return GetChatWithPromptForIncomingMessageEvent(**event_data)
80
+ case InvokationType.GET_CHAT_WITH_PROMPT_FOLLOW_UP:
81
+ return GetChatWithPromptForFollowUpEvent(**event_data)
44
82
  case _:
45
83
  raise ValueError(f"Invalid event type: {event_type}")
46
84
 
@@ -1,3 +1,4 @@
1
+ from letschatty.models.messages.chatty_messages.button import ChattyContentButton
1
2
  from ...models.messages.chatty_messages.schema import ChattyContent, ChattyContentText, ChattyContentContacts, ChattyContentLocation, ChattyContentImage, ChattyContentVideo, ChattyContentAudio, ChattyContentDocument, ChattyContentSticker, ChattyContentReaction
2
3
  class MessageTextOrCaptionOrPreview:
3
4
  @staticmethod
@@ -5,9 +6,9 @@ class MessageTextOrCaptionOrPreview:
5
6
  if isinstance(message_content, ChattyContentText):
6
7
  return message_content.body
7
8
  elif isinstance(message_content, ChattyContentContacts):
8
- return "👥 Mensaje de tipo contacto"
9
- elif isinstance(message_content, ChattyContentLocation):
10
- return "📍 Mensaje de tipo ubicación"
9
+ return f"👤 *Contacto recibido:* {message_content.contacts[0].full_name} \n📞 *Teléfono:* {message_content.contacts[0].phone_number}"
10
+ elif isinstance(message_content, ChattyContentLocation):
11
+ return f"📍 \nLatitud: {message_content.latitude} \nLongitud: {message_content.longitude}_"
11
12
  elif isinstance(message_content, ChattyContentImage):
12
13
  return "🖼️ Mensaje de tipo imagen"
13
14
  elif isinstance(message_content, ChattyContentVideo):
@@ -22,6 +23,7 @@ class MessageTextOrCaptionOrPreview:
22
23
  return "😀 Mensaje de tipo sticker"
23
24
  elif isinstance(message_content, ChattyContentReaction):
24
25
  return "❤️ Mensaje de tipo reacción"
26
+ elif isinstance(message_content, ChattyContentButton):
27
+ return f"{message_content.payload}"
25
28
  else:
26
-
27
29
  return "Vista previa del mensaje"
@@ -10,10 +10,6 @@ if TYPE_CHECKING:
10
10
 
11
11
  class SourcesAndTopicsValidator:
12
12
  """This class provides methods to validate sources and topics."""
13
- @staticmethod
14
- def normalize_source_name(name: str) -> str:
15
- return name.strip().lower()
16
-
17
13
  @staticmethod
18
14
  def source_validation_check(sources : Dict[str,Source], topics : Dict[str,Topic], source : Source, existing_source_id : str | None = None) -> None:
19
15
  """Checks validation of a source against other sources and topics."""
@@ -32,16 +28,9 @@ class SourcesAndTopicsValidator:
32
28
  """This method checks that no duplicated source is created.
33
29
  For Pure Ads, it checks that the ad_id is unique and doesn't exist already.
34
30
  For Other Sources, it checks that exact trigger doesn't exist already. """
35
- normalized_name = SourcesAndTopicsValidator.normalize_source_name(source.name)
36
- existing_source_name = None
37
- if existing_source_id and existing_source_id in sources:
38
- existing_source_name = SourcesAndTopicsValidator.normalize_source_name(sources[existing_source_id].name)
39
- should_check_name = existing_source_name is None or normalized_name != existing_source_name
40
31
  for source_id, source_to_check in sources.items():
41
32
  if source_id == existing_source_id:
42
33
  continue
43
- if should_check_name and SourcesAndTopicsValidator.normalize_source_name(source_to_check.name) == normalized_name:
44
- raise ConflictedSource(f":warning: *Conflict while adding source: Name already exists* \n New source '{source.name}' \n- Id {source.id} \n Existing source '{source_to_check.name}' \n- Id {source_to_check.id}", conflicting_source_id=source_to_check.id)
45
34
  if source == source_to_check: #type: ignore
46
35
  if isinstance(source, OtherSource):
47
36
  raise ConflictedSource(f":warning: *Conflict while adding source: Trigger already exists* \n New source '{source.name}' \n- Id {source.id} \n- Trigger {source.trigger[:30]} \n Existing source '{source_to_check.name}' \n- Id {source_to_check.id} \n- Trigger '{source_to_check.trigger[:30]}'", conflicting_source_id=source_to_check.id)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: letschatty
3
- Version: 0.4.349
3
+ Version: 0.4.351
4
4
  Summary: Models and custom classes to work across the Chattyverse
5
5
  License: MIT
6
6
  Author: Axel