mojentic 0.7.3__py3-none-any.whl → 0.8.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.
@@ -14,7 +14,7 @@ from mojentic.llm.gateways.models import LLMMessage
14
14
  from mojentic.llm.tools.date_resolver import ResolveDateTool
15
15
 
16
16
 
17
- def openai_llm(model="gpt-4o"):
17
+ def openai_llm(model="gpt-5"):
18
18
  api_key = os.getenv("OPENAI_API_KEY")
19
19
  gateway = OpenAIGateway(api_key)
20
20
  llm = LLMBroker(model=model, gateway=gateway)
@@ -60,7 +60,26 @@ check_structured_output(openai_llm(model="o4-mini"))
60
60
  check_tool_use(openai_llm(model="o4-mini"))
61
61
  check_image_analysis(openai_llm(model="gpt-4o"))
62
62
 
63
- check_simple_textgen(ollama_llm())
64
- check_structured_output(ollama_llm())
63
+ # check_simple_textgen(ollama_llm())
64
+ # check_structured_output(ollama_llm())
65
65
  check_tool_use(ollama_llm(model="qwen3:32b"))
66
66
  check_image_analysis(ollama_llm(model="gemma3:27b"))
67
+
68
+ # Test all GPT-5 model variants to confirm they're all reasoning models
69
+ print("\n=== Testing GPT-5 Model Variants ===")
70
+ gpt5_models = [
71
+ "gpt-5",
72
+ "gpt-5-2025-08-07",
73
+ "gpt-5-chat-latest",
74
+ "gpt-5-mini",
75
+ "gpt-5-mini-2025-08-07",
76
+ "gpt-5-nano",
77
+ "gpt-5-nano-2025-08-07"
78
+ ]
79
+
80
+ for model in gpt5_models:
81
+ print(f"\n--- Testing {model} ---")
82
+ try:
83
+ check_simple_textgen(openai_llm(model=model))
84
+ except Exception as e:
85
+ print(f"Error with {model}: {e}")
@@ -0,0 +1,104 @@
1
+ """
2
+ Script to fetch current OpenAI models and update the registry with up-to-date model lists.
3
+ """
4
+
5
+ import os
6
+ from mojentic.llm.gateways.openai import OpenAIGateway
7
+
8
+ def fetch_current_openai_models():
9
+ """Fetch the current list of OpenAI models."""
10
+ api_key = os.getenv("OPENAI_API_KEY")
11
+ if not api_key:
12
+ print("ERROR: OPENAI_API_KEY environment variable not set")
13
+ return None
14
+
15
+ try:
16
+ gateway = OpenAIGateway(api_key)
17
+ models = gateway.get_available_models()
18
+ return models
19
+ except Exception as e:
20
+ print(f"ERROR: Failed to fetch models from OpenAI API: {e}")
21
+ return None
22
+
23
+ def categorize_models(models):
24
+ """Categorize models by type based on naming patterns."""
25
+ reasoning_models = []
26
+ chat_models = []
27
+ embedding_models = []
28
+ other_models = []
29
+
30
+ for model in models:
31
+ model_lower = model.lower()
32
+
33
+ # Reasoning models: o1, o3, o4, and gpt-5 series
34
+ if (any(pattern in model_lower for pattern in ['o1-', 'o3-', 'o4-', 'gpt-5']) or
35
+ model_lower in ['o1', 'o3', 'o4', 'gpt-5']):
36
+ reasoning_models.append(model)
37
+ elif 'embedding' in model_lower:
38
+ embedding_models.append(model)
39
+ elif any(pattern in model_lower for pattern in ['gpt-4', 'gpt-3.5']):
40
+ chat_models.append(model)
41
+ else:
42
+ other_models.append(model)
43
+
44
+ return {
45
+ 'reasoning': sorted(reasoning_models),
46
+ 'chat': sorted(chat_models),
47
+ 'embedding': sorted(embedding_models),
48
+ 'other': sorted(other_models)
49
+ }
50
+
51
+ def print_model_lists(categorized_models):
52
+ """Print the categorized models in a format ready for the registry."""
53
+ print("=== Current OpenAI Models ===\n")
54
+
55
+ print("# Reasoning Models (o1, o3, o4, gpt-5 series)")
56
+ print("reasoning_models = [")
57
+ for model in categorized_models['reasoning']:
58
+ print(f' "{model}",')
59
+ print("]\n")
60
+
61
+ print("# Chat Models (GPT-4 and GPT-4.1 series)")
62
+ print("gpt4_and_newer_models = [")
63
+ gpt4_and_newer = [m for m in categorized_models['chat'] if 'gpt-4' in m.lower()]
64
+ for model in gpt4_and_newer:
65
+ print(f' "{model}",')
66
+ print("]\n")
67
+
68
+ print("# Chat Models (GPT-3.5 series)")
69
+ print("gpt35_models = [")
70
+ gpt35 = [m for m in categorized_models['chat'] if 'gpt-3.5' in m.lower()]
71
+ for model in gpt35:
72
+ print(f' "{model}",')
73
+ print("]\n")
74
+
75
+ print("# Embedding Models")
76
+ print("embedding_models = [")
77
+ for model in categorized_models['embedding']:
78
+ print(f' "{model}",')
79
+ print("]\n")
80
+
81
+ print("# Other Models (for reference)")
82
+ print("# other_models = [")
83
+ for model in categorized_models['other']:
84
+ print(f'# "{model}",')
85
+ print("# ]\n")
86
+
87
+ if __name__ == "__main__":
88
+ print("Fetching current OpenAI models...")
89
+ models = fetch_current_openai_models()
90
+
91
+ if models:
92
+ print(f"Found {len(models)} models\n")
93
+ categorized = categorize_models(models)
94
+ print_model_lists(categorized)
95
+
96
+ print("\n=== Summary ===")
97
+ print(f"Reasoning models: {len(categorized['reasoning'])}")
98
+ print(f"Chat models: {len(categorized['chat'])}")
99
+ print(f"Embedding models: {len(categorized['embedding'])}")
100
+ print(f"Other models: {len(categorized['other'])}")
101
+
102
+ print("\nCopy the model lists above and update the _initialize_default_models() method in openai_model_registry.py")
103
+ else:
104
+ print("Failed to fetch models. Please check your API key and try again.")
@@ -0,0 +1,73 @@
1
+ import os
2
+ from mojentic.llm.gateways.openai import OpenAIGateway
3
+ from mojentic.llm.gateways.models import LLMMessage, MessageRole
4
+
5
+ def check_model_characterization():
6
+ """
7
+ Test the model characterization functionality with different OpenAI models.
8
+ This demonstrates how the gateway adapts parameters based on model type.
9
+ """
10
+ api_key = os.getenv("OPENAI_API_KEY")
11
+ if not api_key:
12
+ print("OPENAI_API_KEY environment variable not set. Skipping actual API calls.")
13
+ return
14
+
15
+ gateway = OpenAIGateway(api_key)
16
+
17
+ # Test messages for chat models
18
+ chat_messages = [
19
+ LLMMessage(role=MessageRole.System, content="You are a helpful assistant."),
20
+ LLMMessage(role=MessageRole.User, content="What is 2 + 2? Give a brief answer.")
21
+ ]
22
+
23
+ # Test messages for reasoning models (no system message supported)
24
+ reasoning_messages = [
25
+ LLMMessage(role=MessageRole.User, content="What is 2 + 2? Give a brief answer.")
26
+ ]
27
+
28
+ # Test with different model types
29
+ test_models = [
30
+ ("gpt-4o", "chat model"),
31
+ ("gpt-4o-mini", "chat model"),
32
+ ("o1-mini", "reasoning model"),
33
+ ("o1-preview", "reasoning model")
34
+ ]
35
+
36
+ print("Testing model characterization and parameter adaptation:")
37
+ print("=" * 60)
38
+
39
+ for model, model_type in test_models:
40
+ print(f"\nTesting {model} ({model_type}):")
41
+
42
+ # Test model classification
43
+ is_reasoning = gateway._is_reasoning_model(model)
44
+ print(f" Classified as reasoning model: {is_reasoning}")
45
+
46
+ # Use appropriate messages based on model type
47
+ messages = reasoning_messages if gateway._is_reasoning_model(model) else chat_messages
48
+
49
+ # Test parameter adaptation
50
+ original_args = {
51
+ 'model': model,
52
+ 'messages': messages,
53
+ 'max_tokens': 100
54
+ }
55
+
56
+ adapted_args = gateway._adapt_parameters_for_model(model, original_args)
57
+
58
+ if 'max_tokens' in adapted_args:
59
+ print(f" Using parameter: max_tokens = {adapted_args['max_tokens']}")
60
+ elif 'max_completion_tokens' in adapted_args:
61
+ print(f" Using parameter: max_completion_tokens = {adapted_args['max_completion_tokens']}")
62
+
63
+ try:
64
+ response = gateway.complete(**adapted_args)
65
+ print(f" Response: {response.content[:50]}...")
66
+ except Exception as e:
67
+ print(f" Error: {str(e)}")
68
+
69
+ print("\n" + "=" * 60)
70
+ print("Model characterization test completed!")
71
+
72
+ if __name__ == "__main__":
73
+ check_model_characterization()
@@ -0,0 +1,140 @@
1
+ """
2
+ Demonstration of the enhanced OpenAI gateway with model registry system.
3
+
4
+ This script shows how the new infrastructure automatically handles parameter adaptation
5
+ for reasoning models vs chat models, provides detailed logging, and offers better
6
+ error handling.
7
+ """
8
+
9
+ import os
10
+ from mojentic.llm.gateways.openai import OpenAIGateway
11
+ from mojentic.llm.gateways.openai_model_registry import get_model_registry
12
+ from mojentic.llm.gateways.models import LLMMessage, MessageRole
13
+
14
+ def demonstrate_model_registry():
15
+ """Demonstrate the model registry capabilities."""
16
+ print("=== Model Registry Demonstration ===")
17
+
18
+ registry = get_model_registry()
19
+
20
+ print("\n1. Registry contains default models:")
21
+ registered_models = registry.get_registered_models()
22
+ reasoning_models = [m for m in registered_models if registry.is_reasoning_model(m)]
23
+ chat_models = [m for m in registered_models if not registry.is_reasoning_model(m) and not m.startswith('text-')]
24
+
25
+ print(f" Reasoning models: {reasoning_models[:3]}...") # Show first 3
26
+ print(f" Chat models: {chat_models[:3]}...") # Show first 3
27
+
28
+ print("\n2. Model capability detection:")
29
+ for model in ["o1-mini", "gpt-4o"]:
30
+ capabilities = registry.get_model_capabilities(model)
31
+ token_param = capabilities.get_token_limit_param()
32
+ print(f" {model}: type={capabilities.model_type.value}, token_param={token_param}")
33
+
34
+ # Handle unknown model separately to show the warning works
35
+ print("\n3. Unknown model handling:")
36
+ print(" unknown-future-model: (will default to chat model with warning)")
37
+ capabilities = registry.get_model_capabilities("unknown-future-model")
38
+ token_param = capabilities.get_token_limit_param()
39
+ print(f" → Defaulted to: type={capabilities.model_type.value}, token_param={token_param}")
40
+
41
+ def demonstrate_parameter_adaptation():
42
+ """Demonstrate parameter adaptation for different model types."""
43
+ print("\n=== Parameter Adaptation Demonstration ===")
44
+
45
+ # This would normally require an API key, but we're just showing the adaptation logic
46
+ gateway = OpenAIGateway("fake-key-for-demo")
47
+
48
+ print("\n1. Reasoning model parameter adaptation (o1-mini):")
49
+ original_args = {
50
+ 'model': 'o1-mini',
51
+ 'messages': [LLMMessage(role=MessageRole.User, content="Hello")],
52
+ 'max_tokens': 1000,
53
+ 'tools': [] # Tools will be removed for reasoning models
54
+ }
55
+
56
+ adapted_args = gateway._adapt_parameters_for_model('o1-mini', original_args)
57
+ print(f" Original: max_tokens={original_args.get('max_tokens')}, has_tools={'tools' in original_args}")
58
+ print(f" Adapted: max_completion_tokens={adapted_args.get('max_completion_tokens')}, has_tools={'tools' in adapted_args}")
59
+
60
+ print("\n2. Chat model parameter adaptation (gpt-4o):")
61
+ original_args = {
62
+ 'model': 'gpt-4o',
63
+ 'messages': [LLMMessage(role=MessageRole.User, content="Hello")],
64
+ 'max_tokens': 1000,
65
+ 'tools': []
66
+ }
67
+
68
+ adapted_args = gateway._adapt_parameters_for_model('gpt-4o', original_args)
69
+ print(f" Original: max_tokens={original_args.get('max_tokens')}, has_tools={'tools' in original_args}")
70
+ print(f" Adapted: max_tokens={adapted_args.get('max_tokens')}, has_tools={'tools' in adapted_args}")
71
+
72
+ def demonstrate_model_validation():
73
+ """Demonstrate model parameter validation."""
74
+ print("\n=== Model Validation Demonstration ===")
75
+
76
+ gateway = OpenAIGateway("fake-key-for-demo")
77
+
78
+ print("\n1. Validating parameters for reasoning model:")
79
+ args = {
80
+ 'model': 'o1-mini',
81
+ 'messages': [LLMMessage(role=MessageRole.User, content="Hello")],
82
+ 'max_tokens': 50000, # High token count - will show warning
83
+ 'tools': [] # Tools for reasoning model - will show warning
84
+ }
85
+
86
+ try:
87
+ gateway._validate_model_parameters('o1-mini', args)
88
+ print(" Validation completed (check logs above for warnings)")
89
+ except Exception as e:
90
+ print(f" Validation error: {e}")
91
+
92
+ def demonstrate_registry_extensibility():
93
+ """Demonstrate how to extend the registry with new models."""
94
+ print("\n=== Registry Extensibility Demonstration ===")
95
+
96
+ registry = get_model_registry()
97
+
98
+ print("\n1. Adding a new model to the registry:")
99
+ from mojentic.llm.gateways.openai_model_registry import ModelCapabilities, ModelType
100
+
101
+ new_capabilities = ModelCapabilities(
102
+ model_type=ModelType.REASONING,
103
+ supports_tools=True, # Hypothetical future reasoning model with tools
104
+ supports_streaming=True,
105
+ max_output_tokens=100000
106
+ )
107
+
108
+ registry.register_model("o5-preview", new_capabilities)
109
+ print(f" Registered o5-preview as reasoning model")
110
+
111
+ # Test the new model
112
+ capabilities = registry.get_model_capabilities("o5-preview")
113
+ print(f" o5-preview: type={capabilities.model_type.value}, supports_tools={capabilities.supports_tools}")
114
+
115
+ print("\n2. Adding a new pattern for model detection:")
116
+ registry.register_pattern("claude", ModelType.CHAT)
117
+ print(" Registered 'claude' pattern for chat models")
118
+
119
+ # Test pattern matching
120
+ capabilities = registry.get_model_capabilities("claude-3-opus")
121
+ print(f" claude-3-opus (inferred): type={capabilities.model_type.value}")
122
+
123
+ if __name__ == "__main__":
124
+ print("OpenAI Gateway Enhanced Infrastructure Demo")
125
+ print("=" * 50)
126
+
127
+ demonstrate_model_registry()
128
+ demonstrate_parameter_adaptation()
129
+ demonstrate_model_validation()
130
+ demonstrate_registry_extensibility()
131
+
132
+ print("\n" + "=" * 50)
133
+ print("Demo completed!")
134
+ print("\nKey Benefits of the New Infrastructure:")
135
+ print("✓ Registry-based model management (easy to extend)")
136
+ print("✓ Automatic parameter adaptation (max_tokens ↔ max_completion_tokens)")
137
+ print("✓ Enhanced logging for debugging")
138
+ print("✓ Parameter validation with helpful warnings")
139
+ print("✓ Pattern matching for unknown models")
140
+ print("✓ Comprehensive test coverage")
@@ -1,15 +1,18 @@
1
1
  import json
