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,617 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Google Vertex AI provider implementation for Omnimancer.
|
|
3
|
+
|
|
4
|
+
This module provides the Google Vertex AI provider implementation using Vertex AI's API
|
|
5
|
+
with support for Google Cloud project configuration, location settings, and service account authentication.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Dict, List
|
|
11
|
+
|
|
12
|
+
import httpx
|
|
13
|
+
|
|
14
|
+
from ..core.models import (
|
|
15
|
+
ChatContext,
|
|
16
|
+
ChatResponse,
|
|
17
|
+
EnhancedModelInfo,
|
|
18
|
+
ToolCall,
|
|
19
|
+
ToolDefinition,
|
|
20
|
+
)
|
|
21
|
+
from ..utils.errors import (
|
|
22
|
+
AuthenticationError,
|
|
23
|
+
ModelNotFoundError,
|
|
24
|
+
NetworkError,
|
|
25
|
+
ProviderError,
|
|
26
|
+
RateLimitError,
|
|
27
|
+
)
|
|
28
|
+
from .base import BaseProvider
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class VertexAIProvider(BaseProvider):
|
|
32
|
+
"""
|
|
33
|
+
Google Vertex AI provider implementation using Vertex AI's API.
|
|
34
|
+
|
|
35
|
+
Supports Google Cloud project and location configuration,
|
|
36
|
+
service account authentication, and Gemini models.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, api_key: str, model: str = "", **kwargs):
|
|
40
|
+
"""
|
|
41
|
+
Initialize Vertex AI provider.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
api_key: Google Cloud API key or service account key
|
|
45
|
+
model: Vertex AI model to use (e.g., 'gemini-1.5-pro', 'gemini-1.5-flash', 'gemini-pro')
|
|
46
|
+
**kwargs: Additional configuration including Vertex AI-specific settings
|
|
47
|
+
"""
|
|
48
|
+
super().__init__(api_key, model or "gemini-1.5-pro", **kwargs)
|
|
49
|
+
|
|
50
|
+
# Vertex AI-specific configuration
|
|
51
|
+
self.vertex_project = kwargs.get("vertex_project")
|
|
52
|
+
self.vertex_location = kwargs.get("vertex_location", "us-central1")
|
|
53
|
+
self.service_account_path = kwargs.get("service_account_path")
|
|
54
|
+
|
|
55
|
+
# Standard parameters
|
|
56
|
+
self.max_tokens = kwargs.get("max_tokens", 8192)
|
|
57
|
+
self.temperature = kwargs.get("temperature", 0.7)
|
|
58
|
+
self.top_p = kwargs.get("top_p", 0.95)
|
|
59
|
+
self.top_k = kwargs.get("top_k", 40)
|
|
60
|
+
|
|
61
|
+
# Safety settings
|
|
62
|
+
self.safety_settings = kwargs.get("safety_settings", {})
|
|
63
|
+
|
|
64
|
+
# Validate required configuration
|
|
65
|
+
if not self.vertex_project:
|
|
66
|
+
raise ValueError("vertex_project is required for Vertex AI provider")
|
|
67
|
+
|
|
68
|
+
# Set up authentication
|
|
69
|
+
self._setup_authentication()
|
|
70
|
+
|
|
71
|
+
# Build base URL
|
|
72
|
+
self.base_url = f"https://{self.vertex_location}-aiplatform.googleapis.com/v1/projects/{self.vertex_project}/locations/{self.vertex_location}/publishers/google/models"
|
|
73
|
+
|
|
74
|
+
def _setup_authentication(self):
|
|
75
|
+
"""Set up Google Cloud authentication."""
|
|
76
|
+
if self.service_account_path:
|
|
77
|
+
# Use service account file
|
|
78
|
+
if os.path.exists(self.service_account_path):
|
|
79
|
+
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = self.service_account_path
|
|
80
|
+
else:
|
|
81
|
+
raise ValueError(
|
|
82
|
+
f"Service account file not found: {self.service_account_path}"
|
|
83
|
+
)
|
|
84
|
+
elif self.api_key and self.api_key != "":
|
|
85
|
+
# Use API key (for testing or specific configurations)
|
|
86
|
+
self.auth_token = self.api_key
|
|
87
|
+
else:
|
|
88
|
+
# Try to use default credentials
|
|
89
|
+
try:
|
|
90
|
+
# This would normally use Google's auth libraries
|
|
91
|
+
# For now, we'll require explicit configuration
|
|
92
|
+
raise ValueError(
|
|
93
|
+
"Either service_account_path or api_key must be provided for Vertex AI"
|
|
94
|
+
)
|
|
95
|
+
except Exception as e:
|
|
96
|
+
raise ValueError(f"Failed to set up Vertex AI authentication: {e}")
|
|
97
|
+
|
|
98
|
+
async def send_message(self, message: str, context: ChatContext) -> ChatResponse:
|
|
99
|
+
"""
|
|
100
|
+
Send a message to Vertex AI API.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
message: User message
|
|
104
|
+
context: Conversation context
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
ChatResponse with Vertex AI's reply
|
|
108
|
+
"""
|
|
109
|
+
try:
|
|
110
|
+
# Prepare request for Vertex AI
|
|
111
|
+
request_data = self._prepare_vertex_request(message, context)
|
|
112
|
+
|
|
113
|
+
# Build URL for the specific model
|
|
114
|
+
url = f"{self.base_url}/{self.model}:generateContent"
|
|
115
|
+
|
|
116
|
+
# Make API request
|
|
117
|
+
async with httpx.AsyncClient() as client:
|
|
118
|
+
response = await client.post(
|
|
119
|
+
url,
|
|
120
|
+
headers=self._get_auth_headers(),
|
|
121
|
+
json=request_data,
|
|
122
|
+
timeout=30.0,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Handle response
|
|
126
|
+
return self._handle_response(response)
|
|
127
|
+
|
|
128
|
+
except httpx.TimeoutException:
|
|
129
|
+
raise NetworkError("Request to Vertex AI API timed out")
|
|
130
|
+
except httpx.RequestError as e:
|
|
131
|
+
raise NetworkError(f"Network error: {e}")
|
|
132
|
+
except Exception as e:
|
|
133
|
+
raise ProviderError(f"Unexpected error: {e}")
|
|
134
|
+
|
|
135
|
+
async def send_message_with_tools(
|
|
136
|
+
self,
|
|
137
|
+
message: str,
|
|
138
|
+
context: ChatContext,
|
|
139
|
+
available_tools: List[ToolDefinition],
|
|
140
|
+
) -> ChatResponse:
|
|
141
|
+
"""
|
|
142
|
+
Send a message with available tools for Vertex AI to use.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
message: User message
|
|
146
|
+
context: Conversation context
|
|
147
|
+
available_tools: List of tools available for the model to use
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
ChatResponse with Vertex AI's reply and any tool calls
|
|
151
|
+
"""
|
|
152
|
+
try:
|
|
153
|
+
# Prepare request for Vertex AI with tools
|
|
154
|
+
request_data = self._prepare_vertex_request_with_tools(
|
|
155
|
+
message, context, available_tools
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Build URL for the specific model
|
|
159
|
+
url = f"{self.base_url}/{self.model}:generateContent"
|
|
160
|
+
|
|
161
|
+
# Make API request
|
|
162
|
+
async with httpx.AsyncClient() as client:
|
|
163
|
+
response = await client.post(
|
|
164
|
+
url,
|
|
165
|
+
headers=self._get_auth_headers(),
|
|
166
|
+
json=request_data,
|
|
167
|
+
timeout=30.0,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Handle response with tool calls
|
|
171
|
+
return self._handle_response_with_tools(response)
|
|
172
|
+
|
|
173
|
+
except httpx.TimeoutException:
|
|
174
|
+
raise NetworkError("Request to Vertex AI API timed out")
|
|
175
|
+
except httpx.RequestError as e:
|
|
176
|
+
raise NetworkError(f"Network error: {e}")
|
|
177
|
+
except Exception as e:
|
|
178
|
+
raise ProviderError(f"Unexpected error: {e}")
|
|
179
|
+
|
|
180
|
+
async def validate_credentials(self) -> bool:
|
|
181
|
+
"""
|
|
182
|
+
Validate Vertex AI credentials by making a test request.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
True if credentials are valid
|
|
186
|
+
"""
|
|
187
|
+
try:
|
|
188
|
+
url = f"{self.base_url}/{self.model}:generateContent"
|
|
189
|
+
|
|
190
|
+
test_request = {
|
|
191
|
+
"contents": [{"role": "user", "parts": [{"text": "Hi"}]}],
|
|
192
|
+
"generationConfig": {
|
|
193
|
+
"maxOutputTokens": 10,
|
|
194
|
+
"temperature": 0.1,
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async with httpx.AsyncClient() as client:
|
|
199
|
+
response = await client.post(
|
|
200
|
+
url,
|
|
201
|
+
headers=self._get_auth_headers(),
|
|
202
|
+
json=test_request,
|
|
203
|
+
timeout=10.0,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
return response.status_code == 200
|
|
207
|
+
|
|
208
|
+
except Exception:
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
def _get_auth_headers(self) -> Dict[str, str]:
|
|
212
|
+
"""
|
|
213
|
+
Get authentication headers for Vertex AI API.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
Dictionary of headers
|
|
217
|
+
"""
|
|
218
|
+
headers = {"Content-Type": "application/json"}
|
|
219
|
+
|
|
220
|
+
if hasattr(self, "auth_token") and self.auth_token:
|
|
221
|
+
headers["Authorization"] = f"Bearer {self.auth_token}"
|
|
222
|
+
|
|
223
|
+
return headers
|
|
224
|
+
|
|
225
|
+
def _prepare_vertex_request(self, message: str, context: ChatContext) -> Dict:
|
|
226
|
+
"""
|
|
227
|
+
Prepare request data for Vertex AI API.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
message: Current user message
|
|
231
|
+
context: Conversation context
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
Request data formatted for Vertex AI API
|
|
235
|
+
"""
|
|
236
|
+
contents = []
|
|
237
|
+
|
|
238
|
+
# Add context messages
|
|
239
|
+
for msg in context.messages:
|
|
240
|
+
role = "user" if msg.role.value == "user" else "model"
|
|
241
|
+
contents.append({"role": role, "parts": [{"text": msg.content}]})
|
|
242
|
+
|
|
243
|
+
# Add current message
|
|
244
|
+
contents.append({"role": "user", "parts": [{"text": message}]})
|
|
245
|
+
|
|
246
|
+
request_data = {
|
|
247
|
+
"contents": contents,
|
|
248
|
+
"generationConfig": {
|
|
249
|
+
"maxOutputTokens": self.max_tokens,
|
|
250
|
+
"temperature": self.temperature,
|
|
251
|
+
"topP": self.top_p,
|
|
252
|
+
"topK": self.top_k,
|
|
253
|
+
},
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
# Add safety settings if configured
|
|
257
|
+
if self.safety_settings:
|
|
258
|
+
request_data["safetySettings"] = self._format_safety_settings()
|
|
259
|
+
|
|
260
|
+
return request_data
|
|
261
|
+
|
|
262
|
+
def _prepare_vertex_request_with_tools(
|
|
263
|
+
self, message: str, context: ChatContext, tools: List[ToolDefinition]
|
|
264
|
+
) -> Dict:
|
|
265
|
+
"""
|
|
266
|
+
Prepare request data for Vertex AI API with tools.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
message: Current user message
|
|
270
|
+
context: Conversation context
|
|
271
|
+
tools: List of available tools
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Request data formatted for Vertex AI API with tools
|
|
275
|
+
"""
|
|
276
|
+
request_data = self._prepare_vertex_request(message, context)
|
|
277
|
+
|
|
278
|
+
# Add tools in Vertex AI format
|
|
279
|
+
if tools:
|
|
280
|
+
request_data["tools"] = [
|
|
281
|
+
self._convert_tool_to_vertex_format(tool) for tool in tools
|
|
282
|
+
]
|
|
283
|
+
|
|
284
|
+
return request_data
|
|
285
|
+
|
|
286
|
+
def _convert_tool_to_vertex_format(self, tool: ToolDefinition) -> Dict:
|
|
287
|
+
"""
|
|
288
|
+
Convert tool definition to Vertex AI format.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
tool: Tool definition
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
Tool formatted for Vertex AI API
|
|
295
|
+
"""
|
|
296
|
+
return {
|
|
297
|
+
"functionDeclarations": [
|
|
298
|
+
{
|
|
299
|
+
"name": tool.name,
|
|
300
|
+
"description": tool.description,
|
|
301
|
+
"parameters": tool.parameters,
|
|
302
|
+
}
|
|
303
|
+
]
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
def _format_safety_settings(self) -> List[Dict]:
|
|
307
|
+
"""
|
|
308
|
+
Format safety settings for Vertex AI API.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
List of safety settings
|
|
312
|
+
"""
|
|
313
|
+
safety_settings = []
|
|
314
|
+
|
|
315
|
+
# Default safety categories
|
|
316
|
+
categories = [
|
|
317
|
+
"HARM_CATEGORY_HARASSMENT",
|
|
318
|
+
"HARM_CATEGORY_HATE_SPEECH",
|
|
319
|
+
"HARM_CATEGORY_SEXUALLY_EXPLICIT",
|
|
320
|
+
"HARM_CATEGORY_DANGEROUS_CONTENT",
|
|
321
|
+
]
|
|
322
|
+
|
|
323
|
+
for category in categories:
|
|
324
|
+
threshold = self.safety_settings.get(category, "BLOCK_MEDIUM_AND_ABOVE")
|
|
325
|
+
safety_settings.append({"category": category, "threshold": threshold})
|
|
326
|
+
|
|
327
|
+
return safety_settings
|
|
328
|
+
|
|
329
|
+
def _handle_response(self, response: httpx.Response) -> ChatResponse:
|
|
330
|
+
"""
|
|
331
|
+
Handle Vertex AI API response.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
response: HTTP response from Vertex AI API
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
ChatResponse object
|
|
338
|
+
|
|
339
|
+
Raises:
|
|
340
|
+
Various provider errors based on response status
|
|
341
|
+
"""
|
|
342
|
+
if response.status_code == 200:
|
|
343
|
+
data = response.json()
|
|
344
|
+
candidates = data.get("candidates", [])
|
|
345
|
+
|
|
346
|
+
if candidates and len(candidates) > 0:
|
|
347
|
+
candidate = candidates[0]
|
|
348
|
+
content = candidate.get("content", {})
|
|
349
|
+
parts = content.get("parts", [])
|
|
350
|
+
|
|
351
|
+
if parts and len(parts) > 0:
|
|
352
|
+
text = parts[0].get("text", "")
|
|
353
|
+
|
|
354
|
+
# Extract usage information
|
|
355
|
+
usage_metadata = data.get("usageMetadata", {})
|
|
356
|
+
total_tokens = usage_metadata.get("totalTokenCount", 0)
|
|
357
|
+
|
|
358
|
+
return ChatResponse(
|
|
359
|
+
content=text,
|
|
360
|
+
model_used=self.model,
|
|
361
|
+
tokens_used=total_tokens,
|
|
362
|
+
timestamp=datetime.now(),
|
|
363
|
+
)
|
|
364
|
+
else:
|
|
365
|
+
raise ProviderError("Empty parts in Vertex AI response")
|
|
366
|
+
else:
|
|
367
|
+
raise ProviderError("Empty candidates in Vertex AI response")
|
|
368
|
+
|
|
369
|
+
elif response.status_code == 401:
|
|
370
|
+
raise AuthenticationError("Invalid Vertex AI credentials")
|
|
371
|
+
elif response.status_code == 429:
|
|
372
|
+
raise RateLimitError("Vertex AI API rate limit exceeded")
|
|
373
|
+
elif response.status_code == 404:
|
|
374
|
+
raise ModelNotFoundError(f"Vertex AI model '{self.model}' not found")
|
|
375
|
+
else:
|
|
376
|
+
try:
|
|
377
|
+
error_data = response.json()
|
|
378
|
+
error_msg = error_data.get("error", {}).get("message", "Unknown error")
|
|
379
|
+
except:
|
|
380
|
+
error_msg = f"HTTP {response.status_code}"
|
|
381
|
+
|
|
382
|
+
raise ProviderError(f"Vertex AI API error: {error_msg}")
|
|
383
|
+
|
|
384
|
+
def _handle_response_with_tools(self, response: httpx.Response) -> ChatResponse:
|
|
385
|
+
"""
|
|
386
|
+
Handle Vertex AI API response with tool calls.
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
response: HTTP response from Vertex AI API
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
ChatResponse object with tool calls
|
|
393
|
+
"""
|
|
394
|
+
if response.status_code == 200:
|
|
395
|
+
data = response.json()
|
|
396
|
+
candidates = data.get("candidates", [])
|
|
397
|
+
|
|
398
|
+
if candidates and len(candidates) > 0:
|
|
399
|
+
candidate = candidates[0]
|
|
400
|
+
content = candidate.get("content", {})
|
|
401
|
+
parts = content.get("parts", [])
|
|
402
|
+
|
|
403
|
+
text_content = ""
|
|
404
|
+
tool_calls = []
|
|
405
|
+
|
|
406
|
+
for part in parts:
|
|
407
|
+
if "text" in part:
|
|
408
|
+
text_content += part["text"]
|
|
409
|
+
elif "functionCall" in part:
|
|
410
|
+
func_call = part["functionCall"]
|
|
411
|
+
tool_calls.append(
|
|
412
|
+
ToolCall(
|
|
413
|
+
name=func_call.get("name", ""),
|
|
414
|
+
arguments=func_call.get("args", {}),
|
|
415
|
+
)
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
# Extract usage information
|
|
419
|
+
usage_metadata = data.get("usageMetadata", {})
|
|
420
|
+
total_tokens = usage_metadata.get("totalTokenCount", 0)
|
|
421
|
+
|
|
422
|
+
return ChatResponse(
|
|
423
|
+
content=text_content,
|
|
424
|
+
model_used=self.model,
|
|
425
|
+
tokens_used=total_tokens,
|
|
426
|
+
tool_calls=tool_calls if tool_calls else None,
|
|
427
|
+
timestamp=datetime.now(),
|
|
428
|
+
)
|
|
429
|
+
else:
|
|
430
|
+
raise ProviderError("Empty candidates in Vertex AI response")
|
|
431
|
+
else:
|
|
432
|
+
return self._handle_response(response)
|
|
433
|
+
|
|
434
|
+
def get_model_info(self) -> EnhancedModelInfo:
|
|
435
|
+
"""
|
|
436
|
+
Get information about the current Vertex AI model.
|
|
437
|
+
"""
|
|
438
|
+
model_configs = {
|
|
439
|
+
"gemini-1.5-pro": {
|
|
440
|
+
"description": "Gemini 1.5 Pro - Advanced reasoning and long context",
|
|
441
|
+
"max_tokens": 2097152,
|
|
442
|
+
"cost_per_million_input": 1.25,
|
|
443
|
+
"cost_per_million_output": 5.0,
|
|
444
|
+
"swe_score": 71.9,
|
|
445
|
+
"supports_tools": True,
|
|
446
|
+
"supports_multimodal": True,
|
|
447
|
+
},
|
|
448
|
+
"gemini-1.5-flash": {
|
|
449
|
+
"description": "Gemini 1.5 Flash - Fast and efficient",
|
|
450
|
+
"max_tokens": 1048576,
|
|
451
|
+
"cost_per_million_input": 0.075,
|
|
452
|
+
"cost_per_million_output": 0.3,
|
|
453
|
+
"swe_score": 61.5,
|
|
454
|
+
"supports_tools": True,
|
|
455
|
+
"supports_multimodal": True,
|
|
456
|
+
},
|
|
457
|
+
"gemini-1.0-pro": {
|
|
458
|
+
"description": "Gemini 1.0 Pro - Balanced performance",
|
|
459
|
+
"max_tokens": 32768,
|
|
460
|
+
"cost_per_million_input": 0.5,
|
|
461
|
+
"cost_per_million_output": 1.5,
|
|
462
|
+
"swe_score": 53.2,
|
|
463
|
+
"supports_tools": True,
|
|
464
|
+
"supports_multimodal": False,
|
|
465
|
+
},
|
|
466
|
+
"gemini-1.0-pro-vision": {
|
|
467
|
+
"description": "Gemini 1.0 Pro Vision - Multimodal capabilities",
|
|
468
|
+
"max_tokens": 16384,
|
|
469
|
+
"cost_per_million_input": 0.25,
|
|
470
|
+
"cost_per_million_output": 0.5,
|
|
471
|
+
"swe_score": 53.2,
|
|
472
|
+
"supports_tools": False,
|
|
473
|
+
"supports_multimodal": True,
|
|
474
|
+
},
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
config = model_configs.get(
|
|
478
|
+
self.model,
|
|
479
|
+
{
|
|
480
|
+
"description": f"Vertex AI model {self.model}",
|
|
481
|
+
"max_tokens": 32768,
|
|
482
|
+
"cost_per_million_input": 1.0,
|
|
483
|
+
"cost_per_million_output": 3.0,
|
|
484
|
+
"swe_score": 55.0,
|
|
485
|
+
"supports_tools": True,
|
|
486
|
+
"supports_multimodal": True,
|
|
487
|
+
},
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
enhanced_info = EnhancedModelInfo(
|
|
491
|
+
name=self.model,
|
|
492
|
+
provider="vertex",
|
|
493
|
+
description=config["description"],
|
|
494
|
+
max_tokens=config["max_tokens"],
|
|
495
|
+
cost_per_million_input=config["cost_per_million_input"],
|
|
496
|
+
cost_per_million_output=config["cost_per_million_output"],
|
|
497
|
+
swe_score=config["swe_score"],
|
|
498
|
+
available=True,
|
|
499
|
+
supports_tools=config["supports_tools"],
|
|
500
|
+
supports_multimodal=config["supports_multimodal"],
|
|
501
|
+
latest_version=self.model == "gemini-1.5-pro",
|
|
502
|
+
context_window=config["max_tokens"],
|
|
503
|
+
is_free=False,
|
|
504
|
+
release_date=datetime(2024, 5, 1), # Approximate release date
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
# Update SWE rating based on score
|
|
508
|
+
enhanced_info.update_swe_rating()
|
|
509
|
+
|
|
510
|
+
return enhanced_info
|
|
511
|
+
|
|
512
|
+
def get_available_models(self) -> List[EnhancedModelInfo]:
|
|
513
|
+
"""
|
|
514
|
+
Get list of available Vertex AI models.
|
|
515
|
+
"""
|
|
516
|
+
models = [
|
|
517
|
+
EnhancedModelInfo(
|
|
518
|
+
name="gemini-1.5-pro",
|
|
519
|
+
provider="vertex",
|
|
520
|
+
description="Gemini 1.5 Pro - Advanced reasoning and long context",
|
|
521
|
+
max_tokens=2097152,
|
|
522
|
+
cost_per_million_input=1.25,
|
|
523
|
+
cost_per_million_output=5.0,
|
|
524
|
+
swe_score=71.9,
|
|
525
|
+
available=True,
|
|
526
|
+
supports_tools=True,
|
|
527
|
+
supports_multimodal=True,
|
|
528
|
+
latest_version=True,
|
|
529
|
+
context_window=2097152,
|
|
530
|
+
is_free=False,
|
|
531
|
+
release_date=datetime(2024, 5, 1),
|
|
532
|
+
),
|
|
533
|
+
EnhancedModelInfo(
|
|
534
|
+
name="gemini-1.5-flash",
|
|
535
|
+
provider="vertex",
|
|
536
|
+
description="Gemini 1.5 Flash - Fast and efficient",
|
|
537
|
+
max_tokens=1048576,
|
|
538
|
+
cost_per_million_input=0.075,
|
|
539
|
+
cost_per_million_output=0.3,
|
|
540
|
+
swe_score=61.5,
|
|
541
|
+
available=True,
|
|
542
|
+
supports_tools=True,
|
|
543
|
+
supports_multimodal=True,
|
|
544
|
+
context_window=1048576,
|
|
545
|
+
is_free=False,
|
|
546
|
+
release_date=datetime(2024, 5, 1),
|
|
547
|
+
),
|
|
548
|
+
EnhancedModelInfo(
|
|
549
|
+
name="gemini-1.0-pro",
|
|
550
|
+
provider="vertex",
|
|
551
|
+
description="Gemini 1.0 Pro - Balanced performance",
|
|
552
|
+
max_tokens=32768,
|
|
553
|
+
cost_per_million_input=0.5,
|
|
554
|
+
cost_per_million_output=1.5,
|
|
555
|
+
swe_score=53.2,
|
|
556
|
+
available=True,
|
|
557
|
+
supports_tools=True,
|
|
558
|
+
supports_multimodal=False,
|
|
559
|
+
context_window=32768,
|
|
560
|
+
is_free=False,
|
|
561
|
+
release_date=datetime(2024, 2, 1),
|
|
562
|
+
),
|
|
563
|
+
EnhancedModelInfo(
|
|
564
|
+
name="gemini-1.0-pro-vision",
|
|
565
|
+
provider="vertex",
|
|
566
|
+
description="Gemini 1.0 Pro Vision - Multimodal capabilities",
|
|
567
|
+
max_tokens=16384,
|
|
568
|
+
cost_per_million_input=0.25,
|
|
569
|
+
cost_per_million_output=0.5,
|
|
570
|
+
swe_score=53.2,
|
|
571
|
+
available=True,
|
|
572
|
+
supports_tools=False,
|
|
573
|
+
supports_multimodal=True,
|
|
574
|
+
context_window=16384,
|
|
575
|
+
is_free=False,
|
|
576
|
+
release_date=datetime(2024, 2, 1),
|
|
577
|
+
),
|
|
578
|
+
]
|
|
579
|
+
|
|
580
|
+
# Update SWE ratings for all models
|
|
581
|
+
for model in models:
|
|
582
|
+
model.update_swe_rating()
|
|
583
|
+
|
|
584
|
+
return models
|
|
585
|
+
|
|
586
|
+
def supports_tools(self) -> bool:
|
|
587
|
+
"""
|
|
588
|
+
Check if Vertex AI provider supports tool calling.
|
|
589
|
+
|
|
590
|
+
Returns:
|
|
591
|
+
True for most models except vision-only variants
|
|
592
|
+
"""
|
|
593
|
+
non_tool_models = ["gemini-1.0-pro-vision"]
|
|
594
|
+
return self.model not in non_tool_models
|
|
595
|
+
|
|
596
|
+
def supports_multimodal(self) -> bool:
|
|
597
|
+
"""
|
|
598
|
+
Check if Vertex AI provider supports multimodal inputs.
|
|
599
|
+
|
|
600
|
+
Returns:
|
|
601
|
+
True for Gemini 1.5 models and vision variants
|
|
602
|
+
"""
|
|
603
|
+
multimodal_models = [
|
|
604
|
+
"gemini-1.5-pro",
|
|
605
|
+
"gemini-1.5-flash",
|
|
606
|
+
"gemini-1.0-pro-vision",
|
|
607
|
+
]
|
|
608
|
+
return any(model in self.model for model in multimodal_models)
|
|
609
|
+
|
|
610
|
+
def supports_streaming(self) -> bool:
|
|
611
|
+
"""
|
|
612
|
+
Check if Vertex AI provider supports streaming responses.
|
|
613
|
+
|
|
614
|
+
Returns:
|
|
615
|
+
True - Vertex AI supports streaming
|
|
616
|
+
"""
|
|
617
|
+
return True
|