|
""" |
|
API client for LLM providers (Anthropic, Together.ai, etc.) |
|
""" |
|
|
|
import os |
|
import logging |
|
from typing import Dict, Any |
|
|
|
|
|
try: |
|
import requests |
|
REQUESTS_AVAILABLE = True |
|
except ImportError: |
|
REQUESTS_AVAILABLE = False |
|
print("Warning: requests module not available. API calls will use mock responses.") |
|
|
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
anthropic_models = [ |
|
"claude-3-5-sonnet-20241022", |
|
"claude-3-sonnet-20240229", |
|
"claude-3-haiku-20240307", |
|
"claude-opus-4-20250514", |
|
"claude-sonnet-4-20250514" |
|
] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
together_models = [ |
|
"Qwen/Qwen2.5-Coder-32B-Instruct", |
|
"nvidia/Llama-3.1-Nemotron-70B-Instruct-HF", |
|
"deepseek-ai/DeepSeek-R1-Distill-Llama-70B", |
|
"meta-llama/Llama-3.3-70B-Instruct-Turbo-Free" |
|
] |
|
|
|
|
|
all_models = anthropic_models + together_models |
|
|
|
def call_llm(model: str, prompt: str, temperature: float = 0.3, max_tokens: int = 4000) -> str: |
|
""" |
|
Call the specified LLM model with the given prompt. |
|
|
|
Args: |
|
model: Model name to use |
|
prompt: Input prompt |
|
temperature: Sampling temperature |
|
max_tokens: Maximum tokens to generate |
|
|
|
Returns: |
|
Model response as string |
|
""" |
|
try: |
|
if model in anthropic_models: |
|
return call_anthropic(model, prompt, temperature, max_tokens) |
|
elif model in together_models: |
|
return call_together_ai(model, prompt, temperature, max_tokens) |
|
else: |
|
return f"Unsupported model: {model}" |
|
except Exception as e: |
|
logger.error(f"Error calling model {model}: {str(e)}") |
|
return f"Error calling model {model}: {str(e)}" |
|
|
|
def call_anthropic(model: str, prompt: str, temperature: float = 0.3, max_tokens: int = 4000) -> str: |
|
""" |
|
Call Anthropic Claude API. |
|
|
|
Args: |
|
model: Claude model name |
|
prompt: Input prompt |
|
temperature: Sampling temperature |
|
max_tokens: Maximum tokens to generate |
|
|
|
Returns: |
|
Model response |
|
""" |
|
if not REQUESTS_AVAILABLE: |
|
return mock_llm_response(model, prompt) |
|
|
|
api_key = os.getenv("ANTHROPIC_API_KEY") |
|
if not api_key: |
|
return mock_llm_response(model, prompt) |
|
|
|
try: |
|
headers = { |
|
"Content-Type": "application/json", |
|
"x-api-key": api_key, |
|
"anthropic-version": "2023-06-01" |
|
} |
|
|
|
payload = { |
|
"model": model, |
|
"max_tokens": max_tokens, |
|
"temperature": temperature, |
|
"messages": [ |
|
{ |
|
"role": "user", |
|
"content": prompt |
|
} |
|
] |
|
} |
|
|
|
response = requests.post( |
|
"https://api.anthropic.com/v1/messages", |
|
headers=headers, |
|
json=payload, |
|
timeout=60 |
|
) |
|
|
|
if response.status_code == 200: |
|
result = response.json() |
|
return result["content"][0]["text"] |
|
else: |
|
return f"Anthropic API error: {response.status_code} - {response.text}" |
|
|
|
except Exception as e: |
|
logger.warning(f"Anthropic API call failed: {str(e)}, using mock response") |
|
return mock_llm_response(model, prompt) |
|
|
|
def call_together_ai(model: str, prompt: str, temperature: float = 0.3, max_tokens: int = 4000) -> str: |
|
""" |
|
Call Together.ai API. |
|
|
|
Args: |
|
model: Together.ai model name |
|
prompt: Input prompt |
|
temperature: Sampling temperature |
|
max_tokens: Maximum tokens to generate |
|
|
|
Returns: |
|
Model response |
|
""" |
|
if not REQUESTS_AVAILABLE: |
|
return mock_llm_response(model, prompt) |
|
|
|
api_key = os.getenv("TOGETHER_API_KEY") |
|
if not api_key: |
|
return mock_llm_response(model, prompt) |
|
|
|
try: |
|
headers = { |
|
"Authorization": f"Bearer {api_key}", |
|
"Content-Type": "application/json" |
|
} |
|
|
|
payload = { |
|
"model": model, |
|
"max_tokens": max_tokens, |
|
"temperature": temperature, |
|
"messages": [ |
|
{ |
|
"role": "user", |
|
"content": prompt |
|
} |
|
] |
|
} |
|
|
|
response = requests.post( |
|
"https://api.together.xyz/v1/chat/completions", |
|
headers=headers, |
|
json=payload, |
|
timeout=60 |
|
) |
|
|
|
if response.status_code == 200: |
|
result = response.json() |
|
return result["choices"][0]["message"]["content"] |
|
else: |
|
return f"Together.ai API error: {response.status_code} - {response.text}" |
|
|
|
except Exception as e: |
|
logger.warning(f"Together.ai API call failed: {str(e)}, using mock response") |
|
return mock_llm_response(model, prompt) |
|
|
|
def test_model_connectivity() -> Dict[str, str]: |
|
""" |
|
Test connectivity to different model providers. |
|
|
|
Returns: |
|
Dictionary with model provider status |
|
""" |
|
results = {} |
|
|
|
|
|
anthropic_key = os.getenv("ANTHROPIC_API_KEY") |
|
if anthropic_key: |
|
test_response = call_anthropic(anthropic_models[0], "Hello", 0.1, 10) |
|
results["anthropic"] = "Connected" if not test_response.startswith("Error") else f"Failed: {test_response}" |
|
else: |
|
results["anthropic"] = "API key not set" |
|
|
|
|
|
together_key = os.getenv("TOGETHER_API_KEY") |
|
if together_key: |
|
test_response = call_together_ai(together_models[0], "Hello", 0.1, 10) |
|
results["together_ai"] = "Connected" if not test_response.startswith("Error") else f"Failed: {test_response}" |
|
else: |
|
results["together_ai"] = "API key not set" |
|
|
|
return results |
|
|
|
def get_model_info(model: str) -> Dict[str, Any]: |
|
""" |
|
Get information about a specific model. |
|
|
|
Args: |
|
model: Model name |
|
|
|
Returns: |
|
Dictionary with model information |
|
""" |
|
if model in anthropic_models: |
|
return { |
|
"provider": "Anthropic", |
|
"model": model, |
|
"type": "Chat", |
|
"max_tokens": 4096, |
|
"supports_functions": True |
|
} |
|
elif model in together_models: |
|
return { |
|
"provider": "Together.ai", |
|
"model": model, |
|
"type": "Chat", |
|
"max_tokens": 4096, |
|
"supports_functions": False |
|
} |
|
else: |
|
return { |
|
"provider": "Unknown", |
|
"model": model, |
|
"error": "Model not found" |
|
} |
|
|
|
def validate_api_keys() -> Dict[str, bool]: |
|
""" |
|
Validate that required API keys are set. |
|
|
|
Returns: |
|
Dictionary with API key validation status |
|
""" |
|
return { |
|
"anthropic": bool(os.getenv("ANTHROPIC_API_KEY")), |
|
"together_ai": bool(os.getenv("TOGETHER_API_KEY")) |
|
} |
|
|
|
|
|
def mock_llm_response(model: str, prompt: str) -> str: |
|
""" |
|
Generate a mock response for testing purposes. |
|
|
|
Args: |
|
model: Model name |
|
prompt: Input prompt |
|
|
|
Returns: |
|
Mock response |
|
""" |
|
return f"""## CORRECTED CODE |
|
```apex |
|
// This is a mock response for model: {model} |
|
trigger MockTrigger on Account (before insert, before update) {{ |
|
// Mock corrected trigger logic |
|
for (Account acc : Trigger.new) {{ |
|
if (Trigger.isInsert) {{ |
|
// Insert logic |
|
}} |
|
if (Trigger.isUpdate) {{ |
|
// Update logic |
|
}} |
|
}} |
|
}} |
|
``` |
|
|
|
## KEY CHANGES |
|
- Added proper trigger context checks |
|
- Implemented bulkification patterns |
|
- Added error handling |
|
|
|
## CRITICAL ISSUES FIXED |
|
1. Missing trigger context: Added Trigger.isInsert/isUpdate checks |
|
2. Governor limits: Implemented proper bulkification |
|
3. Error handling: Added try-catch blocks |
|
|
|
## REMAINING WARNINGS |
|
- Test coverage needed for all scenarios |
|
- Consider adding custom metadata for configuration |
|
""" |
|
|
|
|
|
def call_llm_with_fallback(model: str, prompt: str, temperature: float = 0.3, max_tokens: int = 4000) -> str: |
|
""" |
|
Call LLM with fallback to mock response if API keys are not available. |
|
|
|
Args: |
|
model: Model name |
|
prompt: Input prompt |
|
temperature: Sampling temperature |
|
max_tokens: Maximum tokens to generate |
|
|
|
Returns: |
|
Model response or mock response |
|
""" |
|
api_keys = validate_api_keys() |
|
|
|
if model in anthropic_models and not api_keys["anthropic"]: |
|
logger.warning("Anthropic API key not available, using mock response") |
|
return mock_llm_response(model, prompt) |
|
elif model in together_models and not api_keys["together_ai"]: |
|
logger.warning("Together.ai API key not available, using mock response") |
|
return mock_llm_response(model, prompt) |
|
|
|
return call_llm(model, prompt, temperature, max_tokens) |