mojentic 0.7.4__py3-none-any.whl → 0.8.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,245 @@
1
+ import pytest
2
+ from unittest.mock import MagicMock
3
+
4
+ from mojentic.llm.gateways.openai import OpenAIGateway
5
+ from mojentic.llm.gateways.openai_model_registry import get_model_registry
6
+ from mojentic.llm.gateways.models import LLMMessage, MessageRole, LLMGatewayResponse
7
+
8
+
9
+ @pytest.fixture
10
+ def mock_openai_client(mocker):
11
+ """Mock the OpenAI client."""
12
+ mock_client = MagicMock()
13
+ mock_client.chat.completions.create.return_value = MagicMock()
14
+ mock_client.chat.completions.create.return_value.choices = [MagicMock()]
15
+ mock_client.chat.completions.create.return_value.choices[0].message.content = "Test response"
16
+ mock_client.chat.completions.create.return_value.choices[0].message.tool_calls = None
17
+ return mock_client
18
+
19
+
20
+ @pytest.fixture
21
+ def openai_gateway(mocker, mock_openai_client):
22
+ """Create an OpenAI gateway with mocked client."""
23
+ mocker.patch('mojentic.llm.gateways.openai.OpenAI', return_value=mock_openai_client)
24
+ return OpenAIGateway(api_key="test_key")
25
+
26
+
27
+ class DescribeOpenAIGatewayTemperatureHandling:
28
+ """
29
+ Specification for OpenAI gateway temperature parameter handling.
30
+ """
31
+
32
+ class DescribeGPT5TemperatureRestrictions:
33
+ """
34
+ Specifications for GPT-5 model temperature restrictions.
35
+ """
36
+
37
+ def should_automatically_adjust_unsupported_temperature_for_gpt5(self, openai_gateway, mock_openai_client):
38
+ """
39
+ Given a GPT-5 model that only supports temperature=1.0
40
+ When calling complete with temperature=0.1 (unsupported)
41
+ Then it should automatically adjust to temperature=1.0
42
+ """
43
+ messages = [LLMMessage(role=MessageRole.User, content="Test message")]
44
+
45
+ openai_gateway.complete(
46
+ model="gpt-5",
47
+ messages=messages,
48
+ temperature=0.1
49
+ )
50
+
51
+ # Verify the API was called with temperature=1.0, not 0.1
52
+ call_args = mock_openai_client.chat.completions.create.call_args
53
+ assert call_args[1]['temperature'] == 1.0
54
+
55
+ def should_preserve_supported_temperature_for_gpt5(self, openai_gateway, mock_openai_client):
56
+ """
57
+ Given a GPT-5 model that supports temperature=1.0
58
+ When calling complete with temperature=1.0 (supported)
59
+ Then it should preserve the temperature value
60
+ """
61
+ messages = [LLMMessage(role=MessageRole.User, content="Test message")]
62
+
63
+ openai_gateway.complete(
64
+ model="gpt-5",
65
+ messages=messages,
66
+ temperature=1.0
67
+ )
68
+
69
+ # Verify the API was called with temperature=1.0
70
+ call_args = mock_openai_client.chat.completions.create.call_args
71
+ assert call_args[1]['temperature'] == 1.0
72
+
73
+ def should_preserve_any_temperature_for_gpt4o(self, openai_gateway, mock_openai_client):
74
+ """
75
+ Given a GPT-4o model that supports all temperatures
76
+ When calling complete with temperature=0.1
77
+ Then it should preserve the original temperature value
78
+ """
79
+ messages = [LLMMessage(role=MessageRole.User, content="Test message")]
80
+
81
+ openai_gateway.complete(
82
+ model="gpt-4o",
83
+ messages=messages,
84
+ temperature=0.1
85
+ )
86
+
87
+ # Verify the API was called with temperature=0.1
88
+ call_args = mock_openai_client.chat.completions.create.call_args
89
+ assert call_args[1]['temperature'] == 0.1
90
+
91
+ def should_automatically_adjust_unsupported_temperature_for_o1_mini(self, openai_gateway, mock_openai_client):
92
+ """
93
+ Given an o1-mini model that only supports temperature=1.0
94
+ When calling complete with temperature=0.1 (unsupported)
95
+ Then it should automatically adjust to temperature=1.0
96
+ """
97
+ messages = [LLMMessage(role=MessageRole.User, content="Test message")]
98
+
99
+ openai_gateway.complete(
100
+ model="o1-mini",
101
+ messages=messages,
102
+ temperature=0.1
103
+ )
104
+
105
+ # Verify the API was called with temperature=1.0, not 0.1
106
+ call_args = mock_openai_client.chat.completions.create.call_args
107
+ assert call_args[1]['temperature'] == 1.0
108
+
109
+ def should_automatically_adjust_unsupported_temperature_for_o4_mini(self, openai_gateway, mock_openai_client):
110
+ """
111
+ Given an o4-mini model that only supports temperature=1.0
112
+ When calling complete with temperature=0.1 (unsupported)
113
+ Then it should automatically adjust to temperature=1.0
114
+ """
115
+ messages = [LLMMessage(role=MessageRole.User, content="Test message")]
116
+
117
+ openai_gateway.complete(
118
+ model="o4-mini",
119
+ messages=messages,
120
+ temperature=0.1
121
+ )
122
+
123
+ # Verify the API was called with temperature=1.0, not 0.1
124
+ call_args = mock_openai_client.chat.completions.create.call_args
125
+ assert call_args[1]['temperature'] == 1.0
126
+
127
+ def should_remove_temperature_parameter_for_o3_mini(self, openai_gateway, mock_openai_client):
128
+ """
129
+ Given an o3-mini model that doesn't support temperature parameter at all
130
+ When calling complete with temperature=0.1
131
+ Then it should remove the temperature parameter entirely
132
+ """
133
+ messages = [LLMMessage(role=MessageRole.User, content="Test message")]
134
+
135
+ openai_gateway.complete(
136
+ model="o3-mini",
137
+ messages=messages,
138
+ temperature=0.1
139
+ )
140
+
141
+ # Verify the API was called without temperature parameter
142
+ call_args = mock_openai_client.chat.completions.create.call_args
143
+ assert 'temperature' not in call_args[1]
144
+
145
+
146
+ class DescribeModelCapabilitiesTemperatureRestrictions:
147
+ """
148
+ Specification for model capabilities temperature restriction checks.
149
+ """
150
+
151
+ def should_identify_gpt5_temperature_restrictions(self):
152
+ """
153
+ Given the model registry
154
+ When checking GPT-5 model capabilities
155
+ Then it should indicate temperature=1.0 is supported and temperature=0.1 is not
156
+ """
157
+ registry = get_model_registry()
158
+ capabilities = registry.get_model_capabilities("gpt-5")
159
+
160
+ assert capabilities.supports_temperature(1.0) is True
161
+ assert capabilities.supports_temperature(0.1) is False
162
+ assert capabilities.supported_temperatures == [1.0]
163
+
164
+ def should_allow_all_temperatures_for_gpt4o(self):
165
+ """
166
+ Given the model registry
167
+ When checking GPT-4o model capabilities
168
+ Then it should indicate all temperature values are supported
169
+ """
170
+ registry = get_model_registry()
171
+ capabilities = registry.get_model_capabilities("gpt-4o")
172
+
173
+ assert capabilities.supports_temperature(1.0) is True
174
+ assert capabilities.supports_temperature(0.1) is True
175
+ assert capabilities.supports_temperature(0.7) is True
176
+ assert capabilities.supported_temperatures is None
177
+
178
+ def should_identify_all_gpt5_variants_temperature_restrictions(self):
179
+ """
180
+ Given the model registry
181
+ When checking all GPT-5 variant models
182
+ Then they should all have temperature restrictions to 1.0 only
183
+ """
184
+ registry = get_model_registry()
185
+ gpt5_models = [
186
+ "gpt-5",
187
+ "gpt-5-2025-08-07",
188
+ "gpt-5-chat-latest",
189
+ "gpt-5-codex",
190
+ "gpt-5-mini",
191
+ "gpt-5-mini-2025-08-07",
192
+ "gpt-5-nano",
193
+ "gpt-5-nano-2025-08-07"
194
+ ]
195
+
196
+ for model in gpt5_models:
197
+ capabilities = registry.get_model_capabilities(model)
198
+ assert capabilities.supports_temperature(1.0) is True
199
+ assert capabilities.supports_temperature(0.1) is False
200
+ assert capabilities.supported_temperatures == [1.0]
201
+
202
+ def should_identify_o1_series_temperature_restrictions(self):
203
+ """
204
+ Given the model registry
205
+ When checking o1 series models
206
+ Then they should have temperature restrictions to 1.0 only
207
+ """
208
+ registry = get_model_registry()
209
+ o1_models = ["o1", "o1-mini", "o1-pro", "o1-2024-12-17"]
210
+
211
+ for model in o1_models:
212
+ capabilities = registry.get_model_capabilities(model)
213
+ assert capabilities.supports_temperature(1.0) is True
214
+ assert capabilities.supports_temperature(0.1) is False
215
+ assert capabilities.supported_temperatures == [1.0]
216
+
217
+ def should_identify_o3_series_no_temperature_support(self):
218
+ """
219
+ Given the model registry
220
+ When checking o3 series models
221
+ Then they should not support temperature parameter at all
222
+ """
223
+ registry = get_model_registry()
224
+ o3_models = ["o3", "o3-mini", "o3-pro", "o3-deep-research"]
225
+
226
+ for model in o3_models:
227
+ capabilities = registry.get_model_capabilities(model)
228
+ assert capabilities.supports_temperature(1.0) is False
229
+ assert capabilities.supports_temperature(0.1) is False
230
+ assert capabilities.supported_temperatures == []
231
+
232
+ def should_identify_o4_series_temperature_restrictions(self):
233
+ """
234
+ Given the model registry
235
+ When checking o4 series models
236
+ Then they should have temperature restrictions to 1.0 only
237
+ """
238
+ registry = get_model_registry()
239
+ o4_models = ["o4-mini", "o4-mini-2025-04-16", "o4-mini-deep-research"]
240
+
241
+ for model in o4_models:
242
+ capabilities = registry.get_model_capabilities(model)
243
+ assert capabilities.supports_temperature(1.0) is True
244
+ assert capabilities.supports_temperature(0.1) is False
245
+ assert capabilities.supported_temperatures == [1.0]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mojentic
3
- Version: 0.7.4
3
+ Version: 0.8.1
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,21 @@ 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
+
128
+ ### Model-Specific Limitations
129
+
130
+ Some models have specific parameter restrictions that are automatically handled:
131
+
132
+ - **GPT-5 Series**: Only supports `temperature=1.0` (default). Other temperature values are automatically adjusted with a warning.
133
+ - **o1 & o4 Series**: Only supports `temperature=1.0` (default). Other temperature values are automatically adjusted with a warning.
134
+ - **o3 Series**: Does not support the `temperature` parameter at all. The parameter is automatically removed with a warning.
135
+ - **All Reasoning Models** (o1, o3, o4, GPT-5): Use `max_completion_tokens` instead of `max_tokens`, and have limited tool support.
136
+
137
+ The framework will automatically adapt parameters and log warnings when unsupported values are provided.
138
+
121
139
  ## 🏗️ Project Structure
