Development & Architecture Documentation
This document contains internal design notes, architecture details, and project structure information for developers working on the llm-adapter codebase.
New here? Start with the project overview on the home page: vrraj-llm-adapter docs home.
Source + releases: GitHub repo and PyPI package are linked from the home page.
Note: This guide covers local development, testing, and extending the adapter. For API usage, see the API Reference.
Project Structure
Core Components
src/llm_adapter/llm_adapter.py— standalone adapter implementation (adapted fromchat-with-rag/llm/llm_handler.py)ModelSpec.py— standalone version ofModelSpecmodel_registry.py— registry of supported model keys/capabilities/endpoints__init__.py— exportsLLMAdapter,llm_adapter,LLMError,ModelSpec,model_registry
src/llm_adapter_demo/api.py— FastAPI app exposing/api/modelsand/api/chat, plus mounting the UI under/ui/config.py— environment checks + model options (derived fromllm_adapter.model_registry)
ui/index.html— minimal test UI for trying registry model keysapp.js— frontend wiring to/api/modelsand/api/chatstyles.css— simple styling
examples/openai_adapter_example.py— CLI example callingllm_adapter.createfor OpenAI chatopenai_embedding_example.py— CLI example callingllm_adapter.create_embeddingfor OpenAI embeddingsstreaming_call_example.py— CLI example callingllm_adapter.create(stream=True)and printing deltas as they arrivesetting_openai_base_url.py— Example showing how to configure custom OpenAI base URLget_model_pricing_example.py— Example for retrieving model pricing informationset_adapter_allowed_models.py— Example for configuring model allowlistscustom_registry.py— Template for creating custom model registriesimport_custom_registry.py— Example demonstrating custom registry usagellm_adapter_model_spec_example.py— Comprehensive example demonstrating ModelSpec usage with different providers and parameter configurationstest_magnitude_metadata.py— Example showing magnitude metadata for embeddingstest_provider_agnostic_embeddings.py— Example demonstrating provider auto-detection
tests/unit/— Unit tests that don’t require API keystest_imports.py— Basic package import validation
integration/— Integration tests that require API keystest_llm_adapter.py— Basic chat and embeddings integration teststest_adapter_embedding_calls.py— Advanced embedding functionality teststest_gemini_tool_calls_flow.py— Gemini tool calling integration teststest_openai_tool_calls_flow.py— OpenAI tool calling integration tests
Architecture and Design Notes
Core Components
- Model Registry (
src/llm_adapter/model_registry.py)- Central database of model metadata (
ModelInfo) - Endpoint routing hint (e.g.
responses,chat_completions,embeddings,gemini_sdk,embed_content) - Capability flags (e.g.
temperature,reasoning_effort,tools,stream) - Parameter mappings (e.g.
max_output_tokensvsmax_completion_tokens)
- Central database of model metadata (
- LLM Adapter (
src/llm_adapter/llm_adapter.py)- Routes generation calls based on registry metadata
- Routes embedding calls based on registry metadata
- Applies parameter mapping and capability-based filtering
- Handles provider routing (OpenAI adapter vs Gemini native SDK)
Custom Model Registry (Override / Extend Defaults)
LLMAdapter uses the default registry in src/llm_adapter/model_registry.py.
You can provide your own registry mapping to LLMAdapter(model_registry=...).
The adapter will merge registries as:
- Default registry (package) + user registry overrides
- User entries replace default entries for the same key
User-side Call Signature
from llm_adapter import LLMAdapter
from my_app.my_registry import REGISTRY as USER_REGISTRY
llm = LLMAdapter(model_registry=USER_REGISTRY)
Restricting Which Models Can Be Used (Allowlist)
You can restrict which registry keys may be used via an environment variable. When an allowlist is enabled, models must be referenced by registry key (for example: openai:gpt-4o-mini).
Environment Variable Configuration (LLM_ADAPTER_ALLOWED_MODELS)
export LLM_ADAPTER_ALLOWED_MODELS="openai:gpt-4o-mini,openai:embed_small"
Validating Registries
If you provide your own registry, you can validate it before instantiating the adapter.
from llm_adapter.model_registry import validate_registry
from my_app.my_registry import REGISTRY as USER_REGISTRY
validate_registry(USER_REGISTRY, strict=False)
You can also validate the merged registry (defaults + your overrides) after creating the adapter:
from llm_adapter import LLMAdapter
from llm_adapter.model_registry import validate_registry
from my_app.my_registry import REGISTRY as USER_REGISTRY
llm = LLMAdapter(model_registry=USER_REGISTRY)
validate_registry(llm.model_registry, strict=False)
Merging Custom Registries (Interactive Demo)
The LLM Adapter includes an interactive demo UI that allows you to test custom registries without writing any code. This is perfect for experimenting with new model configurations before integrating them into your application.
Quick Start with Demo UI
- Start the demo server:
cd llm-adapter source .venv/bin/activate # or activate your venv uvicorn llm_adapter_demo.api:app --reload --host 0.0.0.0 --port 8100 -
Open the UI: Navigate to
http://localhost:8100/ui -
Enable custom registry: Check the “Merge custom registry (examples/custom_registry.py)” checkbox
- Select your custom models: The dropdown will now show both default and custom models
Testing Your Custom Registry
Script: examples/import_custom_registry.py
Run this test script to verify your custom registry is set up correctly:
cd llm-adapter
source .venv/bin/activate
python examples/import_custom_registry.py
This script validates:
- Custom registry import and merging
- Model availability (custom + default)
- Pricing lookup for custom models
- Registry validation
- Model resolution functionality
Live Updates
The custom registry is dynamically imported on each request, so you can iterate quickly on model configurations.
Integration Examples
See these files for complete working examples:
examples/custom_registry.py- Sample custom registry with reasoning modelsexamples/import_custom_registry.py- Test script demonstrating programmatic usage
Production Usage
For production use, you can:
- Create your registry in your application package
- Use the same pattern as the demo:
LLMAdapter(model_registry=YOUR_REGISTRY) - Validate before deployment using
validate_registry()
The demo UI provides a sandbox for testing before integrating into your production code.
Custom Registry Override
For quick registry overrides without the demo UI:
- Copy the template from examples/custom_registry.py
# Start with the provided template cp examples/custom_registry.py my_app/my_registry.py - Define your complete model configurations
# Option A: Override existing model (define complete configuration) "openai:custom_reasoning_o3-mini": ModelInfo( provider="openai", model="o3-mini", endpoint="chat_completions", pricing=Pricing(input_per_mm=0.8, output_per_mm=3.2), limits={"max_output_tokens": 3000}, # ... all required fields must be defined ) # Option B: Add entirely new model (define complete configuration) "openai:custom-gpt4-turbo": ModelInfo( key="openai:custom-gpt4-turbo", provider="openai", model="gpt-4-turbo", endpoint="chat_completions", pricing=Pricing(input_per_mm=0.3, output_per_mm=0.9), limits={"max_output_tokens": 4096}, capabilities={"assistant_role": "assistant"}, ) - Instantiate LLMAdapter with your registry
from llm_adapter import LLMAdapter from my_app.my_registry import REGISTRY as CUSTOM_REGISTRY adapter = LLMAdapter(model_registry=CUSTOM_REGISTRY)
Development Workflow
Setting Up Development Environment
- Clone and setup:
git clone https://github.com/vrraj/llm-adapter.git cd llm-adapter bash scripts/llm_adapter_setup.sh - Set API keys in
.env:OPENAI_API_KEY=sk-... GEMINI_API_KEY=AIza... - Run tests:
# Unit tests only pytest # Integration tests (requires API keys) pytest -m integration # All tests pytest -m "integration or unit" - Start demo UI:
make start
Code Organization Principles
- Registry-driven design - All model behavior controlled through model_registry.py
- Provider abstraction - Single interface for multiple LLM providers
- Parameter validation - Automatic filtering and mapping of parameters
- Extensibility - Easy to add new providers and models
- Testing - Comprehensive unit and integration test coverage
Adding New Providers
- Add provider constants to
llm_adapter.py - Implement provider-specific methods for generation and embeddings
- Add client initialization and authentication
- Create model registry entries with proper endpoints and capabilities
- Add integration tests for the new provider
- Update documentation with provider-specific examples
Adding New Models
- Create ModelInfo entries in
model_registry.pyor custom registry - Define capabilities and parameter policies
- Set pricing and limits if applicable
- Test with demo UI before production deployment
- Add examples if model has special capabilities