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.
- _examples/recursive_agent.py +4 -3
- _examples/tracer_demo.py +170 -0
- mojentic/agents/simple_recursive_agent.py +20 -21
- 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 +97 -4
- mojentic/llm/message_composers_spec.py +43 -25
- mojentic/llm/tools/llm_tool.py +28 -1
- 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 +203 -0
- mojentic/tracer/tracer_events.py +138 -0
- mojentic/tracer/tracer_events_spec.py +116 -0
- mojentic/tracer/tracer_system.py +301 -0
- mojentic/tracer/tracer_system_spec.py +266 -0
- {mojentic-0.5.7.dist-info → mojentic-0.6.1.dist-info}/METADATA +20 -10
- {mojentic-0.5.7.dist-info → mojentic-0.6.1.dist-info}/RECORD +23 -16
- {mojentic-0.5.7.dist-info → mojentic-0.6.1.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.1.dist-info}/licenses/LICENSE.md +0 -0
- {mojentic-0.5.7.dist-info → mojentic-0.6.1.dist-info}/top_level.txt +0 -0
|
@@ -176,39 +176,57 @@ class DescribeMessageBuilder:
|
|
|
176
176
|
assert image_path2 in message_builder.image_paths
|
|
177
177
|
assert result is message_builder # Returns self for method chaining
|
|
178
178
|
|
|
179
|
-
def should_add_all_jpg_images_from_directory(self, message_builder, mocker):
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
179
|
+
def should_add_all_jpg_images_from_directory(self, message_builder, mocker, tmp_path):
|
|
180
|
+
# Create a temporary directory with test image files
|
|
181
|
+
dir_path = tmp_path / "images"
|
|
182
|
+
dir_path.mkdir()
|
|
183
|
+
|
|
184
|
+
# Create empty test image files
|
|
185
|
+
image1_path = dir_path / "image1.jpg"
|
|
186
|
+
image2_path = dir_path / "image2.jpg"
|
|
187
|
+
image1_path.touch()
|
|
188
|
+
image2_path.touch()
|
|
189
|
+
|
|
190
|
+
# Create a text file that should be ignored
|
|
191
|
+
text_file_path = dir_path / "text.txt"
|
|
192
|
+
text_file_path.touch()
|
|
193
|
+
|
|
194
|
+
# Use the real directory for the test
|
|
188
195
|
message_builder.add_images(dir_path)
|
|
189
196
|
|
|
190
|
-
|
|
191
|
-
|
|
197
|
+
# Convert to strings for easier comparison
|
|
198
|
+
image_paths_str = [str(p) for p in message_builder.image_paths]
|
|
192
199
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
200
|
+
# Verify only jpg files were added
|
|
201
|
+
assert str(image1_path) in image_paths_str
|
|
202
|
+
assert str(image2_path) in image_paths_str
|
|
203
|
+
assert str(text_file_path) not in image_paths_str
|
|
196
204
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
mocker.patch.object(Path, 'exists', return_value=True)
|
|
205
|
+
def should_add_images_matching_glob_pattern(self, message_builder, tmp_path):
|
|
206
|
+
# Create a temporary directory with test image files
|
|
207
|
+
dir_path = tmp_path / "glob_test"
|
|
208
|
+
dir_path.mkdir()
|
|
202
209
|
|
|
203
|
-
#
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
210
|
+
# Create test image files
|
|
211
|
+
image1_path = dir_path / "image1.jpg"
|
|
212
|
+
image2_path = dir_path / "image2.jpg"
|
|
213
|
+
image3_path = dir_path / "other.png" # Should not match
|
|
214
|
+
image1_path.touch()
|
|
215
|
+
image2_path.touch()
|
|
216
|
+
image3_path.touch()
|
|
217
|
+
|
|
218
|
+
# Use a real glob pattern
|
|
219
|
+
pattern_path = dir_path / "*.jpg"
|
|
207
220
|
|
|
208
221
|
message_builder.add_images(pattern_path)
|
|
209
222
|
|
|
210
|
-
|
|
211
|
-
|
|
223
|
+
# Convert to strings for easier comparison
|
|
224
|
+
image_paths_str = [str(p) for p in message_builder.image_paths]
|
|
225
|
+
|
|
226
|
+
# Verify only jpg files matching the pattern were added
|
|
227
|
+
assert str(image1_path) in image_paths_str
|
|
228
|
+
assert str(image2_path) in image_paths_str
|
|
229
|
+
assert str(image3_path) not in image_paths_str
|
|
212
230
|
|
|
213
231
|
class DescribeLoadContentMethod:
|
|
214
232
|
"""
|
mojentic/llm/tools/llm_tool.py
CHANGED
|
@@ -1,14 +1,41 @@
|
|
|
1
1
|
import json
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
2
3
|
|
|
4
|
+
from mojentic.tracer.tracer_system import TracerSystem
|
|
3
5
|
from mojentic.llm.gateways.models import TextContent
|
|
4
6
|
|
|
5
7
|
|
|
6
8
|
class LLMTool:
|
|
9
|
+
def __init__(self, tracer: Optional[TracerSystem] = None):
|
|
10
|
+
"""
|
|
11
|
+
Initialize an LLM tool with optional tracer system.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
tracer : TracerSystem, optional
|
|
16
|
+
The tracer system to use for recording tool usage.
|
|
17
|
+
"""
|
|
18
|
+
# Use null_tracer if no tracer is provided
|
|
19
|
+
from mojentic.tracer import null_tracer
|
|
20
|
+
self.tracer = tracer or null_tracer
|
|
21
|
+
|
|
7
22
|
def run(self, **kwargs):
|
|
8
23
|
raise NotImplementedError
|
|
9
24
|
|
|
10
|
-
def call_tool(self, **kwargs):
|
|
25
|
+
def call_tool(self, correlation_id: str = None, **kwargs):
|
|
26
|
+
# Execute the tool and capture the result
|
|
11
27
|
result = self.run(**kwargs)
|
|
28
|
+
|
|
29
|
+
# Record the tool call in the tracer system (always safe to call with null_tracer)
|
|
30
|
+
self.tracer.record_tool_call(
|
|
31
|
+
tool_name=self.name,
|
|
32
|
+
arguments=kwargs,
|
|
33
|
+
result=result,
|
|
34
|
+
source=type(self),
|
|
35
|
+
correlation_id=correlation_id
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Format the result
|
|
12
39
|
if isinstance(result, dict):
|
|
13
40
|
result = json.dumps(result)
|
|
14
41
|
return {
|
|
@@ -10,7 +10,8 @@ from mojentic.llm.tools.llm_tool import LLMTool
|
|
|
10
10
|
class MockLLMTool(LLMTool):
|
|
11
11
|
"""A mock implementation of LLMTool for testing purposes."""
|
|
12
12
|
|
|
13
|
-
def __init__(self, run_result=None):
|
|
13
|
+
def __init__(self, run_result=None, tracer=None):
|
|
14
|
+
super().__init__(tracer=tracer)
|
|
14
15
|
self._run_result = run_result
|
|
15
16
|
self._descriptor = {
|
|
16
17
|
"function": {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
"""
|
|
3
|
+
Mojentic tracer module for tracking system operations.
|
|
4
|
+
"""
|
|
5
|
+
from mojentic.tracer.tracer_events import (
|
|
6
|
+
TracerEvent,
|
|
7
|
+
LLMCallTracerEvent,
|
|
8
|
+
LLMResponseTracerEvent,
|
|
9
|
+
ToolCallTracerEvent,
|
|
10
|
+
AgentInteractionTracerEvent
|
|
11
|
+
)
|
|
12
|
+
from mojentic.tracer.tracer_system import TracerSystem
|
|
13
|
+
from mojentic.tracer.null_tracer import NullTracer
|
|
14
|
+
|
|
15
|
+
# Create a singleton NullTracer instance for use throughout the application
|
|
16
|
+
null_tracer = NullTracer()
|
|
@@ -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,203 @@
|
|
|
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,
|
|
43
|
+
correlation_id: str = None) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Do nothing implementation of record_llm_call.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
model : str
|
|
50
|
+
The name of the LLM model being called.
|
|
51
|
+
messages : List[Dict]
|
|
52
|
+
The messages sent to the LLM.
|
|
53
|
+
temperature : float, default=1.0
|
|
54
|
+
The temperature setting for the LLM call.
|
|
55
|
+
tools : List[Dict], optional
|
|
56
|
+
The tools available to the LLM, if any.
|
|
57
|
+
source : Any, optional
|
|
58
|
+
The source of the event.
|
|
59
|
+
correlation_id : str, optional
|
|
60
|
+
UUID string that is copied from cause-to-affect for tracing events.
|
|
61
|
+
"""
|
|
62
|
+
# Do nothing
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
def record_llm_response(self,
|
|
66
|
+
model: str,
|
|
67
|
+
content: str,
|
|
68
|
+
tool_calls: Optional[List[Dict]] = None,
|
|
69
|
+
call_duration_ms: Optional[float] = None,
|
|
70
|
+
source: Any = None,
|
|
71
|
+
correlation_id: str = None) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Do nothing implementation of record_llm_response.
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
model : str
|
|
78
|
+
The name of the LLM model that responded.
|
|
79
|
+
content : str
|
|
80
|
+
The content of the LLM response.
|
|
81
|
+
tool_calls : List[Dict], optional
|
|
82
|
+
Any tool calls made by the LLM in its response.
|
|
83
|
+
call_duration_ms : float, optional
|
|
84
|
+
The duration of the LLM call in milliseconds.
|
|
85
|
+
source : Any, optional
|
|
86
|
+
The source of the event.
|
|
87
|
+
correlation_id : str, optional
|
|
88
|
+
UUID string that is copied from cause-to-affect for tracing events.
|
|
89
|
+
"""
|
|
90
|
+
# Do nothing
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
def record_tool_call(self,
|
|
94
|
+
tool_name: str,
|
|
95
|
+
arguments: Dict[str, Any],
|
|
96
|
+
result: Any,
|
|
97
|
+
caller: Optional[str] = None,
|
|
98
|
+
source: Any = None,
|
|
99
|
+
correlation_id: str = None) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Do nothing implementation of record_tool_call.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
tool_name : str
|
|
106
|
+
The name of the tool being called.
|
|
107
|
+
arguments : Dict[str, Any]
|
|
108
|
+
The arguments provided to the tool.
|
|
109
|
+
result : Any
|
|
110
|
+
The result returned by the tool.
|
|
111
|
+
caller : str, optional
|
|
112
|
+
The name of the agent or component calling the tool.
|
|
113
|
+
source : Any, optional
|
|
114
|
+
The source of the event.
|
|
115
|
+
correlation_id : str, optional
|
|
116
|
+
UUID string that is copied from cause-to-affect for tracing events.
|
|
117
|
+
"""
|
|
118
|
+
# Do nothing
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
def record_agent_interaction(self,
|
|
122
|
+
from_agent: str,
|
|
123
|
+
to_agent: str,
|
|
124
|
+
event_type: str,
|
|
125
|
+
event_id: Optional[str] = None,
|
|
126
|
+
source: Any = None,
|
|
127
|
+
correlation_id: str = None) -> None:
|
|
128
|
+
"""
|
|
129
|
+
Do nothing implementation of record_agent_interaction.
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
from_agent : str
|
|
134
|
+
The name of the agent sending the event.
|
|
135
|
+
to_agent : str
|
|
136
|
+
The name of the agent receiving the event.
|
|
137
|
+
event_type : str
|
|
138
|
+
The type of event being processed.
|
|
139
|
+
event_id : str, optional
|
|
140
|
+
A unique identifier for the event.
|
|
141
|
+
source : Any, optional
|
|
142
|
+
The source of the event.
|
|
143
|
+
correlation_id : str, optional
|
|
144
|
+
UUID string that is copied from cause-to-affect for tracing events.
|
|
145
|
+
"""
|
|
146
|
+
# Do nothing
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
def get_events(self,
|
|
150
|
+
event_type: Optional[Type[TracerEvent]] = None,
|
|
151
|
+
start_time: Optional[float] = None,
|
|
152
|
+
end_time: Optional[float] = None,
|
|
153
|
+
filter_func: Optional[Callable[[TracerEvent], bool]] = None) -> List[TracerEvent]:
|
|
154
|
+
"""
|
|
155
|
+
Return an empty list for any get_events request.
|
|
156
|
+
|
|
157
|
+
Parameters
|
|
158
|
+
----------
|
|
159
|
+
event_type : Type[TracerEvent], optional
|
|
160
|
+
Filter events by this specific tracer event type.
|
|
161
|
+
start_time : float, optional
|
|
162
|
+
Include events with timestamp >= start_time.
|
|
163
|
+
end_time : float, optional
|
|
164
|
+
Include events with timestamp <= end_time.
|
|
165
|
+
filter_func : Callable[[TracerEvent], bool], optional
|
|
166
|
+
Custom filter function to apply to events.
|
|
167
|
+
|
|
168
|
+
Returns
|
|
169
|
+
-------
|
|
170
|
+
List[TracerEvent]
|
|
171
|
+
An empty list.
|
|
172
|
+
"""
|
|
173
|
+
return []
|
|
174
|
+
|
|
175
|
+
def get_last_n_tracer_events(self, n: int, event_type: Optional[Type[TracerEvent]] = None) -> List[TracerEvent]:
|
|
176
|
+
"""
|
|
177
|
+
Return an empty list for any get_last_n_tracer_events request.
|
|
178
|
+
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
n : int
|
|
182
|
+
Number of events to return.
|
|
183
|
+
event_type : Type[TracerEvent], optional
|
|
184
|
+
Filter events by this specific tracer event type.
|
|
185
|
+
|
|
186
|
+
Returns
|
|
187
|
+
-------
|
|
188
|
+
List[TracerEvent]
|
|
189
|
+
An empty list.
|
|
190
|
+
"""
|
|
191
|
+
return []
|
|
192
|
+
|
|
193
|
+
def clear(self) -> None:
|
|
194
|
+
"""Do nothing implementation of clear method."""
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
def enable(self) -> None:
|
|
198
|
+
"""No-op method for interface compatibility."""
|
|
199
|
+
pass
|
|
200
|
+
|
|
201
|
+
def disable(self) -> None:
|
|
202
|
+
"""No-op method for interface compatibility."""
|
|
203
|
+
pass
|