122
140
 
123
141
  ```
@@ -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
@@ -22,6 +23,7 @@ _examples/image_broker_splat.py,sha256=O7rzTFUka32if4G4VuXvhu1O-2lRMWfi0r8gjIE8-
22
23
  _examples/iterative_solver.py,sha256=ANGdC74ymHosVt6xUBjplkJl_W3ALTGxOkDpPLEDcm8,1331
23
24
  _examples/list_models.py,sha256=8noMpGeXOdX5Pf0NXCt_CRurOKEg_5luhWveGntBhe8,578
24
25
  _examples/model_characterization.py,sha256=XwLiUP1ZIrNs4ZLmjLDW-nJQsB66H-BV0bWgBgT3N7k,2571
26
+ _examples/openai_gateway_enhanced_demo.py,sha256=vNgJSeQS78PSUibFR9I3WSrBEoSW5qr0dkf2UNlZRdI,5995
25
27
  _examples/oversized_embeddings.py,sha256=_z2JoqZn0g7VtRsFVWIkngVqzjhQQvCEUYWVxs1I7MM,284
26
28
  _examples/raw.py,sha256=Y2wvgynFuoUs28agE4ijsLYec8VRjiReklqlCH2lERs,442
27
29
  _examples/react.py,sha256=VQ-5MmjUXoHzBFPTV_JrocuOkDzZ8oyUUSYLlEToJ_0,939
@@ -84,9 +86,12 @@ mojentic/llm/gateways/models.py,sha256=OyIaMHKrrx6dHo5FbC8qOFct7PRql9wqbe_BJlgDS
84
86
  mojentic/llm/gateways/ollama.py,sha256=OUUImBNzPte52Gsf-e7TBjDHRvYW5flU9ddxwG2zlzk,7909
85
87
  mojentic/llm/gateways/ollama_messages_adapter.py,sha256=kUN_p2FyN88_trXMcL-Xsn9xPBU7pGKlJwTUEUCf6G4,1404
86
88
  mojentic/llm/gateways/ollama_messages_adapter_spec.py,sha256=gVRbWDrHOa1EiZ0CkEWe0pGn-GKRqdGb-x56HBQeYSE,4981
87
- mojentic/llm/gateways/openai.py,sha256=Wxx_WfG2czOv9Ng8q4JCLFIHGqNs3vaMtE5ggSLBjHk,7787
89
+ mojentic/llm/gateways/openai.py,sha256=ru156JpPW8-Zs3_O7BCBuztu_PkP88uD0qbM1Y-RMU4,14032
88
90
  mojentic/llm/gateways/openai_message_adapter_spec.py,sha256=ITBSV5njldV_x0NPgjmg8Okf9KzevQJ8dTXM-t6ubcg,6612
89
91
  mojentic/llm/gateways/openai_messages_adapter.py,sha256=Scal68JKKdBHB35ok1c5DeWYdD6Wra5oXSsPxJyyXSQ,3947
92
+ mojentic/llm/gateways/openai_model_registry.py,sha256=CPfbwBhdZ94jzLjmaH9dRXGZFk4OD2pOUlW6RFWVAPM,14101
93
+ mojentic/llm/gateways/openai_model_registry_spec.py,sha256=rCyXhiCOKMewkZjdZoawALoEk62yjENeYTpjYuMuXDM,6711
94
+ mojentic/llm/gateways/openai_temperature_handling_spec.py,sha256=PxQpI57RGaWpt1Dj6z2uLeFcP-dRZTHkai-igZTZc9M,9947
90
95
  mojentic/llm/gateways/tokenizer_gateway.py,sha256=ztuqfunlJ6xmyUPPHcC_69-kegiNJD6jdSEde7hDh2w,485
91
96
  mojentic/llm/registry/__init__.py,sha256=P2MHlptrtRPMSWbWl9ojXPmjMwkW0rIn6jwzCkSgnhE,164
92
97
  mojentic/llm/registry/llm_registry.py,sha256=beyrgGrkXx5ZckUJzC1nQ461vra0fF6s_qRaEdi5bsg,2508
@@ -132,8 +137,8 @@ mojentic/tracer/tracer_system.py,sha256=7CPy_2tlsHtXQ4DcO5oo52N9a9WS0GH-mjeINzu6
132
137
  mojentic/tracer/tracer_system_spec.py,sha256=TNm0f9LV__coBx0JGEKyzzNN9mFjCSG_SSrRISO8Xeg,8632
133
138
  mojentic/utils/__init__.py,sha256=lqECkkoFvHFttDnafRE1vvh0Dmna_lwupMToP5VvX5k,115
134
139
  mojentic/utils/formatting.py,sha256=bPrwwdluXdQ8TsFxfWtHNOeMWKNvAfABSoUnnA1g7c8,947
135
- mojentic-0.7.4.dist-info/licenses/LICENSE.md,sha256=txSgV8n5zY1W3NiF5HHsCwlaW0e8We1cSC6TuJUqxXA,1060
136
- mojentic-0.7.4.dist-info/METADATA,sha256=shFHb00Ezpkzo6QJOp1vAIhXGONL2jFH7pzIJimPBao,5475
137
- mojentic-0.7.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
138
- mojentic-0.7.4.dist-info/top_level.txt,sha256=Q-BvPQ8Eu1jnEqK8Xkr6A9C8Xa1z38oPZRHuA5MCTqg,19
139
- mojentic-0.7.4.dist-info/RECORD,,
140
+ mojentic-0.8.1.dist-info/licenses/LICENSE.md,sha256=txSgV8n5zY1W3NiF5HHsCwlaW0e8We1cSC6TuJUqxXA,1060
141
+ mojentic-0.8.1.dist-info/METADATA,sha256=Lo7fKdzH3LXkdkL9d9ONoh00JxyggwF7snTIMvVSP7E,6896
142
+ mojentic-0.8.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
143
+ mojentic-0.8.1.dist-info/top_level.txt,sha256=Q-BvPQ8Eu1jnEqK8Xkr6A9C8Xa1z38oPZRHuA5MCTqg,19
144
+ mojentic-0.8.1.dist-info/RECORD,,