2
2
  from itertools import islice
3
- from typing import Type, List, Iterable
3
+ from typing import Type, List, Iterable, Optional
4
4
 
5
5
  import numpy as np
6
6
  import structlog
7
- from openai import OpenAI
7
+ from openai import OpenAI, BadRequestError
8
+ from pydantic import BaseModel
8
9
 
9
10
  from mojentic.llm.gateways.llm_gateway import LLMGateway
10
- from mojentic.llm.gateways.models import LLMToolCall, LLMGatewayResponse
11
+ from mojentic.llm.gateways.models import LLMToolCall, LLMGatewayResponse, LLMMessage
11
12
  from mojentic.llm.gateways.openai_messages_adapter import adapt_messages_to_openai
13
+ from mojentic.llm.gateways.openai_model_registry import get_model_registry, ModelType
12
14
  from mojentic.llm.gateways.tokenizer_gateway import TokenizerGateway
15
+ from mojentic.llm.tools.llm_tool import LLMTool
13
16
 
14
17
  logger = structlog.get_logger()
15
18
 
@@ -24,20 +27,117 @@ class OpenAIGateway(LLMGateway):
24
27
  The OpenAI API key to use.
25
28
  """
26
29
 
27
- def __init__(self, api_key: str, base_url: str = None):
30
+ def __init__(self, api_key: str, base_url: Optional[str] = None):
28
31
  self.client = OpenAI(api_key=api_key, base_url=base_url)
32
+ self.model_registry = get_model_registry()
29
33
 
30
- def complete(self, **args) -> LLMGatewayResponse:
34
+ def _is_reasoning_model(self, model: str) -> bool:
35
+ """
36
+ Determine if a model is a reasoning model that requires max_completion_tokens.
37
+
38
+ Parameters
39
+ ----------
40
+ model : str
41
+ The model name to classify.
42
+
43
+ Returns
44
+ -------
45
+ bool
46
+ True if the model is a reasoning model, False if it's a chat model.
47
+ """
48
+ return self.model_registry.is_reasoning_model(model)
49
+
50
+ def _adapt_parameters_for_model(self, model: str, args: dict) -> dict:
51
+ """
52
+ Adapt parameters based on the model type and capabilities.
53
+
54
+ Parameters
55
+ ----------
56
+ model : str
57
+ The model name.
58
+ args : dict
59
+ The original arguments.
60
+
61
+ Returns
62
+ -------
63
+ dict
64
+ The adapted arguments with correct parameter names for the model type.
65
+ """
66
+ adapted_args = args.copy()
67
+ capabilities = self.model_registry.get_model_capabilities(model)
68
+
69
+ logger.debug("Adapting parameters for model",
70
+ model=model,
71
+ model_type=capabilities.model_type.value,
72
+ supports_tools=capabilities.supports_tools,
73
+ supports_streaming=capabilities.supports_streaming)
74
+
75
+ # Handle token limit parameter conversion
76
+ if 'max_tokens' in adapted_args:
77
+ token_param = capabilities.get_token_limit_param()
78
+ if token_param != 'max_tokens':
79
+ # Convert max_tokens to max_completion_tokens for reasoning models
80
+ adapted_args[token_param] = adapted_args.pop('max_tokens')
81
+ logger.info("Converted token limit parameter for model",
82
+ model=model,
83
+ from_param='max_tokens',
84
+ to_param=token_param,
85
+ value=adapted_args[token_param])
86
+
87
+ # Validate tool usage for models that don't support tools
88
+ if 'tools' in adapted_args and adapted_args['tools'] and not capabilities.supports_tools:
89
+ logger.warning("Model does not support tools, removing tool configuration",
90
+ model=model,
91
+ num_tools=len(adapted_args['tools']))
92
+ adapted_args['tools'] = None # Set to None instead of removing the key
93
+
94
+ return adapted_args
95
+
96
+ def _validate_model_parameters(self, model: str, args: dict) -> None:
97
+ """
98
+ Validate that the parameters are compatible with the model.
99
+
100
+ Parameters
101
+ ----------
102
+ model : str
103
+ The model name.
104
+ args : dict
105
+ The arguments to validate.
106
+ """
107
+ capabilities = self.model_registry.get_model_capabilities(model)
108
+
109
+ # Warning for tools on reasoning models that don't support them
110
+ if (capabilities.model_type == ModelType.REASONING and
111
+ not capabilities.supports_tools and
112
+ 'tools' in args and args['tools']):
113
+ logger.warning(
114
+ "Reasoning model may not support tools",
115
+ model=model,
116
+ num_tools=len(args['tools'])
117
+ )
118
+
119
+ # Validate token limits (check both possible parameter names)
120
+ token_value = args.get('max_tokens') or args.get('max_completion_tokens')
121
+ if token_value and capabilities.max_output_tokens:
122
+ if token_value > capabilities.max_output_tokens:
123
+ logger.warning(
124
+ "Requested token limit exceeds model maximum",
125
+ model=model,
126
+ requested=token_value,
127
+ max_allowed=capabilities.max_output_tokens
128
+ )
129
+
130
+ def complete(self, **kwargs) -> LLMGatewayResponse:
31
131
  """
