mojentic 0.6.2__py3-none-any.whl → 0.7.2__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.
Files changed (37) hide show
  1. _examples/async_dispatcher_example.py +241 -0
  2. _examples/async_llm_example.py +236 -0
  3. _examples/broker_as_tool.py +13 -10
  4. _examples/coding_file_tool.py +170 -77
  5. _examples/file_tool.py +5 -3
  6. mojentic/__init__.py +2 -7
  7. mojentic/agents/__init__.py +11 -2
  8. mojentic/agents/async_aggregator_agent.py +162 -0
  9. mojentic/agents/async_aggregator_agent_spec.py +227 -0
  10. mojentic/agents/async_llm_agent.py +197 -0
  11. mojentic/agents/async_llm_agent_spec.py +166 -0
  12. mojentic/agents/base_async_agent.py +27 -0
  13. mojentic/async_dispatcher.py +134 -0
  14. mojentic/async_dispatcher_spec.py +244 -0
  15. mojentic/context/__init__.py +4 -0
  16. mojentic/llm/__init__.py +14 -2
  17. mojentic/llm/gateways/__init__.py +22 -0
  18. mojentic/llm/gateways/models.py +3 -3
  19. mojentic/llm/gateways/ollama.py +4 -4
  20. mojentic/llm/gateways/openai.py +3 -3
  21. mojentic/llm/gateways/openai_messages_adapter.py +8 -4
  22. mojentic/llm/llm_broker.py +4 -4
  23. mojentic/llm/message_composers.py +1 -1
  24. mojentic/llm/registry/__init__.py +6 -0
  25. mojentic/llm/registry/populate_registry_from_ollama.py +13 -12
  26. mojentic/llm/tools/__init__.py +18 -0
  27. mojentic/llm/tools/date_resolver.py +5 -2
  28. mojentic/llm/tools/ephemeral_task_manager/__init__.py +8 -8
  29. mojentic/llm/tools/file_manager.py +603 -42
  30. mojentic/llm/tools/file_manager_spec.py +723 -0
  31. mojentic/llm/tools/tool_wrapper.py +7 -3
  32. mojentic/tracer/__init__.py +8 -3
  33. {mojentic-0.6.2.dist-info → mojentic-0.7.2.dist-info}/METADATA +4 -2
  34. {mojentic-0.6.2.dist-info → mojentic-0.7.2.dist-info}/RECORD +37 -27
  35. {mojentic-0.6.2.dist-info → mojentic-0.7.2.dist-info}/WHEEL +0 -0
  36. {mojentic-0.6.2.dist-info → mojentic-0.7.2.dist-info}/licenses/LICENSE.md +0 -0
  37. {mojentic-0.6.2.dist-info → mojentic-0.7.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,134 @@
1
+ import asyncio
2
+ import logging
3
+ from collections import deque
4
+ from typing import Optional, Type
5
+ from uuid import uuid4
6
+
7
+ import structlog
8
+
9
+ from mojentic.event import TerminateEvent
10
+
11
+ logger = structlog.get_logger()
12
+
13
+
14
+ class AsyncDispatcher:
15
+ """
16
+ AsyncDispatcher class is an asynchronous version of the Dispatcher class.
17
+ It uses asyncio and deque for event processing.
18
+ """
19
+ def __init__(self, router, shared_working_memory=None, batch_size=5, tracer=None):
20
+ """
21
+ Initialize the AsyncDispatcher.
22
+
23
+ Parameters
24
+ ----------
25
+ router : Router
26
+ The router to use for routing events to agents
27
+ shared_working_memory : SharedWorkingMemory, optional
28
+ The shared working memory to use
29
+ batch_size : int, optional
30
+ The number of events to process in each batch
31
+ tracer : Tracer, optional
32
+ The tracer to use for tracing events
33
+ """
34
+ self.router = router
35
+ self.batch_size = batch_size
36
+ self.event_queue = deque()
37
+ self._stop_event = asyncio.Event()
38
+ self._task = None
39
+
40
+ # Use null_tracer if no tracer is provided
41
+ from mojentic.tracer import null_tracer
42
+ self.tracer = tracer or null_tracer
43
+
44
+ async def start(self):
45
+ """
46
+ Start the event dispatch task.
47
+ """
48
+ logger.debug("Starting event dispatch task")
49
+ self._task = asyncio.create_task(self._dispatch_events())
50
+ return self
51
+
52
+ async def stop(self):
53
+ """
54
+ Stop the event dispatch task.
55
+ """
56
+ self._stop_event.set()
57
+ if self._task:
58
+ await self._task
59
+
60
+ async def wait_for_empty_queue(self, timeout=None):
61
+ """
62
+ Wait for the event queue to be empty.
63
+
64
+ Parameters
65
+ ----------
66
+ timeout : float, optional
67
+ The timeout in seconds
68
+
69
+ Returns
70
+ -------
71
+ bool
72
+ True if the queue is empty, False if the timeout was reached
73
+ """
74
+ start_time = asyncio.get_event_loop().time()
75
+ while len(self.event_queue) > 0:
76
+ if timeout is not None and asyncio.get_event_loop().time() - start_time > timeout:
77
+ return False
78
+ await asyncio.sleep(0.1)
79
+ return True
80
+
81
+ def dispatch(self, event):
82
+ """
83
+ Dispatch an event to the event queue.
84
+
85
+ Parameters
86
+ ----------
87
+ event : Event
88
+ The event to dispatch
89
+ """
90
+ logger.log(logging.DEBUG, f"Dispatching event: {event}")
91
+ if event.correlation_id is None:
92
+ event.correlation_id = str(uuid4())
93
+ self.event_queue.append(event)
94
+
95
+ async def _dispatch_events(self):
96
+ """
97
+ Dispatch events from the event queue to agents.
98
+ """
99
+ while not self._stop_event.is_set():
100
+ for _ in range(self.batch_size):
101
+ logger.debug("Checking for events")
102
+ if len(self.event_queue) > 0:
103
+ logger.debug(f"{len(self.event_queue)} events in queue")
104
+ event = self.event_queue.popleft()
105
+ logger.debug(f"Processing event: {event}")
106
+ agents = self.router.get_agents(event)
107
+ logger.debug(f"Found {len(agents)} agents for event type {type(event)}")
108
+ events = []
109
+ for agent in agents:
110
+ logger.debug(f"Sending event to agent {agent}")
111
+
112
+ # Record agent interaction in tracer system
113
+ self.tracer.record_agent_interaction(
114
+ from_agent=str(event.source),
115
+ to_agent=str(type(agent)),
116
+ event_type=str(type(event).__name__),
117
+ event_id=event.correlation_id,
118
+ source=type(self)
119
+ )
120
+
121
+ # Process the event through the agent
122
+ # If the agent is an async agent, await its receive_event method
123
+ if hasattr(agent, 'receive_event_async'):
124
+ received_events = await agent.receive_event_async(event)
125
+ else:
126
+ received_events = agent.receive_event(event)
127
+
128
+ logger.debug(f"Agent {agent} returned {len(received_events)} events")
129
+ events.extend(received_events)
130
+ for fe in events:
131
+ if type(fe) is TerminateEvent:
132
+ self._stop_event.set()
133
+ self.dispatch(fe)
134
+ await asyncio.sleep(0.1) # Use asyncio.sleep instead of time.sleep
@@ -0,0 +1,244 @@
1
+ import asyncio
2
+ import pytest
3
+ import pytest_asyncio
4
+ from unittest.mock import AsyncMock, MagicMock
5
+
6
+ from mojentic.async_dispatcher import AsyncDispatcher
7
+ from mojentic.event import Event, TerminateEvent
8
+ from mojentic.agents.base_async_agent import BaseAsyncAgent
9
+ from mojentic.router import Router
10
+
11
+
12
+ class SampleEvent(Event):
13
+ """A simple event for testing."""
14
+ message: str
15
+
16
+
17
+ class SampleResponseEvent(Event):
18
+ """A response event for testing."""
19
+ response: str
20
+
21
+
22
+ class AsyncTestAgent(BaseAsyncAgent):
23
+ """A test async agent that returns a SampleResponseEvent."""
24
+
25
+ async def receive_event_async(self, event):
26
+ if isinstance(event, SampleEvent):
27
+ return [SampleResponseEvent(
28
+ source=type(self),
29
+ correlation_id=event.correlation_id,
30
+ response=f"Processed: {event.message}"
31
+ )]
32
+ return []
33
+
34
+
35
+ class SyncTestAgent:
36
+ """A test sync agent that returns a SampleResponseEvent."""
37
+
38
+ def __init__(self):
39
+ # Create a mock for tracking calls
40
+ self._mock = MagicMock()
41
+
42
+ def receive_event(self, event):
43
+ # Track the call
44
+ self._mock(event)
45
+
46
+ if isinstance(event, SampleEvent):
47
+ return [SampleResponseEvent(
48
+ source=type(self),
49
+ correlation_id=event.correlation_id,
50
+ response=f"Processed sync: {event.message}"
51
+ )]
52
+ return []
53
+
54
+ def assert_called_once(self):
55
+ # Delegate to the mock
56
+ self._mock.assert_called_once()
57
+
58
+
59
+ @pytest.fixture
60
+ def router():
61
+ """Create a router for testing."""
62
+ return Router()
63
+
64
+
65
+ @pytest.fixture
66
+ def async_agent():
67
+ """Create an async agent for testing."""
68
+ return AsyncTestAgent()
69
+
70
+
71
+ @pytest.fixture
72
+ def sync_agent():
73
+ """Create a sync agent for testing."""
74
+ return SyncTestAgent()
75
+
76
+
77
+ @pytest_asyncio.fixture
78
+ async def dispatcher(router):
79
+ """Create and start an AsyncDispatcher for testing."""
80
+ dispatcher = AsyncDispatcher(router)
81
+ await dispatcher.start()
82
+ yield dispatcher
83
+ await dispatcher.stop()
84
+
85
+
86
+ @pytest.mark.asyncio
87
+ async def test_async_dispatcher_init(router):
88
+ """Test that the AsyncDispatcher initializes correctly."""
89
+ dispatcher = AsyncDispatcher(router)
90
+
91
+ assert dispatcher.router == router
92
+ assert dispatcher.batch_size == 5
93
+ assert len(dispatcher.event_queue) == 0
94
+ assert dispatcher._task is None
95
+
96
+
97
+ @pytest.mark.asyncio
98
+ async def test_async_dispatcher_start_stop(router):
99
+ """Test that the AsyncDispatcher starts and stops correctly."""
100
+ dispatcher = AsyncDispatcher(router)
101
+
102
+ # Start the dispatcher
103
+ await dispatcher.start()
104
+ assert dispatcher._task is not None
105
+ assert not dispatcher._stop_event.is_set()
106
+
107
+ # Stop the dispatcher
108
+ await dispatcher.stop()
109
+ assert dispatcher._stop_event.is_set()
110
+
111
+
112
+ @pytest.mark.asyncio
113
+ async def test_async_dispatcher_dispatch(dispatcher, router, async_agent):
114
+ """Test that the AsyncDispatcher dispatches events correctly."""
115
+ # Register the agent
116
+ router.add_route(SampleEvent, async_agent)
117
+
118
+ # Create and dispatch an event
119
+ event = SampleEvent(source=str, message="Hello")
120
+ dispatcher.dispatch(event)
121
+
122
+ # Wait for the event to be processed
123
+ await dispatcher.wait_for_empty_queue(timeout=1)
124
+
125
+ # The event should have been processed and removed from the queue
126
+ assert len(dispatcher.event_queue) == 0
127
+
128
+
129
+ @pytest.mark.asyncio
130
+ async def test_async_dispatcher_with_async_agent(dispatcher, router, async_agent):
131
+ """Test that the AsyncDispatcher works with async agents."""
132
+ # Register the agent
133
+ router.add_route(SampleEvent, async_agent)
134
+
135
+ # Create and dispatch an event
136
+ event = SampleEvent(source=str, message="Hello")
137
+ dispatcher.dispatch(event)
138
+
139
+ # Wait for the event to be processed
140
+ await dispatcher.wait_for_empty_queue(timeout=1)
141
+
142
+ # The event should have been processed and removed from the queue
143
+ assert len(dispatcher.event_queue) == 0
144
+
145
+
146
+ @pytest.mark.asyncio
147
+ async def test_async_dispatcher_with_sync_agent(dispatcher, router, sync_agent):
148
+ """Test that the AsyncDispatcher works with sync agents."""
149
+ # Register the agent
150
+ router.add_route(SampleEvent, sync_agent)
151
+
152
+ # Create and dispatch an event
153
+ event = SampleEvent(source=str, message="Hello")
154
+ dispatcher.dispatch(event)
155
+
156
+ # Wait for the event to be processed
157
+ await dispatcher.wait_for_empty_queue(timeout=1)
158
+
159
+ # The event should have been processed and removed from the queue
160
+ assert len(dispatcher.event_queue) == 0
161
+
162
+ # Verify that the sync agent's receive_event method was called
163
+ sync_agent.assert_called_once()
164
+
165
+
166
+ @pytest.mark.asyncio
167
+ async def test_async_dispatcher_terminate_event(dispatcher, router):
168
+ """Test that the AsyncDispatcher handles TerminateEvent correctly."""
169
+ # Create a mock agent that returns a TerminateEvent
170
+ class TerminateAgent(BaseAsyncAgent):
171
+ async def receive_event_async(self, event):
172
+ return [TerminateEvent(source=type(self))]
173
+
174
+ terminate_agent = TerminateAgent()
175
+
176
+ # Register the agent
177
+ router.add_route(SampleEvent, terminate_agent)
178
+
179
+ # Create and dispatch an event
180
+ event = SampleEvent(source=str, message="Hello")
181
+ dispatcher.dispatch(event)
182
+
183
+ # Wait a moment for the event to be processed
184
+ await asyncio.sleep(0.5)
185
+
186
+ # The dispatcher should have stopped
187
+ assert dispatcher._stop_event.is_set()
188
+
189
+
190
+ @pytest.mark.asyncio
191
+ async def test_async_dispatcher_wait_for_empty_queue(dispatcher):
192
+ """Test that wait_for_empty_queue works correctly."""
193
+ # Queue is initially empty
194
+ assert await dispatcher.wait_for_empty_queue() is True
195
+
196
+ # Add multiple events to the queue to test batch processing
197
+ for i in range(10):
198
+ event = SampleEvent(source=str, message=f"Event {i}")
199
+ dispatcher.dispatch(event)
200
+
201
+ # Queue is not empty
202
+ assert len(dispatcher.event_queue) > 0
203
+
204
+ # Wait for the queue to be empty with a sufficient timeout
205
+ # The dispatcher should process all events
206
+ assert await dispatcher.wait_for_empty_queue(timeout=1) is True
207
+
208
+ # Verify the queue is empty
209
+ assert len(dispatcher.event_queue) == 0
210
+
211
+
212
+ @pytest.mark.asyncio
213
+ async def test_async_dispatcher_batch_processing(dispatcher, router, async_agent):
214
+ """Test that the AsyncDispatcher processes events in batches."""
215
+ # Register the agent
216
+ router.add_route(SampleEvent, async_agent)
217
+
218
+ # Set a small batch size
219
+ dispatcher.batch_size = 2
220
+
221
+ # Create and dispatch multiple events
222
+ for i in range(5):
223
+ event = SampleEvent(source=str, message=f"Event {i}")
224
+ dispatcher.dispatch(event)
225
+
226
+ # Wait for all events to be processed
227
+ await dispatcher.wait_for_empty_queue(timeout=2)
228
+
229
+ # All events should have been processed
230
+ assert len(dispatcher.event_queue) == 0
231
+
232
+
233
+ @pytest.mark.asyncio
234
+ async def test_async_dispatcher_correlation_id(dispatcher):
235
+ """Test that the AsyncDispatcher assigns correlation_id if not provided."""
236
+ # Create an event without a correlation_id
237
+ event = SampleEvent(source=str, message="Hello")
238
+ assert event.correlation_id is None
239
+
240
+ # Dispatch the event
241
+ dispatcher.dispatch(event)
242
+
243
+ # The event should now have a correlation_id
244
+ assert event.correlation_id is not None
@@ -1 +1,5 @@
1
+ """
2
+ Mojentic context module for managing shared working memory and context.
3
+ """
4
+
1
5
  from .shared_working_memory import SharedWorkingMemory
mojentic/llm/__init__.py CHANGED
@@ -1,4 +1,16 @@
1
+ """
2
+ Mojentic LLM module for interacting with Large Language Models.
3
+ """
4
+
5
+ # Main LLM components
1
6
  from .llm_broker import LLMBroker
2
- from .registry.llm_registry import LLMRegistry
3
7
  from .chat_session import ChatSession
4
- from .message_composers import MessageBuilder, FileTypeSensor
8
+ from .message_composers import MessageBuilder, FileTypeSensor
9
+ from .registry.llm_registry import LLMRegistry
10
+
11
+ # Re-export gateway components at the LLM level
12
+ from .gateways.models import (
13
+ LLMMessage,
14
+ LLMGatewayResponse,
15
+ MessageRole
16
+ )
@@ -1,3 +1,25 @@
1
+ """
2
+ Mojentic LLM gateways module for connecting to various LLM providers.
3
+ """
4
+
5
+ # Gateway implementations
1
6
  from .llm_gateway import LLMGateway
2
7
  from .ollama import OllamaGateway
3
8
  from .openai import OpenAIGateway
9
+ from .anthropic import AnthropicGateway
10
+ from .file_gateway import FileGateway
11
+ from .embeddings_gateway import EmbeddingsGateway
12
+ from .tokenizer_gateway import TokenizerGateway
13
+
14
+ # Message adapters
15
+ from .anthropic_messages_adapter import adapt_messages_to_anthropic
16
+ from .ollama_messages_adapter import adapt_messages_to_ollama
17
+ from .openai_messages_adapter import adapt_messages_to_openai
18
+
19
+ # Common models
20
+ from .models import (
21
+ LLMMessage,
22
+ MessageRole,
23
+ LLMGatewayResponse,
24
+ LLMToolCall
25
+ )
@@ -46,7 +46,7 @@ class LLMToolCall(BaseModel):
46
46
  """
47
47
  A tool call to be made available to the LLM.
48
48
 
49
- Parameters
49
+ Attributes
50
50
  ----------
51
51
  id : Optional[str]
52
52
  The identifier of the tool call.
@@ -64,7 +64,7 @@ class LLMMessage(BaseModel):
64
64
  """
65
65
  A message to be sent to the LLM. These would accumulate during a chat session with an LLM.
66
66
 
67
- Parameters
67
+ Attributes
68
68
  ----------
69
69
  role : MessageRole
70
70
  The role of the message in the conversation.
@@ -89,7 +89,7 @@ class LLMGatewayResponse(BaseModel):
89
89
  """
90
90
  The response from the LLM gateway, abstracting you from the quirks of a specific LLM.
91
91
 
92
- Parameters
92
+ Attributes
93
93
  ----------
94
94
  content : Optional[Union[str, dict[str, str]]]
95
95
  The content of the response.
@@ -41,8 +41,8 @@ class OllamaGateway(LLMGateway):
41
41
  """
42
42
  Complete the LLM request by delegating to the Ollama service.
43
43
 
44
- Parameters
45
- ----------
44
+ Keyword Arguments
45
+ ----------------
46
46
  model : str
47
47
  The name of the model to use, as appears in `ollama list`.
48
48
  messages : List[LLMMessage]
@@ -107,8 +107,8 @@ class OllamaGateway(LLMGateway):
107
107
  """
108
108
  Stream the LLM response from Ollama service.
109
109
 
110
- Parameters
111
- ----------
110
+ Keyword Arguments
111
+ ----------------
112
112
  model : str
113
113
  The name of the model to use, as appears in `ollama list`.
114
114
  messages : List[LLMMessage]
@@ -31,8 +31,8 @@ class OpenAIGateway(LLMGateway):
31
31
  """
32
32
  Complete the LLM request by delegating to the OpenAI service.
33
33
 
34
- Parameters
35
- ----------
34
+ Keyword Arguments
35
+ ----------------
36
36
  model : str
37
37
  The name of the model to use, as appears in `ollama list`.
38
38
  messages : List[LLMMessage]
@@ -148,4 +148,4 @@ class OpenAIGateway(LLMGateway):
148
148
  tokenizer = TokenizerGateway()
149
149
  tokens = tokenizer.encode(text)
150
150
  chunks_iterator = self._batched(tokens, chunk_length)
151
- yield from chunks_iterator
151
+ yield from chunks_iterator
@@ -48,13 +48,17 @@ def get_image_type(file_path: str) -> str:
48
48
  file_path: Path to the image file
49
49
 
50
50
  Returns:
51
- Image type (e.g., 'jpg', 'png')
51
+ Image type (e.g., 'jpeg', 'png')
52
52
  """
53
53
  _, ext = os.path.splitext(file_path)
54
54
  image_type = ext.lstrip('.').lower()
55
- if image_type not in ['jpeg', 'jpg', 'png', 'gif', 'webp']:
56
- image_type = 'jpeg' # Default to jpeg if unknown extension
57
- return image_type
55
+
56
+ # Convert 'jpg' to 'jpeg'
57
+ if image_type == 'jpg':
58
+ return 'jpeg'
59
+
60
+ # Use 'jpeg' for unknown extensions, otherwise use the detected type
61
+ return image_type if image_type in ['jpeg', 'png', 'gif', 'webp'] else 'jpeg'
58
62
 
59
63
 
60
64
  def adapt_messages_to_openai(messages: List[LLMMessage]):
@@ -88,7 +88,7 @@ class LLMBroker():
88
88
  logger.info(f"Requesting llm response with approx {approximate_tokens} tokens")
89
89
 
90
90
  # Convert messages to serializable dict for audit
91
- messages_for_tracer = [m.dict() for m in messages]
91
+ messages_for_tracer = [m.model_dump() for m in messages]
92
92
 
93
93
  # Record LLM call in tracer
94
94
  tools_for_tracer = [{"name": t.name, "description": t.description} for t in tools] if tools else None
@@ -115,7 +115,7 @@ class LLMBroker():
115
115
  call_duration_ms = (time.time() - start_time) * 1000
116
116
 
117
117
  # Record LLM response in tracer
118
- tool_calls_for_tracer = [tc.dict() for tc in result.tool_calls] if result.tool_calls else None
118
+ tool_calls_for_tracer = [tc.model_dump() for tc in result.tool_calls] if result.tool_calls else None
119
119
  self.tracer.record_llm_response(
120
120
  self.model,
121
121
  result.content,
@@ -199,7 +199,7 @@ class LLMBroker():
199
199
  logger.info(f"Requesting llm response with approx {approximate_tokens} tokens")
200
200
 
201
201
  # Convert messages to serializable dict for audit
202
- messages_for_tracer = [m.dict() for m in messages]
202
+ messages_for_tracer = [m.model_dump() for m in messages]
203
203
 
204
204
  # Record LLM call in tracer
205
205
  self.tracer.record_llm_call(
@@ -221,7 +221,7 @@ class LLMBroker():
221
221
 
222
222
  # Record LLM response in tracer with object representation
223
223
  # Convert object to string for tracer
224
- object_str = str(result.object.dict()) if hasattr(result.object, "dict") else str(result.object)
224
+ object_str = str(result.object.model_dump()) if hasattr(result.object, "model_dump") else str(result.object)
225
225
  self.tracer.record_llm_response(
226
226
  self.model,
227
227
  f"Structured response: {object_str}",
@@ -216,7 +216,7 @@ class MessageBuilder():
216
216
 
217
217
  return self
218
218
 
219
- def add_files(self, *file_paths: Union[str, Path]) -> "MessageBuilder":
219
+ def add_files(self, *file_paths: List[Union[str, Path]]) -> "MessageBuilder":
220
220
  """
221
221
  Add multiple text files to the message, ignoring binary files.
222
222
 
@@ -0,0 +1,6 @@
1
+ """
2
+ Mojentic LLM registry module for managing model registrations.
3
+ """
4
+
5
+ from .llm_registry import LLMRegistry
6
+ from .models import ModelInfo, Modality, Quantization
@@ -46,15 +46,16 @@ def register_llms_from_ollama(url: str, registry: LLMRegistry):
46
46
  registry.register(entry)
47
47
 
48
48
 
49
- # Example usage
50
- ollama_url = "http://localhost:11434/api/tags"
51
- registry = LLMRegistry()
52
- register_llms_from_ollama(ollama_url, registry)
53
-
54
- # print(f"Tool using: {registry.find_first(tools=True, structured_output=True).name}")
55
- print(f"Fastest model: {registry.find_fastest().name}")
56
- print(f"Fastest model with tools: {registry.find_fastest(tools=True).name}")
57
- print(f"Fastest model with structured output: {registry.find_fastest(structured_output=True).name}")
58
- print(f"Smartest model: {registry.find_smartest().name}")
59
- print(f"Smartest model with tools: {registry.find_smartest(tools=True).name}")
60
- print(f"Smartest model with structured output: {registry.find_smartest(structured_output=True).name}")
49
+ if __name__ == "__main__":
50
+ # Example usage
51
+ ollama_url = "http://localhost:11434/api/tags"
52
+ registry = LLMRegistry()
53
+ register_llms_from_ollama(ollama_url, registry)
54
+
55
+ # print(f"Tool using: {registry.find_first(tools=True, structured_output=True).name}")
56
+ print(f"Fastest model: {registry.find_fastest().name}")
57
+ print(f"Fastest model with tools: {registry.find_fastest(tools=True).name}")
58
+ print(f"Fastest model with structured output: {registry.find_fastest(structured_output=True).name}")
59
+ print(f"Smartest model: {registry.find_smartest().name}")
60
+ print(f"Smartest model with tools: {registry.find_smartest(tools=True).name}")
61
+ print(f"Smartest model with structured output: {registry.find_smartest(structured_output=True).name}")
@@ -0,0 +1,18 @@
1
+ """
2
+ Mojentic LLM tools module for extending LLM capabilities.
3
+ """
4
+
5
+ # Base tool class
6
+ from .llm_tool import LLMTool
7
+ from .tool_wrapper import ToolWrapper
8
+
9
+ # Common tools
10
+ from .ask_user_tool import AskUserTool
11
+ from .current_datetime import CurrentDateTimeTool
12
+ from .date_resolver import ResolveDateTool
13
+ from .organic_web_search import OrganicWebSearchTool
14
+ from .tell_user_tool import TellUserTool
15
+
16
+ # Import tool modules
17
+ from . import file_manager
18
+ from . import ephemeral_task_manager
@@ -1,11 +1,14 @@
1
- from typing import Optional
1
+ from typing import Optional, TYPE_CHECKING
2
2
 
3
3
  from parsedatetime import Calendar, VERSION_CONTEXT_STYLE
4
4
  from pytz import timezone
5
5
 
6
- from mojentic.llm import LLMBroker
7
6
  from mojentic.llm.tools.llm_tool import LLMTool
8
7
 
8
+ # Avoid circular imports with TYPE_CHECKING
9
+ if TYPE_CHECKING:
10
+ from mojentic.llm.llm_broker import LLMBroker
11
+
9
12
 
10
13
  class ResolveDateTool(LLMTool):
11
14
  def run(self, relative_date_found: str, reference_date_in_iso8601: Optional[str] = None) -> dict[str, str]:
@@ -5,14 +5,14 @@ This module provides tools for appending, prepending, inserting, starting, compl
5
5
  Tasks follow a state machine that transitions from PENDING through IN_PROGRESS to COMPLETED.
6
6
  """
7
7
 
8
- from mojentic.llm.tools.ephemeral_task_manager.append_task_tool import AppendTaskTool
9
- from mojentic.llm.tools.ephemeral_task_manager.clear_tasks_tool import ClearTasksTool
10
- from mojentic.llm.tools.ephemeral_task_manager.complete_task_tool import CompleteTaskTool
11
- from mojentic.llm.tools.ephemeral_task_manager.insert_task_after_tool import InsertTaskAfterTool
12
- from mojentic.llm.tools.ephemeral_task_manager.list_tasks_tool import ListTasksTool
13
- from mojentic.llm.tools.ephemeral_task_manager.prepend_task_tool import PrependTaskTool
14
- from mojentic.llm.tools.ephemeral_task_manager.start_task_tool import StartTaskTool
15
- from mojentic.llm.tools.ephemeral_task_manager.ephemeral_task_list import EphemeralTaskList, Task
8
+ from .append_task_tool import AppendTaskTool
9
+ from .clear_tasks_tool import ClearTasksTool
10
+ from .complete_task_tool import CompleteTaskTool
11
+ from .insert_task_after_tool import InsertTaskAfterTool
12
+ from .list_tasks_tool import ListTasksTool
13
+ from .prepend_task_tool import PrependTaskTool
14
+ from .start_task_tool import StartTaskTool
15
+ from .ephemeral_task_list import EphemeralTaskList, Task
16
16
 
17
17
  __all__ = [
18
18
  "EphemeralTaskList",