- Implemented ReAct loop in agent.py - Added Anthropic provider using httpx (no SDK) - Created tools: read, write, edit, bash - Added session management with JSONL format - Included configuration system with env var support - Added CLI interface and example usage - Minimal dependencies: only httpx
118 lines
3.6 KiB
Python
118 lines
3.6 KiB
Python
"""
|
|
Anthropic provider using httpx
|
|
"""
|
|
|
|
import httpx
|
|
import json
|
|
import uuid
|
|
from typing import List, Dict, Any, Optional
|
|
from .base import BaseProvider
|
|
|
|
|
|
class AnthropicProvider(BaseProvider):
|
|
"""Anthropic provider using httpx directly"""
|
|
|
|
API_BASE = "https://api.anthropic.com/v1"
|
|
API_VERSION = "2023-06-01"
|
|
|
|
def __init__(self, config):
|
|
super().__init__(config)
|
|
self.client = httpx.AsyncClient()
|
|
|
|
async def __aenter__(self):
|
|
return self
|
|
|
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
await self.client.aclose()
|
|
|
|
async def chat_completion(
|
|
self,
|
|
messages: List[Dict[str, Any]],
|
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
system_prompt: Optional[str] = None
|
|
) -> Dict[str, Any]:
|
|
"""Get chat completion from Anthropic API"""
|
|
|
|
headers = {
|
|
"x-api-key": self.config.api_key,
|
|
"anthropic-version": self.API_VERSION,
|
|
"content-type": "application/json"
|
|
}
|
|
|
|
payload = {
|
|
"model": self.config.model,
|
|
"max_tokens": self.config.max_tokens,
|
|
"messages": messages
|
|
}
|
|
|
|
if system_prompt:
|
|
payload["system"] = system_prompt
|
|
|
|
if tools:
|
|
payload["tools"] = tools
|
|
|
|
try:
|
|
response = await self.client.post(
|
|
f"{self.API_BASE}/messages",
|
|
headers=headers,
|
|
json=payload,
|
|
timeout=60.0
|
|
)
|
|
|
|
if response.status_code != 200:
|
|
error_text = response.text
|
|
return {
|
|
"error": f"API Error {response.status_code}: {error_text}",
|
|
"content": f"Error: Anthropic API returned {response.status_code}"
|
|
}
|
|
|
|
result = response.json()
|
|
|
|
# Extract content and tool calls
|
|
content_blocks = result.get("content", [])
|
|
text_content = ""
|
|
tool_calls = []
|
|
|
|
for block in content_blocks:
|
|
if block.get("type") == "text":
|
|
text_content += block.get("text", "")
|
|
elif block.get("type") == "tool_use":
|
|
tool_calls.append({
|
|
"id": block.get("id"),
|
|
"name": block.get("name"),
|
|
"arguments": block.get("input", {})
|
|
})
|
|
|
|
return {
|
|
"content": text_content,
|
|
"tool_calls": tool_calls,
|
|
"raw_response": result
|
|
}
|
|
|
|
except httpx.TimeoutException:
|
|
return {
|
|
"error": "Request timed out",
|
|
"content": "Error: Request to Anthropic API timed out"
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"error": str(e),
|
|
"content": f"Error: {str(e)}"
|
|
}
|
|
|
|
def parse_tool_calls(self, response: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
"""Parse tool calls from Anthropic response"""
|
|
return response.get("tool_calls", [])
|
|
|
|
def format_tool_result(self, tool_call_id: str, result: str) -> Dict[str, Any]:
|
|
"""Format tool result for Anthropic API"""
|
|
return {
|
|
"role": "user",
|
|
"content": [
|
|
{
|
|
"type": "tool_result",
|
|
"tool_use_id": tool_call_id,
|
|
"content": result
|
|
}
|
|
]
|
|
} |