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.
- _examples/broker_examples.py +22 -3
- _examples/fetch_openai_models.py +104 -0
- _examples/model_characterization.py +73 -0
- _examples/openai_gateway_enhanced_demo.py +140 -0
- mojentic/llm/gateways/openai.py +189 -19
- mojentic/llm/gateways/openai_model_registry.py +327 -0
- mojentic/llm/gateways/openai_model_registry_spec.py +181 -0
- {mojentic-0.7.3.dist-info → mojentic-0.8.0.dist-info}/METADATA +12 -5
- {mojentic-0.7.3.dist-info → mojentic-0.8.0.dist-info}/RECORD +12 -7
- {mojentic-0.7.3.dist-info → mojentic-0.8.0.dist-info}/WHEEL +0 -0
- {mojentic-0.7.3.dist-info → mojentic-0.8.0.dist-info}/licenses/LICENSE.md +0 -0
- {mojentic-0.7.3.dist-info → mojentic-0.8.0.dist-info}/top_level.txt +0 -0
_examples/broker_examples.py
CHANGED
|
@@ -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-
|
|
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")
|
mojentic/llm/gateways/openai.py
CHANGED
|
@@ -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
|
|
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
|
|
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':
|
|
61
|
-
'messages': adapt_messages_to_openai(
|
|
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
|
|
209
|
+
if adapted_args['object_model'] is not None:
|
|
67
210
|
completion = self.client.beta.chat.completions.parse
|
|
68
|
-
openai_args['response_format'] =
|
|
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
|
-
|
|
71
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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'
|
|
247
|
+
if adapted_args.get('object_model') is not None:
|
|
82
248
|
try:
|
|
83
249
|
response_content = response.choices[0].message.content
|
|
84
|
-
|
|
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=
|
|
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.
|
|
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
|
[](LICENSE.md)
|
|
45
45
|
[](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-
|
|
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
|
|
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=
|
|
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.
|
|
135
|
-
mojentic-0.
|
|
136
|
-
mojentic-0.
|
|
137
|
-
mojentic-0.
|
|
138
|
-
mojentic-0.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|