To build a resilient, configurable AI invoice app in Python that supports multiple pluggable components (OCR, embedding, LLMs, vector DBs, etc.), you should architect your project using a modular plugin-like pattern with dynamic configuration.
Here’s how to do that:
Since the project follows a functional/layered architecture, the resilient configuration layout is mapped to the current structure as follows:
| Resilient Module | Actual Location | Description |
|---|---|---|
ocr/ |
ingestion/image_processor.py |
PaddleOCR implementation for text extraction |
llm/ |
brain/chatbot/engine.py |
LLM client and prompt engineering logic |
embed/ & vectordb/ |
brain/chatbot/vector_retriever.py |
Supabase/PGVector integration and retrieval |
agents/ |
ingestion/orchestrator.py |
Workflow management and stage fallback logic |
config/ |
core/config.py & core/resilience/ |
App settings and the new mapping layer |
This mapping is managed via a Compatibility Layer in core/resilience/ to avoid massive file restructuring while providing the same "pluggable" benefits.
ai_invoice_app/
├── config/
│ ├── base.yaml
│ ├── dev.yaml
│ ├── prod.yaml
│ └── schema.py # Typed config models
├── ocr/
│ ├── base.py
│ ├── paddleocr.py
│ └── deepseek.py
├── embed/
│ ├── base.py
│ ├── sentence_transformers.py
│ ├── openai.py
│ ├── nomic_api.py
│ └── nomic_ollama.py
├── llm/
│ ├── base.py
│ ├── openai_gpt.py
│ ├── gemini.py
│ └── deepseek.py
├── vectordb/
│ ├── base.py
│ ├── postgres.py
│ └── supabase.py
├── agents/
│ ├── llamaindex.py
│ ├── langchain.py
│ └── custom_orchestrator.py
├── app/
│ ├── etl_pipeline.py
│ ├── query_api.py
│ └── runner.py
├── main.py
├── requirements.txt
└── README.md
config/schema.py
from pydantic import BaseModel
class AppConfig(BaseModel):
ocr_backend: str
embed_backend: str
llm_backend: str
vectordb_backend: str
agent_backend: str
environment: strconfig_loader.py
import yaml
from config.schema import AppConfig
def load_config(env="dev") -> AppConfig:
with open(f"config/{env}.yaml") as f:
raw = yaml.safe_load(f)
return AppConfig(**raw)Example dev.yaml
ocr_backend: paddleocr
embed_backend: sentence_transformers
llm_backend: openai_gpt
vectordb_backend: postgres
agent_backend: llamaindex
environment: devocr/__init__.py
from .paddleocr import PaddleOCRWrapper
from .deepseek import DeepseekOCRWrapper
def get_ocr(config):
if config.ocr_backend == "paddleocr":
return PaddleOCRWrapper()
elif config.ocr_backend == "deepseek":
return DeepseekOCRWrapper()Do the same for embed, llm, vectordb, and agents.
To enable dynamic switching without restructuring the codebase, we use a mapping layer:
core/resilience/mapping.py
from brain.chatbot.engine import ChatbotEngine
from ingestion.image_processor import process_image
MODULE_MAPPING = {
"ocr": { "paddleocr": process_image },
"llm": { "deepseek-chat": ChatbotEngine }
}core/resilience/provider.py
def get_ocr_adapter(module_id="paddleocr"):
return MODULE_MAPPING["ocr"][module_id]main.py
from config_loader import load_config
from ocr import get_ocr
from embed import get_embedder
from llm import get_llm
from vectordb import get_vectordb
from agents import get_agent
config = load_config("dev")
ocr = get_ocr(config)
embedder = get_embedder(config)
llm = get_llm(config)
vectordb = get_vectordb(config)
agent = get_agent(config, llm, vectordb)
# Now run app logic: ingest, OCR, embed, index, query, etc.-
Use
try/exceptwrappers with fallback mechanisms for OCR or embedding failure. -
Validate config at startup: if a backend is unsupported or missing env vars, log error and default to safe mode.
-
Add feature toggles in config for:
enable_human_in_loop: true fallback_to_openai_if_fail: true log_level: DEBUG
- Use
.envfiles withpython-dotenvoros.environto load secrets and keys. - Build Docker images with config volume mount support.
- Store configs in S3, Firebase, Supabase, or a config server for remote switching.
| Purpose | Library |
|---|---|
| Config schema | pydantic, pyyaml |
| OCR | paddleocr, DeepSeek API |
| Embeddings | sentence-transformers, openai, nomic, ollama |
| LLM | openai, deepseek, google-generativeai |
| Vector DB | pgvector, supabase-py |
| Agentic Logic | llama-index, langchain |
| API Interface | FastAPI, Litestar |
| CLI Control | typer |
Would you like me to generate the base code scaffold with examples for each backend in this plugin system?
flowchart TD
A[Operator or Maintainer] -->|Create/Validate| B[Configuration API]
B --> C[Configuration Service]
C --> D[Validation Engine]
D --> E[Capability Contracts]
C --> F[Activation Queue]
F --> G[Active Configuration]
G --> H[Module Registry]
H --> I[Processing Stages]
I --> J[Pipeline Execution]
J -->|Failure| K[Fallback Policy]
K --> J
C --> L[Version History]
C --> M[Observability Logs/Metrics]
The system exposes configuration management endpoints for operators and maintainers.
- Configuration changes are restricted to users with roles:
operatorormaintainer - Provide
X-User-Roleheader when calling configuration endpoints
GET /api/v1/modules- list available modulesGET /api/v1/stages- list processing stagesPOST /api/v1/configurations- create a configuration draftPOST /api/v1/configurations/validate- validate a configurationGET /api/v1/configurations/active- fetch active configurationGET /api/v1/configurations/{config_id}- fetch configuration by idPOST /api/v1/configurations/{config_id}/activate- activate configuration (queued if processing is active)POST /api/v1/configurations/{config_id}/rollback- rollback to a prior version
curl -X POST "/api/v1/configurations" \
-H "Content-Type: application/json" \
-H "X-User-Role: operator" \
-d '{
"selections": [
{ "stage_id": "ocr", "module_id": "ocr-v1" }
]
}'The application now supports the resilient configuration architecture through a Functional Mapping Layer. This allows the app to retain its current efficient folder structure while providing the pluggable extensibility required by the resilience design.
- Dynamic Module Registry: Modules are automatically bootstrapped and registered in-memory at application startup.
- Compatibility Layer:
core/resilience/maps conceptual modules (e.g.,paddleocr) directly to functional code (e.g.,ingestion/image_processor.py). - Zero-Restructuring Design: Added the resilience feature without modifying the core functional layout of
brain/oringestion/. - API Ready: The internal
ModuleRegistryis now populated with live references, ready for consumption by the Configuration APIs.
This implementation proves that the system can switch between different backend implementations (like shifting from PaddleOCR to a future Cloud-based OCR) simply by updating a static mapping or a database entry, without touching the business logic layers.