agents/agent.py (107 lines of code) (raw):

"""Agent implementation with Claude API and tools.""" import asyncio import os from contextlib import AsyncExitStack from dataclasses import dataclass from typing import Any from anthropic import Anthropic from .tools.base import Tool from .utils.connections import setup_mcp_connections from .utils.history_util import MessageHistory from .utils.tool_util import execute_tools @dataclass class ModelConfig: """Configuration settings for Claude model parameters.""" model: str = "claude-3-7-sonnet-20250219" max_tokens: int = 4096 temperature: float = 1.0 context_window_tokens: int = 180000 class Agent: """Claude-powered agent with tool use capabilities.""" def __init__( self, name: str, system: str, tools: list[Tool] | None = None, mcp_servers: list[dict[str, Any]] | None = None, config: ModelConfig | None = None, verbose: bool = False, client: Anthropic | None = None, ): self.name = name self.system = system self.verbose = verbose self.tools = list(tools or []) self.config = config or ModelConfig() self.mcp_servers = mcp_servers or [] self.client = client or Anthropic( api_key=os.environ.get("ANTHROPIC_API_KEY", "") ) self.history = MessageHistory( model=self.config.model, system=self.system, context_window_tokens=self.config.context_window_tokens, client=self.client, ) if self.verbose: print(f"\n[{self.name}] Agent initialized") def _prepare_api_params(self) -> dict[str, Any]: """Prepare parameters for Claude API call.""" # Use system prompt directly without prefixing return { "model": self.config.model, "max_tokens": self.config.max_tokens, "temperature": self.config.temperature, "system": self.system, "messages": self.history.format_for_api(), "tools": [tool.to_dict() for tool in self.tools], } async def _agent_loop(self, user_input: str) -> list[dict[str, Any]]: """Process user input and handle tool calls in a loop""" if self.verbose: print(f"\n[{self.name}] Received: {user_input}") await self.history.add_message("user", user_input, None) tool_dict = {tool.name: tool for tool in self.tools} while True: self.history.truncate() params = self._prepare_api_params() response = self.client.messages.create(**params) tool_calls = [ block for block in response.content if block.type == "tool_use" ] if self.verbose: for block in response.content: if block.type == "text": print(f"\n[{self.name}] Output: {block.text}") elif block.type == "tool_use": params_str = ", ".join( [f"{k}={v}" for k, v in block.input.items()] ) print( f"\n[{self.name}] Tool call: " f"{block.name}({params_str})" ) await self.history.add_message( "assistant", response.content, response.usage ) if tool_calls: tool_results = await execute_tools( tool_calls, tool_dict, ) if self.verbose: for block in tool_results: print( f"\n[{self.name}] Tool result: " f"{block.get('content')}" ) await self.history.add_message("user", tool_results) else: return response async def run_async(self, user_input: str) -> list[dict[str, Any]]: """Run agent with MCP tools asynchronously.""" async with AsyncExitStack() as stack: original_tools = list(self.tools) try: mcp_tools = await setup_mcp_connections( self.mcp_servers, stack ) self.tools.extend(mcp_tools) return await self._agent_loop(user_input) finally: self.tools = original_tools def run(self, user_input: str) -> list[dict[str, Any]]: """Run agent synchronously""" return asyncio.run(self.run_async(user_input))