mojentic 0.5.7__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.
- _examples/recursive_agent.py +2 -1
- _examples/tracer_demo.py +109 -0
- mojentic/dispatcher.py +17 -1
- mojentic/llm/gateways/openai_message_adapter_spec.py +7 -7
- mojentic/llm/gateways/openai_messages_adapter.py +58 -15
- mojentic/llm/llm_broker.py +85 -2
- mojentic/llm/message_composers_spec.py +43 -25
- mojentic/llm/tools/llm_tool.py +26 -0
- mojentic/llm/tools/llm_tool_spec.py +2 -1
- mojentic/tracer/__init__.py +16 -0
- mojentic/tracer/event_store.py +111 -0
- mojentic/tracer/event_store_spec.py +210 -0
- mojentic/tracer/null_tracer.py +191 -0
- mojentic/tracer/tracer_events.py +136 -0
- mojentic/tracer/tracer_events_spec.py +116 -0
- mojentic/tracer/tracer_system.py +285 -0
- mojentic/tracer/tracer_system_spec.py +255 -0
- {mojentic-0.5.7.dist-info → mojentic-0.6.0.dist-info}/METADATA +20 -10
- {mojentic-0.5.7.dist-info → mojentic-0.6.0.dist-info}/RECORD +22 -15
- {mojentic-0.5.7.dist-info → mojentic-0.6.0.dist-info}/WHEEL +1 -1
- mojentic/audit/event_store.py +0 -6
- mojentic/audit/event_store_spec.py +0 -26
- {mojentic-0.5.7.dist-info → mojentic-0.6.0.dist-info}/licenses/LICENSE.md +0 -0
- {mojentic-0.5.7.dist-info → mojentic-0.6.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from typing import Any, Callable, Dict, List, Optional, Type, Union
|
|
2
|
+
import time
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from mojentic.event import Event
|
|
6
|
+
from mojentic.tracer.tracer_events import TracerEvent
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EventStore:
|
|
10
|
+
"""
|
|
11
|
+
Store for capturing and querying events, particularly useful for tracer events.
|
|
12
|
+
"""
|
|
13
|
+
def __init__(self, on_store_callback: Optional[Callable[[Event], None]] = None):
|
|
14
|
+
"""
|
|
15
|
+
Initialize an EventStore.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
on_store_callback : Callable[[Event], None], optional
|
|
20
|
+
A callback function that will be called whenever an event is stored.
|
|
21
|
+
The callback receives the stored event as its argument.
|
|
22
|
+
"""
|
|
23
|
+
self.events = []
|
|
24
|
+
self.on_store_callback = on_store_callback
|
|
25
|
+
|
|
26
|
+
def store(self, event: Event) -> None:
|
|
27
|
+
"""
|
|
28
|
+
Store an event in the event store.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
event : Event
|
|
33
|
+
The event to store.
|
|
34
|
+
"""
|
|
35
|
+
self.events.append(event)
|
|
36
|
+
|
|
37
|
+
# Call the callback if it exists
|
|
38
|
+
if self.on_store_callback is not None:
|
|
39
|
+
self.on_store_callback(event)
|
|
40
|
+
|
|
41
|
+
def get_events(self,
|
|
42
|
+
event_type: Optional[Type[Event]] = None,
|
|
43
|
+
start_time: Optional[float] = None,
|
|
44
|
+
end_time: Optional[float] = None,
|
|
45
|
+
filter_func: Optional[Callable[[Event], bool]] = None) -> List[Event]:
|
|
46
|
+
"""
|
|
47
|
+
Get events from the store, optionally filtered by type, time range, and custom filter function.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
event_type : Type[Event], optional
|
|
52
|
+
Filter events by this specific event type.
|
|
53
|
+
start_time : float, optional
|
|
54
|
+
Include events with timestamp >= start_time (only applies to TracerEvent types).
|
|
55
|
+
end_time : float, optional
|
|
56
|
+
Include events with timestamp <= end_time (only applies to TracerEvent types).
|
|
57
|
+
filter_func : Callable[[Event], bool], optional
|
|
58
|
+
Custom filter function to apply to events.
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
List[Event]
|
|
63
|
+
Events that match the filter criteria.
|
|
64
|
+
"""
|
|
65
|
+
result = self.events
|
|
66
|
+
|
|
67
|
+
# Filter by event type if specified
|
|
68
|
+
if event_type is not None:
|
|
69
|
+
result = [e for e in result if isinstance(e, event_type)]
|
|
70
|
+
|
|
71
|
+
# Filter by time range if dealing with TracerEvents
|
|
72
|
+
if start_time is not None:
|
|
73
|
+
result = [e for e in result if isinstance(e, TracerEvent) and e.timestamp >= start_time]
|
|
74
|
+
|
|
75
|
+
if end_time is not None:
|
|
76
|
+
result = [e for e in result if isinstance(e, TracerEvent) and e.timestamp <= end_time]
|
|
77
|
+
|
|
78
|
+
# Apply custom filter function if provided
|
|
79
|
+
if filter_func is not None:
|
|
80
|
+
result = [e for e in result if filter_func(e)]
|
|
81
|
+
|
|
82
|
+
return result
|
|
83
|
+
|
|
84
|
+
def clear(self) -> None:
|
|
85
|
+
"""
|
|
86
|
+
Clear all events from the store.
|
|
87
|
+
"""
|
|
88
|
+
self.events = []
|
|
89
|
+
|
|
90
|
+
def get_last_n_events(self, n: int, event_type: Optional[Type[Event]] = None) -> List[Event]:
|
|
91
|
+
"""
|
|
92
|
+
Get the last N events, optionally filtered by type.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
n : int
|
|
97
|
+
Number of events to return.
|
|
98
|
+
event_type : Type[Event], optional
|
|
99
|
+
Filter events by this specific event type.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
List[Event]
|
|
104
|
+
The last N events that match the filter criteria.
|
|
105
|
+
"""
|
|
106
|
+
if event_type is not None:
|
|
107
|
+
filtered = [e for e in self.events if isinstance(e, event_type)]
|
|
108
|
+
else:
|
|
109
|
+
filtered = self.events
|
|
110
|
+
|
|
111
|
+
return filtered[-n:] if n < len(filtered) else filtered
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
from mojentic import Event
|
|
5
|
+
from mojentic.tracer.tracer_events import TracerEvent, LLMCallTracerEvent, AgentInteractionTracerEvent
|
|
6
|
+
from mojentic.tracer.event_store import EventStore
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestEvent(Event):
|
|
10
|
+
"""A simple event for testing."""
|
|
11
|
+
value: int
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestTracerEvent(TracerEvent):
|
|
15
|
+
"""A simple tracer event for testing."""
|
|
16
|
+
value: int
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DescribeEventStore:
|
|
20
|
+
"""
|
|
21
|
+
The immutable event store is key to the traceability of agent interactions.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def should_store_an_event(self):
|
|
25
|
+
"""
|
|
26
|
+
Given an event
|
|
27
|
+
When asked to store the event
|
|
28
|
+
Then the event should be stored
|
|
29
|
+
"""
|
|
30
|
+
# Given
|
|
31
|
+
event = Event(
|
|
32
|
+
source=DescribeEventStore,
|
|
33
|
+
)
|
|
34
|
+
event_store = EventStore()
|
|
35
|
+
|
|
36
|
+
# When
|
|
37
|
+
event_store.store(event)
|
|
38
|
+
|
|
39
|
+
# Then
|
|
40
|
+
assert event in event_store.events
|
|
41
|
+
|
|
42
|
+
def should_filter_events_by_type(self):
|
|
43
|
+
"""
|
|
44
|
+
Given several events of different types
|
|
45
|
+
When filtered by a specific type
|
|
46
|
+
Then only events of that type should be returned
|
|
47
|
+
"""
|
|
48
|
+
# Given
|
|
49
|
+
event_store = EventStore()
|
|
50
|
+
event1 = Event(source=DescribeEventStore)
|
|
51
|
+
event2 = TestEvent(source=DescribeEventStore, value=42)
|
|
52
|
+
event3 = TestEvent(source=DescribeEventStore, value=43)
|
|
53
|
+
event_store.store(event1)
|
|
54
|
+
event_store.store(event2)
|
|
55
|
+
event_store.store(event3)
|
|
56
|
+
|
|
57
|
+
# When
|
|
58
|
+
result = event_store.get_events(event_type=TestEvent)
|
|
59
|
+
|
|
60
|
+
# Then
|
|
61
|
+
assert len(result) == 2
|
|
62
|
+
assert event1 not in result
|
|
63
|
+
assert event2 in result
|
|
64
|
+
assert event3 in result
|
|
65
|
+
|
|
66
|
+
def should_filter_tracer_events_by_time_range(self):
|
|
67
|
+
"""
|
|
68
|
+
Given several tracer events with different timestamps
|
|
69
|
+
When filtered by time range
|
|
70
|
+
Then only events within that time range should be returned
|
|
71
|
+
"""
|
|
72
|
+
# Given
|
|
73
|
+
event_store = EventStore()
|
|
74
|
+
now = time.time()
|
|
75
|
+
event1 = TestTracerEvent(source=DescribeEventStore, timestamp=now - 100, value=1)
|
|
76
|
+
event2 = TestTracerEvent(source=DescribeEventStore, timestamp=now - 50, value=2)
|
|
77
|
+
event3 = TestTracerEvent(source=DescribeEventStore, timestamp=now, value=3)
|
|
78
|
+
event_store.store(event1)
|
|
79
|
+
event_store.store(event2)
|
|
80
|
+
event_store.store(event3)
|
|
81
|
+
|
|
82
|
+
# When
|
|
83
|
+
result = event_store.get_events(start_time=now - 75, end_time=now - 25)
|
|
84
|
+
|
|
85
|
+
# Then
|
|
86
|
+
assert len(result) == 1
|
|
87
|
+
assert event1 not in result
|
|
88
|
+
assert event2 in result
|
|
89
|
+
assert event3 not in result
|
|
90
|
+
|
|
91
|
+
def should_apply_custom_filter_function(self):
|
|
92
|
+
"""
|
|
93
|
+
Given several events
|
|
94
|
+
When filtered with a custom filter function
|
|
95
|
+
Then only events matching the filter should be returned
|
|
96
|
+
"""
|
|
97
|
+
# Given
|
|
98
|
+
event_store = EventStore()
|
|
99
|
+
event1 = TestEvent(source=DescribeEventStore, value=10)
|
|
100
|
+
event2 = TestEvent(source=DescribeEventStore, value=20)
|
|
101
|
+
event3 = TestEvent(source=DescribeEventStore, value=30)
|
|
102
|
+
event_store.store(event1)
|
|
103
|
+
event_store.store(event2)
|
|
104
|
+
event_store.store(event3)
|
|
105
|
+
|
|
106
|
+
# When
|
|
107
|
+
result = event_store.get_events(filter_func=lambda e: isinstance(e, TestEvent) and e.value > 15)
|
|
108
|
+
|
|
109
|
+
# Then
|
|
110
|
+
assert len(result) == 2
|
|
111
|
+
assert event1 not in result
|
|
112
|
+
assert event2 in result
|
|
113
|
+
assert event3 in result
|
|
114
|
+
|
|
115
|
+
def should_clear_events(self):
|
|
116
|
+
"""
|
|
117
|
+
Given several stored events
|
|
118
|
+
When the event store is cleared
|
|
119
|
+
Then all events should be removed
|
|
120
|
+
"""
|
|
121
|
+
# Given
|
|
122
|
+
event_store = EventStore()
|
|
123
|
+
event_store.store(Event(source=DescribeEventStore))
|
|
124
|
+
event_store.store(Event(source=DescribeEventStore))
|
|
125
|
+
assert len(event_store.events) == 2
|
|
126
|
+
|
|
127
|
+
# When
|
|
128
|
+
event_store.clear()
|
|
129
|
+
|
|
130
|
+
# Then
|
|
131
|
+
assert len(event_store.events) == 0
|
|
132
|
+
|
|
133
|
+
def should_get_last_n_events(self):
|
|
134
|
+
"""
|
|
135
|
+
Given several events
|
|
136
|
+
When requesting the last N events
|
|
137
|
+
Then only the most recent N events should be returned
|
|
138
|
+
"""
|
|
139
|
+
# Given
|
|
140
|
+
event_store = EventStore()
|
|
141
|
+
event1 = TestEvent(source=DescribeEventStore, value=1)
|
|
142
|
+
event2 = TestEvent(source=DescribeEventStore, value=2)
|
|
143
|
+
event3 = TestEvent(source=DescribeEventStore, value=3)
|
|
144
|
+
event4 = Event(source=DescribeEventStore)
|
|
145
|
+
event_store.store(event1)
|
|
146
|
+
event_store.store(event2)
|
|
147
|
+
event_store.store(event3)
|
|
148
|
+
event_store.store(event4)
|
|
149
|
+
|
|
150
|
+
# When
|
|
151
|
+
result = event_store.get_last_n_events(2)
|
|
152
|
+
|
|
153
|
+
# Then
|
|
154
|
+
assert len(result) == 2
|
|
155
|
+
assert event1 not in result
|
|
156
|
+
assert event2 not in result
|
|
157
|
+
assert event3 in result
|
|
158
|
+
assert event4 in result
|
|
159
|
+
|
|
160
|
+
def should_get_last_n_events_by_type(self):
|
|
161
|
+
"""
|
|
162
|
+
Given several events of different types
|
|
163
|
+
When requesting the last N events of a specific type
|
|
164
|
+
Then only the most recent N events of that type should be returned
|
|
165
|
+
"""
|
|
166
|
+
# Given
|
|
167
|
+
event_store = EventStore()
|
|
168
|
+
event1 = TestEvent(source=DescribeEventStore, value=1)
|
|
169
|
+
event2 = Event(source=DescribeEventStore)
|
|
170
|
+
event3 = TestEvent(source=DescribeEventStore, value=2)
|
|
171
|
+
event4 = TestEvent(source=DescribeEventStore, value=3)
|
|
172
|
+
event_store.store(event1)
|
|
173
|
+
event_store.store(event2)
|
|
174
|
+
event_store.store(event3)
|
|
175
|
+
event_store.store(event4)
|
|
176
|
+
|
|
177
|
+
# When
|
|
178
|
+
result = event_store.get_last_n_events(2, event_type=TestEvent)
|
|
179
|
+
|
|
180
|
+
# Then
|
|
181
|
+
assert len(result) == 2
|
|
182
|
+
assert event1 not in result
|
|
183
|
+
assert event2 not in result
|
|
184
|
+
assert event3 in result
|
|
185
|
+
assert event4 in result
|
|
186
|
+
|
|
187
|
+
def should_call_callback_when_storing_event(self):
|
|
188
|
+
"""
|
|
189
|
+
Given an event store with a callback
|
|
190
|
+
When an event is stored
|
|
191
|
+
Then the callback should be called with the event
|
|
192
|
+
"""
|
|
193
|
+
# Given
|
|
194
|
+
called_events = []
|
|
195
|
+
|
|
196
|
+
def callback(event):
|
|
197
|
+
called_events.append(event)
|
|
198
|
+
|
|
199
|
+
event_store = EventStore(on_store_callback=callback)
|
|
200
|
+
event1 = TestEvent(source=DescribeEventStore, value=1)
|
|
201
|
+
event2 = TestEvent(source=DescribeEventStore, value=2)
|
|
202
|
+
|
|
203
|
+
# When
|
|
204
|
+
event_store.store(event1)
|
|
205
|
+
event_store.store(event2)
|
|
206
|
+
|
|
207
|
+
# Then
|
|
208
|
+
assert len(called_events) == 2
|
|
209
|
+
assert called_events[0] == event1
|
|
210
|
+
assert called_events[1] == event2
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NullTracer implementation to eliminate conditional checks in the code.
|
|
3
|
+
|
|
4
|
+
This module provides a NullTracer that implements the same interface as TracerSystem
|
|
5
|
+
but performs no operations, following the Null Object Pattern.
|
|
6
|
+
"""
|
|
7
|
+
from typing import Any, Callable, Dict, List, Optional, Type
|
|
8
|
+
|
|
9
|
+
from mojentic.tracer.tracer_events import TracerEvent
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class NullTracer:
|
|
13
|
+
"""
|
|
14
|
+
A no-op implementation of TracerSystem that silently discards all tracing operations.
|
|
15
|
+
|
|
16
|
+
This class follows the Null Object Pattern to eliminate conditional checks in client code.
|
|
17
|
+
All record methods are overridden to do nothing, and all query methods return empty results.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
"""Initialize the NullTracer with disabled state."""
|
|
22
|
+
self.enabled = False
|
|
23
|
+
self.event_store = None
|
|
24
|
+
|
|
25
|
+
def record_event(self, event: TracerEvent) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Do nothing implementation of record_event.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
event : TracerEvent
|
|
32
|
+
The tracer event to record (will be ignored).
|
|
33
|
+
"""
|
|
34
|
+
# Do nothing
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
def record_llm_call(self,
|
|
38
|
+
model: str,
|
|
39
|
+
messages: List[Dict],
|
|
40
|
+
temperature: float = 1.0,
|
|
41
|
+
tools: Optional[List[Dict]] = None,
|
|
42
|
+
source: Any = None) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Do nothing implementation of record_llm_call.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
model : str
|
|
49
|
+
The name of the LLM model being called.
|
|
50
|
+
messages : List[Dict]
|
|
51
|
+
The messages sent to the LLM.
|
|
52
|
+
temperature : float, default=1.0
|
|
53
|
+
The temperature setting for the LLM call.
|
|
54
|
+
tools : List[Dict], optional
|
|
55
|
+
The tools available to the LLM, if any.
|
|
56
|
+
source : Any, optional
|
|
57
|
+
The source of the event.
|
|
58
|
+
"""
|
|
59
|
+
# Do nothing
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
def record_llm_response(self,
|
|
63
|
+
model: str,
|
|
64
|
+
content: str,
|
|
65
|
+
tool_calls: Optional[List[Dict]] = None,
|
|
66
|
+
call_duration_ms: Optional[float] = None,
|
|
67
|
+
source: Any = None) -> None:
|
|
68
|
+
"""
|
|
69
|
+
Do nothing implementation of record_llm_response.
|
|
70
|
+
|
|
71
|
+
Parameters
|
|
72
|
+
----------
|
|
73
|
+
model : str
|
|
74
|
+
The name of the LLM model that responded.
|
|
75
|
+
content : str
|
|
76
|
+
The content of the LLM response.
|
|
77
|
+
tool_calls : List[Dict], optional
|
|
78
|
+
Any tool calls made by the LLM in its response.
|
|
79
|
+
call_duration_ms : float, optional
|
|
80
|
+
The duration of the LLM call in milliseconds.
|
|
81
|
+
source : Any, optional
|
|
82
|
+
The source of the event.
|
|
83
|
+
"""
|
|
84
|
+
# Do nothing
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
def record_tool_call(self,
|
|
88
|
+
tool_name: str,
|
|
89
|
+
arguments: Dict[str, Any],
|
|
90
|
+
result: Any,
|
|
91
|
+
caller: Optional[str] = None,
|
|
92
|
+
source: Any = None) -> None:
|
|
93
|
+
"""
|
|
94
|
+
Do nothing implementation of record_tool_call.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
tool_name : str
|
|
99
|
+
The name of the tool being called.
|
|
100
|
+
arguments : Dict[str, Any]
|
|
101
|
+
The arguments provided to the tool.
|
|
102
|
+
result : Any
|
|
103
|
+
The result returned by the tool.
|
|
104
|
+
caller : str, optional
|
|
105
|
+
The name of the agent or component calling the tool.
|
|
106
|
+
source : Any, optional
|
|
107
|
+
The source of the event.
|
|
108
|
+
"""
|
|
109
|
+
# Do nothing
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
def record_agent_interaction(self,
|
|
113
|
+
from_agent: str,
|
|
114
|
+
to_agent: str,
|
|
115
|
+
event_type: str,
|
|
116
|
+
event_id: Optional[str] = None,
|
|
117
|
+
source: Any = None) -> None:
|
|
118
|
+
"""
|
|
119
|
+
Do nothing implementation of record_agent_interaction.
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
from_agent : str
|
|
124
|
+
The name of the agent sending the event.
|
|
125
|
+
to_agent : str
|
|
126
|
+
The name of the agent receiving the event.
|
|
127
|
+
event_type : str
|
|
128
|
+
The type of event being processed.
|
|
129
|
+
event_id : str, optional
|
|
130
|
+
A unique identifier for the event.
|
|
131
|
+
source : Any, optional
|
|
132
|
+
The source of the event.
|
|
133
|
+
"""
|
|
134
|
+
# Do nothing
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
def get_events(self,
|
|
138
|
+
event_type: Optional[Type[TracerEvent]] = None,
|
|
139
|
+
start_time: Optional[float] = None,
|
|
140
|
+
end_time: Optional[float] = None,
|
|
141
|
+
filter_func: Optional[Callable[[TracerEvent], bool]] = None) -> List[TracerEvent]:
|
|
142
|
+
"""
|
|
143
|
+
Return an empty list for any get_events request.
|
|
144
|
+
|
|
145
|
+
Parameters
|
|
146
|
+
----------
|
|
147
|
+
event_type : Type[TracerEvent], optional
|
|
148
|
+
Filter events by this specific tracer event type.
|
|
149
|
+
start_time : float, optional
|
|
150
|
+
Include events with timestamp >= start_time.
|
|
151
|
+
end_time : float, optional
|
|
152
|
+
Include events with timestamp <= end_time.
|
|
153
|
+
filter_func : Callable[[TracerEvent], bool], optional
|
|
154
|
+
Custom filter function to apply to events.
|
|
155
|
+
|
|
156
|
+
Returns
|
|
157
|
+
-------
|
|
158
|
+
List[TracerEvent]
|
|
159
|
+
An empty list.
|
|
160
|
+
"""
|
|
161
|
+
return []
|
|
162
|
+
|
|
163
|
+
def get_last_n_tracer_events(self, n: int, event_type: Optional[Type[TracerEvent]] = None) -> List[TracerEvent]:
|
|
164
|
+
"""
|
|
165
|
+
Return an empty list for any get_last_n_tracer_events request.
|
|
166
|
+
|
|
167
|
+
Parameters
|
|
168
|
+
----------
|
|
169
|
+
n : int
|
|
170
|
+
Number of events to return.
|
|
171
|
+
event_type : Type[TracerEvent], optional
|
|
172
|
+
Filter events by this specific tracer event type.
|
|
173
|
+
|
|
174
|
+
Returns
|
|
175
|
+
-------
|
|
176
|
+
List[TracerEvent]
|
|
177
|
+
An empty list.
|
|
178
|
+
"""
|
|
179
|
+
return []
|
|
180
|
+
|
|
181
|
+
def clear(self) -> None:
|
|
182
|
+
"""Do nothing implementation of clear method."""
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
def enable(self) -> None:
|
|
186
|
+
"""No-op method for interface compatibility."""
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
def disable(self) -> None:
|
|
190
|
+
"""No-op method for interface compatibility."""
|
|
191
|
+
pass
|
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
|
|
7
|
+
from pydantic import Field
|
|
8
|
+
|
|
9
|
+
from mojentic.event import Event
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TracerEvent(Event):
|
|
13
|
+
"""
|
|
14
|
+
Base class for all tracer-specific events.
|
|
15
|
+
|
|
16
|
+
Tracer events are used to track system interactions for observability purposes.
|
|
17
|
+
They are distinct from regular events which are used for agent communication.
|
|
18
|
+
"""
|
|
19
|
+
timestamp: float = Field(..., description="Timestamp when the event occurred")
|
|
20
|
+
|
|
21
|
+
def printable_summary(self) -> str:
|
|
22
|
+
"""
|
|
23
|
+
Return a formatted string summary of the event.
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
str
|
|
28
|
+
A formatted string with the event information.
|
|
29
|
+
"""
|
|
30
|
+
event_time = datetime.fromtimestamp(self.timestamp).strftime("%H:%M:%S.%f")[:-3]
|
|
31
|
+
return f"[{event_time}] {type(self).__name__}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class LLMCallTracerEvent(TracerEvent):
|
|
35
|
+
"""
|
|
36
|
+
Records when an LLM is called with specific messages.
|
|
37
|
+
"""
|
|
38
|
+
model: str = Field(..., description="The LLM model that was used")
|
|
39
|
+
messages: List[dict] = Field(..., description="The messages sent to the LLM")
|
|
40
|
+
temperature: float = Field(1.0, description="The temperature setting used for the call")
|
|
41
|
+
tools: Optional[List[Dict]] = Field(None, description="The tools available to the LLM, if any")
|
|
42
|
+
|
|
43
|
+
def printable_summary(self) -> str:
|
|
44
|
+
"""Return a formatted summary of the LLM call event."""
|
|
45
|
+
base_summary = super().printable_summary()
|
|
46
|
+
summary = f"{base_summary}\n Model: {self.model}"
|
|
47
|
+
|
|
48
|
+
if self.messages:
|
|
49
|
+
msg_count = len(self.messages)
|
|
50
|
+
summary += f"\n Messages: {msg_count} message{'s' if msg_count != 1 else ''}"
|
|
51
|
+
|
|
52
|
+
if self.temperature != 1.0:
|
|
53
|
+
summary += f"\n Temperature: {self.temperature}"
|
|
54
|
+
|
|
55
|
+
if self.tools:
|
|
56
|
+
tool_names = [tool.get('name', 'unknown') for tool in self.tools]
|
|
57
|
+
summary += f"\n Available Tools: {', '.join(tool_names)}"
|
|
58
|
+
|
|
59
|
+
return summary
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class LLMResponseTracerEvent(TracerEvent):
|
|
63
|
+
"""
|
|
64
|
+
Records when an LLM responds to a call.
|
|
65
|
+
"""
|
|
66
|
+
model: str = Field(..., description="The LLM model that was used")
|
|
67
|
+
content: str = Field(..., description="The content of the LLM response")
|
|
68
|
+
tool_calls: Optional[List[Dict]] = Field(None, description="Any tool calls made by the LLM")
|
|
69
|
+
call_duration_ms: Optional[float] = Field(None, description="Duration of the LLM call in milliseconds")
|
|
70
|
+
|
|
71
|
+
def printable_summary(self) -> str:
|
|
72
|
+
"""Return a formatted summary of the LLM response event."""
|
|
73
|
+
base_summary = super().printable_summary()
|
|
74
|
+
summary = f"{base_summary}\n Model: {self.model}"
|
|
75
|
+
|
|
76
|
+
if self.content:
|
|
77
|
+
content_preview = self.content[:100] + "..." if len(self.content) > 100 else self.content
|
|
78
|
+
summary += f"\n Content: {content_preview}"
|
|
79
|
+
|
|
80
|
+
if self.tool_calls:
|
|
81
|
+
tool_count = len(self.tool_calls)
|
|
82
|
+
summary += f"\n Tool Calls: {tool_count} call{'s' if tool_count != 1 else ''}"
|
|
83
|
+
|
|
84
|
+
if self.call_duration_ms is not None:
|
|
85
|
+
summary += f"\n Duration: {self.call_duration_ms:.2f}ms"
|
|
86
|
+
|
|
87
|
+
return summary
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ToolCallTracerEvent(TracerEvent):
|
|
91
|
+
"""
|
|
92
|
+
Records when a tool is called during agent execution.
|
|
93
|
+
"""
|
|
94
|
+
tool_name: str = Field(..., description="Name of the tool that was called")
|
|
95
|
+
arguments: Dict[str, Any] = Field(..., description="Arguments provided to the tool")
|
|
96
|
+
result: Any = Field(..., description="Result returned by the tool")
|
|
97
|
+
caller: Optional[str] = Field(None, description="Name of the agent or component that called the tool")
|
|
98
|
+
|
|
99
|
+
def printable_summary(self) -> str:
|
|
100
|
+
"""Return a formatted summary of the tool call event."""
|
|
101
|
+
base_summary = super().printable_summary()
|
|
102
|
+
summary = f"{base_summary}\n Tool: {self.tool_name}"
|
|
103
|
+
|
|
104
|
+
if self.arguments:
|
|
105
|
+
summary += f"\n Arguments: {self.arguments}"
|
|
106
|
+
|
|
107
|
+
if self.result is not None:
|
|
108
|
+
result_str = str(self.result)
|
|
109
|
+
result_preview = result_str[:100] + "..." if len(result_str) > 100 else result_str
|
|
110
|
+
summary += f"\n Result: {result_preview}"
|
|
111
|
+
|
|
112
|
+
if self.caller:
|
|
113
|
+
summary += f"\n Caller: {self.caller}"
|
|
114
|
+
|
|
115
|
+
return summary
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class AgentInteractionTracerEvent(TracerEvent):
|
|
119
|
+
"""
|
|
120
|
+
Records interactions between agents.
|
|
121
|
+
"""
|
|
122
|
+
from_agent: str = Field(..., description="Name of the agent sending the event")
|
|
123
|
+
to_agent: str = Field(..., description="Name of the agent receiving the event")
|
|
124
|
+
event_type: str = Field(..., description="Type of event being processed")
|
|
125
|
+
event_id: Optional[str] = Field(None, description="Unique identifier for the event")
|
|
126
|
+
|
|
127
|
+
def printable_summary(self) -> str:
|
|
128
|
+
"""Return a formatted summary of the agent interaction event."""
|
|
129
|
+
base_summary = super().printable_summary()
|
|
130
|
+
summary = f"{base_summary}\n From: {self.from_agent} → To: {self.to_agent}"
|
|
131
|
+
summary += f"\n Event Type: {self.event_type}"
|
|
132
|
+
|
|
133
|
+
if self.event_id:
|
|
134
|
+
summary += f"\n Event ID: {self.event_id}"
|
|
135
|
+
|
|
136
|
+
return summary
|