32
132
  Complete the LLM request by delegating to the OpenAI service.
33
133
 
34
134
  Keyword Arguments
35
135
  ----------------
36
136
  model : str
37
- The name of the model to use, as appears in `ollama list`.
137
+ The name of the model to use.
38
138
  messages : List[LLMMessage]
39
139
  A list of messages to send to the LLM.
40
- object_model : Optional[BaseModel]
140
+ object_model : Optional[Type[BaseModel]]
41
141
  The model to use for validating the response.
42
142
  tools : Optional[List[LLMTool]]
43
143
  A list of tools to use with the LLM. If a tool call is requested, the tool will be called and the output
@@ -56,35 +156,105 @@ class OpenAIGateway(LLMGateway):
56
156
  LLMGatewayResponse
57
157
  The response from the OpenAI service.
58
158
  """
159
+ # Extract parameters from kwargs with defaults
160
+ model = kwargs.get('model')
161
+ messages = kwargs.get('messages')
162
+ object_model = kwargs.get('object_model', None)
163
+ tools = kwargs.get('tools', None)
164
+ temperature = kwargs.get('temperature', 1.0)
165
+ num_ctx = kwargs.get('num_ctx', 32768)
166
+ max_tokens = kwargs.get('max_tokens', 16384)
167
+ num_predict = kwargs.get('num_predict', -1)
168
+
169
+ if not model:
170
+ raise ValueError("'model' parameter is required")
171
+ if not messages:
172
+ raise ValueError("'messages' parameter is required")
173
+
174
+ # Convert parameters to dict for processing
175
+ args = {
176
+ 'model': model,
177
+ 'messages': messages,
178
+ 'object_model': object_model,
179
+ 'tools': tools,
180
+ 'temperature': temperature,
181
+ 'num_ctx': num_ctx,
182
+ 'max_tokens': max_tokens,
183
+ 'num_predict': num_predict
184
+ }
185
+
186
+ # Adapt parameters based on model type
187
+ try:
188
+ adapted_args = self._adapt_parameters_for_model(model, args)
189
+ except Exception as e:
190
+ logger.error("Failed to adapt parameters for model",
191
+ model=model,
192
+ error=str(e))
193
+ raise
194
+
195
+ # Validate parameters after adaptation
196
+ self._validate_model_parameters(model, adapted_args)
197
+
59
198
  openai_args = {
60
- 'model': args['model'],
61
- 'messages': adapt_messages_to_openai(args['messages']),
199
+ 'model': adapted_args['model'],
200
+ 'messages': adapt_messages_to_openai(adapted_args['messages']),
62
201
  }
63
202
 
203
+ # Add temperature if specified
204
+ if 'temperature' in adapted_args:
205
+ openai_args['temperature'] = adapted_args['temperature']
206
+
64
207
  completion = self.client.chat.completions.create
65
208
 
66
- if 'object_model' in args and args['object_model'] is not None:
209
+ if adapted_args['object_model'] is not None:
67
210
  completion = self.client.beta.chat.completions.parse
68
- openai_args['response_format'] = args['object_model']
211
+ openai_args['response_format'] = adapted_args['object_model']
212
+
213
+ if adapted_args.get('tools') is not None:
214
+ openai_args['tools'] = [t.descriptor for t in adapted_args['tools']]
69
215
 
70
- if 'tools' in args and args['tools'] is not None:
71
- openai_args['tools'] = [t.descriptor for t in args['tools']]
216
+ # Handle both max_tokens (for chat models) and max_completion_tokens (for reasoning models)
217
+ if 'max_tokens' in adapted_args:
218
+ openai_args['max_tokens'] = adapted_args['max_tokens']
219
+ elif 'max_completion_tokens' in adapted_args:
220
+ openai_args['max_completion_tokens'] = adapted_args['max_completion_tokens']
72
221
 
73
- if 'max_tokens' in args:
74
- openai_args['max_tokens'] = args['max_tokens']
222
+ logger.debug("Making OpenAI API call",
223
+ model=openai_args['model'],
224
+ has_tools='tools' in openai_args,
225
+ has_object_model='response_format' in openai_args,
226
+ token_param='max_completion_tokens' if 'max_completion_tokens' in openai_args else 'max_tokens')
75
227
 
76
- response = completion(**openai_args)
228
+ try:
229
+ response = completion(**openai_args)
230
+ except BadRequestError as e:
231
+ # Enhanced error handling for parameter issues
232
+ if "max_tokens" in str(e) and "max_completion_tokens" in str(e):
233
+ logger.error("Parameter error detected - model may require different token parameter",
234
+ model=model,
235
+ error=str(e),
236
+ suggestion="This model may be a reasoning model requiring max_completion_tokens")
237
+ raise e
238
+ except Exception as e:
239
+ logger.error("OpenAI API call failed",
240
+ model=model,
241
+ error=str(e))
242
+ raise e
77
243
 
78
244
  object = None
79
245
  tool_calls: List[LLMToolCall] = []
80
246
 
81
- if 'object_model' in args and args['object_model'] is not None:
247
+ if adapted_args.get('object_model') is not None:
82
248
  try:
83
249
  response_content = response.choices[0].message.content
84
- object = args['object_model'].model_validate_json(response_content)
250
+ if response_content is not None:
251
+ object = adapted_args['object_model'].model_validate_json(response_content)
252
+ else:
253
+ logger.error("No response content available for object validation", object_model=adapted_args['object_model'])
85
254
  except Exception as e:
255
+ response_content = response.choices[0].message.content if response.choices else "No response content"
86
256
  logger.error("Failed to validate model", error=str(e), response=response_content,
87
- object_model=args['object_model'])
257
+ object_model=adapted_args['object_model'])
88
258
 
89
259
  if response.choices[0].message.tool_calls is not None:
90
260
  for t in response.choices[0].message.tool_calls:
@@ -0,0 +1,327 @@
1
+ """
2
+ OpenAI Model Registry for managing model-specific configurations and capabilities.
3
+
4
+ This module provides infrastructure for categorizing OpenAI models and managing
5
+ their specific parameter requirements and capabilities.
6
+ """
7
+
8
+ from enum import Enum
9
+ from typing import Dict, Set, Optional, List, TYPE_CHECKING
10
+ from dataclasses import dataclass
11
+
12
+ import structlog
13
+
14
+ if TYPE_CHECKING:
15
+ from mojentic.llm.gateways.openai import OpenAIGateway
16
+
17
+ logger = structlog.get_logger()
18
+
19
+
20
+ class ModelType(Enum):
21
+ """Classification of OpenAI model types based on their capabilities and parameters."""
22
+ REASONING = "reasoning" # Models like o1, o3 that use max_completion_tokens
23
+ CHAT = "chat" # Standard chat models that use max_tokens
24
+ EMBEDDING = "embedding" # Text embedding models
25
+ MODERATION = "moderation" # Content moderation models
26
+
27
+
28
+ @dataclass
29
+ class ModelCapabilities:
30
+ """Defines the capabilities and parameter requirements for a model."""
31
+ model_type: ModelType
32
+ supports_tools: bool = True
33
+ supports_streaming: bool = True
34
+ supports_vision: bool = False
35
+ max_context_tokens: Optional[int] = None
36
+ max_output_tokens: Optional[int] = None
37
+
38
+ def get_token_limit_param(self) -> str:
39
+ """Get the correct parameter name for token limits based on model type."""
40
+ if self.model_type == ModelType.REASONING:
41
+ return "max_completion_tokens"
42
+ return "max_tokens"
43
+
44
+
45
+ class OpenAIModelRegistry:
46
+ """
47
+ Registry for managing OpenAI model configurations and capabilities.
48
+
49
+ This class provides a centralized way to manage model-specific configurations,
50
+ parameter mappings, and capabilities for OpenAI models.
51
+ """
52
+
53
+ def __init__(self):
54
+ self._models: Dict[str, ModelCapabilities] = {}
55
+ self._pattern_mappings: Dict[str, ModelType] = {}
56
+ self._initialize_default_models()
57
+
58
+ def _initialize_default_models(self):
59
+ """Initialize the registry with known OpenAI models and their capabilities."""
60
+
61
+ # Reasoning Models (o1, o3, o4, gpt-5 series) - Updated 2025-09-28
62
+ reasoning_models = [
63
+ "o1", "o1-2024-12-17", "o1-mini", "o1-mini-2024-09-12",
64
+ "o1-pro", "o1-pro-2025-03-19",
65
+ "o3", "o3-2025-04-16", "o3-deep-research", "o3-deep-research-2025-06-26",
66
+ "o3-mini", "o3-mini-2025-01-31", "o3-pro", "o3-pro-2025-06-10",
67
+ "o4-mini", "o4-mini-2025-04-16", "o4-mini-deep-research",
68
+ "o4-mini-deep-research-2025-06-26",
69
+ "gpt-5", "gpt-5-2025-08-07", "gpt-5-chat-latest", "gpt-5-codex",
70
+ "gpt-5-mini", "gpt-5-mini-2025-08-07", "gpt-5-nano", "gpt-5-nano-2025-08-07"
71
+ ]
72
+
73
+ for model in reasoning_models:
74
+ # Deep research models and GPT-5 might have different capabilities
75
+ is_deep_research = "deep-research" in model
76
+ is_gpt5 = "gpt-5" in model
77
+ is_mini_or_nano = ("mini" in model or "nano" in model)
78
+
79
+ # GPT-5 models may support more features than o1/o3/o4
80
+ supports_tools = is_gpt5 # GPT-5 might support tools
81
+ supports_streaming = is_gpt5 # GPT-5 might support streaming
82
+
83
+ # Set context and output tokens based on model tier
84
+ if is_gpt5:
85
+ context_tokens = 300000 if not is_mini_or_nano else 200000
86
+ output_tokens = 50000 if not is_mini_or_nano else 32768
87
+ elif is_deep_research:
88
+ context_tokens = 200000
89
+ output_tokens = 100000
90
+ else:
91
+ context_tokens = 128000
92
+ output_tokens = 32768
93
+
94
+ self._models[model] = ModelCapabilities(
95
+ model_type=ModelType.REASONING,
96
+ supports_tools=supports_tools,
97
+ supports_streaming=supports_streaming,
98
+ supports_vision=False, # Vision support would need to be confirmed for GPT-5
99
+ max_context_tokens=context_tokens,
100
+ max_output_tokens=output_tokens
101
+ )
102
+
103
+ # Chat Models (GPT-4 and GPT-4.1 series) - Updated 2025-09-28
104
+ # Note: GPT-5 series moved to reasoning models
105
+ gpt4_and_newer_models = [
106
+ "chatgpt-4o-latest",
107
+ "gpt-4", "gpt-4-0125-preview", "gpt-4-0613", "gpt-4-1106-preview",
108
+ "gpt-4-turbo", "gpt-4-turbo-2024-04-09", "gpt-4-turbo-preview",
109
+ "gpt-4.1", "gpt-4.1-2025-04-14", "gpt-4.1-mini", "gpt-4.1-mini-2025-04-14",
110
+ "gpt-4.1-nano", "gpt-4.1-nano-2025-04-14",
111
+ "gpt-4o", "gpt-4o-2024-05-13", "gpt-4o-2024-08-06", "gpt-4o-2024-11-20",
112
+ "gpt-4o-audio-preview", "gpt-4o-audio-preview-2024-10-01",
113
+ "gpt-4o-audio-preview-2024-12-17", "gpt-4o-audio-preview-2025-06-03",
114
+ "gpt-4o-mini", "gpt-4o-mini-2024-07-18",
115
+ "gpt-4o-mini-audio-preview", "gpt-4o-mini-audio-preview-2024-12-17",
116
+ "gpt-4o-mini-realtime-preview", "gpt-4o-mini-realtime-preview-2024-12-17",
117
+ "gpt-4o-mini-search-preview", "gpt-4o-mini-search-preview-2025-03-11",
118
+ "gpt-4o-mini-transcribe", "gpt-4o-mini-tts",
119
+ "gpt-4o-realtime-preview", "gpt-4o-realtime-preview-2024-10-01",
120
+ "gpt-4o-realtime-preview-2024-12-17", "gpt-4o-realtime-preview-2025-06-03",
121
+ "gpt-4o-search-preview", "gpt-4o-search-preview-2025-03-11",
122
+ "gpt-4o-transcribe"
123
+ ]
124
+
125
+ for model in gpt4_and_newer_models:
126
+ # Determine capabilities based on model features
127
+ vision_support = ("gpt-4o" in model or "audio-preview" in model or "realtime" in model)
128
+ is_mini_or_nano = ("mini" in model or "nano" in model)
129
+ is_audio = "audio" in model or "realtime" in model or "transcribe" in model
130
+ is_gpt41 = "gpt-4.1" in model
131
+
132
+ # Set context and output tokens based on model tier
133
+ if is_gpt41:
134
+ context_tokens = 200000 if not is_mini_or_nano else 128000
135
+ output_tokens = 32768 if not is_mini_or_nano else 16384
136
+ elif "gpt-4o" in model:
137
+ context_tokens = 128000
138
+ output_tokens = 16384
139
+ else: # GPT-4 series
140
+ context_tokens = 32000
141
+ output_tokens = 8192
142
+
143
+ self._models[model] = ModelCapabilities(
144
+ model_type=ModelType.CHAT,
145
+ supports_tools=True,
146
+ supports_streaming=not is_audio, # Audio models may not support streaming
147
+ supports_vision=vision_support,
148
+ max_context_tokens=context_tokens,
149
+ max_output_tokens=output_tokens
150
+ )
151
+
152
+ # Chat Models (GPT-3.5 series) - Updated 2025-09-28
153
+ gpt35_models = [
154
+ "gpt-3.5-turbo", "gpt-3.5-turbo-0125", "gpt-3.5-turbo-1106",
155
+ "gpt-3.5-turbo-16k", "gpt-3.5-turbo-instruct", "gpt-3.5-turbo-instruct-0914"
156
+ ]
157
+
158
+ for model in gpt35_models:
159
+ context_tokens = 16385 if "16k" not in model else 16385
160
+ self._models[model] = ModelCapabilities(
161
+ model_type=ModelType.CHAT,
162
+ supports_tools="instruct" not in model, # Instruct models don't support tools
163
+ supports_streaming="instruct" not in model, # Instruct models don't support streaming
164
+ supports_vision=False,
165
+ max_context_tokens=context_tokens,
166
+ max_output_tokens=4096
167
+ )
168
+
169
+ # Embedding Models - Updated 2025-09-28
170
+ embedding_models = [
171
+ "text-embedding-3-large", "text-embedding-3-small", "text-embedding-ada-002"
172
+ ]
173
+
174
+ for model in embedding_models:
175
+ self._models[model] = ModelCapabilities(
176
+ model_type=ModelType.EMBEDDING,
177
+ supports_tools=False,
178
+ supports_streaming=False,
179
+ supports_vision=False
180
+ )
181
+
182
+ # Pattern mappings for unknown models - Updated 2025-09-28
183
+ self._pattern_mappings = {
184
+ "o1": ModelType.REASONING,
185
+ "o3": ModelType.REASONING,
186
+ "o4": ModelType.REASONING,
187
+ "gpt-5": ModelType.REASONING, # GPT-5 is a reasoning model
188
+ "gpt-4": ModelType.CHAT,
189
+ "gpt-4.1": ModelType.CHAT,
190
+ "gpt-3.5": ModelType.CHAT,
191
+ "chatgpt": ModelType.CHAT,
192
+ "text-embedding": ModelType.EMBEDDING,
193
+ "text-moderation": ModelType.MODERATION
194
+ }
195
+
196
+ def get_model_capabilities(self, model_name: str) -> ModelCapabilities:
197
+ """
198
+ Get the capabilities for a specific model.
199
+
200
+ Parameters
201
+ ----------
202
+ model_name : str
203
+ The name of the model to look up.
204
+
205
+ Returns
206
+ -------
207
+ ModelCapabilities
208
+ The capabilities for the model.
209
+ """
210
+ # Direct lookup first
211
+ if model_name in self._models:
212
+ return self._models[model_name]
213
+
214
+ # Pattern matching for unknown models
215
+ model_lower = model_name.lower()
216
+ for pattern, model_type in self._pattern_mappings.items():
217
+ if pattern in model_lower:
218
+ logger.warning(
219
+ "Using pattern matching for unknown model",
220
+ model=model_name,
221
+ pattern=pattern,
222
+ inferred_type=model_type.value
223
+ )
224
+ # Return default capabilities for the inferred type
225
+ return self._get_default_capabilities_for_type(model_type)
226
+
227
+ # Default to chat model if no pattern matches
228
+ logger.warning(
229
+ "Unknown model, defaulting to chat model capabilities",
230
+ model=model_name
231
+ )
232
+ return self._get_default_capabilities_for_type(ModelType.CHAT)
233
+
234
+ def _get_default_capabilities_for_type(self, model_type: ModelType) -> ModelCapabilities:
235
+ """Get default capabilities for a model type."""
236
+ if model_type == ModelType.REASONING:
237
+ return ModelCapabilities(
238
+ model_type=ModelType.REASONING,
239
+ supports_tools=False,
240
+ supports_streaming=False,
241
+ supports_vision=False
242
+ )
243
+ elif model_type == ModelType.CHAT:
244
+ return ModelCapabilities(
245
+ model_type=ModelType.CHAT,
246
+ supports_tools=True,
247
+ supports_streaming=True,
248
+ supports_vision=False
249
+ )
250
+ elif model_type == ModelType.EMBEDDING:
251
+ return ModelCapabilities(
252
+ model_type=ModelType.EMBEDDING,
253
+ supports_tools=False,
254
+ supports_streaming=False,
255
+ supports_vision=False
256
+ )
257
+ else: # MODERATION
258
+ return ModelCapabilities(
259
+ model_type=ModelType.MODERATION,
260
+ supports_tools=False,
261
+ supports_streaming=False,
262
+ supports_vision=False
263
+ )
264
+
265
+ def is_reasoning_model(self, model_name: str) -> bool:
266
+ """
267
+ Check if a model is a reasoning model.
268
+
269
+ Parameters
270
+ ----------
271
+ model_name : str
272
+ The name of the model to check.
273
+
274
+ Returns
275
+ -------
276
+ bool
277
+ True if the model is a reasoning model, False otherwise.
278
+ """
279
+ capabilities = self.get_model_capabilities(model_name)
280
+ return capabilities.model_type == ModelType.REASONING
281
+
282
+ def get_registered_models(self) -> List[str]:
283
+ """
284
+ Get a list of all explicitly registered models.
285
+
286
+ Returns
287
+ -------
288
+ List[str]
289
+ List of registered model names.
290
+ """
291
+ return list(self._models.keys())
292
+
293
+ def register_model(self, model_name: str, capabilities: ModelCapabilities):
294
+ """
295
+ Register a new model with its capabilities.
296
+
297
+ Parameters
298
+ ----------
299
+ model_name : str
300
+ The name of the model to register.
301
+ capabilities : ModelCapabilities
302
+ The capabilities of the model.
303
+ """
304
+ self._models[model_name] = capabilities
305
+ logger.info("Registered new model", model=model_name, type=capabilities.model_type.value)
306
+
307
+ def register_pattern(self, pattern: str, model_type: ModelType):
308
+ """
309
+ Register a pattern for inferring model types.
310
+
311
+ Parameters
312
+ ----------
313
+ pattern : str
314
+ The pattern to match in model names.
315
+ model_type : ModelType
316
+ The type to infer for matching models.
317
+ """
318
+ self._pattern_mappings[pattern] = model_type
319
+ logger.info("Registered new pattern", pattern=pattern, type=model_type.value)
320
+
321
+
322
+ # Global registry instance
323
+ _registry = OpenAIModelRegistry()
324
+
325
+ def get_model_registry() -> OpenAIModelRegistry:
326
+ """Get the global OpenAI model registry instance."""
327
+ return _registry
@@ -0,0 +1,181 @@
1
+ """
2
+ Tests for the OpenAI Model Registry system.
3
+ """
4
+
5
+ import pytest
6
+ from mojentic.llm.gateways.openai_model_registry import (
7
+ OpenAIModelRegistry,
8
+ ModelType,
9
+ ModelCapabilities,
10
+ get_model_registry
11
+ )
12
+
13
+
14
+ class DescribeOpenAIModelRegistry:
15
+ """Specification for the OpenAI Model Registry."""
16
+
17
+ def should_initialize_with_default_models(self):
18
+ """
19
+ Given a new model registry
20
+ When initialized
21
+ Then it should contain default models for all major OpenAI model families
22
+ """
23
+ registry = OpenAIModelRegistry()
24
+ registered_models = registry.get_registered_models()
25
+
26
+ # Check that we have reasoning models
27
+ assert "o1" in registered_models
28
+ assert "o1-mini" in registered_models
29
+
30
+ # Check that we have chat models
31
+ assert "gpt-4o" in registered_models
32
+ assert "gpt-4o-mini" in registered_models
33
+ assert "gpt-3.5-turbo" in registered_models
34
+
35
+ # Check that we have embedding models
36
+ assert "text-embedding-3-large" in registered_models
37
+ assert "text-embedding-3-small" in registered_models
38
+
39
+ def should_identify_reasoning_models_correctly(self):
40
+ """
41
+ Given various model names
42
+ When checking if they are reasoning models
43
+ Then it should correctly classify known reasoning models
44
+ """
45
+ registry = OpenAIModelRegistry()
46
+
47
+ # Test known reasoning models
48
+ assert registry.is_reasoning_model("o1-preview") is True
49
+ assert registry.is_reasoning_model("o1-mini") is True
50
+ assert registry.is_reasoning_model("o3-mini") is True
51
+
52
+ # Test chat models
53
+ assert registry.is_reasoning_model("gpt-4o") is False
54
+ assert registry.is_reasoning_model("gpt-4o-mini") is False
55
+ assert registry.is_reasoning_model("gpt-3.5-turbo") is False
56
+
57
+ def should_use_pattern_matching_for_unknown_models(self):
58
+ """
59
+ Given an unknown model name that matches a pattern
60
+ When getting model capabilities
61
+ Then it should infer the correct model type
62
+ """
63
+ registry = OpenAIModelRegistry()
64
+
65
+ # Test unknown reasoning model
66
+ capabilities = registry.get_model_capabilities("o1-super-new")
67
+ assert capabilities.model_type == ModelType.REASONING
68
+ assert capabilities.get_token_limit_param() == "max_completion_tokens"
69
+
70
+ # Test unknown chat model
71
+ capabilities = registry.get_model_capabilities("gpt-4-future")
72
+ assert capabilities.model_type == ModelType.CHAT
73
+ assert capabilities.get_token_limit_param() == "max_tokens"
74
+
75
+ def should_return_correct_token_limit_parameters(self):
76
+ """
77
+ Given models of different types
78
+ When getting their token limit parameters
79
+ Then it should return the correct parameter name
80
+ """
81
+ registry = OpenAIModelRegistry()
82
+
83
+ # Reasoning models should use max_completion_tokens
84
+ o1_capabilities = registry.get_model_capabilities("o1-mini")
85
+ assert o1_capabilities.get_token_limit_param() == "max_completion_tokens"
86
+
87
+ # Chat models should use max_tokens
88
+ gpt4_capabilities = registry.get_model_capabilities("gpt-4o")
89
+ assert gpt4_capabilities.get_token_limit_param() == "max_tokens"
90
+
91
+ def should_allow_registering_new_models(self):
92
+ """
93
+ Given a new model with specific capabilities
94
+ When registering it in the registry
95
+ Then it should be available for lookup
96
+ """
97
+ registry = OpenAIModelRegistry()
98
+
99
+ new_capabilities = ModelCapabilities(
100
+ model_type=ModelType.REASONING,
101
+ supports_tools=True,
102
+ supports_streaming=True,
103
+ max_output_tokens=50000
104
+ )
105
+
106
+ registry.register_model("o5-preview", new_capabilities)
107
+
108
+ retrieved_capabilities = registry.get_model_capabilities("o5-preview")
109
+ assert retrieved_capabilities.model_type == ModelType.REASONING
110
+ assert retrieved_capabilities.supports_tools is True
111
+ assert retrieved_capabilities.max_output_tokens == 50000
112
+
113
+ def should_allow_registering_new_patterns(self):
114
+ """
115
+ Given a new pattern for model type inference
116
+ When registering it in the registry
117
+ Then it should be used for unknown models matching the pattern
118
+ """
119
+ registry = OpenAIModelRegistry()
120
+
121
+ registry.register_pattern("claude", ModelType.CHAT)
122
+
123
+ capabilities = registry.get_model_capabilities("claude-3-opus")
124
+ assert capabilities.model_type == ModelType.CHAT
125
+
126
+ def should_handle_completely_unknown_models(self):
127
+ """
128
+ Given a completely unknown model name with no matching patterns
129
+ When getting model capabilities
130
+ Then it should default to chat model capabilities
131
+ """
132
+ registry = OpenAIModelRegistry()
133
+
134
+ capabilities = registry.get_model_capabilities("completely-unknown-model-xyz")
135
+ assert capabilities.model_type == ModelType.CHAT
136
+ assert capabilities.get_token_limit_param() == "max_tokens"
137
+
138
+ def should_provide_global_registry_instance(self):
139
+ """
140
+ Given the global registry function
141
+ When called multiple times
142
+ Then it should return the same instance
143
+ """
144
+ registry1 = get_model_registry()
145
+ registry2 = get_model_registry()
146
+
147
+ assert registry1 is registry2
148
+
149
+ def should_handle_model_capabilities_dataclass_correctly(self):
150
+ """
151
+ Given model capabilities
152
+ When created with different parameters
153
+ Then it should handle defaults and customizations correctly
154
+ """
155
+ # Test with defaults
156
+ default_caps = ModelCapabilities(model_type=ModelType.CHAT)
157
+ assert default_caps.supports_tools is True
158
+ assert default_caps.supports_streaming is True
159
+ assert default_caps.supports_vision is False
160
+
161
+ # Test with custom values
162
+ custom_caps = ModelCapabilities(
163
+ model_type=ModelType.REASONING,
164
+ supports_tools=False,
165
+ supports_vision=True,
166
+ max_context_tokens=100000
167
+ )
168
+ assert custom_caps.supports_tools is False
169
+ assert custom_caps.supports_vision is True
170
+ assert custom_caps.max_context_tokens == 100000
171
+
172
+ def should_have_correct_model_type_enum_values(self):
173
+ """
174
+ Given the ModelType enum
175
+ When accessing its values
176
+ Then it should have all expected model types
177
+ """
178
+ assert ModelType.REASONING.value == "reasoning"
179
+ assert ModelType.CHAT.value == "chat"
180
+ assert ModelType.EMBEDDING.value == "embedding"
181
+ assert ModelType.MODERATION.value == "moderation"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mojentic
3
- Version: 0.7.3
3
+ Version: 0.8.0
4
4
  Summary: Mojentic is an agentic framework that aims to provide a simple and flexible way to assemble teams of agents to solve complex problems.
5
5
  Author-email: Stacey Vetzal <stacey@vetzal.com>
6
6
  Project-URL: Homepage, https://github.com/svetzal/mojentic
@@ -39,7 +39,7 @@ Dynamic: license-file
39
39
 
40
40
  # Mojentic
41
41
 
42
- Mojentic is a framework that provides a simple and flexible way to interact with Large Language Models (LLMs). It offers integration with various LLM providers and includes tools for structured output generation, task automation, and more. The future direction is to facilitate a team of agents, but the current focus is on robust LLM interaction capabilities.
42
+ Mojentic is a framework that provides a simple and flexible way to interact with Large Language Models (LLMs). It offers integration with various LLM providers and includes tools for structured output generation, task automation, and more. With comprehensive support for all OpenAI models including GPT-5 and automatic parameter adaptation, Mojentic handles the complexities of different model types seamlessly. The future direction is to facilitate a team of agents, but the current focus is on robust LLM interaction capabilities.
43
43
 
44
44
  [![GitHub](https://img.shields.io/github/license/svetzal/mojentic)](LICENSE.md)
45
45
  [![Python Version](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/downloads/)
@@ -48,6 +48,8 @@ Mojentic is a framework that provides a simple and flexible way to interact with
48
48
  ## 🚀 Features
49
49
 
50
50
  - **LLM Integration**: Support for multiple LLM providers (OpenAI, Ollama)
51
+ - **Latest OpenAI Models**: Full support for GPT-5, GPT-4.1, and all reasoning models (o1, o3, o4 series)
52
+ - **Automatic Model Adaptation**: Seamless parameter handling across different OpenAI model types
51
53
  - **Structured Output**: Generate structured data from LLM responses using Pydantic models
52
54
  - **Tools Integration**: Utilities for date resolution, image analysis, and more
53
55
  - **Multi-modal Capabilities**: Process and analyze images alongside text
@@ -84,8 +86,9 @@ from mojentic.llm.gateways.models import LLMMessage
84
86
  from mojentic.llm.tools.date_resolver import ResolveDateTool
85
87
  from pydantic import BaseModel, Field
86
88
 
87
- # Initialize with OpenAI
88
- openai_llm = LLMBroker(model="gpt-4o", gateway=OpenAIGateway(api_key="your_api_key"))
89
+ # Initialize with OpenAI (supports all models including GPT-5, GPT-4.1, reasoning models)
90
+ openai_llm = LLMBroker(model="gpt-5", gateway=OpenAIGateway(api_key="your_api_key"))
91
+ # Or use other models: "gpt-4o", "gpt-4.1", "o1-mini", "o3-mini", etc.
89
92
 
90
93
  # Or use Ollama for local LLMs
91
94
  ollama_llm = LLMBroker(model="llama3")
@@ -99,7 +102,7 @@ class Sentiment(BaseModel):
99
102
  label: str = Field(..., description="Label for the sentiment")
100
103
 
101
104
  sentiment = openai_llm.generate_object(
102
- messages=[LLMMessage(content="Hello, how are you?")],
105
+ messages=[LLMMessage(content="Hello, how are you?")],
103
106
  object_model=Sentiment
104
107
  )
105
108
  print(sentiment.label)
@@ -118,6 +121,10 @@ result = openai_llm.generate(messages=[
118
121
  print(result)
119
122
  ```
120
123
 
124
+ ## 🤖 OpenAI Model Support
125
+
126
+ The framework automatically handles parameter differences between model types, so you can switch between any models without code changes.
127
+
121
128
  ## 🏗️ Project Structure
122
129
 
123
130
  ```
@@ -2,7 +2,7 @@ _examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  _examples/async_dispatcher_example.py,sha256=rlaShGpXUsTN7RwVxOo8sbxQjYYJ13gKRB9PWQ3sVTE,8454
3
3
  _examples/async_llm_example.py,sha256=IgYBMawHhaKWbA6zkurH-raqBez9nODfjHEAHI7PRek,8708
4
4
  _examples/broker_as_tool.py,sha256=QJHryYqIXSUYkK62LhMspbNdK3yL0DoenWHxUK5c_fk,2971
5
- _examples/broker_examples.py,sha256=7tES2KyKc3uigBuxkwOTf5UGhp7nVeVqLhd8fEHSWEc,1901
5
+ _examples/broker_examples.py,sha256=-fNqOLl4lwja3_sQwVJyT-2Qwy-B0ol9UWs6zSP2QKk,2397
6
6
  _examples/broker_image_examples.py,sha256=8dpZ2RFmRYhJqoeWklzlPxmvj9m4M393452SO_D2bJY,1133
7
7
  _examples/characterize_ollama.py,sha256=_TzPEAoTuB-saTiaypEQAsUpiBeM37l9I5wCMU3GM4E,2521
8
8
  _examples/characterize_openai.py,sha256=JQQNjsHIEegjLAS3uzDmq_Ci_ZqXTqjcoVaC-rS_R_8,564
@@ -14,6 +14,7 @@ _examples/design_analysis.py,sha256=66JxvnA065QV__WXpxJX_eTBDan34nGcKvao75UPLlw,
14
14
  _examples/embeddings.py,sha256=94DAMsMB35BU98hOfOeOsbqntcequFSdtaStox2hTvk,267
15
15
  _examples/ensures_files_exist.py,sha256=LAwX9YtUZGI6NT254PY7oC5yg6q-b9Q_Dez9gGrabQM,2009
16
16
  _examples/ephemeral_task_manager_example.py,sha256=Q1YpLPwDkB-ytIGgCh2eWOr5EJ0FE-osxJ0B0wDlQkY,1491
17
+ _examples/fetch_openai_models.py,sha256=06FrBZfV4FJ7MM9MczbrzFD8wUmXKZOjzPFFxwSVlj4,3545
17
18
  _examples/file_deduplication.py,sha256=jYayx4BCrk1uJwVBkjIXzguLQOWLREi4PBPzoK9LuOU,1665
18
19
  _examples/file_tool.py,sha256=fUzaIF8qqIy3-E6TJ89jOSD2dwjuO_gBi_1vfCzjuC4,2335
19
20
  _examples/image_analysis.py,sha256=Kj49vLQD1DIpvv5P7rir4BqzsVCTgq-tfppqXcYVJkA,503
@@ -21,6 +22,8 @@ _examples/image_broker.py,sha256=SoIphNj98zk1i7EQc7M2n2O_9ShDHwErmJ6jf67mzX0,355
21
22
  _examples/image_broker_splat.py,sha256=O7rzTFUka32if4G4VuXvhu1O-2lRMWfi0r8gjIE8-0Y,1934
22
23
  _examples/iterative_solver.py,sha256=ANGdC74ymHosVt6xUBjplkJl_W3ALTGxOkDpPLEDcm8,1331
23
24
  _examples/list_models.py,sha256=8noMpGeXOdX5Pf0NXCt_CRurOKEg_5luhWveGntBhe8,578
25
+ _examples/model_characterization.py,sha256=XwLiUP1ZIrNs4ZLmjLDW-nJQsB66H-BV0bWgBgT3N7k,2571
26
+ _examples/openai_gateway_enhanced_demo.py,sha256=vNgJSeQS78PSUibFR9I3WSrBEoSW5qr0dkf2UNlZRdI,5995
24
27
  _examples/oversized_embeddings.py,sha256=_z2JoqZn0g7VtRsFVWIkngVqzjhQQvCEUYWVxs1I7MM,284
25
28
  _examples/raw.py,sha256=Y2wvgynFuoUs28agE4ijsLYec8VRjiReklqlCH2lERs,442
26
29
  _examples/react.py,sha256=VQ-5MmjUXoHzBFPTV_JrocuOkDzZ8oyUUSYLlEToJ_0,939
@@ -83,9 +86,11 @@ mojentic/llm/gateways/models.py,sha256=OyIaMHKrrx6dHo5FbC8qOFct7PRql9wqbe_BJlgDS
83
86
  mojentic/llm/gateways/ollama.py,sha256=OUUImBNzPte52Gsf-e7TBjDHRvYW5flU9ddxwG2zlzk,7909
84
87
  mojentic/llm/gateways/ollama_messages_adapter.py,sha256=kUN_p2FyN88_trXMcL-Xsn9xPBU7pGKlJwTUEUCf6G4,1404
85
88
  mojentic/llm/gateways/ollama_messages_adapter_spec.py,sha256=gVRbWDrHOa1EiZ0CkEWe0pGn-GKRqdGb-x56HBQeYSE,4981
86
- mojentic/llm/gateways/openai.py,sha256=O2iMHGGnw96zgni10jE7L1c5HoskGOO5yMqlp8bIu8w,5690
89
+ mojentic/llm/gateways/openai.py,sha256=42A-8etuDRBSy18q5Qp6S1yndyOJpu3p8Pu0Dd1orFU,12806
87
90
  mojentic/llm/gateways/openai_message_adapter_spec.py,sha256=ITBSV5njldV_x0NPgjmg8Okf9KzevQJ8dTXM-t6ubcg,6612
88
91
  mojentic/llm/gateways/openai_messages_adapter.py,sha256=Scal68JKKdBHB35ok1c5DeWYdD6Wra5oXSsPxJyyXSQ,3947
92
+ mojentic/llm/gateways/openai_model_registry.py,sha256=4BIWQOl-5yAbug3UHUtpbj3kpkadNoy4sMgThyPi-i8,12858
93
+ mojentic/llm/gateways/openai_model_registry_spec.py,sha256=rCyXhiCOKMewkZjdZoawALoEk62yjENeYTpjYuMuXDM,6711
89
94
  mojentic/llm/gateways/tokenizer_gateway.py,sha256=ztuqfunlJ6xmyUPPHcC_69-kegiNJD6jdSEde7hDh2w,485
90
95
  mojentic/llm/registry/__init__.py,sha256=P2MHlptrtRPMSWbWl9ojXPmjMwkW0rIn6jwzCkSgnhE,164
91
96
  mojentic/llm/registry/llm_registry.py,sha256=beyrgGrkXx5ZckUJzC1nQ461vra0fF6s_qRaEdi5bsg,2508
@@ -131,8 +136,8 @@ mojentic/tracer/tracer_system.py,sha256=7CPy_2tlsHtXQ4DcO5oo52N9a9WS0GH-mjeINzu6
131
136
  mojentic/tracer/tracer_system_spec.py,sha256=TNm0f9LV__coBx0JGEKyzzNN9mFjCSG_SSrRISO8Xeg,8632
132
137
  mojentic/utils/__init__.py,sha256=lqECkkoFvHFttDnafRE1vvh0Dmna_lwupMToP5VvX5k,115
133
138
  mojentic/utils/formatting.py,sha256=bPrwwdluXdQ8TsFxfWtHNOeMWKNvAfABSoUnnA1g7c8,947
134
- mojentic-0.7.3.dist-info/licenses/LICENSE.md,sha256=txSgV8n5zY1W3NiF5HHsCwlaW0e8We1cSC6TuJUqxXA,1060
135
- mojentic-0.7.3.dist-info/METADATA,sha256=YTPkYyv6DMo1lluiT6uY5t0xDz1wGyh7dsdRP5ZYTig,5475
136
- mojentic-0.7.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
137
- mojentic-0.7.3.dist-info/top_level.txt,sha256=Q-BvPQ8Eu1jnEqK8Xkr6A9C8Xa1z38oPZRHuA5MCTqg,19
138
- mojentic-0.7.3.dist-info/RECORD,,
139
+ mojentic-0.8.0.dist-info/licenses/LICENSE.md,sha256=txSgV8n5zY1W3NiF5HHsCwlaW0e8We1cSC6TuJUqxXA,1060
140
+ mojentic-0.8.0.dist-info/METADATA,sha256=EpjGKSzORxFrSG94rw5rUvm4vztoEMfGCTkXON-W49k,6154
141
+ mojentic-0.8.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
142
+ mojentic-0.8.0.dist-info/top_level.txt,sha256=Q-BvPQ8Eu1jnEqK8Xkr6A9C8Xa1z38oPZRHuA5MCTqg,19
143
+ mojentic-0.8.0.dist-info/RECORD,,