BorisovAI
All posts
New Featureai-agentsClaude Code

Adapter Pattern: Untangling the AI Agent Architecture

Adapter Pattern: Untangling the AI Agent Architecture

Refactoring a Multi-Adapter AI Agent Architecture: From Chaos to Clean Design

The ai-agents project had grown organically, but its core orchestration logic was tangled with specific implementations. The task was ambitious: rebuild the entire system around an adapter pattern, create a probabilistic tool router, and add Windows desktop support—all while maintaining backward compatibility.

I started with the adapter layer. The foundation needed four abstract base classes: LLMAdapter for language models, DatabaseAdapter for data persistence, VectorStoreAdapter for embeddings, SearchAdapter for information retrieval, and PlatformAdapter for messaging. Each defined a clean contract that implementations would honor. Then came the concrete adapters—AnthropicAdapter wrapping the AsyncAnthropic SDK with full streaming and tool-use support, ClaudeCLIAdapter leveraging the Claude CLI for zero-cost local inference, SQLiteAdapter backed by aiosqlite with WAL mode enabled for concurrency, SearxNGAdapter handling multi-instance search with intelligent failover, and TelegramPlatformAdapter wrapping aiogram’s Bot API. A simple factory pattern tied everything together, letting configuration drive which concrete implementation got instantiated.

The orchestrator redesign came next. Instead of baking implementations directly into the core, the AgentOrchestrator now accepted adapters through dependency injection. The entire chat-with-tools loop—streaming responses, managing tool calls, handling errors—lived in one cohesive place. Backward compatibility wasn’t sacrificed; existing code could still use AgentCore(settings) through a thin wrapper that internally created the full orchestrator with sensible defaults.

Then came the interesting challenge: the probabilistic tool router. Tools in complex systems aren’t always called by their exact names. The router implemented four scoring layers—regex matching at 0.95 confidence for explicit patterns, exact name matching at 0.85 for direct calls, semantic similarity using embeddings for fuzzy understanding, and keyword detection at 0.3–0.7 for contextual hints. The route(query, top_k=5) method returned ranked candidates with scores automatically injected into the system prompt, letting the LLM see confidence levels during decision-making.

The desktop plugin surprised me with its elegance. PyStray provided the system tray icon with color-coded status (green running, yellow waiting, red error), pystray’s context menu offered quick actions, and pywebview embedded the existing FastAPI UI directly into a native window. Windows toast notifications kept users informed without disrupting workflow.

Here’s something worth knowing: adapter patterns aren’t just about swapping implementations—they’re about shifting power. By inverting dependencies, the core never knows or cares whether it’s using AnthropicAdapter or ClaudeCLIAdapter. New team members can add a PostgresAdapter or SlackPlatformAdapter without touching orchestrator code. This scales astonishingly well.

After twenty new files, updated configuration handling, and restructured dependencies, all tests passed. The system was more extensible, type-safe thanks to Pydantic models, and ready for new adapters. What started as architectural debt became a foundation for growth.

😄 I hope your code behaves the same on Monday as it did on Friday.

Metadata

Session ID:
grouped_ai-agents_20260211_0821
Branch:
HEAD
Dev Joke
Если npm работает — не трогай. Если не работает — тоже не трогай, станет хуже.

Rate this content

0/1000