mojentic 0.5.6__py3-none-any.whl → 0.6.0__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,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,285 @@
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) -> None:
67
+ """
68
+ Record an LLM call event.
69
+
70
+ Parameters
71
+ ----------
72
+ model : str
73
+ The name of the LLM model being called.
74
+ messages : List[Dict]
75
+ The messages sent to the LLM.
76
+ temperature : float, default=1.0
77
+ The temperature setting for the LLM call.
78
+ tools : List[Dict], optional
79
+ The tools available to the LLM, if any.
80
+ source : Any, optional
81
+ The source of the event. If None, the TracerSystem class will be used.
82
+ """
83
+ if not self.enabled:
84
+ return
85
+
86
+ event = LLMCallTracerEvent(
87
+ source=source or type(self),
88
+ timestamp=time.time(),
89
+ model=model,
90
+ messages=messages,
91
+ temperature=temperature,
92
+ tools=tools
93
+ )
94
+ self.event_store.store(event)
95
+
96
+ def record_llm_response(self,
97
+ model: str,
98
+ content: str,
99
+ tool_calls: Optional[List[Dict]] = None,
100
+ call_duration_ms: Optional[float] = None,
101
+ source: Any = None) -> None:
102
+ """
103
+ Record an LLM response event.
104
+
105
+ Parameters
106
+ ----------
107
+ model : str
108
+ The name of the LLM model that responded.
109
+ content : str
110
+ The content of the LLM response.
111
+ tool_calls : List[Dict], optional
112
+ Any tool calls made by the LLM in its response.
113
+ call_duration_ms : float, optional
114
+ The duration of the LLM call in milliseconds.
115
+ source : Any, optional
116
+ The source of the event. If None, the TracerSystem class will be used.
117
+ """
118
+ if not self.enabled:
119
+ return
120
+
121
+ event = LLMResponseTracerEvent(
122
+ source=source or type(self),
123
+ timestamp=time.time(),
124
+ model=model,
125
+ content=content,
126
+ tool_calls=tool_calls,
127
+ call_duration_ms=call_duration_ms
128
+ )
129
+ self.event_store.store(event)
130
+
131
+ def record_tool_call(self,
132
+ tool_name: str,
133
+ arguments: Dict[str, Any],
134
+ result: Any,
135
+ caller: Optional[str] = None,
136
+ source: Any = None) -> None:
137
+ """
138
+ Record a tool call event.
139
+
140
+ Parameters
141
+ ----------
142
+ tool_name : str
143
+ The name of the tool being called.
144
+ arguments : Dict[str, Any]
145
+ The arguments provided to the tool.
146
+ result : Any
147
+ The result returned by the tool.
148
+ caller : str, optional
149
+ The name of the agent or component calling the tool.
150
+ source : Any, optional
151
+ The source of the event. If None, the TracerSystem class will be used.
152
+ """
153
+ if not self.enabled:
154
+ return
155
+
156
+ event = ToolCallTracerEvent(
157
+ source=source or type(self),
158
+ timestamp=time.time(),
159
+ tool_name=tool_name,
160
+ arguments=arguments,
161
+ result=result,
162
+ caller=caller
163
+ )
164
+ self.event_store.store(event)
165
+
166
+ def record_agent_interaction(self,
167
+ from_agent: str,
168
+ to_agent: str,
169
+ event_type: str,
170
+ event_id: Optional[str] = None,
171
+ source: Any = None) -> None:
172
+ """
173
+ Record an agent interaction event.
174
+
175
+ Parameters
176
+ ----------
177
+ from_agent : str
178
+ The name of the agent sending the event.
179
+ to_agent : str
180
+ The name of the agent receiving the event.
181
+ event_type : str
182
+ The type of event being processed.
183
+ event_id : str, optional
184
+ A unique identifier for the event.
185
+ source : Any, optional
186
+ The source of the event. If None, the TracerSystem class will be used.
187
+ """
188
+ if not self.enabled:
189
+ return
190
+
191
+ event = AgentInteractionTracerEvent(
192
+ source=source or type(self),
193
+ timestamp=time.time(),
194
+ from_agent=from_agent,
195
+ to_agent=to_agent,
196
+ event_type=event_type,
197
+ event_id=event_id
198
+ )
199
+ self.event_store.store(event)
200
+
201
+ def get_events(self,
202
+ event_type: Optional[Type[TracerEvent]] = None,
203
+ start_time: Optional[float] = None,
204
+ end_time: Optional[float] = None,
205
+ filter_func: Optional[Callable[[TracerEvent], bool]] = None) -> List[TracerEvent]:
206
+ """
207
+ Get tracer events from the store, optionally filtered.
208
+
209
+ This is a convenience wrapper around the EventStore's get_events method,
210
+ specifically for tracer events.
211
+
212
+ Parameters
213
+ ----------
214
+ event_type : Type[TracerEvent], optional
215
+ Filter events by this specific tracer event type.
216
+ start_time : float, optional
217
+ Include events with timestamp >= start_time.
218
+ end_time : float, optional
219
+ Include events with timestamp <= end_time.
220
+ filter_func : Callable[[TracerEvent], bool], optional
221
+ Custom filter function to apply to events.
222
+
223
+ Returns
224
+ -------
225
+ List[TracerEvent]
226
+ Events that match the filter criteria.
227
+ """
228
+ # First filter to only TracerEvents
229
+ events = self.event_store.get_events(event_type=TracerEvent)
230
+
231
+ # Then apply additional filters
232
+ if event_type is not None:
233
+ events = [e for e in events if isinstance(e, event_type)]
234
+
235
+ if start_time is not None:
236
+ events = [e for e in events if e.timestamp >= start_time]
237
+
238
+ if end_time is not None:
239
+ events = [e for e in events if e.timestamp <= end_time]
240
+
241
+ if filter_func is not None:
242
+ events = [e for e in events if filter_func(e)]
243
+
244
+ return events
245
+
246
+ def get_last_n_tracer_events(self, n: int, event_type: Optional[Type[TracerEvent]] = None) -> List[TracerEvent]:
247
+ """
248
+ Get the last N tracer events, optionally filtered by type.
249
+
250
+ Parameters
251
+ ----------
252
+ n : int
253
+ Number of events to return.
254
+ event_type : Type[TracerEvent], optional
255
+ Filter events by this specific tracer event type.
256
+
257
+ Returns
258
+ -------
259
+ List[TracerEvent]
260
+ The last N tracer events that match the filter criteria.
261
+ """
262
+ base_type = event_type or TracerEvent
263
+ return self.event_store.get_last_n_events(n, event_type=base_type)
264
+
265
+ def clear(self) -> None:
266
+ """
267
+ Clear all events from the event store.
268
+ """
269
+ self.event_store.clear()
270
+
271
+ def enable(self) -> None:
272
+ """
273
+ Enable the tracer system.
274
+ """
275
+ self.enabled = True
276
+
277
+ def disable(self) -> None:
278
+ """
279
+ Disable the tracer system.
280
+ """
281
+ self.enabled = False
282
+
283
+
284
+ # Import and use the null tracer directly in client code via:
285
+ # from mojentic.tracer import null_tracer
@@ -0,0 +1,255 @@
1
+ import time
2
+ from typing import Dict, List, Optional, Type, Union
3
+
4
+ import pytest
5
+
6
+ from mojentic.tracer.tracer_events import (
7
+ TracerEvent,
8
+ LLMCallTracerEvent,
9
+ LLMResponseTracerEvent,
10
+ ToolCallTracerEvent,
11
+ AgentInteractionTracerEvent
12
+ )
13
+ from mojentic.tracer.tracer_system import TracerSystem
14
+ from mojentic.tracer.event_store import EventStore
15
+
16
+
17
+ class DescribeTracerSystem:
18
+ """
19
+ Tests for the TracerSystem class.
20
+ """
21
+
22
+ def should_initialize_with_default_event_store(self):
23
+ """
24
+ Given no event store
25
+ When initializing a tracer system
26
+ Then it should create a default event store
27
+ """
28
+ # Given / When
29
+ tracer_system = TracerSystem()
30
+
31
+ # Then
32
+ assert tracer_system.event_store is not None
33
+ assert isinstance(tracer_system.event_store, EventStore)
34
+ assert tracer_system.enabled is True
35
+
36
+ def should_initialize_with_provided_event_store(self):
37
+ """
38
+ Given an event store
39
+ When initializing a tracer system with it
40
+ Then it should use the provided event store
41
+ """
42
+ # Given
43
+ event_store = EventStore()
44
+
45
+ # When
46
+ tracer_system = TracerSystem(event_store=event_store)
47
+
48
+ # Then
49
+ assert tracer_system.event_store is event_store
50
+
51
+ def should_record_llm_call(self):
52
+ """
53
+ Given an enabled tracer system
54
+ When recording an LLM call
55
+ Then it should store an LLMCallTracerEvent
56
+ """
57
+ # Given
58
+ tracer_system = TracerSystem()
59
+
60
+ # When
61
+ messages = [{"role": "system", "content": "You are a helpful assistant."}]
62
+ tracer_system.record_llm_call("test-model", messages, 0.7)
63
+
64
+ # Then
65
+ events = tracer_system.get_events(event_type=LLMCallTracerEvent)
66
+ assert len(events) == 1
67
+ event = events[0]
68
+ assert event.model == "test-model"
69
+ assert event.messages == messages
70
+ assert event.temperature == 0.7
71
+
72
+ def should_record_llm_response(self):
73
+ """
74
+ Given an enabled tracer system
75
+ When recording an LLM response
76
+ Then it should store an LLMResponseTracerEvent
77
+ """
78
+ # Given
79
+ tracer_system = TracerSystem()
80
+
81
+ # When
82
+ tracer_system.record_llm_response(
83
+ "test-model",
84
+ "This is a test response",
85
+ call_duration_ms=150.5
86
+ )
87
+
88
+ # Then
89
+ events = tracer_system.get_events(event_type=LLMResponseTracerEvent)
90
+ assert len(events) == 1
91
+ event = events[0]
92
+ assert event.model == "test-model"
93
+ assert event.content == "This is a test response"
94
+ assert event.call_duration_ms == 150.5
95
+
96
+ def should_record_tool_call(self):
97
+ """
98
+ Given an enabled tracer system
99
+ When recording a tool call
100
+ Then it should store a ToolCallTracerEvent
101
+ """
102
+ # Given
103
+ tracer_system = TracerSystem()
104
+
105
+ # When
106
+ arguments = {"query": "test query"}
107
+ tracer_system.record_tool_call(
108
+ "test-tool",
109
+ arguments,
110
+ "test result",
111
+ "TestAgent"
112
+ )
113
+
114
+ # Then
115
+ events = tracer_system.get_events(event_type=ToolCallTracerEvent)
116
+ assert len(events) == 1
117
+ event = events[0]
118
+ assert event.tool_name == "test-tool"
119
+ assert event.arguments == arguments
120
+ assert event.result == "test result"
121
+ assert event.caller == "TestAgent"
122
+
123
+ def should_record_agent_interaction(self):
124
+ """
125
+ Given an enabled tracer system
126
+ When recording an agent interaction
127
+ Then it should store an AgentInteractionTracerEvent
128
+ """
129
+ # Given
130
+ tracer_system = TracerSystem()
131
+
132
+ # When
133
+ tracer_system.record_agent_interaction(
134
+ "AgentA",
135
+ "AgentB",
136
+ "RequestEvent",
137
+ "12345"
138
+ )
139
+
140
+ # Then
141
+ events = tracer_system.get_events(event_type=AgentInteractionTracerEvent)
142
+ assert len(events) == 1
143
+ event = events[0]
144
+ assert event.from_agent == "AgentA"
145
+ assert event.to_agent == "AgentB"
146
+ assert event.event_type == "RequestEvent"
147
+ assert event.event_id == "12345"
148
+
149
+ def should_not_record_when_disabled(self):
150
+ """
151
+ Given a disabled tracer system
152
+ When recording events
153
+ Then no events should be stored
154
+ """
155
+ # Given
156
+ tracer_system = TracerSystem(enabled=False)
157
+
158
+ # When
159
+ tracer_system.record_llm_call("test-model", [])
160
+ tracer_system.record_llm_response("test-model", "response")
161
+ tracer_system.record_tool_call("test-tool", {}, "result")
162
+ tracer_system.record_agent_interaction("AgentA", "AgentB", "Event")
163
+
164
+ # Then
165
+ assert len(tracer_system.get_events()) == 0
166
+
167
+ def should_filter_events_by_time_range(self):
168
+ """
169
+ Given several tracer events with different timestamps
170
+ When filtered by time range
171
+ Then only events within that time range should be returned
172
+ """
173
+ # Given
174
+ tracer_system = TracerSystem()
175
+
176
+ # Create events with specific timestamps
177
+ now = time.time()
178
+ tracer_system.event_store.store(
179
+ LLMCallTracerEvent(source=DescribeTracerSystem, timestamp=now - 100, model="model1", messages=[])
180
+ )
181
+ tracer_system.event_store.store(
182
+ LLMCallTracerEvent(source=DescribeTracerSystem, timestamp=now - 50, model="model2", messages=[])
183
+ )
184
+ tracer_system.event_store.store(
185
+ LLMCallTracerEvent(source=DescribeTracerSystem, timestamp=now, model="model3", messages=[])
186
+ )
187
+
188
+ # When
189
+ result = tracer_system.get_events(start_time=now - 75, end_time=now - 25)
190
+
191
+ # Then
192
+ assert len(result) == 1
193
+ assert result[0].model == "model2"
194
+
195
+ def should_get_last_n_tracer_events(self):
196
+ """
197
+ Given several tracer events of different types
198
+ When requesting the last N tracer events of a specific type
199
+ Then only the most recent N events of that type should be returned
200
+ """
201
+ # Given
202
+ tracer_system = TracerSystem()
203
+
204
+ tracer_system.record_llm_call("model1", [])
205
+ tracer_system.record_tool_call("tool1", {}, "result1")
206
+ tracer_system.record_llm_call("model2", [])
207
+ tracer_system.record_llm_call("model3", [])
208
+
209
+ # When
210
+ result = tracer_system.get_last_n_tracer_events(2, event_type=LLMCallTracerEvent)
211
+
212
+ # Then
213
+ assert len(result) == 2
214
+ assert result[0].model == "model2"
215
+ assert result[1].model == "model3"
216
+
217
+ def should_enable_and_disable(self):
218
+ """
219
+ Given a tracer system
220
+ When enabling and disabling it
221
+ Then its enabled state should change accordingly
222
+ """
223
+ # Given
224
+ tracer_system = TracerSystem(enabled=False)
225
+ assert not tracer_system.enabled
226
+
227
+ # When
228
+ tracer_system.enable()
229
+
230
+ # Then
231
+ assert tracer_system.enabled
232
+
233
+ # When
234
+ tracer_system.disable()
235
+
236
+ # Then
237
+ assert not tracer_system.enabled
238
+
239
+ def should_clear_events(self):
240
+ """
241
+ Given a tracer system with events
242
+ When clearing events
243
+ Then all events should be removed
244
+ """
245
+ # Given
246
+ tracer_system = TracerSystem()
247
+ tracer_system.record_llm_call("model1", [])
248
+ tracer_system.record_tool_call("tool1", {}, "result1")
249
+ assert len(tracer_system.get_events()) == 2
250
+
251
+ # When
252
+ tracer_system.clear()
253
+
254
+ # Then
255
+ assert len(tracer_system.get_events()) == 0