""" 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""" # OAuth tokens (sk-ant-oat*) use Bearer auth, regular keys use x-api-key if self.api_key.startswith("sk-ant-oat"): headers = { "Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}", "anthropic-version": self.anthropic_version } else: 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