mojentic 0.5.7__py3-none-any.whl → 0.6.1__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.
@@ -0,0 +1,138 @@
1
+ """
2
+ Defines tracer event types for tracking system interactions.
3
+ """
4
+ from typing import Any, Dict, List, Optional, Type
5
+ from datetime import datetime
6
+ import uuid
7
+
8
+ from pydantic import Field
9
+
10
+ from mojentic.event import Event
11
+
12
+
13
+ class TracerEvent(Event):
14
+ """
15
+ Base class for all tracer-specific events.
16
+
17
+ Tracer events are used to track system interactions for observability purposes.
18
+ They are distinct from regular events which are used for agent communication.
19
+ """
20
+ timestamp: float = Field(..., description="Timestamp when the event occurred")
21
+ correlation_id: str = Field(default_factory=lambda: str(uuid.uuid4()), description="UUID string that is copied from cause-to-affect for tracing events")
22
+
23
+ def printable_summary(self) -> str:
24
+ """
25
+ Return a formatted string summary of the event.
26
+
27
+ Returns
28
+ -------
29
+ str
30
+ A formatted string with the event information.
31
+ """
32
+ event_time = datetime.fromtimestamp(self.timestamp).strftime("%H:%M:%S.%f")[:-3]
33
+ return f"[{event_time}] {type(self).__name__} (correlation_id: {self.correlation_id})"
34
+
35
+
36
+ class LLMCallTracerEvent(TracerEvent):
37
+ """
38
+ Records when an LLM is called with specific messages.
39
+ """
40
+ model: str = Field(..., description="The LLM model that was used")
41
+ messages: List[dict] = Field(..., description="The messages sent to the LLM")
42
+ temperature: float = Field(1.0, description="The temperature setting used for the call")
43
+ tools: Optional[List[Dict]] = Field(None, description="The tools available to the LLM, if any")
44
+
45
+ def printable_summary(self) -> str:
46
+ """Return a formatted summary of the LLM call event."""
47
+ base_summary = super().printable_summary()
48
+ summary = f"{base_summary}\n Model: {self.model}"
49
+
50
+ if self.messages:
51
+ msg_count = len(self.messages)
52
+ summary += f"\n Messages: {msg_count} message{'s' if msg_count != 1 else ''}"
53
+
54
+ if self.temperature != 1.0:
55
+ summary += f"\n Temperature: {self.temperature}"
56
+
57
+ if self.tools:
58
+ tool_names = [tool.get('name', 'unknown') for tool in self.tools]
59
+ summary += f"\n Available Tools: {', '.join(tool_names)}"
60
+
61
+ return summary
62
+
63
+
64
+ class LLMResponseTracerEvent(TracerEvent):
65
+ """
66
+ Records when an LLM responds to a call.
67
+ """
68
+ model: str = Field(..., description="The LLM model that was used")
69
+ content: str = Field(..., description="The content of the LLM response")
70
+ tool_calls: Optional[List[Dict]] = Field(None, description="Any tool calls made by the LLM")
71
+ call_duration_ms: Optional[float] = Field(None, description="Duration of the LLM call in milliseconds")
72
+
73
+ def printable_summary(self) -> str:
74
+ """Return a formatted summary of the LLM response event."""
75
+ base_summary = super().printable_summary()
76
+ summary = f"{base_summary}\n Model: {self.model}"
77
+
78
+ if self.content:
79
+ content_preview = self.content[:100] + "..." if len(self.content) > 100 else self.content
80
+ summary += f"\n Content: {content_preview}"
81
+
82
+ if self.tool_calls:
83
+ tool_count = len(self.tool_calls)
84
+ summary += f"\n Tool Calls: {tool_count} call{'s' if tool_count != 1 else ''}"
85
+
86
+ if self.call_duration_ms is not None:
87
+ summary += f"\n Duration: {self.call_duration_ms:.2f}ms"
88
+
89
+ return summary
90
+
91
+
92
+ class ToolCallTracerEvent(TracerEvent):
93
+ """
94
+ Records when a tool is called during agent execution.
95
+ """
96
+ tool_name: str = Field(..., description="Name of the tool that was called")
97
+ arguments: Dict[str, Any] = Field(..., description="Arguments provided to the tool")
98
+ result: Any = Field(..., description="Result returned by the tool")
99
+ caller: Optional[str] = Field(None, description="Name of the agent or component that called the tool")
100
+
101
+ def printable_summary(self) -> str:
102
+ """Return a formatted summary of the tool call event."""
103
+ base_summary = super().printable_summary()
104
+ summary = f"{base_summary}\n Tool: {self.tool_name}"
105
+
106
+ if self.arguments:
107
+ summary += f"\n Arguments: {self.arguments}"
108
+
109
+ if self.result is not None:
110
+ result_str = str(self.result)
111
+ result_preview = result_str[:100] + "..." if len(result_str) > 100 else result_str
112
+ summary += f"\n Result: {result_preview}"
113
+
114
+ if self.caller:
115
+ summary += f"\n Caller: {self.caller}"
116
+
117
+ return summary
118
+
119
+
120
+ class AgentInteractionTracerEvent(TracerEvent):
121
+ """
122
+ Records interactions between agents.
123
+ """
124
+ from_agent: str = Field(..., description="Name of the agent sending the event")
125
+ to_agent: str = Field(..., description="Name of the agent receiving the event")
126
+ event_type: str = Field(..., description="Type of event being processed")
127
+ event_id: Optional[str] = Field(None, description="Unique identifier for the event")
128
+
129
+ def printable_summary(self) -> str:
130
+ """Return a formatted summary of the agent interaction event."""
131
+ base_summary = super().printable_summary()
132
+ summary = f"{base_summary}\n From: {self.from_agent} → To: {self.to_agent}"
133
+ summary += f"\n Event Type: {self.event_type}"
134
+
135
+ if self.event_id:
136
+ summary += f"\n Event ID: {self.event_id}"
137
+
138
+ return summary
@@ -0,0 +1,116 @@
1
+ import time
2
+ from typing import Dict, List
3
+
4
+ from mojentic.tracer.tracer_events import (
5
+ TracerEvent,
6
+ LLMCallTracerEvent,
7
+ LLMResponseTracerEvent,
8
+ ToolCallTracerEvent,
9
+ AgentInteractionTracerEvent
10
+ )
11
+
12
+
13
+ class DescribeTracerEvents:
14
+ """
15
+ Test the tracer event classes to ensure they can be instantiated and have the required properties.
16
+ """
17
+
18
+ def should_create_base_tracer_event(self):
19
+ """
20
+ Test creating a base tracer event.
21
+ """
22
+ # Given / When
23
+ event = TracerEvent(
24
+ source=DescribeTracerEvents,
25
+ timestamp=time.time()
26
+ )
27
+
28
+ # Then
29
+ assert isinstance(event, TracerEvent)
30
+ assert isinstance(event.timestamp, float)
31
+ assert event.source == DescribeTracerEvents
32
+
33
+ def should_create_llm_call_tracer_event(self):
34
+ """
35
+ Test creating an LLM call tracer event.
36
+ """
37
+ # Given / When
38
+ messages = [{"role": "system", "content": "You are a helpful assistant"}]
39
+ event = LLMCallTracerEvent(
40
+ source=DescribeTracerEvents,
41
+ timestamp=time.time(),
42
+ model="test-model",
43
+ messages=messages,
44
+ temperature=0.7,
45
+ tools=None
46
+ )
47
+
48
+ # Then
49
+ assert isinstance(event, LLMCallTracerEvent)
50
+ assert event.model == "test-model"
51
+ assert event.messages == messages
52
+ assert event.temperature == 0.7
53
+ assert event.tools is None
54
+
55
+ def should_create_llm_response_tracer_event(self):
56
+ """
57
+ Test creating an LLM response tracer event.
58
+ """
59
+ # Given / When
60
+ event = LLMResponseTracerEvent(
61
+ source=DescribeTracerEvents,
62
+ timestamp=time.time(),
63
+ model="test-model",
64
+ content="This is a test response",
65
+ call_duration_ms=150.5
66
+ )
67
+
68
+ # Then
69
+ assert isinstance(event, LLMResponseTracerEvent)
70
+ assert event.model == "test-model"
71
+ assert event.content == "This is a test response"
72
+ assert event.call_duration_ms == 150.5
73
+ assert event.tool_calls is None
74
+
75
+ def should_create_tool_call_tracer_event(self):
76
+ """
77
+ Test creating a tool call tracer event.
78
+ """
79
+ # Given / When
80
+ arguments = {"query": "test query"}
81
+ event = ToolCallTracerEvent(
82
+ source=DescribeTracerEvents,
83
+ timestamp=time.time(),
84
+ tool_name="test-tool",
85
+ arguments=arguments,
86
+ result="test result",
87
+ caller="TestAgent"
88
+ )
89
+
90
+ # Then
91
+ assert isinstance(event, ToolCallTracerEvent)
92
+ assert event.tool_name == "test-tool"
93
+ assert event.arguments == arguments
94
+ assert event.result == "test result"
95
+ assert event.caller == "TestAgent"
96
+
97
+ def should_create_agent_interaction_tracer_event(self):
98
+ """
99
+ Test creating an agent interaction tracer event.
100
+ """
101
+ # Given / When
102
+ event = AgentInteractionTracerEvent(
103
+ source=DescribeTracerEvents,
104
+ timestamp=time.time(),
105
+ from_agent="AgentA",
106
+ to_agent="AgentB",
107
+ event_type="RequestEvent",
108
+ event_id="12345"
109
+ )
110
+
111
+ # Then
112
+ assert isinstance(event, AgentInteractionTracerEvent)
113
+ assert event.from_agent == "AgentA"
114
+ assert event.to_agent == "AgentB"
115
+ assert event.event_type == "RequestEvent"
116
+ assert event.event_id == "12345"
@@ -0,0 +1,301 @@
1
+ """
2
+ TracerSystem module for coordinating tracer events.
3
+
4
+ This provides a central system for recording, filtering, and querying tracer events.
5
+ """
6
+ import time
7
+ from typing import Any, Callable, Dict, List, Optional, Type, Union
8
+
9
+ import structlog
10
+
11
+ from mojentic.tracer.tracer_events import (
12
+ TracerEvent,
13
+ LLMCallTracerEvent,
14
+ LLMResponseTracerEvent,
15
+ ToolCallTracerEvent,
16
+ AgentInteractionTracerEvent
17
+ )
18
+ from mojentic.tracer.event_store import EventStore
19
+ from mojentic.event import Event
20
+
21
+ logger = structlog.get_logger()
22
+
23
+
24
+ class TracerSystem:
25
+ """
26
+ Central system for capturing and querying tracer events.
27
+
28
+ The TracerSystem is responsible for recording events related to LLM calls,
29
+ tool usage, and agent interactions, providing a way to trace through the
30
+ major events of the system.
31
+ """
32
+
33
+ def __init__(self, event_store: Optional[EventStore] = None, enabled: bool = True):
34
+ """
35
+ Initialize the tracer system.
36
+
37
+ Parameters
38
+ ----------
39
+ event_store : EventStore, optional
40
+ The event store to use for storing events. If None, a new EventStore will be created.
41
+ enabled : bool, default=True
42
+ Whether the tracer system is enabled. If False, no events will be recorded.
43
+ """
44
+ self.event_store = event_store or EventStore()
45
+ self.enabled = enabled
46
+
47
+ def record_event(self, event: TracerEvent) -> None:
48
+ """
49
+ Record a tracer event in the event store.
50
+
51
+ Parameters
52
+ ----------
53
+ event : TracerEvent
54
+ The tracer event to record.
55
+ """
56
+ if not self.enabled:
57
+ return
58
+
59
+ self.event_store.store(event)
60
+
61
+ def record_llm_call(self,
62
+ model: str,
63
+ messages: List[Dict],
64
+ temperature: float = 1.0,
65
+ tools: Optional[List[Dict]] = None,
66
+ source: Any = None,
67
+ correlation_id: str = None) -> None:
68
+ """
69
+ Record an LLM call event.
70
+
71
+ Parameters
72
+ ----------
73
+ model : str
74
+ The name of the LLM model being called.
75
+ messages : List[Dict]
76
+ The messages sent to the LLM.
77
+ temperature : float, default=1.0
78
+ The temperature setting for the LLM call.
79
+ tools : List[Dict], optional
80
+ The tools available to the LLM, if any.
81
+ source : Any, optional
82
+ The source of the event. If None, the TracerSystem class will be used.
83
+ correlation_id : str, required
84
+ UUID string that is copied from cause-to-affect for tracing events.
85
+ """
86
+ if not self.enabled:
87
+ return
88
+
89
+ event = LLMCallTracerEvent(
90
+ source=source or type(self),
91
+ timestamp=time.time(),
92
+ model=model,
93
+ messages=messages,
94
+ temperature=temperature,
95
+ tools=tools,
96
+ correlation_id=correlation_id
97
+ )
98
+ self.event_store.store(event)
99
+
100
+ def record_llm_response(self,
101
+ model: str,
102
+ content: str,
103
+ tool_calls: Optional[List[Dict]] = None,
104
+ call_duration_ms: Optional[float] = None,
105
+ source: Any = None,
106
+ correlation_id: str = None) -> None:
107
+ """
108
+ Record an LLM response event.
109
+
110
+ Parameters
111
+ ----------
112
+ model : str
113
+ The name of the LLM model that responded.
114
+ content : str
115
+ The content of the LLM response.
116
+ tool_calls : List[Dict], optional
117
+ Any tool calls made by the LLM in its response.
118
+ call_duration_ms : float, optional
119
+ The duration of the LLM call in milliseconds.
120
+ source : Any, optional
121
+ The source of the event. If None, the TracerSystem class will be used.
122
+ correlation_id : str, required
123
+ UUID string that is copied from cause-to-affect for tracing events.
124
+ """
125
+ if not self.enabled:
126
+ return
127
+
128
+ event = LLMResponseTracerEvent(
129
+ source=source or type(self),
130
+ timestamp=time.time(),
131
+ model=model,
132
+ content=content,
133
+ tool_calls=tool_calls,
134
+ call_duration_ms=call_duration_ms,
135
+ correlation_id=correlation_id
136
+ )
137
+ self.event_store.store(event)
138
+
139
+ def record_tool_call(self,
140
+ tool_name: str,
141
+ arguments: Dict[str, Any],
142
+ result: Any,
143
+ caller: Optional[str] = None,
144
+ source: Any = None,
145
+ correlation_id: str = None) -> None:
146
+ """
147
+ Record a tool call event.
148
+
149
+ Parameters
150
+ ----------
151
+ tool_name : str
152
+ The name of the tool being called.
153
+ arguments : Dict[str, Any]
154
+ The arguments provided to the tool.
155
+ result : Any
156
+ The result returned by the tool.
157
+ caller : str, optional
158
+ The name of the agent or component calling the tool.
159
+ source : Any, optional
160
+ The source of the event. If None, the TracerSystem class will be used.
161
+ correlation_id : str, required
162
+ UUID string that is copied from cause-to-affect for tracing events.
163
+ """
164
+ if not self.enabled:
165
+ return
166
+
167
+ event = ToolCallTracerEvent(
168
+ source=source or type(self),
169
+ timestamp=time.time(),
170
+ tool_name=tool_name,
171
+ arguments=arguments,
172
+ result=result,
173
+ caller=caller,
174
+ correlation_id=correlation_id
175
+ )
176
+ self.event_store.store(event)
177
+
178
+ def record_agent_interaction(self,
179
+ from_agent: str,
180
+ to_agent: str,
181
+ event_type: str,
182
+ event_id: Optional[str] = None,
183
+ source: Any = None,
184
+ correlation_id: str = None) -> None:
185
+ """
186
+ Record an agent interaction event.
187
+
188
+ Parameters
189
+ ----------
190
+ from_agent : str
191
+ The name of the agent sending the event.
192
+ to_agent : str
193
+ The name of the agent receiving the event.
194
+ event_type : str
195
+ The type of event being processed.
196
+ event_id : str, optional
197
+ A unique identifier for the event.
198
+ source : Any, optional
199
+ The source of the event. If None, the TracerSystem class will be used.
200
+ correlation_id : str, required
201
+ UUID string that is copied from cause-to-affect for tracing events.
202
+ """
203
+ if not self.enabled:
204
+ return
205
+
206
+ event = AgentInteractionTracerEvent(
207
+ source=source or type(self),
208
+ timestamp=time.time(),
209
+ from_agent=from_agent,
210
+ to_agent=to_agent,
211
+ event_type=event_type,
212
+ event_id=event_id,
213
+ correlation_id=correlation_id
214
+ )
215
+ self.event_store.store(event)
216
+
217
+ def get_events(self,
218
+ event_type: Optional[Type[TracerEvent]] = None,
219
+ start_time: Optional[float] = None,
220
+ end_time: Optional[float] = None,
221
+ filter_func: Optional[Callable[[TracerEvent], bool]] = None) -> List[TracerEvent]:
222
+ """
223
+ Get tracer events from the store, optionally filtered.
224
+
225
+ This is a convenience wrapper around the EventStore's get_events method,
226
+ specifically for tracer events.
227
+
228
+ Parameters
229
+ ----------
230
+ event_type : Type[TracerEvent], optional
231
+ Filter events by this specific tracer event type.
232
+ start_time : float, optional
233
+ Include events with timestamp >= start_time.
234
+ end_time : float, optional
235
+ Include events with timestamp <= end_time.
236
+ filter_func : Callable[[TracerEvent], bool], optional
237
+ Custom filter function to apply to events.
238
+
239
+ Returns
240
+ -------
241
+ List[TracerEvent]
242
+ Events that match the filter criteria.
243
+ """
244
+ # First filter to only TracerEvents
245
+ events = self.event_store.get_events(event_type=TracerEvent)
246
+
247
+ # Then apply additional filters
248
+ if event_type is not None:
249
+ events = [e for e in events if isinstance(e, event_type)]
250
+
251
+ if start_time is not None:
252
+ events = [e for e in events if e.timestamp >= start_time]
253
+
254
+ if end_time is not None:
255
+ events = [e for e in events if e.timestamp <= end_time]
256
+
257
+ if filter_func is not None:
258
+ events = [e for e in events if filter_func(e)]
259
+
260
+ return events
261
+
262
+ def get_last_n_tracer_events(self, n: int, event_type: Optional[Type[TracerEvent]] = None) -> List[TracerEvent]:
263
+ """
264
+ Get the last N tracer events, optionally filtered by type.
265
+
266
+ Parameters
267
+ ----------
268
+ n : int
269
+ Number of events to return.
270
+ event_type : Type[TracerEvent], optional
271
+ Filter events by this specific tracer event type.
272
+
273
+ Returns
274
+ -------
275
+ List[TracerEvent]
276
+ The last N tracer events that match the filter criteria.
277
+ """
278
+ base_type = event_type or TracerEvent
279
+ return self.event_store.get_last_n_events(n, event_type=base_type)
280
+
281
+ def clear(self) -> None:
282
+ """
283
+ Clear all events from the event store.
284
+ """
285
+ self.event_store.clear()
286
+
287
+ def enable(self) -> None:
288
+ """
289
+ Enable the tracer system.
290
+ """
291
+ self.enabled = True
292
+
293
+ def disable(self) -> None:
294
+ """
295
+ Disable the tracer system.
296
+ """
297
+ self.enabled = False
298
+
299
+
300
+ # Import and use the null tracer directly in client code via:
301
+ # from mojentic.tracer import null_tracer