96 lines
3.3 KiB
Python
96 lines
3.3 KiB
Python
"""
|
|
Anthropic Claude provider using httpx
|
|
"""
|
|
|
|
import httpx
|
|
import json
|
|
import logging
|
|
from typing import Dict, List, Any, Optional
|
|
from .base import BaseProvider
|
|
|
|
logger = logging.getLogger("picogent.anthropic")
|
|
|
|
|
|
class AnthropicProvider(BaseProvider):
|
|
"""Anthropic Claude provider using direct API calls"""
|
|
|
|
def __init__(self, api_key: str, model: str = "claude-sonnet-4-20250514"):
|
|
super().__init__(api_key, model)
|
|
self.base_url = "https://api.anthropic.com/v1"
|
|
self.anthropic_version = "2023-06-01"
|
|
|
|
async def generate_response(
|
|
self,
|
|
messages: List[Dict[str, Any]],
|
|
system_prompt: str,
|
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
max_tokens: int = 8192
|
|
) -> Dict[str, Any]:
|
|
"""Generate response using Anthropic Messages API"""
|
|
|
|
headers = {
|
|
"Content-Type": "application/json",
|
|
"x-api-key": self.api_key,
|
|
"anthropic-version": self.anthropic_version
|
|
}
|
|
|
|
payload = {
|
|
"model": self.model,
|
|
"max_tokens": max_tokens,
|
|
"system": system_prompt,
|
|
"messages": messages
|
|
}
|
|
|
|
# Add tools if provided
|
|
if tools:
|
|
payload["tools"] = tools
|
|
|
|
logger.info(f"POST {self.base_url}/messages | model={self.model} | key={self.api_key[:8]}...{self.api_key[-4:]} (len={len(self.api_key)})")
|
|
logger.debug(f"Headers: x-api-key={self.api_key[:8]}..., anthropic-version={self.anthropic_version}")
|
|
logger.debug(f"Messages count: {len(messages)}, tools: {len(tools) if tools else 0}")
|
|
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.post(
|
|
f"{self.base_url}/messages",
|
|
headers=headers,
|
|
json=payload,
|
|
timeout=120.0
|
|
)
|
|
|
|
logger.info(f"Response status: {response.status_code}")
|
|
if response.status_code != 200:
|
|
error_text = response.text
|
|
logger.error(f"API error: {error_text}")
|
|
raise Exception(f"Anthropic API error {response.status_code}: {error_text}")
|
|
|
|
result = response.json()
|
|
|
|
# Parse the response
|
|
content = result.get("content", [])
|
|
|
|
# Extract text content and tool calls
|
|
text_parts = []
|
|
tool_calls = []
|
|
|
|
for block in content:
|
|
if block.get("type") == "text":
|
|
text_parts.append(block.get("text", ""))
|
|
elif block.get("type") == "tool_use":
|
|
tool_calls.append({
|
|
"id": block.get("id"),
|
|
"name": block.get("name"),
|
|
"input": block.get("input", {})
|
|
})
|
|
|
|
response_data = {
|
|
"content": "\n".join(text_parts) if text_parts else "",
|
|
"usage": result.get("usage", {}),
|
|
"model": result.get("model", self.model)
|
|
}
|
|
|
|
if tool_calls:
|
|
response_data["tool_calls"] = tool_calls
|
|
# Also include raw content for session storage
|
|
response_data["raw_content"] = content
|
|
|
|
return response_data |