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,711 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AWS Bedrock provider implementation for Omnimancer.
|
|
3
|
+
|
|
4
|
+
This module provides the AWS Bedrock provider implementation using AWS Bedrock's Converse API
|
|
5
|
+
with support for Claude models, API key authentication, and region configuration.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
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 BedrockProvider(BaseProvider):
|
|
32
|
+
"""
|
|
33
|
+
AWS Bedrock provider implementation using AWS Bedrock's Converse API.
|
|
34
|
+
|
|
35
|
+
Supports Claude models through Bedrock, API key authentication,
|
|
36
|
+
and region configuration.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, api_key: str, model: str = "", **kwargs):
|
|
40
|
+
"""
|
|
41
|
+
Initialize AWS Bedrock provider.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
api_key: AWS Bedrock API key (30-day key from AWS console)
|
|
45
|
+
model: Bedrock model ID or ARN to use (e.g., 'anthropic.claude-3-sonnet-20240229-v1:0'
|
|
46
|
+
or 'arn:aws:bedrock:us-west-2::foundation-model/meta.llama4-maverick-17b-instruct-v1:0')
|
|
47
|
+
**kwargs: Additional configuration including region settings
|
|
48
|
+
"""
|
|
49
|
+
# Validate API key is provided
|
|
50
|
+
if not api_key:
|
|
51
|
+
raise ValueError("API key is required for AWS Bedrock provider")
|
|
52
|
+
|
|
53
|
+
# Validate model is provided (Bedrock requires explicit model specification)
|
|
54
|
+
if not model:
|
|
55
|
+
raise ValueError(
|
|
56
|
+
"Model ID is required for AWS Bedrock provider. "
|
|
57
|
+
"Use a model ID (e.g., 'anthropic.claude-3-sonnet-20240229-v1:0') "
|
|
58
|
+
"or inference profile (e.g., 'us.anthropic.claude-3-sonnet-20240229-v1:0')"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
super().__init__(api_key, model, **kwargs)
|
|
62
|
+
|
|
63
|
+
# AWS-specific configuration
|
|
64
|
+
self.aws_region = kwargs.get("aws_region", "us-east-1")
|
|
65
|
+
|
|
66
|
+
# Standard parameters
|
|
67
|
+
self.max_tokens = kwargs.get("max_tokens", 4096)
|
|
68
|
+
self.temperature = kwargs.get("temperature", 0.7)
|
|
69
|
+
self.top_p = kwargs.get("top_p", 1.0)
|
|
70
|
+
self.top_k = kwargs.get("top_k", 250)
|
|
71
|
+
|
|
72
|
+
# Build base URL for Bedrock API key authentication
|
|
73
|
+
self.base_url = f"https://bedrock-runtime.{self.aws_region}.amazonaws.com"
|
|
74
|
+
|
|
75
|
+
def _is_arn(self, model_id: str) -> bool:
|
|
76
|
+
"""Check if the model ID is in ARN format."""
|
|
77
|
+
return model_id.startswith("arn:aws:bedrock:")
|
|
78
|
+
|
|
79
|
+
def _extract_model_id_from_arn(self, arn: str) -> str:
|
|
80
|
+
"""Extract the model ID from an ARN."""
|
|
81
|
+
if self._is_arn(arn):
|
|
82
|
+
# ARN format: arn:aws:bedrock:region::foundation-model/model-id
|
|
83
|
+
# or: arn:aws:bedrock:region::inference-profile/profile-id
|
|
84
|
+
return arn.split("/")[-1]
|
|
85
|
+
return arn
|
|
86
|
+
|
|
87
|
+
def _convert_to_arn(
|
|
88
|
+
self, model_id: str, model_type: str = "foundation-model"
|
|
89
|
+
) -> str:
|
|
90
|
+
"""Convert a model ID to ARN format for the current region."""
|
|
91
|
+
if self._is_arn(model_id):
|
|
92
|
+
return model_id
|
|
93
|
+
return f"arn:aws:bedrock:{self.aws_region}::{model_type}/{model_id}"
|
|
94
|
+
|
|
95
|
+
def _suggest_arn_conversion(self, model_id: str, error_message: str) -> dict:
|
|
96
|
+
"""Suggest ARN conversion when permission denied."""
|
|
97
|
+
if not self._is_arn(model_id):
|
|
98
|
+
foundation_arn = self._convert_to_arn(model_id, "foundation-model")
|
|
99
|
+
inference_arn = (
|
|
100
|
+
self._convert_to_arn(f"us.{model_id}", "inference-profile")
|
|
101
|
+
if not model_id.startswith("us.")
|
|
102
|
+
else self._convert_to_arn(model_id, "inference-profile")
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
"success": False,
|
|
107
|
+
"message": error_message,
|
|
108
|
+
"suggestion": f"Try using ARN format to specify the exact region. Suggestions:\n"
|
|
109
|
+
f" Foundation model: {foundation_arn}\n"
|
|
110
|
+
f" Inference profile: {inference_arn}",
|
|
111
|
+
"suggested_arns": {
|
|
112
|
+
"foundation_model": foundation_arn,
|
|
113
|
+
"inference_profile": inference_arn,
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
return {"success": False, "message": error_message}
|
|
117
|
+
|
|
118
|
+
async def send_message(self, message: str, context: ChatContext) -> ChatResponse:
|
|
119
|
+
"""
|
|
120
|
+
Send a message to AWS Bedrock API.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
message: User message
|
|
124
|
+
context: Conversation context
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
ChatResponse with Bedrock's reply
|
|
128
|
+
"""
|
|
129
|
+
try:
|
|
130
|
+
# Prepare request for Bedrock
|
|
131
|
+
request_body = self._prepare_bedrock_request(message, context)
|
|
132
|
+
|
|
133
|
+
# Build URL for the specific model
|
|
134
|
+
url = f"{self.base_url}/model/{self.model}/converse"
|
|
135
|
+
|
|
136
|
+
# Create headers with Bearer token authentication
|
|
137
|
+
headers = {
|
|
138
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
139
|
+
"Content-Type": "application/json",
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# Make API request
|
|
143
|
+
async with httpx.AsyncClient() as client:
|
|
144
|
+
response = await client.post(
|
|
145
|
+
url, headers=headers, content=request_body, timeout=30.0
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Handle response
|
|
149
|
+
return self._handle_response(response)
|
|
150
|
+
|
|
151
|
+
except httpx.TimeoutException:
|
|
152
|
+
raise NetworkError("Request to AWS Bedrock API timed out")
|
|
153
|
+
except httpx.RequestError as e:
|
|
154
|
+
raise NetworkError(f"Network error: {e}")
|
|
155
|
+
except Exception as e:
|
|
156
|
+
raise ProviderError(f"AWS Bedrock API error: {str(e)}")
|
|
157
|
+
|
|
158
|
+
async def send_message_with_tools(
|
|
159
|
+
self,
|
|
160
|
+
message: str,
|
|
161
|
+
context: ChatContext,
|
|
162
|
+
available_tools: List[ToolDefinition],
|
|
163
|
+
) -> ChatResponse:
|
|
164
|
+
"""
|
|
165
|
+
Send a message with available tools for Bedrock to use.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
message: User message
|
|
169
|
+
context: Conversation context
|
|
170
|
+
available_tools: List of tools available for the model to use
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
ChatResponse with Bedrock's reply and any tool calls
|
|
174
|
+
"""
|
|
175
|
+
try:
|
|
176
|
+
# Prepare request for Bedrock with tools
|
|
177
|
+
request_body = self._prepare_bedrock_request_with_tools(
|
|
178
|
+
message, context, available_tools
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Build URL for the specific model - use appropriate endpoint based on model format
|
|
182
|
+
if self._is_arn(self.model):
|
|
183
|
+
# For ARN format, extract the actual model ID for the URL
|
|
184
|
+
model_for_url = self._extract_model_id_from_arn(self.model)
|
|
185
|
+
url = f"{self.base_url}/model/{model_for_url}/converse"
|
|
186
|
+
else:
|
|
187
|
+
url = f"{self.base_url}/model/{self.model}/converse"
|
|
188
|
+
|
|
189
|
+
# Create headers with Bearer token authentication
|
|
190
|
+
headers = {
|
|
191
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
192
|
+
"Content-Type": "application/json",
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
# Make API request
|
|
196
|
+
async with httpx.AsyncClient() as client:
|
|
197
|
+
response = await client.post(
|
|
198
|
+
url, headers=headers, content=request_body, timeout=30.0
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# Handle response with tool calls
|
|
202
|
+
return self._handle_response_with_tools(response)
|
|
203
|
+
|
|
204
|
+
except httpx.TimeoutException:
|
|
205
|
+
raise NetworkError("Request to AWS Bedrock API timed out")
|
|
206
|
+
except httpx.RequestError as e:
|
|
207
|
+
raise NetworkError(f"Network error: {e}")
|
|
208
|
+
except Exception as e:
|
|
209
|
+
raise ProviderError(f"AWS Bedrock API error: {str(e)}")
|
|
210
|
+
|
|
211
|
+
async def validate_credentials(self) -> bool:
|
|
212
|
+
"""
|
|
213
|
+
Validate AWS Bedrock API key by checking API access (not model-specific).
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
True if API key is valid (can access Bedrock API)
|
|
217
|
+
"""
|
|
218
|
+
try:
|
|
219
|
+
# Test API key by listing foundation models (not model-specific)
|
|
220
|
+
url = f"https://bedrock.{self.aws_region}.amazonaws.com/foundation-models"
|
|
221
|
+
headers = {
|
|
222
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
223
|
+
"Content-Type": "application/json",
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async with httpx.AsyncClient() as client:
|
|
227
|
+
response = await client.get(url, headers=headers, timeout=10.0)
|
|
228
|
+
|
|
229
|
+
# API key is valid if we can access the foundation models endpoint
|
|
230
|
+
return response.status_code == 200
|
|
231
|
+
|
|
232
|
+
except Exception:
|
|
233
|
+
return False
|
|
234
|
+
|
|
235
|
+
async def validate_model_access(self) -> dict:
|
|
236
|
+
"""
|
|
237
|
+
Test access to the specific model configured.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Dict with 'success', 'message', and optional 'suggestion' keys
|
|
241
|
+
"""
|
|
242
|
+
try:
|
|
243
|
+
# Test the specific model with a minimal request
|
|
244
|
+
test_request = {
|
|
245
|
+
"messages": [{"role": "user", "content": [{"text": "Hi"}]}],
|
|
246
|
+
"inferenceConfig": {"maxTokens": 10, "temperature": 0.1},
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
# If using ARN format, include the model ARN in the request
|
|
250
|
+
if self._is_arn(self.model):
|
|
251
|
+
test_request["modelId"] = self.model
|
|
252
|
+
|
|
253
|
+
test_body = json.dumps(test_request)
|
|
254
|
+
|
|
255
|
+
# Build URL for validation - use appropriate endpoint based on model format
|
|
256
|
+
if self._is_arn(self.model):
|
|
257
|
+
model_for_url = self._extract_model_id_from_arn(self.model)
|
|
258
|
+
url = f"{self.base_url}/model/{model_for_url}/converse"
|
|
259
|
+
else:
|
|
260
|
+
url = f"{self.base_url}/model/{self.model}/converse"
|
|
261
|
+
|
|
262
|
+
headers = {
|
|
263
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
264
|
+
"Content-Type": "application/json",
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async with httpx.AsyncClient() as client:
|
|
268
|
+
response = await client.post(
|
|
269
|
+
url, headers=headers, content=test_body, timeout=10.0
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
if response.status_code == 200:
|
|
273
|
+
return {
|
|
274
|
+
"success": True,
|
|
275
|
+
"message": f"Model {self.model} is accessible",
|
|
276
|
+
}
|
|
277
|
+
else:
|
|
278
|
+
try:
|
|
279
|
+
error_data = response.json()
|
|
280
|
+
error_msg = error_data.get(
|
|
281
|
+
"message", error_data.get("Message", "Unknown error")
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
# Parse different error types and provide intelligent suggestions
|
|
285
|
+
if "not authorized" in error_msg or "explicit deny" in error_msg:
|
|
286
|
+
return self._suggest_arn_conversion(
|
|
287
|
+
self.model,
|
|
288
|
+
f"Access denied to model {self.model}. This may be due to SCP restrictions or regional access issues.",
|
|
289
|
+
)
|
|
290
|
+
elif (
|
|
291
|
+
"not supported" in error_msg or "inference profile" in error_msg
|
|
292
|
+
):
|
|
293
|
+
if not self._is_arn(self.model):
|
|
294
|
+
# Suggest ARN format for inference profile access
|
|
295
|
+
inference_arn = (
|
|
296
|
+
self._convert_to_arn(
|
|
297
|
+
f"us.{self.model}", "inference-profile"
|
|
298
|
+
)
|
|
299
|
+
if not self.model.startswith("us.")
|
|
300
|
+
else self._convert_to_arn(
|
|
301
|
+
self.model, "inference-profile"
|
|
302
|
+
)
|
|
303
|
+
)
|
|
304
|
+
return {
|
|
305
|
+
"success": False,
|
|
306
|
+
"message": f"Model {self.model} requires inference profile access",
|
|
307
|
+
"suggestion": f"Try using inference profile ARN: {inference_arn}",
|
|
308
|
+
}
|
|
309
|
+
else:
|
|
310
|
+
return {
|
|
311
|
+
"success": False,
|
|
312
|
+
"message": f"Model {self.model} requires inference profile access",
|
|
313
|
+
"suggestion": "Some models require using inference profiles instead of direct model IDs.",
|
|
314
|
+
}
|
|
315
|
+
elif "don't have access" in error_msg:
|
|
316
|
+
return self._suggest_arn_conversion(
|
|
317
|
+
self.model,
|
|
318
|
+
f"No access to model {self.model}. You may need to request access in the AWS Bedrock console.",
|
|
319
|
+
)
|
|
320
|
+
else:
|
|
321
|
+
return self._suggest_arn_conversion(
|
|
322
|
+
self.model, f"Model test failed: {error_msg}"
|
|
323
|
+
)
|
|
324
|
+
except:
|
|
325
|
+
return {
|
|
326
|
+
"success": False,
|
|
327
|
+
"message": f"Model test failed with HTTP {response.status_code}",
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
except Exception as e:
|
|
331
|
+
return {
|
|
332
|
+
"success": False,
|
|
333
|
+
"message": f"Failed to test model: {str(e)}",
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
def _prepare_bedrock_request(self, message: str, context: ChatContext) -> str:
|
|
337
|
+
"""
|
|
338
|
+
Prepare request body for AWS Bedrock Converse API.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
message: Current user message
|
|
342
|
+
context: Conversation context
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
JSON string formatted for Bedrock Converse API
|
|
346
|
+
"""
|
|
347
|
+
messages = []
|
|
348
|
+
|
|
349
|
+
# Add context messages
|
|
350
|
+
for msg in context.messages:
|
|
351
|
+
messages.append(
|
|
352
|
+
{"role": msg.role.value, "content": [{"text": msg.content}]}
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
# Add current message
|
|
356
|
+
messages.append({"role": "user", "content": [{"text": message}]})
|
|
357
|
+
|
|
358
|
+
request_data = {
|
|
359
|
+
"messages": messages,
|
|
360
|
+
"inferenceConfig": {
|
|
361
|
+
"maxTokens": self.max_tokens,
|
|
362
|
+
"temperature": self.temperature,
|
|
363
|
+
"topP": self.top_p,
|
|
364
|
+
},
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
# If using ARN format, include the model ARN in the request
|
|
368
|
+
if self._is_arn(self.model):
|
|
369
|
+
request_data["modelId"] = self.model
|
|
370
|
+
|
|
371
|
+
return json.dumps(request_data)
|
|
372
|
+
|
|
373
|
+
def _prepare_bedrock_request_with_tools(
|
|
374
|
+
self, message: str, context: ChatContext, tools: List[ToolDefinition]
|
|
375
|
+
) -> str:
|
|
376
|
+
"""
|
|
377
|
+
Prepare request body for AWS Bedrock Converse API with tools.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
message: Current user message
|
|
381
|
+
context: Conversation context
|
|
382
|
+
tools: List of available tools
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
JSON string formatted for Bedrock Converse API with tools
|
|
386
|
+
"""
|
|
387
|
+
messages = []
|
|
388
|
+
|
|
389
|
+
# Add context messages
|
|
390
|
+
for msg in context.messages:
|
|
391
|
+
messages.append(
|
|
392
|
+
{"role": msg.role.value, "content": [{"text": msg.content}]}
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
# Add current message
|
|
396
|
+
messages.append({"role": "user", "content": [{"text": message}]})
|
|
397
|
+
|
|
398
|
+
request_data = {
|
|
399
|
+
"messages": messages,
|
|
400
|
+
"inferenceConfig": {
|
|
401
|
+
"maxTokens": self.max_tokens,
|
|
402
|
+
"temperature": self.temperature,
|
|
403
|
+
"topP": self.top_p,
|
|
404
|
+
},
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
# Add tools in Bedrock Converse format
|
|
408
|
+
if tools:
|
|
409
|
+
request_data["toolConfig"] = {
|
|
410
|
+
"tools": [self._convert_tool_to_bedrock_format(tool) for tool in tools]
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return json.dumps(request_data)
|
|
414
|
+
|
|
415
|
+
def _convert_tool_to_bedrock_format(self, tool: ToolDefinition) -> Dict:
|
|
416
|
+
"""
|
|
417
|
+
Convert tool definition to Bedrock format.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
tool: Tool definition
|
|
421
|
+
|
|
422
|
+
Returns:
|
|
423
|
+
Tool formatted for Bedrock API
|
|
424
|
+
"""
|
|
425
|
+
return {
|
|
426
|
+
"name": tool.name,
|
|
427
|
+
"description": tool.description,
|
|
428
|
+
"input_schema": tool.parameters,
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
def _handle_response(self, response: httpx.Response) -> ChatResponse:
|
|
432
|
+
"""
|
|
433
|
+
Handle AWS Bedrock Converse API response.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
response: HTTP response from Bedrock Converse API
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
ChatResponse object
|
|
440
|
+
|
|
441
|
+
Raises:
|
|
442
|
+
Various provider errors based on response status
|
|
443
|
+
"""
|
|
444
|
+
if response.status_code == 200:
|
|
445
|
+
data = response.json()
|
|
446
|
+
|
|
447
|
+
# Handle Bedrock Converse API response format
|
|
448
|
+
if "output" in data:
|
|
449
|
+
output = data.get("output", {})
|
|
450
|
+
message = output.get("message", {})
|
|
451
|
+
content_blocks = message.get("content", [])
|
|
452
|
+
|
|
453
|
+
if content_blocks and len(content_blocks) > 0:
|
|
454
|
+
text = content_blocks[0].get("text", "")
|
|
455
|
+
usage = data.get("usage", {})
|
|
456
|
+
|
|
457
|
+
return ChatResponse(
|
|
458
|
+
content=text,
|
|
459
|
+
model_used=self.model,
|
|
460
|
+
tokens_used=usage.get("outputTokens", 0),
|
|
461
|
+
timestamp=datetime.now(),
|
|
462
|
+
)
|
|
463
|
+
else:
|
|
464
|
+
raise ProviderError("Empty content in Bedrock response")
|
|
465
|
+
else:
|
|
466
|
+
raise ProviderError("Invalid response format from Bedrock Converse API")
|
|
467
|
+
|
|
468
|
+
elif response.status_code == 401:
|
|
469
|
+
raise AuthenticationError("Invalid API key for Bedrock")
|
|
470
|
+
elif response.status_code == 429:
|
|
471
|
+
raise RateLimitError("AWS Bedrock API rate limit exceeded")
|
|
472
|
+
elif response.status_code == 404:
|
|
473
|
+
raise ModelNotFoundError(f"Bedrock model '{self.model}' not found")
|
|
474
|
+
else:
|
|
475
|
+
try:
|
|
476
|
+
error_data = response.json()
|
|
477
|
+
error_msg = error_data.get(
|
|
478
|
+
"message", error_data.get("Message", "Unknown error")
|
|
479
|
+
)
|
|
480
|
+
# Include more error details for debugging
|
|
481
|
+
error_code = error_data.get("__type", error_data.get("code", ""))
|
|
482
|
+
if error_code:
|
|
483
|
+
error_msg = f"{error_code}: {error_msg}"
|
|
484
|
+
except:
|
|
485
|
+
error_msg = f"HTTP {response.status_code}: {response.text[:200] if response.text else 'No response body'}"
|
|
486
|
+
|
|
487
|
+
raise ProviderError(f"AWS Bedrock API error: {error_msg}")
|
|
488
|
+
|
|
489
|
+
def _handle_response_with_tools(self, response: httpx.Response) -> ChatResponse:
|
|
490
|
+
"""
|
|
491
|
+
Handle AWS Bedrock Converse API response with tool calls.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
response: HTTP response from Bedrock Converse API
|
|
495
|
+
|
|
496
|
+
Returns:
|
|
497
|
+
ChatResponse object with tool calls
|
|
498
|
+
"""
|
|
499
|
+
if response.status_code == 200:
|
|
500
|
+
data = response.json()
|
|
501
|
+
|
|
502
|
+
if "output" in data:
|
|
503
|
+
output = data.get("output", {})
|
|
504
|
+
message = output.get("message", {})
|
|
505
|
+
content_blocks = message.get("content", [])
|
|
506
|
+
text_content = ""
|
|
507
|
+
tool_calls = []
|
|
508
|
+
|
|
509
|
+
for block in content_blocks:
|
|
510
|
+
if block.get("type") == "text":
|
|
511
|
+
text_content += block.get("text", "")
|
|
512
|
+
elif block.get("type") == "toolUse":
|
|
513
|
+
tool_calls.append(
|
|
514
|
+
ToolCall(
|
|
515
|
+
name=block.get("name", ""),
|
|
516
|
+
arguments=block.get("input", {}),
|
|
517
|
+
)
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
usage = data.get("usage", {})
|
|
521
|
+
|
|
522
|
+
return ChatResponse(
|
|
523
|
+
content=text_content,
|
|
524
|
+
model_used=self.model,
|
|
525
|
+
tokens_used=usage.get("outputTokens", 0),
|
|
526
|
+
tool_calls=tool_calls if tool_calls else None,
|
|
527
|
+
timestamp=datetime.now(),
|
|
528
|
+
)
|
|
529
|
+
else:
|
|
530
|
+
raise ProviderError("Invalid response format from Bedrock Converse API")
|
|
531
|
+
else:
|
|
532
|
+
return self._handle_response(response)
|
|
533
|
+
|
|
534
|
+
def get_model_info(self) -> EnhancedModelInfo:
|
|
535
|
+
"""
|
|
536
|
+
Get information about the current Bedrock model.
|
|
537
|
+
"""
|
|
538
|
+
model_configs = {
|
|
539
|
+
"anthropic.claude-3-sonnet-20240229-v1:0": {
|
|
540
|
+
"description": "Claude 3 Sonnet on AWS Bedrock - Balanced performance",
|
|
541
|
+
"max_tokens": 200000,
|
|
542
|
+
"cost_per_million_input": 3.0,
|
|
543
|
+
"cost_per_million_output": 15.0,
|
|
544
|
+
"swe_score": 73.0,
|
|
545
|
+
"supports_tools": True,
|
|
546
|
+
"supports_multimodal": True,
|
|
547
|
+
},
|
|
548
|
+
"anthropic.claude-3-haiku-20240307-v1:0": {
|
|
549
|
+
"description": "Claude 3 Haiku on AWS Bedrock - Fast and efficient",
|
|
550
|
+
"max_tokens": 200000,
|
|
551
|
+
"cost_per_million_input": 0.25,
|
|
552
|
+
"cost_per_million_output": 1.25,
|
|
553
|
+
"swe_score": 75.9,
|
|
554
|
+
"supports_tools": True,
|
|
555
|
+
"supports_multimodal": True,
|
|
556
|
+
},
|
|
557
|
+
"anthropic.claude-3-opus-20240229-v1:0": {
|
|
558
|
+
"description": "Claude 3 Opus on AWS Bedrock - Most capable",
|
|
559
|
+
"max_tokens": 200000,
|
|
560
|
+
"cost_per_million_input": 15.0,
|
|
561
|
+
"cost_per_million_output": 75.0,
|
|
562
|
+
"swe_score": 84.9,
|
|
563
|
+
"supports_tools": True,
|
|
564
|
+
"supports_multimodal": True,
|
|
565
|
+
},
|
|
566
|
+
"anthropic.claude-3-5-sonnet-20241022-v2:0": {
|
|
567
|
+
"description": "Claude 3.5 Sonnet on AWS Bedrock - Latest and most capable",
|
|
568
|
+
"max_tokens": 200000,
|
|
569
|
+
"cost_per_million_input": 3.0,
|
|
570
|
+
"cost_per_million_output": 15.0,
|
|
571
|
+
"swe_score": 88.7,
|
|
572
|
+
"supports_tools": True,
|
|
573
|
+
"supports_multimodal": True,
|
|
574
|
+
},
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
config = model_configs.get(
|
|
578
|
+
self.model,
|
|
579
|
+
{
|
|
580
|
+
"description": f"AWS Bedrock model {self.model}",
|
|
581
|
+
"max_tokens": 200000,
|
|
582
|
+
"cost_per_million_input": 3.0,
|
|
583
|
+
"cost_per_million_output": 15.0,
|
|
584
|
+
"swe_score": 70.0,
|
|
585
|
+
"supports_tools": True,
|
|
586
|
+
"supports_multimodal": True,
|
|
587
|
+
},
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
enhanced_info = EnhancedModelInfo(
|
|
591
|
+
name=self.model,
|
|
592
|
+
provider="bedrock",
|
|
593
|
+
description=config["description"],
|
|
594
|
+
max_tokens=config["max_tokens"],
|
|
595
|
+
cost_per_million_input=config["cost_per_million_input"],
|
|
596
|
+
cost_per_million_output=config["cost_per_million_output"],
|
|
597
|
+
swe_score=config["swe_score"],
|
|
598
|
+
available=True,
|
|
599
|
+
supports_tools=config["supports_tools"],
|
|
600
|
+
supports_multimodal=config["supports_multimodal"],
|
|
601
|
+
latest_version="claude-3-5-sonnet" in self.model,
|
|
602
|
+
context_window=config["max_tokens"],
|
|
603
|
+
is_free=False,
|
|
604
|
+
release_date=datetime(2024, 6, 1), # Approximate Bedrock availability
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
# Update SWE rating based on score
|
|
608
|
+
enhanced_info.update_swe_rating()
|
|
609
|
+
|
|
610
|
+
return enhanced_info
|
|
611
|
+
|
|
612
|
+
def get_available_models(self) -> List[EnhancedModelInfo]:
|
|
613
|
+
"""
|
|
614
|
+
Get list of available AWS Bedrock models.
|
|
615
|
+
"""
|
|
616
|
+
models = [
|
|
617
|
+
EnhancedModelInfo(
|
|
618
|
+
name="anthropic.claude-3-5-sonnet-20241022-v2:0",
|
|
619
|
+
provider="bedrock",
|
|
620
|
+
description="Claude 3.5 Sonnet on AWS Bedrock - Latest and most capable",
|
|
621
|
+
max_tokens=200000,
|
|
622
|
+
cost_per_million_input=3.0,
|
|
623
|
+
cost_per_million_output=15.0,
|
|
624
|
+
swe_score=88.7,
|
|
625
|
+
available=True,
|
|
626
|
+
supports_tools=True,
|
|
627
|
+
supports_multimodal=True,
|
|
628
|
+
latest_version=True,
|
|
629
|
+
context_window=200000,
|
|
630
|
+
is_free=False,
|
|
631
|
+
release_date=datetime(2024, 10, 1),
|
|
632
|
+
),
|
|
633
|
+
EnhancedModelInfo(
|
|
634
|
+
name="anthropic.claude-3-opus-20240229-v1:0",
|
|
635
|
+
provider="bedrock",
|
|
636
|
+
description="Claude 3 Opus on AWS Bedrock - Most capable",
|
|
637
|
+
max_tokens=200000,
|
|
638
|
+
cost_per_million_input=15.0,
|
|
639
|
+
cost_per_million_output=75.0,
|
|
640
|
+
swe_score=84.9,
|
|
641
|
+
available=True,
|
|
642
|
+
supports_tools=True,
|
|
643
|
+
supports_multimodal=True,
|
|
644
|
+
context_window=200000,
|
|
645
|
+
is_free=False,
|
|
646
|
+
release_date=datetime(2024, 6, 1),
|
|
647
|
+
),
|
|
648
|
+
EnhancedModelInfo(
|
|
649
|
+
name="anthropic.claude-3-sonnet-20240229-v1:0",
|
|
650
|
+
provider="bedrock",
|
|
651
|
+
description="Claude 3 Sonnet on AWS Bedrock - Balanced performance",
|
|
652
|
+
max_tokens=200000,
|
|
653
|
+
cost_per_million_input=3.0,
|
|
654
|
+
cost_per_million_output=15.0,
|
|
655
|
+
swe_score=73.0,
|
|
656
|
+
available=True,
|
|
657
|
+
supports_tools=True,
|
|
658
|
+
supports_multimodal=True,
|
|
659
|
+
context_window=200000,
|
|
660
|
+
is_free=False,
|
|
661
|
+
release_date=datetime(2024, 6, 1),
|
|
662
|
+
),
|
|
663
|
+
EnhancedModelInfo(
|
|
664
|
+
name="anthropic.claude-3-haiku-20240307-v1:0",
|
|
665
|
+
provider="bedrock",
|
|
666
|
+
description="Claude 3 Haiku on AWS Bedrock - Fast and efficient",
|
|
667
|
+
max_tokens=200000,
|
|
668
|
+
cost_per_million_input=0.25,
|
|
669
|
+
cost_per_million_output=1.25,
|
|
670
|
+
swe_score=75.9,
|
|
671
|
+
available=True,
|
|
672
|
+
supports_tools=True,
|
|
673
|
+
supports_multimodal=True,
|
|
674
|
+
context_window=200000,
|
|
675
|
+
is_free=False,
|
|
676
|
+
release_date=datetime(2024, 6, 1),
|
|
677
|
+
),
|
|
678
|
+
]
|
|
679
|
+
|
|
680
|
+
# Update SWE ratings for all models
|
|
681
|
+
for model in models:
|
|
682
|
+
model.update_swe_rating()
|
|
683
|
+
|
|
684
|
+
return models
|
|
685
|
+
|
|
686
|
+
def supports_tools(self) -> bool:
|
|
687
|
+
"""
|
|
688
|
+
Check if Bedrock provider supports tool calling.
|
|
689
|
+
|
|
690
|
+
Returns:
|
|
691
|
+
True - Claude models on Bedrock support tool calling
|
|
692
|
+
"""
|
|
693
|
+
return True
|
|
694
|
+
|
|
695
|
+
def supports_multimodal(self) -> bool:
|
|
696
|
+
"""
|
|
697
|
+
Check if Bedrock provider supports multimodal inputs.
|
|
698
|
+
|
|
699
|
+
Returns:
|
|
700
|
+
True - Claude 3 models support multimodal inputs
|
|
701
|
+
"""
|
|
702
|
+
return True
|
|
703
|
+
|
|
704
|
+
def supports_streaming(self) -> bool:
|
|
705
|
+
"""
|
|
706
|
+
Check if Bedrock provider supports streaming responses.
|
|
707
|
+
|
|
708
|
+
Returns:
|
|
709
|
+
True - Bedrock supports streaming
|
|
710
|
+
"""
|
|
711
|
+
return True
|