omnimancer-cli 0.1.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.
- omnimancer/__init__.py +42 -0
- omnimancer/__main__.py +9 -0
- omnimancer/cli/__init__.py +8 -0
- omnimancer/cli/agent_manager.py +826 -0
- omnimancer/cli/agent_wizard.py +944 -0
- omnimancer/cli/approval_formatter.py +734 -0
- omnimancer/cli/approval_integration.py +734 -0
- omnimancer/cli/approval_prompt.py +1286 -0
- omnimancer/cli/batch_approval_display.py +617 -0
- omnimancer/cli/batch_approval_filters.py +729 -0
- omnimancer/cli/batch_operation_monitor.py +775 -0
- omnimancer/cli/commands/performance_cmd.py +624 -0
- omnimancer/cli/commands.py +757 -0
- omnimancer/cli/completion_handler.py +281 -0
- omnimancer/cli/config_setup_wizard.py +783 -0
- omnimancer/cli/dynamic_commands.py +371 -0
- omnimancer/cli/handlers/__init__.py +18 -0
- omnimancer/cli/handlers/agent_handler.py +865 -0
- omnimancer/cli/handlers/agent_persona_handler.py +1048 -0
- omnimancer/cli/handlers/custom_agent_handler.py +352 -0
- omnimancer/cli/handlers/permissions_handler.py +1609 -0
- omnimancer/cli/interface.py +6695 -0
- omnimancer/cli/performance_dashboard.py +612 -0
- omnimancer/core/__init__.py +34 -0
- omnimancer/core/agent/__init__.py +256 -0
- omnimancer/core/agent/agent_config.py +715 -0
- omnimancer/core/agent/agent_switcher.py +668 -0
- omnimancer/core/agent/approval_context.py +460 -0
- omnimancer/core/agent/approval_dialog.py +902 -0
- omnimancer/core/agent/approval_interface.py +628 -0
- omnimancer/core/agent/approval_manager.py +760 -0
- omnimancer/core/agent/cli_integration.py +470 -0
- omnimancer/core/agent/config.py +845 -0
- omnimancer/core/agent/custom_agent_integration.py +654 -0
- omnimancer/core/agent/diff_renderer.py +706 -0
- omnimancer/core/agent/file_content_display.py +503 -0
- omnimancer/core/agent/file_modification_workflow.py +658 -0
- omnimancer/core/agent/file_system_manager.py +1790 -0
- omnimancer/core/agent/input_handler.py +614 -0
- omnimancer/core/agent/metrics_collector.py +771 -0
- omnimancer/core/agent/optimization_engine.py +729 -0
- omnimancer/core/agent/performance_monitor.py +717 -0
- omnimancer/core/agent/persona.py +870 -0
- omnimancer/core/agent/persona_validator.py +672 -0
- omnimancer/core/agent/program_executor.py +748 -0
- omnimancer/core/agent/proposed_changes_integration.py +663 -0
- omnimancer/core/agent/read_before_write_errors.py +492 -0
- omnimancer/core/agent/read_before_write_ui.py +647 -0
- omnimancer/core/agent/rich_renderer.py +763 -0
- omnimancer/core/agent/status_config.py +238 -0
- omnimancer/core/agent/status_core.py +446 -0
- omnimancer/core/agent/status_integrated_engine.py +540 -0
- omnimancer/core/agent/status_manager.py +878 -0
- omnimancer/core/agent/status_ui_components.py +535 -0
- omnimancer/core/agent/template_applicator.py +548 -0
- omnimancer/core/agent/terminal_status_listener.py +394 -0
- omnimancer/core/agent/token_tracker.py +679 -0
- omnimancer/core/agent/types.py +60 -0
- omnimancer/core/agent/web_client.py +745 -0
- omnimancer/core/agent/workflow_orchestrator.py +826 -0
- omnimancer/core/agent_engine.py +1625 -0
- omnimancer/core/agent_mode_manager.py +706 -0
- omnimancer/core/agent_progress_ui.py +545 -0
- omnimancer/core/chat_manager.py +141 -0
- omnimancer/core/config_manager.py +2199 -0
- omnimancer/core/config_migration.py +772 -0
- omnimancer/core/config_provider.py +1974 -0
- omnimancer/core/config_security_validator.py +393 -0
- omnimancer/core/config_validator.py +768 -0
- omnimancer/core/conversation_manager.py +352 -0
- omnimancer/core/engine.py +787 -0
- omnimancer/core/enhanced_input.py +523 -0
- omnimancer/core/env_loader.py +83 -0
- omnimancer/core/fallback_manager.py +568 -0
- omnimancer/core/health_monitor.py +462 -0
- omnimancer/core/history_manager.py +462 -0
- omnimancer/core/mcp_integration_layer.py +878 -0
- omnimancer/core/models.py +1889 -0
- omnimancer/core/provider_capabilities.py +403 -0
- omnimancer/core/provider_initializer.py +450 -0
- omnimancer/core/provider_registry.py +432 -0
- omnimancer/core/security/__init__.py +16 -0
- omnimancer/core/security/approval_workflow.py +406 -0
- omnimancer/core/security/audit_logger.py +511 -0
- omnimancer/core/security/permission_controller.py +511 -0
- omnimancer/core/security/sandbox_manager.py +379 -0
- omnimancer/core/security/security_manager.py +530 -0
- omnimancer/core/setup_wizard.py +345 -0
- omnimancer/core/setup_wizard_core.py +449 -0
- omnimancer/core/setup_wizard_guide_generator.py +376 -0
- omnimancer/core/setup_wizard_provider_setup.py +454 -0
- omnimancer/core/setup_wizard_ui.py +317 -0
- omnimancer/core/setup_wizard_validation.py +371 -0
- omnimancer/core/signal_handler.py +174 -0
- omnimancer/mcp/__init__.py +11 -0
- omnimancer/mcp/client.py +725 -0
- omnimancer/mcp/manager.py +649 -0
- omnimancer/providers/__init__.py +24 -0
- omnimancer/providers/azure.py +608 -0
- omnimancer/providers/base.py +262 -0
- omnimancer/providers/bedrock.py +711 -0
- omnimancer/providers/claude.py +426 -0
- omnimancer/providers/claude_code.py +406 -0
- omnimancer/providers/cohere.py +412 -0
- omnimancer/providers/factory.py +988 -0
- omnimancer/providers/gemini.py +708 -0
- omnimancer/providers/mistral.py +554 -0
- omnimancer/providers/ollama.py +510 -0
- omnimancer/providers/openai.py +362 -0
- omnimancer/providers/openrouter.py +709 -0
- omnimancer/providers/perplexity.py +476 -0
- omnimancer/providers/vertex.py +617 -0
- omnimancer/providers/xai.py +484 -0
- omnimancer/ui/__init__.py +6 -0
- omnimancer/ui/cancellation_handler.py +235 -0
- omnimancer/ui/progress_indicator.py +318 -0
- omnimancer/utils/__init__.py +22 -0
- omnimancer/utils/error_handler.py +589 -0
- omnimancer/utils/errors.py +667 -0
- omnimancer/utils/retry.py +238 -0
- omnimancer_cli-0.1.0.dist-info/METADATA +413 -0
- omnimancer_cli-0.1.0.dist-info/RECORD +126 -0
- omnimancer_cli-0.1.0.dist-info/WHEEL +5 -0
- omnimancer_cli-0.1.0.dist-info/entry_points.txt +4 -0
- omnimancer_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- omnimancer_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Azure OpenAI provider implementation for Omnimancer.
|
|
3
|
+
|
|
4
|
+
This module provides the Azure OpenAI provider implementation using Azure's OpenAI Service
|
|
5
|
+
with support for deployment names, API versioning, and Azure-specific authentication.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import Dict, List
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
from ..core.models import (
|
|
14
|
+
ChatContext,
|
|
15
|
+
ChatResponse,
|
|
16
|
+
EnhancedModelInfo,
|
|
17
|
+
ToolCall,
|
|
18
|
+
ToolDefinition,
|
|
19
|
+
)
|
|
20
|
+
from ..utils.errors import (
|
|
21
|
+
AuthenticationError,
|
|
22
|
+
ModelNotFoundError,
|
|
23
|
+
NetworkError,
|
|
24
|
+
ProviderError,
|
|
25
|
+
RateLimitError,
|
|
26
|
+
)
|
|
27
|
+
from .base import BaseProvider
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AzureProvider(BaseProvider):
|
|
31
|
+
"""
|
|
32
|
+
Azure OpenAI provider implementation using Azure OpenAI Service.
|
|
33
|
+
|
|
34
|
+
Supports Azure-specific endpoint configuration, deployment names,
|
|
35
|
+
API versioning, and Azure authentication methods.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, api_key: str, model: str = "", **kwargs):
|
|
39
|
+
"""
|
|
40
|
+
Initialize Azure OpenAI provider.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
api_key: Azure OpenAI API key
|
|
44
|
+
model: Model deployment name in Azure (e.g., 'gpt-4', 'gpt-35-turbo', custom deployment name)
|
|
45
|
+
**kwargs: Additional configuration including Azure-specific settings
|
|
46
|
+
"""
|
|
47
|
+
super().__init__(api_key, model or "gpt-4", **kwargs)
|
|
48
|
+
|
|
49
|
+
# Azure-specific configuration
|
|
50
|
+
self.azure_endpoint = kwargs.get("azure_endpoint")
|
|
51
|
+
self.azure_deployment = kwargs.get("azure_deployment", model)
|
|
52
|
+
self.api_version = kwargs.get("api_version", "2024-02-15-preview")
|
|
53
|
+
|
|
54
|
+
# Standard OpenAI parameters
|
|
55
|
+
self.max_tokens = kwargs.get("max_tokens", 4096)
|
|
56
|
+
self.temperature = kwargs.get("temperature", 0.7)
|
|
57
|
+
self.top_p = kwargs.get("top_p", 1.0)
|
|
58
|
+
self.frequency_penalty = kwargs.get("frequency_penalty", 0.0)
|
|
59
|
+
self.presence_penalty = kwargs.get("presence_penalty", 0.0)
|
|
60
|
+
|
|
61
|
+
# Validate required Azure configuration
|
|
62
|
+
if not self.azure_endpoint:
|
|
63
|
+
raise ValueError("azure_endpoint is required for Azure OpenAI provider")
|
|
64
|
+
|
|
65
|
+
# Ensure endpoint format is correct
|
|
66
|
+
if not self.azure_endpoint.startswith("https://"):
|
|
67
|
+
self.azure_endpoint = f"https://{self.azure_endpoint}"
|
|
68
|
+
|
|
69
|
+
if not self.azure_endpoint.endswith(".openai.azure.com"):
|
|
70
|
+
if not self.azure_endpoint.endswith(".openai.azure.com/"):
|
|
71
|
+
self.azure_endpoint = (
|
|
72
|
+
f"{self.azure_endpoint.rstrip('/')}.openai.azure.com"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
async def send_message(self, message: str, context: ChatContext) -> ChatResponse:
|
|
76
|
+
"""
|
|
77
|
+
Send a message to Azure OpenAI API.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
message: User message
|
|
81
|
+
context: Conversation context
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
ChatResponse with Azure OpenAI's reply
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
# Prepare messages for Azure OpenAI API
|
|
88
|
+
messages = self._prepare_messages(message, context)
|
|
89
|
+
|
|
90
|
+
# Build request payload
|
|
91
|
+
payload = {
|
|
92
|
+
"messages": messages,
|
|
93
|
+
"max_tokens": self.max_tokens,
|
|
94
|
+
"temperature": self.temperature,
|
|
95
|
+
"top_p": self.top_p,
|
|
96
|
+
"frequency_penalty": self.frequency_penalty,
|
|
97
|
+
"presence_penalty": self.presence_penalty,
|
|
98
|
+
"stream": False,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# Build URL for Azure OpenAI
|
|
102
|
+
url = self._build_azure_url("chat/completions")
|
|
103
|
+
|
|
104
|
+
# Make API request
|
|
105
|
+
async with httpx.AsyncClient() as client:
|
|
106
|
+
response = await client.post(
|
|
107
|
+
url,
|
|
108
|
+
headers={
|
|
109
|
+
"Content-Type": "application/json",
|
|
110
|
+
"api-key": self.api_key,
|
|
111
|
+
},
|
|
112
|
+
json=payload,
|
|
113
|
+
timeout=30.0,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Handle response
|
|
117
|
+
return self._handle_response(response)
|
|
118
|
+
|
|
119
|
+
except httpx.TimeoutException:
|
|
120
|
+
raise NetworkError("Request to Azure OpenAI API timed out")
|
|
121
|
+
except httpx.RequestError as e:
|
|
122
|
+
raise NetworkError(f"Network error: {e}")
|
|
123
|
+
except (
|
|
124
|
+
AuthenticationError,
|
|
125
|
+
RateLimitError,
|
|
126
|
+
ModelNotFoundError,
|
|
127
|
+
ProviderError,
|
|
128
|
+
):
|
|
129
|
+
# Re-raise provider-specific errors without wrapping
|
|
130
|
+
raise
|
|
131
|
+
except Exception as e:
|
|
132
|
+
raise ProviderError(f"Unexpected error: {e}")
|
|
133
|
+
|
|
134
|
+
async def send_message_with_tools(
|
|
135
|
+
self,
|
|
136
|
+
message: str,
|
|
137
|
+
context: ChatContext,
|
|
138
|
+
available_tools: List[ToolDefinition],
|
|
139
|
+
) -> ChatResponse:
|
|
140
|
+
"""
|
|
141
|
+
Send a message with available tools for Azure OpenAI to use.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
message: User message
|
|
145
|
+
context: Conversation context
|
|
146
|
+
available_tools: List of tools available for the model to use
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
ChatResponse with Azure OpenAI's reply and any tool calls
|
|
150
|
+
"""
|
|
151
|
+
try:
|
|
152
|
+
# Prepare messages for Azure OpenAI API
|
|
153
|
+
messages = self._prepare_messages(message, context)
|
|
154
|
+
|
|
155
|
+
# Convert tools to Azure OpenAI format
|
|
156
|
+
tools = self._convert_tools_to_azure_format(available_tools)
|
|
157
|
+
|
|
158
|
+
# Build request payload
|
|
159
|
+
payload = {
|
|
160
|
+
"messages": messages,
|
|
161
|
+
"max_tokens": self.max_tokens,
|
|
162
|
+
"temperature": self.temperature,
|
|
163
|
+
"top_p": self.top_p,
|
|
164
|
+
"frequency_penalty": self.frequency_penalty,
|
|
165
|
+
"presence_penalty": self.presence_penalty,
|
|
166
|
+
"tools": tools,
|
|
167
|
+
"tool_choice": "auto",
|
|
168
|
+
"stream": False,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# Build URL for Azure OpenAI
|
|
172
|
+
url = self._build_azure_url("chat/completions")
|
|
173
|
+
|
|
174
|
+
# Make API request
|
|
175
|
+
async with httpx.AsyncClient() as client:
|
|
176
|
+
response = await client.post(
|
|
177
|
+
url,
|
|
178
|
+
headers={
|
|
179
|
+
"Content-Type": "application/json",
|
|
180
|
+
"api-key": self.api_key,
|
|
181
|
+
},
|
|
182
|
+
json=payload,
|
|
183
|
+
timeout=30.0,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Handle response with tool calls
|
|
187
|
+
return self._handle_response_with_tools(response)
|
|
188
|
+
|
|
189
|
+
except httpx.TimeoutException:
|
|
190
|
+
raise NetworkError("Request to Azure OpenAI API timed out")
|
|
191
|
+
except httpx.RequestError as e:
|
|
192
|
+
raise NetworkError(f"Network error: {e}")
|
|
193
|
+
except (
|
|
194
|
+
AuthenticationError,
|
|
195
|
+
RateLimitError,
|
|
196
|
+
ModelNotFoundError,
|
|
197
|
+
ProviderError,
|
|
198
|
+
):
|
|
199
|
+
# Re-raise provider-specific errors without wrapping
|
|
200
|
+
raise
|
|
201
|
+
except Exception as e:
|
|
202
|
+
raise ProviderError(f"Unexpected error: {e}")
|
|
203
|
+
|
|
204
|
+
async def validate_credentials(self) -> bool:
|
|
205
|
+
"""
|
|
206
|
+
Validate Azure OpenAI API credentials by making a test request.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
True if credentials are valid
|
|
210
|
+
"""
|
|
211
|
+
try:
|
|
212
|
+
url = self._build_azure_url("chat/completions")
|
|
213
|
+
|
|
214
|
+
async with httpx.AsyncClient() as client:
|
|
215
|
+
response = await client.post(
|
|
216
|
+
url,
|
|
217
|
+
headers={
|
|
218
|
+
"Content-Type": "application/json",
|
|
219
|
+
"api-key": self.api_key,
|
|
220
|
+
},
|
|
221
|
+
json={
|
|
222
|
+
"messages": [{"role": "user", "content": "Hi"}],
|
|
223
|
+
"max_tokens": 10,
|
|
224
|
+
},
|
|
225
|
+
timeout=10.0,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
return response.status_code == 200
|
|
229
|
+
|
|
230
|
+
except Exception:
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
def _build_azure_url(self, endpoint: str) -> str:
|
|
234
|
+
"""
|
|
235
|
+
Build Azure OpenAI API URL.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
endpoint: API endpoint (e.g., "chat/completions")
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Complete Azure OpenAI API URL
|
|
242
|
+
"""
|
|
243
|
+
base_url = f"{self.azure_endpoint}/openai/deployments/{self.azure_deployment}"
|
|
244
|
+
return f"{base_url}/{endpoint}?api-version={self.api_version}"
|
|
245
|
+
|
|
246
|
+
def _prepare_messages(
|
|
247
|
+
self, message: str, context: ChatContext
|
|
248
|
+
) -> List[Dict[str, str]]:
|
|
249
|
+
"""
|
|
250
|
+
Prepare messages for Azure OpenAI API format.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
message: Current user message
|
|
254
|
+
context: Conversation context
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
List of messages formatted for Azure OpenAI API
|
|
258
|
+
"""
|
|
259
|
+
messages = []
|
|
260
|
+
|
|
261
|
+
# Add context messages
|
|
262
|
+
for msg in context.messages:
|
|
263
|
+
messages.append({"role": msg.role.value, "content": msg.content})
|
|
264
|
+
|
|
265
|
+
# Add current message
|
|
266
|
+
messages.append({"role": "user", "content": message})
|
|
267
|
+
|
|
268
|
+
return messages
|
|
269
|
+
|
|
270
|
+
def _convert_tools_to_azure_format(self, tools: List[ToolDefinition]) -> List[Dict]:
|
|
271
|
+
"""
|
|
272
|
+
Convert tool definitions to Azure OpenAI API format.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
tools: List of tool definitions
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
List of tools formatted for Azure OpenAI API
|
|
279
|
+
"""
|
|
280
|
+
azure_tools = []
|
|
281
|
+
|
|
282
|
+
for tool in tools:
|
|
283
|
+
azure_tool = {
|
|
284
|
+
"type": "function",
|
|
285
|
+
"function": {
|
|
286
|
+
"name": tool.name,
|
|
287
|
+
"description": tool.description,
|
|
288
|
+
"parameters": tool.parameters,
|
|
289
|
+
},
|
|
290
|
+
}
|
|
291
|
+
azure_tools.append(azure_tool)
|
|
292
|
+
|
|
293
|
+
return azure_tools
|
|
294
|
+
|
|
295
|
+
def _handle_response(self, response: httpx.Response) -> ChatResponse:
|
|
296
|
+
"""
|
|
297
|
+
Handle Azure OpenAI API response.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
response: HTTP response from Azure OpenAI API
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
ChatResponse object
|
|
304
|
+
|
|
305
|
+
Raises:
|
|
306
|
+
Various provider errors based on response status
|
|
307
|
+
"""
|
|
308
|
+
if response.status_code == 200:
|
|
309
|
+
data = response.json()
|
|
310
|
+
choices = data.get("choices", [])
|
|
311
|
+
|
|
312
|
+
if choices and len(choices) > 0:
|
|
313
|
+
message = choices[0].get("message", {})
|
|
314
|
+
content = message.get("content", "")
|
|
315
|
+
usage = data.get("usage", {})
|
|
316
|
+
|
|
317
|
+
# Use model name instead of deployment name for consistency
|
|
318
|
+
model_name = self.model if self.model else self.azure_deployment
|
|
319
|
+
return ChatResponse(
|
|
320
|
+
content=content,
|
|
321
|
+
model_used=model_name,
|
|
322
|
+
tokens_used=usage.get("total_tokens", 0),
|
|
323
|
+
timestamp=datetime.now(),
|
|
324
|
+
)
|
|
325
|
+
else:
|
|
326
|
+
raise ProviderError("Empty response from Azure OpenAI API")
|
|
327
|
+
|
|
328
|
+
elif response.status_code == 401:
|
|
329
|
+
raise AuthenticationError(
|
|
330
|
+
"Invalid Azure OpenAI API key or insufficient permissions"
|
|
331
|
+
)
|
|
332
|
+
elif response.status_code == 429:
|
|
333
|
+
raise RateLimitError("Azure OpenAI API rate limit exceeded")
|
|
334
|
+
elif response.status_code == 404:
|
|
335
|
+
raise ModelNotFoundError(
|
|
336
|
+
f"Azure deployment '{self.azure_deployment}' not found"
|
|
337
|
+
)
|
|
338
|
+
else:
|
|
339
|
+
try:
|
|
340
|
+
error_data = response.json()
|
|
341
|
+
error_msg = error_data.get("error", {}).get("message", "Unknown error")
|
|
342
|
+
except:
|
|
343
|
+
error_msg = f"HTTP {response.status_code}"
|
|
344
|
+
|
|
345
|
+
raise ProviderError(f"Azure OpenAI API error: {error_msg}")
|
|
346
|
+
|
|
347
|
+
def _handle_response_with_tools(self, response: httpx.Response) -> ChatResponse:
|
|
348
|
+
"""
|
|
349
|
+
Handle Azure OpenAI API response with tool calls.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
response: HTTP response from Azure OpenAI API
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
ChatResponse object with tool calls
|
|
356
|
+
"""
|
|
357
|
+
if response.status_code == 200:
|
|
358
|
+
data = response.json()
|
|
359
|
+
choices = data.get("choices", [])
|
|
360
|
+
|
|
361
|
+
if choices and len(choices) > 0:
|
|
362
|
+
message = choices[0].get("message", {})
|
|
363
|
+
content = message.get("content", "")
|
|
364
|
+
usage = data.get("usage", {})
|
|
365
|
+
|
|
366
|
+
# Extract tool calls if present
|
|
367
|
+
tool_calls = []
|
|
368
|
+
if "tool_calls" in message:
|
|
369
|
+
for tool_call in message["tool_calls"]:
|
|
370
|
+
if tool_call.get("type") == "function":
|
|
371
|
+
function = tool_call.get("function", {})
|
|
372
|
+
tool_calls.append(
|
|
373
|
+
ToolCall(
|
|
374
|
+
name=function.get("name", ""),
|
|
375
|
+
arguments=function.get("arguments", {}),
|
|
376
|
+
)
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
# Use model name instead of deployment name for consistency
|
|
380
|
+
model_name = self.model if self.model else self.azure_deployment
|
|
381
|
+
return ChatResponse(
|
|
382
|
+
content=content,
|
|
383
|
+
model_used=model_name,
|
|
384
|
+
tokens_used=usage.get("total_tokens", 0),
|
|
385
|
+
tool_calls=tool_calls if tool_calls else None,
|
|
386
|
+
timestamp=datetime.now(),
|
|
387
|
+
)
|
|
388
|
+
else:
|
|
389
|
+
raise ProviderError("Empty response from Azure OpenAI API")
|
|
390
|
+
else:
|
|
391
|
+
return self._handle_response(response)
|
|
392
|
+
|
|
393
|
+
def get_model_info(self) -> EnhancedModelInfo:
|
|
394
|
+
"""
|
|
395
|
+
Get information about the current Azure OpenAI model.
|
|
396
|
+
"""
|
|
397
|
+
# Map common Azure deployment names to model info
|
|
398
|
+
model_configs = {
|
|
399
|
+
"gpt-4": {
|
|
400
|
+
"description": "GPT-4 via Azure OpenAI Service",
|
|
401
|
+
"max_tokens": 8192,
|
|
402
|
+
"cost_per_million_input": 30.0,
|
|
403
|
+
"cost_per_million_output": 60.0,
|
|
404
|
+
"swe_score": 67.0,
|
|
405
|
+
"supports_tools": True,
|
|
406
|
+
"supports_multimodal": False,
|
|
407
|
+
},
|
|
408
|
+
"gpt-4-turbo": {
|
|
409
|
+
"description": "GPT-4 Turbo on Azure - Enhanced performance",
|
|
410
|
+
"max_tokens": 128000,
|
|
411
|
+
"cost_per_million_input": 10.0,
|
|
412
|
+
"cost_per_million_output": 30.0,
|
|
413
|
+
"swe_score": 69.1,
|
|
414
|
+
"supports_tools": True,
|
|
415
|
+
"supports_multimodal": True,
|
|
416
|
+
},
|
|
417
|
+
"gpt-4o": {
|
|
418
|
+
"description": "GPT-4o on Azure - Optimized for speed and efficiency",
|
|
419
|
+
"max_tokens": 128000,
|
|
420
|
+
"cost_per_million_input": 5.0,
|
|
421
|
+
"cost_per_million_output": 15.0,
|
|
422
|
+
"swe_score": 71.2,
|
|
423
|
+
"supports_tools": True,
|
|
424
|
+
"supports_multimodal": True,
|
|
425
|
+
},
|
|
426
|
+
"gpt-35-turbo": {
|
|
427
|
+
"description": "GPT-3.5 Turbo via Azure OpenAI Service",
|
|
428
|
+
"max_tokens": 4096,
|
|
429
|
+
"cost_per_million_input": 0.5,
|
|
430
|
+
"cost_per_million_output": 1.5,
|
|
431
|
+
"swe_score": 48.1,
|
|
432
|
+
"supports_tools": True,
|
|
433
|
+
"supports_multimodal": False,
|
|
434
|
+
},
|
|
435
|
+
"gpt-35-turbo-16k": {
|
|
436
|
+
"description": "GPT-3.5 Turbo 16K on Azure - Extended context",
|
|
437
|
+
"max_tokens": 16384,
|
|
438
|
+
"cost_per_million_input": 3.0,
|
|
439
|
+
"cost_per_million_output": 4.0,
|
|
440
|
+
"swe_score": 48.1,
|
|
441
|
+
"supports_tools": True,
|
|
442
|
+
"supports_multimodal": False,
|
|
443
|
+
},
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
# Try to match deployment name to known models
|
|
447
|
+
config = None
|
|
448
|
+
for model_name, model_config in model_configs.items():
|
|
449
|
+
if model_name in self.azure_deployment.lower():
|
|
450
|
+
config = model_config
|
|
451
|
+
break
|
|
452
|
+
|
|
453
|
+
# Default configuration if no match found
|
|
454
|
+
if not config:
|
|
455
|
+
model_name_for_desc = self.model if self.model else self.azure_deployment
|
|
456
|
+
config = {
|
|
457
|
+
"description": f"Azure OpenAI model {model_name_for_desc}",
|
|
458
|
+
"max_tokens": 4096,
|
|
459
|
+
"cost_per_million_input": 10.0,
|
|
460
|
+
"cost_per_million_output": 30.0,
|
|
461
|
+
"swe_score": 50.0,
|
|
462
|
+
"supports_tools": True,
|
|
463
|
+
"supports_multimodal": False,
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
# Use model name instead of deployment name for consistency
|
|
467
|
+
model_name = self.model if self.model else self.azure_deployment
|
|
468
|
+
enhanced_info = EnhancedModelInfo(
|
|
469
|
+
name=model_name,
|
|
470
|
+
provider="azure",
|
|
471
|
+
description=config["description"],
|
|
472
|
+
max_tokens=config["max_tokens"],
|
|
473
|
+
cost_per_million_input=config["cost_per_million_input"],
|
|
474
|
+
cost_per_million_output=config["cost_per_million_output"],
|
|
475
|
+
swe_score=config["swe_score"],
|
|
476
|
+
available=True,
|
|
477
|
+
supports_tools=config["supports_tools"],
|
|
478
|
+
supports_multimodal=config["supports_multimodal"],
|
|
479
|
+
latest_version="gpt-4o" in self.azure_deployment.lower(),
|
|
480
|
+
context_window=config["max_tokens"],
|
|
481
|
+
is_free=False,
|
|
482
|
+
release_date=datetime(2024, 5, 1), # Approximate Azure availability
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
# Update SWE rating based on score
|
|
486
|
+
enhanced_info.update_swe_rating()
|
|
487
|
+
|
|
488
|
+
return enhanced_info
|
|
489
|
+
|
|
490
|
+
def get_available_models(self) -> List[EnhancedModelInfo]:
|
|
491
|
+
"""
|
|
492
|
+
Get list of available Azure OpenAI models.
|
|
493
|
+
|
|
494
|
+
Note: This returns common Azure deployment configurations.
|
|
495
|
+
Actual availability depends on your Azure subscription and region.
|
|
496
|
+
"""
|
|
497
|
+
models = [
|
|
498
|
+
EnhancedModelInfo(
|
|
499
|
+
name="gpt-4",
|
|
500
|
+
provider="azure",
|
|
501
|
+
description="GPT-4 on Azure - Most capable model",
|
|
502
|
+
max_tokens=8192,
|
|
503
|
+
cost_per_million_input=30.0,
|
|
504
|
+
cost_per_million_output=60.0,
|
|
505
|
+
swe_score=67.0,
|
|
506
|
+
available=True,
|
|
507
|
+
supports_tools=True,
|
|
508
|
+
supports_multimodal=False,
|
|
509
|
+
context_window=8192,
|
|
510
|
+
is_free=False,
|
|
511
|
+
release_date=datetime(2024, 5, 1),
|
|
512
|
+
),
|
|
513
|
+
EnhancedModelInfo(
|
|
514
|
+
name="gpt-4-turbo",
|
|
515
|
+
provider="azure",
|
|
516
|
+
description="GPT-4 Turbo on Azure - Enhanced performance",
|
|
517
|
+
max_tokens=128000,
|
|
518
|
+
cost_per_million_input=10.0,
|
|
519
|
+
cost_per_million_output=30.0,
|
|
520
|
+
swe_score=69.1,
|
|
521
|
+
available=True,
|
|
522
|
+
supports_tools=True,
|
|
523
|
+
supports_multimodal=True,
|
|
524
|
+
context_window=128000,
|
|
525
|
+
is_free=False,
|
|
526
|
+
release_date=datetime(2024, 5, 1),
|
|
527
|
+
),
|
|
528
|
+
EnhancedModelInfo(
|
|
529
|
+
name="gpt-4o",
|
|
530
|
+
provider="azure",
|
|
531
|
+
description="GPT-4o on Azure - Optimized for speed and efficiency",
|
|
532
|
+
max_tokens=128000,
|
|
533
|
+
cost_per_million_input=5.0,
|
|
534
|
+
cost_per_million_output=15.0,
|
|
535
|
+
swe_score=71.2,
|
|
536
|
+
available=True,
|
|
537
|
+
supports_tools=True,
|
|
538
|
+
supports_multimodal=True,
|
|
539
|
+
latest_version=True,
|
|
540
|
+
context_window=128000,
|
|
541
|
+
is_free=False,
|
|
542
|
+
release_date=datetime(2024, 5, 1),
|
|
543
|
+
),
|
|
544
|
+
EnhancedModelInfo(
|
|
545
|
+
name="gpt-35-turbo",
|
|
546
|
+
provider="azure",
|
|
547
|
+
description="GPT-3.5 Turbo on Azure - Fast and efficient",
|
|
548
|
+
max_tokens=4096,
|
|
549
|
+
cost_per_million_input=0.5,
|
|
550
|
+
cost_per_million_output=1.5,
|
|
551
|
+
swe_score=48.1,
|
|
552
|
+
available=True,
|
|
553
|
+
supports_tools=True,
|
|
554
|
+
supports_multimodal=False,
|
|
555
|
+
context_window=4096,
|
|
556
|
+
is_free=False,
|
|
557
|
+
release_date=datetime(2024, 5, 1),
|
|
558
|
+
),
|
|
559
|
+
EnhancedModelInfo(
|
|
560
|
+
name="gpt-35-turbo-16k",
|
|
561
|
+
provider="azure",
|
|
562
|
+
description="GPT-3.5 Turbo 16K on Azure - Extended context",
|
|
563
|
+
max_tokens=16384,
|
|
564
|
+
cost_per_million_input=3.0,
|
|
565
|
+
cost_per_million_output=4.0,
|
|
566
|
+
swe_score=48.1,
|
|
567
|
+
available=True,
|
|
568
|
+
supports_tools=True,
|
|
569
|
+
supports_multimodal=False,
|
|
570
|
+
context_window=16384,
|
|
571
|
+
is_free=False,
|
|
572
|
+
release_date=datetime(2024, 5, 1),
|
|
573
|
+
),
|
|
574
|
+
]
|
|
575
|
+
|
|
576
|
+
# Update SWE ratings for all models
|
|
577
|
+
for model in models:
|
|
578
|
+
model.update_swe_rating()
|
|
579
|
+
|
|
580
|
+
return models
|
|
581
|
+
|
|
582
|
+
def supports_tools(self) -> bool:
|
|
583
|
+
"""
|
|
584
|
+
Check if Azure OpenAI provider supports tool calling.
|
|
585
|
+
|
|
586
|
+
Returns:
|
|
587
|
+
True - Azure OpenAI supports function calling
|
|
588
|
+
"""
|
|
589
|
+
return True
|
|
590
|
+
|
|
591
|
+
def supports_multimodal(self) -> bool:
|
|
592
|
+
"""
|
|
593
|
+
Check if Azure OpenAI provider supports multimodal inputs.
|
|
594
|
+
|
|
595
|
+
Returns:
|
|
596
|
+
True for GPT-4 models with vision support
|
|
597
|
+
"""
|
|
598
|
+
vision_models = ["gpt-4-turbo", "gpt-4o", "gpt-4-vision"]
|
|
599
|
+
return any(model in self.azure_deployment.lower() for model in vision_models)
|
|
600
|
+
|
|
601
|
+
def supports_streaming(self) -> bool:
|
|
602
|
+
"""
|
|
603
|
+
Check if Azure OpenAI provider supports streaming responses.
|
|
604
|
+
|
|
605
|
+
Returns:
|
|
606
|
+
True - Azure OpenAI supports streaming
|
|
607
|
+
"""
|
|
608
|
+
return True
|