mojentic 0.9.0__py3-none-any.whl → 1.0.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/async_dispatcher_example.py +12 -4
- _examples/async_llm_example.py +1 -2
- _examples/broker_as_tool.py +39 -14
- _examples/broker_examples.py +4 -6
- _examples/characterize_ollama.py +1 -1
- _examples/characterize_openai.py +1 -1
- _examples/chat_session.py +1 -1
- _examples/chat_session_with_tool.py +1 -1
- _examples/coding_file_tool.py +1 -3
- _examples/current_datetime_tool_example.py +1 -1
- _examples/embeddings.py +1 -1
- _examples/ephemeral_task_manager_example.py +13 -9
- _examples/fetch_openai_models.py +10 -3
- _examples/file_deduplication.py +6 -6
- _examples/image_analysis.py +2 -3
- _examples/image_broker.py +1 -1
- _examples/image_broker_splat.py +1 -1
- _examples/iterative_solver.py +2 -2
- _examples/model_characterization.py +2 -0
- _examples/openai_gateway_enhanced_demo.py +15 -5
- _examples/raw.py +1 -1
- _examples/react/agents/decisioning_agent.py +173 -15
- _examples/react/agents/summarization_agent.py +89 -0
- _examples/react/agents/thinking_agent.py +84 -14
- _examples/react/agents/tool_call_agent.py +83 -0
- _examples/react/formatters.py +38 -4
- _examples/react/models/base.py +60 -11
- _examples/react/models/events.py +76 -8
- _examples/react.py +71 -21
- _examples/recursive_agent.py +1 -1
- _examples/solver_chat_session.py +1 -7
- _examples/streaming.py +7 -4
- _examples/tell_user_example.py +3 -3
- _examples/tracer_demo.py +15 -17
- _examples/tracer_qt_viewer.py +49 -46
- mojentic/__init__.py +3 -3
- mojentic/agents/__init__.py +26 -8
- mojentic/agents/{agent_broker.py → agent_event_adapter.py} +3 -3
- mojentic/agents/async_aggregator_agent_spec.py +32 -33
- mojentic/agents/async_llm_agent.py +9 -5
- mojentic/agents/async_llm_agent_spec.py +21 -22
- mojentic/agents/base_async_agent.py +2 -2
- mojentic/agents/base_llm_agent.py +6 -2
- mojentic/agents/iterative_problem_solver.py +11 -5
- mojentic/agents/simple_recursive_agent.py +11 -10
- mojentic/agents/simple_recursive_agent_spec.py +423 -0
- mojentic/async_dispatcher.py +0 -1
- mojentic/async_dispatcher_spec.py +1 -1
- mojentic/context/__init__.py +0 -2
- mojentic/dispatcher.py +7 -8
- mojentic/llm/__init__.py +5 -5
- mojentic/llm/gateways/__init__.py +19 -18
- mojentic/llm/gateways/anthropic.py +1 -0
- mojentic/llm/gateways/anthropic_messages_adapter.py +0 -1
- mojentic/llm/gateways/llm_gateway.py +1 -1
- mojentic/llm/gateways/ollama.py +2 -0
- mojentic/llm/gateways/openai.py +62 -58
- mojentic/llm/gateways/openai_message_adapter_spec.py +3 -3
- mojentic/llm/gateways/openai_model_registry.py +7 -6
- mojentic/llm/gateways/openai_model_registry_spec.py +1 -2
- mojentic/llm/gateways/openai_temperature_handling_spec.py +2 -2
- mojentic/llm/llm_broker.py +7 -5
- mojentic/llm/llm_broker_spec.py +7 -2
- mojentic/llm/message_composers.py +6 -3
- mojentic/llm/message_composers_spec.py +5 -1
- mojentic/llm/registry/__init__.py +0 -3
- mojentic/llm/tools/__init__.py +0 -9
- mojentic/llm/tools/ask_user_tool.py +11 -5
- mojentic/llm/tools/current_datetime.py +9 -6
- mojentic/llm/tools/date_resolver.py +10 -4
- mojentic/llm/tools/date_resolver_spec.py +0 -1
- mojentic/llm/tools/ephemeral_task_manager/append_task_tool.py +4 -1
- mojentic/llm/tools/ephemeral_task_manager/ephemeral_task_list.py +1 -1
- mojentic/llm/tools/ephemeral_task_manager/insert_task_after_tool.py +4 -1
- mojentic/llm/tools/ephemeral_task_manager/prepend_task_tool.py +5 -2
- mojentic/llm/tools/file_manager.py +131 -28
- mojentic/llm/tools/file_manager_spec.py +0 -3
- mojentic/llm/tools/llm_tool.py +1 -1
- mojentic/llm/tools/llm_tool_spec.py +0 -2
- mojentic/llm/tools/organic_web_search.py +4 -2
- mojentic/llm/tools/tell_user_tool.py +6 -2
- mojentic/llm/tools/tool_wrapper.py +2 -2
- mojentic/tracer/__init__.py +1 -10
- mojentic/tracer/event_store.py +7 -8
- mojentic/tracer/event_store_spec.py +1 -2
- mojentic/tracer/null_tracer.py +37 -43
- mojentic/tracer/tracer_events.py +8 -2
- mojentic/tracer/tracer_events_spec.py +6 -7
- mojentic/tracer/tracer_system.py +37 -36
- mojentic/tracer/tracer_system_spec.py +21 -6
- mojentic/utils/__init__.py +1 -1
- mojentic/utils/formatting.py +1 -0
- {mojentic-0.9.0.dist-info → mojentic-1.0.0.dist-info}/METADATA +44 -26
- mojentic-1.0.0.dist-info/RECORD +149 -0
- mojentic-0.9.0.dist-info/RECORD +0 -146
- {mojentic-0.9.0.dist-info → mojentic-1.0.0.dist-info}/WHEEL +0 -0
- {mojentic-0.9.0.dist-info → mojentic-1.0.0.dist-info}/licenses/LICENSE.md +0 -0
- {mojentic-0.9.0.dist-info → mojentic-1.0.0.dist-info}/top_level.txt +0 -0
mojentic/async_dispatcher.py
CHANGED
mojentic/context/__init__.py
CHANGED
mojentic/dispatcher.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import threading
|
|
3
3
|
from time import sleep
|
|
4
|
-
from typing import Optional, Type
|
|
5
4
|
from uuid import uuid4
|
|
6
5
|
|
|
7
6
|
import structlog
|
|
@@ -18,7 +17,7 @@ class Dispatcher:
|
|
|
18
17
|
self.event_queue = []
|
|
19
18
|
self._stop_event = threading.Event()
|
|
20
19
|
self._thread = threading.Thread(target=self._dispatch_events)
|
|
21
|
-
|
|
20
|
+
|
|
22
21
|
# Use null_tracer if no tracer is provided
|
|
23
22
|
from mojentic.tracer import null_tracer
|
|
24
23
|
self.tracer = tracer or null_tracer
|
|
@@ -49,16 +48,16 @@ class Dispatcher:
|
|
|
49
48
|
events = []
|
|
50
49
|
for agent in agents:
|
|
51
50
|
logger.debug(f"Sending event to agent {agent}")
|
|
52
|
-
|
|
51
|
+
|
|
53
52
|
# Record agent interaction in tracer system
|
|
54
53
|
self.tracer.record_agent_interaction(
|
|
55
54
|
from_agent=str(event.source),
|
|
56
55
|
to_agent=str(type(agent)),
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
event_type=str(type(event).__name__),
|
|
57
|
+
event_id=event.correlation_id,
|
|
58
|
+
source=type(self)
|
|
59
|
+
)
|
|
60
|
+
|
|
62
61
|
# Process the event through the agent
|
|
63
62
|
received_events = agent.receive_event(event)
|
|
64
63
|
logger.debug(f"Agent {agent} returned {len(events)} events")
|
mojentic/llm/__init__.py
CHANGED
|
@@ -3,13 +3,13 @@ Mojentic LLM module for interacting with Large Language Models.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
# Main LLM components
|
|
6
|
-
from .llm_broker import LLMBroker
|
|
7
|
-
from .chat_session import ChatSession
|
|
8
|
-
from .message_composers import MessageBuilder, FileTypeSensor
|
|
9
|
-
from .registry.llm_registry import LLMRegistry
|
|
6
|
+
from .llm_broker import LLMBroker # noqa: F401
|
|
7
|
+
from .chat_session import ChatSession # noqa: F401
|
|
8
|
+
from .message_composers import MessageBuilder, FileTypeSensor # noqa: F401
|
|
9
|
+
from .registry.llm_registry import LLMRegistry # noqa: F401
|
|
10
10
|
|
|
11
11
|
# Re-export gateway components at the LLM level
|
|
12
|
-
from .gateways.models import (
|
|
12
|
+
from .gateways.models import ( # noqa: F401
|
|
13
13
|
LLMMessage,
|
|
14
14
|
LLMGatewayResponse,
|
|
15
15
|
MessageRole
|
|
@@ -3,23 +3,24 @@ Mojentic LLM gateways module for connecting to various LLM providers.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
# Gateway implementations
|
|
6
|
-
from .llm_gateway import LLMGateway
|
|
7
|
-
from .ollama import OllamaGateway
|
|
8
|
-
from .openai import OpenAIGateway
|
|
9
|
-
from .anthropic import AnthropicGateway
|
|
10
|
-
from .
|
|
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
|
|
6
|
+
from mojentic.llm.gateways.llm_gateway import LLMGateway
|
|
7
|
+
from mojentic.llm.gateways.ollama import OllamaGateway
|
|
8
|
+
from mojentic.llm.gateways.openai import OpenAIGateway
|
|
9
|
+
from mojentic.llm.gateways.anthropic import AnthropicGateway
|
|
10
|
+
from mojentic.llm.gateways.tokenizer_gateway import TokenizerGateway
|
|
11
|
+
from mojentic.llm.gateways.embeddings_gateway import EmbeddingsGateway
|
|
18
12
|
|
|
19
13
|
# Common models
|
|
20
|
-
from .models import
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
from mojentic.llm.gateways.models import LLMMessage, LLMToolCall, LLMGatewayResponse
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"LLMGateway",
|
|
18
|
+
"OllamaGateway",
|
|
19
|
+
"OpenAIGateway",
|
|
20
|
+
"AnthropicGateway",
|
|
21
|
+
"TokenizerGateway",
|
|
22
|
+
"EmbeddingsGateway",
|
|
23
|
+
"LLMMessage",
|
|
24
|
+
"LLMToolCall",
|
|
25
|
+
"LLMGatewayResponse",
|
|
26
|
+
]
|
mojentic/llm/gateways/ollama.py
CHANGED
|
@@ -9,6 +9,7 @@ from mojentic.llm.gateways.ollama_messages_adapter import adapt_messages_to_olla
|
|
|
9
9
|
|
|
10
10
|
logger = structlog.get_logger()
|
|
11
11
|
|
|
12
|
+
|
|
12
13
|
class StreamingResponse(BaseModel):
|
|
13
14
|
"""
|
|
14
15
|
Wrapper for streaming response chunks.
|
|
@@ -23,6 +24,7 @@ class StreamingResponse(BaseModel):
|
|
|
23
24
|
content: Optional[str] = None
|
|
24
25
|
tool_calls: Optional[List] = None
|
|
25
26
|
|
|
27
|
+
|
|
26
28
|
class OllamaGateway(LLMGateway):
|
|
27
29
|
"""
|
|
28
30
|
This class is a gateway to the Ollama LLM service.
|
mojentic/llm/gateways/openai.py
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
3
|
from itertools import islice
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import List, Iterable, Optional, Iterator, Dict
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
import structlog
|
|
8
8
|
from openai import OpenAI, BadRequestError
|
|
9
|
-
from pydantic import BaseModel
|
|
10
9
|
|
|
11
10
|
from mojentic.llm.gateways.llm_gateway import LLMGateway
|
|
12
|
-
from mojentic.llm.gateways.models import LLMToolCall, LLMGatewayResponse
|
|
11
|
+
from mojentic.llm.gateways.models import LLMToolCall, LLMGatewayResponse
|
|
13
12
|
from mojentic.llm.gateways.openai_messages_adapter import adapt_messages_to_openai
|
|
14
13
|
from mojentic.llm.gateways.openai_model_registry import get_model_registry, ModelType
|
|
15
14
|
from mojentic.llm.gateways.tokenizer_gateway import TokenizerGateway
|
|
16
|
-
from mojentic.llm.tools.llm_tool import LLMTool
|
|
17
15
|
from mojentic.llm.gateways.ollama import StreamingResponse
|
|
18
16
|
|
|
19
17
|
logger = structlog.get_logger()
|
|
@@ -77,10 +75,10 @@ class OpenAIGateway(LLMGateway):
|
|
|
77
75
|
capabilities = self.model_registry.get_model_capabilities(model)
|
|
78
76
|
|
|
79
77
|
logger.debug("Adapting parameters for model",
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
model=model,
|
|
79
|
+
model_type=capabilities.model_type.value,
|
|
80
|
+
supports_tools=capabilities.supports_tools,
|
|
81
|
+
supports_streaming=capabilities.supports_streaming)
|
|
84
82
|
|
|
85
83
|
# Handle token limit parameter conversion
|
|
86
84
|
if 'max_tokens' in adapted_args:
|
|
@@ -89,16 +87,16 @@ class OpenAIGateway(LLMGateway):
|
|
|
89
87
|
# Convert max_tokens to max_completion_tokens for reasoning models
|
|
90
88
|
adapted_args[token_param] = adapted_args.pop('max_tokens')
|
|
91
89
|
logger.info("Converted token limit parameter for model",
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
model=model,
|
|
91
|
+
from_param='max_tokens',
|
|
92
|
+
to_param=token_param,
|
|
93
|
+
value=adapted_args[token_param])
|
|
96
94
|
|
|
97
95
|
# Validate tool usage for models that don't support tools
|
|
98
96
|
if 'tools' in adapted_args and adapted_args['tools'] and not capabilities.supports_tools:
|
|
99
97
|
logger.warning("Model does not support tools, removing tool configuration",
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
model=model,
|
|
99
|
+
num_tools=len(adapted_args['tools']))
|
|
102
100
|
adapted_args['tools'] = None # Set to None instead of removing the key
|
|
103
101
|
|
|
104
102
|
# Handle temperature restrictions for specific models
|
|
@@ -108,18 +106,19 @@ class OpenAIGateway(LLMGateway):
|
|
|
108
106
|
# Check if model supports temperature parameter at all
|
|
109
107
|
if capabilities.supported_temperatures == []:
|
|
110
108
|
# Model doesn't support temperature parameter at all - remove it
|
|
111
|
-
logger.warning("Model does not support temperature parameter
|
|
112
|
-
|
|
113
|
-
|
|
109
|
+
logger.warning("Model does not support temperature parameter at all",
|
|
110
|
+
model=model,
|
|
111
|
+
requested_temperature=temperature)
|
|
114
112
|
adapted_args.pop('temperature', None)
|
|
115
113
|
elif not capabilities.supports_temperature(temperature):
|
|
116
114
|
# Model supports temperature but not this specific value - use default
|
|
117
115
|
default_temp = 1.0
|
|
118
|
-
logger.warning(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
116
|
+
logger.warning(
|
|
117
|
+
"Model does not support requested temperature, using default",
|
|
118
|
+
model=model,
|
|
119
|
+
requested_temperature=temperature,
|
|
120
|
+
default_temperature=default_temp,
|
|
121
|
+
supported_temperatures=capabilities.supported_temperatures)
|
|
123
122
|
adapted_args['temperature'] = default_temp
|
|
124
123
|
|
|
125
124
|
return adapted_args
|
|
@@ -139,13 +138,12 @@ class OpenAIGateway(LLMGateway):
|
|
|
139
138
|
|
|
140
139
|
# Warning for tools on reasoning models that don't support them
|
|
141
140
|
if (capabilities.model_type == ModelType.REASONING and
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
not capabilities.supports_tools and
|
|
142
|
+
'tools' in args and args['tools']):
|
|
144
143
|
logger.warning(
|
|
145
144
|
"Reasoning model may not support tools",
|
|
146
145
|
model=model,
|
|
147
|
-
num_tools=len(args['tools'])
|
|
148
|
-
)
|
|
146
|
+
num_tools=len(args['tools']))
|
|
149
147
|
|
|
150
148
|
# Validate token limits (check both possible parameter names)
|
|
151
149
|
token_value = args.get('max_tokens') or args.get('max_completion_tokens')
|
|
@@ -155,8 +153,7 @@ class OpenAIGateway(LLMGateway):
|
|
|
155
153
|
"Requested token limit exceeds model maximum",
|
|
156
154
|
model=model,
|
|
157
155
|
requested=token_value,
|
|
158
|
-
max_allowed=capabilities.max_output_tokens
|
|
159
|
-
)
|
|
156
|
+
max_allowed=capabilities.max_output_tokens)
|
|
160
157
|
|
|
161
158
|
def complete(self, **kwargs) -> LLMGatewayResponse:
|
|
162
159
|
"""
|
|
@@ -219,8 +216,8 @@ class OpenAIGateway(LLMGateway):
|
|
|
219
216
|
adapted_args = self._adapt_parameters_for_model(model, args)
|
|
220
217
|
except Exception as e:
|
|
221
218
|
logger.error("Failed to adapt parameters for model",
|
|
222
|
-
|
|
223
|
-
|
|
219
|
+
model=model,
|
|
220
|
+
error=str(e))
|
|
224
221
|
raise
|
|
225
222
|
|
|
226
223
|
# Validate parameters after adaptation
|
|
@@ -251,25 +248,26 @@ class OpenAIGateway(LLMGateway):
|
|
|
251
248
|
openai_args['max_completion_tokens'] = adapted_args['max_completion_tokens']
|
|
252
249
|
|
|
253
250
|
logger.debug("Making OpenAI API call",
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
251
|
+
model=openai_args['model'],
|
|
252
|
+
has_tools='tools' in openai_args,
|
|
253
|
+
has_object_model='response_format' in openai_args,
|
|
254
|
+
token_param='max_completion_tokens' if 'max_completion_tokens' in openai_args else 'max_tokens')
|
|
258
255
|
|
|
259
256
|
try:
|
|
260
257
|
response = completion(**openai_args)
|
|
261
258
|
except BadRequestError as e:
|
|
262
259
|
# Enhanced error handling for parameter issues
|
|
263
260
|
if "max_tokens" in str(e) and "max_completion_tokens" in str(e):
|
|
264
|
-
logger.error(
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
261
|
+
logger.error(
|
|
262
|
+
"Parameter error detected - model may require different token parameter",
|
|
263
|
+
model=model,
|
|
264
|
+
error=str(e),
|
|
265
|
+
suggestion="This model may be a reasoning model requiring max_completion_tokens")
|
|
268
266
|
raise e
|
|
269
267
|
except Exception as e:
|
|
270
268
|
logger.error("OpenAI API call failed",
|
|
271
|
-
|
|
272
|
-
|
|
269
|
+
model=model,
|
|
270
|
+
error=str(e))
|
|
273
271
|
raise e
|
|
274
272
|
|
|
275
273
|
object = None
|
|
@@ -281,11 +279,16 @@ class OpenAIGateway(LLMGateway):
|
|
|
281
279
|
if response_content is not None:
|
|
282
280
|
object = adapted_args['object_model'].model_validate_json(response_content)
|
|
283
281
|
else:
|
|
284
|
-
logger.error(
|
|
282
|
+
logger.error(
|
|
283
|
+
"No response content available for object validation",
|
|
284
|
+
object_model=adapted_args['object_model'])
|
|
285
285
|
except Exception as e:
|
|
286
|
-
response_content = response.choices[0].message.content
|
|
287
|
-
|
|
288
|
-
|
|
286
|
+
response_content = (response.choices[0].message.content
|
|
287
|
+
if response.choices else "No response content")
|
|
288
|
+
logger.error("Failed to validate model",
|
|
289
|
+
error=str(e),
|
|
290
|
+
response=response_content,
|
|
291
|
+
object_model=adapted_args['object_model'])
|
|
289
292
|
|
|
290
293
|
if response.choices[0].message.tool_calls is not None:
|
|
291
294
|
for t in response.choices[0].message.tool_calls:
|
|
@@ -363,8 +366,8 @@ class OpenAIGateway(LLMGateway):
|
|
|
363
366
|
adapted_args = self._adapt_parameters_for_model(model, args)
|
|
364
367
|
except Exception as e:
|
|
365
368
|
logger.error("Failed to adapt parameters for model",
|
|
366
|
-
|
|
367
|
-
|
|
369
|
+
model=model,
|
|
370
|
+
error=str(e))
|
|
368
371
|
raise
|
|
369
372
|
|
|
370
373
|
# Validate parameters after adaptation
|
|
@@ -399,23 +402,24 @@ class OpenAIGateway(LLMGateway):
|
|
|
399
402
|
openai_args['max_completion_tokens'] = adapted_args['max_completion_tokens']
|
|
400
403
|
|
|
401
404
|
logger.debug("Making OpenAI streaming API call",
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
+
model=openai_args['model'],
|
|
406
|
+
has_tools='tools' in openai_args,
|
|
407
|
+
token_param='max_completion_tokens' if 'max_completion_tokens' in openai_args else 'max_tokens')
|
|
405
408
|
|
|
406
409
|
try:
|
|
407
410
|
stream = self.client.chat.completions.create(**openai_args)
|
|
408
411
|
except BadRequestError as e:
|
|
409
412
|
if "max_tokens" in str(e) and "max_completion_tokens" in str(e):
|
|
410
|
-
logger.error(
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
413
|
+
logger.error(
|
|
414
|
+
"Parameter error detected - model may require different token parameter",
|
|
415
|
+
model=model,
|
|
416
|
+
error=str(e),
|
|
417
|
+
suggestion="This model may be a reasoning model requiring max_completion_tokens")
|
|
414
418
|
raise e
|
|
415
419
|
except Exception as e:
|
|
416
420
|
logger.error("OpenAI streaming API call failed",
|
|
417
|
-
|
|
418
|
-
|
|
421
|
+
model=model,
|
|
422
|
+
error=str(e))
|
|
419
423
|
raise e
|
|
420
424
|
|
|
421
425
|
# Accumulate tool calls as they stream in
|
|
@@ -477,9 +481,9 @@ class OpenAIGateway(LLMGateway):
|
|
|
477
481
|
complete_tool_calls.append(tool_call)
|
|
478
482
|
except json.JSONDecodeError as e:
|
|
479
483
|
logger.error("Failed to parse tool call arguments",
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
484
|
+
tool_name=tc['name'],
|
|
485
|
+
arguments=tc['arguments'],
|
|
486
|
+
error=str(e))
|
|
483
487
|
|
|
484
488
|
if complete_tool_calls:
|
|
485
489
|
# Convert to the format expected by ollama's tool calls for compatibility
|
|
@@ -93,11 +93,11 @@ class DescribeOpenAIMessagesAdapter:
|
|
|
93
93
|
Then it should convert to the correct format with structured content array
|
|
94
94
|
"""
|
|
95
95
|
# Patch our own methods that encapsulate external library calls
|
|
96
|
-
mocker.patch('mojentic.llm.gateways.openai_messages_adapter.read_file_as_binary',
|
|
96
|
+
mocker.patch('mojentic.llm.gateways.openai_messages_adapter.read_file_as_binary',
|
|
97
97
|
return_value=b'fake_image_data')
|
|
98
|
-
mocker.patch('mojentic.llm.gateways.openai_messages_adapter.encode_base64',
|
|
98
|
+
mocker.patch('mojentic.llm.gateways.openai_messages_adapter.encode_base64',
|
|
99
99
|
return_value='ZmFrZV9pbWFnZV9kYXRhX2VuY29kZWQ=')
|
|
100
|
-
mocker.patch('mojentic.llm.gateways.openai_messages_adapter.get_image_type',
|
|
100
|
+
mocker.patch('mojentic.llm.gateways.openai_messages_adapter.get_image_type',
|
|
101
101
|
side_effect=lambda path: 'jpg' if path.endswith('.jpg') else 'png')
|
|
102
102
|
|
|
103
103
|
image_paths = ["/path/to/image1.jpg", "/path/to/image2.png"]
|
|
@@ -6,13 +6,13 @@ their specific parameter requirements and capabilities.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from enum import Enum
|
|
9
|
-
from typing import Dict,
|
|
9
|
+
from typing import Dict, Optional, List, TYPE_CHECKING
|
|
10
10
|
from dataclasses import dataclass
|
|
11
11
|
|
|
12
12
|
import structlog
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
|
-
|
|
15
|
+
pass
|
|
16
16
|
|
|
17
17
|
logger = structlog.get_logger()
|
|
18
18
|
|
|
@@ -20,9 +20,9 @@ logger = structlog.get_logger()
|
|
|
20
20
|
class ModelType(Enum):
|
|
21
21
|
"""Classification of OpenAI model types based on their capabilities and parameters."""
|
|
22
22
|
REASONING = "reasoning" # Models like o1, o3 that use max_completion_tokens
|
|
23
|
-
CHAT = "chat"
|
|
24
|
-
EMBEDDING = "embedding"
|
|
25
|
-
MODERATION = "moderation"
|
|
23
|
+
CHAT = "chat" # Standard chat models that use max_tokens
|
|
24
|
+
EMBEDDING = "embedding" # Text embedding models
|
|
25
|
+
MODERATION = "moderation" # Content moderation models
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
@dataclass
|
|
@@ -346,6 +346,7 @@ class OpenAIModelRegistry:
|
|
|
346
346
|
# Global registry instance
|
|
347
347
|
_registry = OpenAIModelRegistry()
|
|
348
348
|
|
|
349
|
+
|
|
349
350
|
def get_model_registry() -> OpenAIModelRegistry:
|
|
350
351
|
"""Get the global OpenAI model registry instance."""
|
|
351
|
-
return _registry
|
|
352
|
+
return _registry
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Tests for the OpenAI Model Registry system.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
import pytest
|
|
6
5
|
from mojentic.llm.gateways.openai_model_registry import (
|
|
7
6
|
OpenAIModelRegistry,
|
|
8
7
|
ModelType,
|
|
@@ -178,4 +177,4 @@ class DescribeOpenAIModelRegistry:
|
|
|
178
177
|
assert ModelType.REASONING.value == "reasoning"
|
|
179
178
|
assert ModelType.CHAT.value == "chat"
|
|
180
179
|
assert ModelType.EMBEDDING.value == "embedding"
|
|
181
|
-
assert ModelType.MODERATION.value == "moderation"
|
|
180
|
+
assert ModelType.MODERATION.value == "moderation"
|
|
@@ -3,7 +3,7 @@ from unittest.mock import MagicMock
|
|
|
3
3
|
|
|
4
4
|
from mojentic.llm.gateways.openai import OpenAIGateway
|
|
5
5
|
from mojentic.llm.gateways.openai_model_registry import get_model_registry
|
|
6
|
-
from mojentic.llm.gateways.models import LLMMessage, MessageRole
|
|
6
|
+
from mojentic.llm.gateways.models import LLMMessage, MessageRole
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
@pytest.fixture
|
|
@@ -242,4 +242,4 @@ class DescribeModelCapabilitiesTemperatureRestrictions:
|
|
|
242
242
|
capabilities = registry.get_model_capabilities(model)
|
|
243
243
|
assert capabilities.supports_temperature(1.0) is True
|
|
244
244
|
assert capabilities.supports_temperature(0.1) is False
|
|
245
|
-
assert capabilities.supported_temperatures == [1.0]
|
|
245
|
+
assert capabilities.supported_temperatures == [1.0]
|
mojentic/llm/llm_broker.py
CHANGED
|
@@ -7,7 +7,7 @@ from pydantic import BaseModel
|
|
|
7
7
|
|
|
8
8
|
from mojentic.llm.gateways.llm_gateway import LLMGateway
|
|
9
9
|
from mojentic.llm.gateways.models import MessageRole, LLMMessage, LLMGatewayResponse, LLMToolCall
|
|
10
|
-
from mojentic.llm.gateways.ollama import OllamaGateway
|
|
10
|
+
from mojentic.llm.gateways.ollama import OllamaGateway
|
|
11
11
|
from mojentic.llm.gateways.tokenizer_gateway import TokenizerGateway
|
|
12
12
|
from mojentic.tracer.tracer_system import TracerSystem
|
|
13
13
|
|
|
@@ -183,8 +183,8 @@ class LLMBroker():
|
|
|
183
183
|
return result.content
|
|
184
184
|
|
|
185
185
|
def generate_stream(self, messages: List[LLMMessage], tools=None, temperature=1.0, num_ctx=32768,
|
|
186
|
-
|
|
187
|
-
|
|
186
|
+
num_predict=-1, max_tokens=16384,
|
|
187
|
+
correlation_id: str = None) -> Iterator[str]:
|
|
188
188
|
"""
|
|
189
189
|
Generate a streaming text response from the LLM.
|
|
190
190
|
|
|
@@ -334,8 +334,10 @@ class LLMBroker():
|
|
|
334
334
|
tool_calls=[tool_call]))
|
|
335
335
|
|
|
336
336
|
# Recursively stream the response after tool execution
|
|
337
|
-
yield from self.generate_stream(
|
|
338
|
-
|
|
337
|
+
yield from self.generate_stream(
|
|
338
|
+
messages, tools, temperature, num_ctx, num_predict,
|
|
339
|
+
max_tokens, correlation_id=correlation_id
|
|
340
|
+
)
|
|
339
341
|
return # Exit after recursive call
|
|
340
342
|
else:
|
|
341
343
|
logger.warn('Function not found', function=tool_name)
|
mojentic/llm/llm_broker_spec.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from unittest.mock import MagicMock
|
|
2
1
|
|
|
3
2
|
import pytest
|
|
4
3
|
from pydantic import BaseModel
|
|
@@ -11,10 +10,12 @@ class SimpleModel(BaseModel):
|
|
|
11
10
|
text: str
|
|
12
11
|
number: int
|
|
13
12
|
|
|
13
|
+
|
|
14
14
|
class NestedModel(BaseModel):
|
|
15
15
|
title: str
|
|
16
16
|
details: SimpleModel
|
|
17
17
|
|
|
18
|
+
|
|
18
19
|
class ComplexModel(BaseModel):
|
|
19
20
|
name: str
|
|
20
21
|
items: list[SimpleModel]
|
|
@@ -120,7 +121,11 @@ class DescribeLLMBroker:
|
|
|
120
121
|
metadata={"key1": "value1", "key2": "value2"}
|
|
121
122
|
)
|
|
122
123
|
mock_gateway.complete.return_value = LLMGatewayResponse(
|
|
123
|
-
content=
|
|
124
|
+
content=(
|
|
125
|
+
'{"name": "test", "items": [{"text": "item1", "number": 1}, '
|
|
126
|
+
'{"text": "item2", "number": 2}], '
|
|
127
|
+
'"metadata": {"key1": "value1", "key2": "value2"}}'
|
|
128
|
+
),
|
|
124
129
|
object=mock_object,
|
|
125
130
|
tool_calls=[]
|
|
126
131
|
)
|
|
@@ -12,7 +12,7 @@ class FileTypeSensor:
|
|
|
12
12
|
"""
|
|
13
13
|
Initialize the TypeSensor with a default mapping of file extensions to language declarations.
|
|
14
14
|
|
|
15
|
-
The TypeSensor is used to determine the appropriate language syntax highlighting
|
|
15
|
+
The TypeSensor is used to determine the appropriate language syntax highlighting
|
|
16
16
|
for code blocks in markdown based on file extensions.
|
|
17
17
|
"""
|
|
18
18
|
self.extension_map: Dict[str, str] = {
|
|
@@ -129,7 +129,6 @@ class MessageBuilder():
|
|
|
129
129
|
f"{content.strip()}\n"
|
|
130
130
|
f"```\n")
|
|
131
131
|
|
|
132
|
-
|
|
133
132
|
def add_image(self, image_path: Union[str, Path]) -> "MessageBuilder":
|
|
134
133
|
"""
|
|
135
134
|
Add a single image to the message.
|
|
@@ -253,7 +252,11 @@ class MessageBuilder():
|
|
|
253
252
|
|
|
254
253
|
return self
|
|
255
254
|
|
|
256
|
-
def load_content(
|
|
255
|
+
def load_content(
|
|
256
|
+
self,
|
|
257
|
+
file_path: Union[str, Path],
|
|
258
|
+
template_values: Optional[Dict[str, Union[str, Path]]] = None
|
|
259
|
+
) -> "MessageBuilder":
|
|
257
260
|
"""
|
|
258
261
|
Load content from a file into the content field of the MessageBuilder.
|
|
259
262
|
|
|
@@ -14,10 +14,12 @@ def file_gateway(mocker):
|
|
|
14
14
|
file_gateway.is_binary.return_value = False
|
|
15
15
|
return file_gateway
|
|
16
16
|
|
|
17
|
+
|
|
17
18
|
@pytest.fixture
|
|
18
19
|
def file_path():
|
|
19
20
|
return Path("/path/to/file.txt")
|
|
20
21
|
|
|
22
|
+
|
|
21
23
|
@pytest.fixture
|
|
22
24
|
def whitespace_file_content():
|
|
23
25
|
return "\n\n \n test file content with whitespace \n\n \n"
|
|
@@ -122,7 +124,9 @@ class DescribeMessageBuilder:
|
|
|
122
124
|
assert "test file content" in result
|
|
123
125
|
assert "```" in result
|
|
124
126
|
|
|
125
|
-
def should_strip_whitespace_from_file_content(
|
|
127
|
+
def should_strip_whitespace_from_file_content(
|
|
128
|
+
self, message_builder, file_gateway, file_path, whitespace_file_content, mocker
|
|
129
|
+
):
|
|
126
130
|
# Use the fixtures instead of creating file path and content directly
|
|
127
131
|
file_gateway.read.return_value = whitespace_file_content
|
|
128
132
|
mocker.patch.object(message_builder.type_sensor, 'get_language', return_value='text')
|
mojentic/llm/tools/__init__.py
CHANGED
|
@@ -3,16 +3,7 @@ Mojentic LLM tools module for extending LLM capabilities.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
# Base tool class
|
|
6
|
-
from .llm_tool import LLMTool
|
|
7
|
-
from .tool_wrapper import ToolWrapper
|
|
8
6
|
|
|
9
7
|
# 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
8
|
|
|
16
9
|
# Import tool modules
|
|
17
|
-
from . import file_manager
|
|
18
|
-
from . import ephemeral_task_manager
|