Building a Robust Market Research Assistant: Clean Architecture and LLM Tool Routing in Python
When designing AI-powered financial or analytics pipelines, developers frequently run into two major failure modes: Tight Coupling: LLM orchestration logic is directly bound to external market APIs. Any breaking change from a data vendor breaks the entire agent pipeline. Fragile Outputs: Relying on raw text generation for deterministic indicators creates hallucinated figures and pipeline crashes downstream. To solve this in Trading-research-assistant , the system applies Hexagonal Architecture (Ports and Adapters) , strict schema validation with Pydantic, and decoupled inference routing. High-Level Architecture (Ports & Adapters) The core domain layer remains completely isolated from external HTTP clients, third-party market APIs, and specific inference engines. +---------------------------------------------+ | User / CLI / API | +---------------------------------------------+ | v +---------------------------------------------+ | Application Layer | | (ResearchCoordinator, AnalysisOrchestrator) | +---------------------------------------------+ | | v v [ MarketDataPort ] [ LLMInferencePort ] ^ ^ | (implements) | (implements) +------------------------+ +------------------------+ | Adapters: | | Adapters: | | - OandaAdapter | | - OllamaAdapter | | - TwelveDataAdapter | | - OpenRouterAdapter | | - MockDataAdapter | | - ClaudeAdapter | +------------------------+ +------------------------+ Key Architectural Benefits Zero-Cost Unit Testing: Fast mock adapters allow full integration tests without consuming rate limits or paid API credits. Resilient Failovers: If a primary provider hits rate limits (HTTP 429) or service outages, the orchestrator switches to a fallback adapter implementing the identical port contract. Strict Interface Contracts Data boundaries between adapters and application services are enforced using typing.Protocol and immutable Pydantic schemas. from datetime import datetime from typing import Protocol , Sequence from pydantic import BaseModel , Field cl