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.
- _examples/broker_examples.py +22 -3
- _examples/fetch_openai_models.py +104 -0
- _examples/openai_gateway_enhanced_demo.py +140 -0
- mojentic/llm/gateways/openai.py +164 -31
- mojentic/llm/gateways/openai_model_registry.py +351 -0
- mojentic/llm/gateways/openai_model_registry_spec.py +181 -0
- mojentic/llm/gateways/openai_temperature_handling_spec.py +245 -0
- {mojentic-0.7.4.dist-info → mojentic-0.8.1.dist-info}/METADATA +23 -5
- {mojentic-0.7.4.dist-info → mojentic-0.8.1.dist-info}/RECORD +12 -7
- {mojentic-0.7.4.dist-info → mojentic-0.8.1.dist-info}/WHEEL +0 -0
- {mojentic-0.7.4.dist-info → mojentic-0.8.1.dist-info}/licenses/LICENSE.md +0 -0
- {mojentic-0.7.4.dist-info → mojentic-0.8.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,351 @@
|
|
|
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
|
+
supported_temperatures: Optional[List[float]] = None # None means all temperatures supported
|
|
38
|
+
|
|
39
|
+
def get_token_limit_param(self) -> str:
|
|
40
|
+
"""Get the correct parameter name for token limits based on model type."""
|
|
41
|
+
if self.model_type == ModelType.REASONING:
|
|
42
|
+
return "max_completion_tokens"
|
|
43
|
+
return "max_tokens"
|
|
44
|
+
|
|
45
|
+
def supports_temperature(self, temperature: float) -> bool:
|
|
46
|
+
"""Check if the model supports a specific temperature value."""
|
|
47
|
+
if self.supported_temperatures is None:
|
|
48
|
+
return True # All temperatures supported if not restricted
|
|
49
|
+
if self.supported_temperatures == []:
|
|
50
|
+
return False # No temperature values supported (parameter not allowed)
|
|
51
|
+
return temperature in self.supported_temperatures
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class OpenAIModelRegistry:
|
|
55
|
+
"""
|
|
56
|
+
Registry for managing OpenAI model configurations and capabilities.
|
|
57
|
+
|
|
58
|
+
This class provides a centralized way to manage model-specific configurations,
|
|
59
|
+
parameter mappings, and capabilities for OpenAI models.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self):
|
|
63
|
+
self._models: Dict[str, ModelCapabilities] = {}
|
|
64
|
+
self._pattern_mappings: Dict[str, ModelType] = {}
|
|
65
|
+
self._initialize_default_models()
|
|
66
|
+
|
|
67
|
+
def _initialize_default_models(self):
|
|
68
|
+
"""Initialize the registry with known OpenAI models and their capabilities."""
|
|
69
|
+
|
|
70
|
+
# Reasoning Models (o1, o3, o4, gpt-5 series) - Updated 2025-09-28
|
|
71
|
+
reasoning_models = [
|
|
72
|
+
"o1", "o1-2024-12-17", "o1-mini", "o1-mini-2024-09-12",
|
|
73
|
+
"o1-pro", "o1-pro-2025-03-19",
|
|
74
|
+
"o3", "o3-2025-04-16", "o3-deep-research", "o3-deep-research-2025-06-26",
|
|
75
|
+
"o3-mini", "o3-mini-2025-01-31", "o3-pro", "o3-pro-2025-06-10",
|
|
76
|
+
"o4-mini", "o4-mini-2025-04-16", "o4-mini-deep-research",
|
|
77
|
+
"o4-mini-deep-research-2025-06-26",
|
|
78
|
+
"gpt-5", "gpt-5-2025-08-07", "gpt-5-chat-latest", "gpt-5-codex",
|
|
79
|
+
"gpt-5-mini", "gpt-5-mini-2025-08-07", "gpt-5-nano", "gpt-5-nano-2025-08-07"
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
for model in reasoning_models:
|
|
83
|
+
# Deep research models and GPT-5 might have different capabilities
|
|
84
|
+
is_deep_research = "deep-research" in model
|
|
85
|
+
is_gpt5 = "gpt-5" in model
|
|
86
|
+
is_o1_series = model.startswith("o1")
|
|
87
|
+
is_o3_series = model.startswith("o3")
|
|
88
|
+
is_o4_series = model.startswith("o4")
|
|
89
|
+
is_mini_or_nano = ("mini" in model or "nano" in model)
|
|
90
|
+
|
|
91
|
+
# GPT-5 models may support more features than o1/o3/o4
|
|
92
|
+
supports_tools = is_gpt5 # GPT-5 might support tools
|
|
93
|
+
supports_streaming = is_gpt5 # GPT-5 might support streaming
|
|
94
|
+
|
|
95
|
+
# Set context and output tokens based on model tier
|
|
96
|
+
if is_gpt5:
|
|
97
|
+
context_tokens = 300000 if not is_mini_or_nano else 200000
|
|
98
|
+
output_tokens = 50000 if not is_mini_or_nano else 32768
|
|
99
|
+
elif is_deep_research:
|
|
100
|
+
context_tokens = 200000
|
|
101
|
+
output_tokens = 100000
|
|
102
|
+
else:
|
|
103
|
+
context_tokens = 128000
|
|
104
|
+
output_tokens = 32768
|
|
105
|
+
|
|
106
|
+
# Temperature restrictions based on model series
|
|
107
|
+
if is_gpt5 or is_o1_series or is_o4_series:
|
|
108
|
+
# GPT-5, o1, and o4 series only support temperature=1.0
|
|
109
|
+
supported_temps = [1.0]
|
|
110
|
+
elif is_o3_series:
|
|
111
|
+
# o3 series doesn't support temperature parameter at all
|
|
112
|
+
supported_temps = []
|
|
113
|
+
else:
|
|
114
|
+
# Other reasoning models support all temperatures
|
|
115
|
+
supported_temps = None
|
|
116
|
+
|
|
117
|
+
self._models[model] = ModelCapabilities(
|
|
118
|
+
model_type=ModelType.REASONING,
|
|
119
|
+
supports_tools=supports_tools,
|
|
120
|
+
supports_streaming=supports_streaming,
|
|
121
|
+
supports_vision=False, # Vision support would need to be confirmed for GPT-5
|
|
122
|
+
max_context_tokens=context_tokens,
|
|
123
|
+
max_output_tokens=output_tokens,
|
|
124
|
+
supported_temperatures=supported_temps
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Chat Models (GPT-4 and GPT-4.1 series) - Updated 2025-09-28
|
|
128
|
+
# Note: GPT-5 series moved to reasoning models
|
|
129
|
+
gpt4_and_newer_models = [
|
|
130
|
+
"chatgpt-4o-latest",
|
|
131
|
+
"gpt-4", "gpt-4-0125-preview", "gpt-4-0613", "gpt-4-1106-preview",
|
|
132
|
+
"gpt-4-turbo", "gpt-4-turbo-2024-04-09", "gpt-4-turbo-preview",
|
|
133
|
+
"gpt-4.1", "gpt-4.1-2025-04-14", "gpt-4.1-mini", "gpt-4.1-mini-2025-04-14",
|
|
134
|
+
"gpt-4.1-nano", "gpt-4.1-nano-2025-04-14",
|
|
135
|
+
"gpt-4o", "gpt-4o-2024-05-13", "gpt-4o-2024-08-06", "gpt-4o-2024-11-20",
|
|
136
|
+
"gpt-4o-audio-preview", "gpt-4o-audio-preview-2024-10-01",
|
|
137
|
+
"gpt-4o-audio-preview-2024-12-17", "gpt-4o-audio-preview-2025-06-03",
|
|
138
|
+
"gpt-4o-mini", "gpt-4o-mini-2024-07-18",
|
|
139
|
+
"gpt-4o-mini-audio-preview", "gpt-4o-mini-audio-preview-2024-12-17",
|
|
140
|
+
"gpt-4o-mini-realtime-preview", "gpt-4o-mini-realtime-preview-2024-12-17",
|
|
141
|
+
"gpt-4o-mini-search-preview", "gpt-4o-mini-search-preview-2025-03-11",
|
|
142
|
+
"gpt-4o-mini-transcribe", "gpt-4o-mini-tts",
|
|
143
|
+
"gpt-4o-realtime-preview", "gpt-4o-realtime-preview-2024-10-01",
|
|
144
|
+
"gpt-4o-realtime-preview-2024-12-17", "gpt-4o-realtime-preview-2025-06-03",
|
|
145
|
+
"gpt-4o-search-preview", "gpt-4o-search-preview-2025-03-11",
|
|
146
|
+
"gpt-4o-transcribe"
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
for model in gpt4_and_newer_models:
|
|
150
|
+
# Determine capabilities based on model features
|
|
151
|
+
vision_support = ("gpt-4o" in model or "audio-preview" in model or "realtime" in model)
|
|
152
|
+
is_mini_or_nano = ("mini" in model or "nano" in model)
|
|
153
|
+
is_audio = "audio" in model or "realtime" in model or "transcribe" in model
|
|
154
|
+
is_gpt41 = "gpt-4.1" in model
|
|
155
|
+
|
|
156
|
+
# Set context and output tokens based on model tier
|
|
157
|
+
if is_gpt41:
|
|
158
|
+
context_tokens = 200000 if not is_mini_or_nano else 128000
|
|
159
|
+
output_tokens = 32768 if not is_mini_or_nano else 16384
|
|
160
|
+
elif "gpt-4o" in model:
|
|
161
|
+
context_tokens = 128000
|
|
162
|
+
output_tokens = 16384
|
|
163
|
+
else: # GPT-4 series
|
|
164
|
+
context_tokens = 32000
|
|
165
|
+
output_tokens = 8192
|
|
166
|
+
|
|
167
|
+
self._models[model] = ModelCapabilities(
|
|
168
|
+
model_type=ModelType.CHAT,
|
|
169
|
+
supports_tools=True,
|
|
170
|
+
supports_streaming=not is_audio, # Audio models may not support streaming
|
|
171
|
+
supports_vision=vision_support,
|
|
172
|
+
max_context_tokens=context_tokens,
|
|
173
|
+
max_output_tokens=output_tokens
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Chat Models (GPT-3.5 series) - Updated 2025-09-28
|
|
177
|
+
gpt35_models = [
|
|
178
|
+
"gpt-3.5-turbo", "gpt-3.5-turbo-0125", "gpt-3.5-turbo-1106",
|
|
179
|
+
"gpt-3.5-turbo-16k", "gpt-3.5-turbo-instruct", "gpt-3.5-turbo-instruct-0914"
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
for model in gpt35_models:
|
|
183
|
+
context_tokens = 16385 if "16k" not in model else 16385
|
|
184
|
+
self._models[model] = ModelCapabilities(
|
|
185
|
+
model_type=ModelType.CHAT,
|
|
186
|
+
supports_tools="instruct" not in model, # Instruct models don't support tools
|
|
187
|
+
supports_streaming="instruct" not in model, # Instruct models don't support streaming
|
|
188
|
+
supports_vision=False,
|
|
189
|
+
max_context_tokens=context_tokens,
|
|
190
|
+
max_output_tokens=4096
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Embedding Models - Updated 2025-09-28
|
|
194
|
+
embedding_models = [
|
|
195
|
+
"text-embedding-3-large", "text-embedding-3-small", "text-embedding-ada-002"
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
for model in embedding_models:
|
|
199
|
+
self._models[model] = ModelCapabilities(
|
|
200
|
+
model_type=ModelType.EMBEDDING,
|
|
201
|
+
supports_tools=False,
|
|
202
|
+
supports_streaming=False,
|
|
203
|
+
supports_vision=False
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Pattern mappings for unknown models - Updated 2025-09-28
|
|
207
|
+
self._pattern_mappings = {
|
|
208
|
+
"o1": ModelType.REASONING,
|
|
209
|
+
"o3": ModelType.REASONING,
|
|
210
|
+
"o4": ModelType.REASONING,
|
|
211
|
+
"gpt-5": ModelType.REASONING, # GPT-5 is a reasoning model
|
|
212
|
+
"gpt-4": ModelType.CHAT,
|
|
213
|
+
"gpt-4.1": ModelType.CHAT,
|
|
214
|
+
"gpt-3.5": ModelType.CHAT,
|
|
215
|
+
"chatgpt": ModelType.CHAT,
|
|
216
|
+
"text-embedding": ModelType.EMBEDDING,
|
|
217
|
+
"text-moderation": ModelType.MODERATION
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
def get_model_capabilities(self, model_name: str) -> ModelCapabilities:
|
|
221
|
+
"""
|
|
222
|
+
Get the capabilities for a specific model.
|
|
223
|
+
|
|
224
|
+
Parameters
|
|
225
|
+
----------
|
|
226
|
+
model_name : str
|
|
227
|
+
The name of the model to look up.
|
|
228
|
+
|
|
229
|
+
Returns
|
|
230
|
+
-------
|
|
231
|
+
ModelCapabilities
|
|
232
|
+
The capabilities for the model.
|
|
233
|
+
"""
|
|
234
|
+
# Direct lookup first
|
|
235
|
+
if model_name in self._models:
|
|
236
|
+
return self._models[model_name]
|
|
237
|
+
|
|
238
|
+
# Pattern matching for unknown models
|
|
239
|
+
model_lower = model_name.lower()
|
|
240
|
+
for pattern, model_type in self._pattern_mappings.items():
|
|
241
|
+
if pattern in model_lower:
|
|
242
|
+
logger.warning(
|
|
243
|
+
"Using pattern matching for unknown model",
|
|
244
|
+
model=model_name,
|
|
245
|
+
pattern=pattern,
|
|
246
|
+
inferred_type=model_type.value
|
|
247
|
+
)
|
|
248
|
+
# Return default capabilities for the inferred type
|
|
249
|
+
return self._get_default_capabilities_for_type(model_type)
|
|
250
|
+
|
|
251
|
+
# Default to chat model if no pattern matches
|
|
252
|
+
logger.warning(
|
|
253
|
+
"Unknown model, defaulting to chat model capabilities",
|
|
254
|
+
model=model_name
|
|
255
|
+
)
|
|
256
|
+
return self._get_default_capabilities_for_type(ModelType.CHAT)
|
|
257
|
+
|
|
258
|
+
def _get_default_capabilities_for_type(self, model_type: ModelType) -> ModelCapabilities:
|
|
259
|
+
"""Get default capabilities for a model type."""
|
|
260
|
+
if model_type == ModelType.REASONING:
|
|
261
|
+
return ModelCapabilities(
|
|
262
|
+
model_type=ModelType.REASONING,
|
|
263
|
+
supports_tools=False,
|
|
264
|
+
supports_streaming=False,
|
|
265
|
+
supports_vision=False
|
|
266
|
+
)
|
|
267
|
+
elif model_type == ModelType.CHAT:
|
|
268
|
+
return ModelCapabilities(
|
|
269
|
+
model_type=ModelType.CHAT,
|
|
270
|
+
supports_tools=True,
|
|
271
|
+
supports_streaming=True,
|
|
272
|
+
supports_vision=False
|
|
273
|
+
)
|
|
274
|
+
elif model_type == ModelType.EMBEDDING:
|
|
275
|
+
return ModelCapabilities(
|
|
276
|
+
model_type=ModelType.EMBEDDING,
|
|
277
|
+
supports_tools=False,
|
|
278
|
+
supports_streaming=False,
|
|
279
|
+
supports_vision=False
|
|
280
|
+
)
|
|
281
|
+
else: # MODERATION
|
|
282
|
+
return ModelCapabilities(
|
|
283
|
+
model_type=ModelType.MODERATION,
|
|
284
|
+
supports_tools=False,
|
|
285
|
+
supports_streaming=False,
|
|
286
|
+
supports_vision=False
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
def is_reasoning_model(self, model_name: str) -> bool:
|
|
290
|
+
"""
|
|
291
|
+
Check if a model is a reasoning model.
|
|
292
|
+
|
|
293
|
+
Parameters
|
|
294
|
+
----------
|
|
295
|
+
model_name : str
|
|
296
|
+
The name of the model to check.
|
|
297
|
+
|
|
298
|
+
Returns
|
|
299
|
+
-------
|
|
300
|
+
bool
|
|
301
|
+
True if the model is a reasoning model, False otherwise.
|
|
302
|
+
"""
|
|
303
|
+
capabilities = self.get_model_capabilities(model_name)
|
|
304
|
+
return capabilities.model_type == ModelType.REASONING
|
|
305
|
+
|
|
306
|
+
def get_registered_models(self) -> List[str]:
|
|
307
|
+
"""
|
|
308
|
+
Get a list of all explicitly registered models.
|
|
309
|
+
|
|
310
|
+
Returns
|
|
311
|
+
-------
|
|
312
|
+
List[str]
|
|
313
|
+
List of registered model names.
|
|
314
|
+
"""
|
|
315
|
+
return list(self._models.keys())
|
|
316
|
+
|
|
317
|
+
def register_model(self, model_name: str, capabilities: ModelCapabilities):
|
|
318
|
+
"""
|
|
319
|
+
Register a new model with its capabilities.
|
|
320
|
+
|
|
321
|
+
Parameters
|
|
322
|
+
----------
|
|
323
|
+
model_name : str
|
|
324
|
+
The name of the model to register.
|
|
325
|
+
capabilities : ModelCapabilities
|
|
326
|
+
The capabilities of the model.
|
|
327
|
+
"""
|
|
328
|
+
self._models[model_name] = capabilities
|
|
329
|
+
logger.info("Registered new model", model=model_name, type=capabilities.model_type.value)
|
|
330
|
+
|
|
331
|
+
def register_pattern(self, pattern: str, model_type: ModelType):
|
|
332
|
+
"""
|
|
333
|
+
Register a pattern for inferring model types.
|
|
334
|
+
|
|
335
|
+
Parameters
|
|
336
|
+
----------
|
|
337
|
+
pattern : str
|
|
338
|
+
The pattern to match in model names.
|
|
339
|
+
model_type : ModelType
|
|
340
|
+
The type to infer for matching models.
|
|
341
|
+
"""
|
|
342
|
+
self._pattern_mappings[pattern] = model_type
|
|
343
|
+
logger.info("Registered new pattern", pattern=pattern, type=model_type.value)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
# Global registry instance
|
|
347
|
+
_registry = OpenAIModelRegistry()
|
|
348
|
+
|
|
349
|
+
def get_model_registry() -> OpenAIModelRegistry:
|
|
350
|
+
"""Get the global OpenAI model registry instance."""
|
|
351
|
+
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"
|