You've built a solid foundation for an LLM agent system with tool-calling capabilities! The core architecture is clean - you're using Pydantic for data validation, implementing a basic tool registration system, and have a working chat loop with tool execution. The automatic schema generation from docstrings is particularly clever. This shows good understanding of the fundamentals.
Your main.py is doing too much. The file mixing tool implementations, core agent logic, and utility functions makes it hard to navigate and test.
The current approach catches exceptions but doesn't differentiate between types. Consider more granular error handling with custom exceptions.
You're importing Callable but not fully utilizing Python's typing system. Add complete type hints throughout.
The current regex-based approach for parsing docstrings is brittle. Consider using inspect module or a proper docstring parser.
# Suggested structure:
src/
agents/
__init__.py
base.py # Agent base class
openrouter.py # OpenRouter-specific implementation
tools/
__init__.py
base.py # ToolDefinition class
filesystem.py # File operation tools
registry.py # Tool registry/management
utils/
__init__.py
schema.py # Schema generation utilities
config.py # Configuration managementReplace the manual dictionary creation with a decorator-based system:
@tool("read_file")
def read_file(path: str) -> str:
"""Reads file content"""
...Move away from hardcoded values:
# config.py
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
openrouter_api_key: str
default_model: str = "anthropic/claude-sonnet-4"
base_url: str = "https://openrouter.ai/api/v1"
class Config:
env_file = ".env"import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)# tests/test_tools.py
def test_file_operations():
# Test your file tools
# tests/test_agent.py
def test_tool_execution():
# Mock LLM responsesAdd ability to save/load conversations:
class ConversationManager:
def save(self, filepath: str):
...
def load(self, filepath: str):
...For better UX with long responses:
def get_llm_response_stream(self):
for chunk in self.client.chat.completions.create(stream=True, ...):
yield chunkFor a coding agent, consider Docker containers or restricted Python execution environments for safety.
- Refactor into proper package structure
- Add comprehensive error handling
- Implement logging
- Create test suite
- Code Analysis Tools: AST parsing, linting integration
- Project Management: Create/manage entire project structures
- Version Control: Git operations
- Testing Tools: Run tests, analyze coverage
- Documentation Generation: Auto-generate docs from code
- Multi-Agent Collaboration: Specialized agents (architect, coder, tester)
- Code Execution Sandbox: Safe environment for running generated code
- Learning/Memory: Store successful patterns, learn from corrections
- IDE Integration: VSCode extension or Language Server Protocol
- Rate Limiting: Handle API limits gracefully
- Caching: Cache tool results and LLM responses
- Observability: Metrics, tracing (OpenTelemetry)
- Web Interface: FastAPI/Gradio UI
- Deployment: Docker, Kubernetes configs
Consider litellm for provider-agnostic LLM calls:
from litellm import completion
response = completion(
model="openrouter/anthropic/claude-sonnet-4",
messages=[...],
api_key=api_key
)You're on the right track! The core concepts are solid, but the project needs better organization and production-ready practices. Focus first on restructuring the code, adding tests, and improving the tool system. Once that foundation is solid, you can build increasingly sophisticated coding capabilities. Remember: clean, tested, modular code will make you stand out to employers more than complex features in a monolithic file.
Keep iterating, and don't hesitate to show your refactoring process in your portfolio - it demonstrates growth mindset and engineering maturity!