Abstract Factory Pattern

The Abstract Factory pattern is perfect for LLM applications that need to create families of related objects, such as different AI providers, model configurations, or complete AI system stacks with consistent interfaces.

Why Abstract Factory for LLM?

LLM applications often require:

  • Provider abstraction: Support multiple AI providers (OpenAI, Anthropic, Google, etc.)

  • Environment consistency: Different configurations for development, testing, and production

  • Family cohesion: Related components that work together (client + embeddings + tools)

  • Easy switching: Change entire AI stacks without code modifications

Key LLM Use Cases

1. Multi-Provider AI Client Factory

Creating consistent interfaces for different AI providers:

from abc import ABC, abstractmethod

# Abstract products
class LLMClient(ABC):
    @abstractmethod
    def generate(self, prompt, **kwargs):
        pass
    
    @abstractmethod
    def stream_generate(self, prompt, **kwargs):
        pass

class EmbeddingClient(ABC):
    @abstractmethod
    def embed_text(self, text):
        pass
    
    @abstractmethod
    def embed_documents(self, documents):
        pass

class ImageClient(ABC):
    @abstractmethod
    def generate_image(self, prompt):
        pass
    
    @abstractmethod
    def analyze_image(self, image):
        pass

# Abstract factory
class AIProviderFactory(ABC):
    @abstractmethod
    def create_llm_client(self):
        pass
    
    @abstractmethod
    def create_embedding_client(self):
        pass
    
    @abstractmethod
    def create_image_client(self):
        pass

# Concrete implementations for OpenAI
class OpenAILLMClient(LLMClient):
    def generate(self, prompt, **kwargs):
        return openai.ChatCompletion.create(
            model="gpt-4",
            messages=[{"role": "user", "content": prompt}],
            **kwargs
        )
    
    def stream_generate(self, prompt, **kwargs):
        return openai.ChatCompletion.create(
            model="gpt-4",
            messages=[{"role": "user", "content": prompt}],
            stream=True,
            **kwargs
        )

class OpenAIEmbeddingClient(EmbeddingClient):
    def embed_text(self, text):
        return openai.Embedding.create(
            model="text-embedding-ada-002",
            input=text
        )
    
    def embed_documents(self, documents):
        return [self.embed_text(doc) for doc in documents]

class OpenAIImageClient(ImageClient):
    def generate_image(self, prompt):
        return openai.Image.create(
            prompt=prompt,
            n=1,
            size="1024x1024"
        )
    
    def analyze_image(self, image):
        return openai.ChatCompletion.create(
            model="gpt-4-vision-preview",
            messages=[{
                "role": "user",
                "content": [{"type": "image_url", "image_url": image}]
            }]
        )

class OpenAIFactory(AIProviderFactory):
    def create_llm_client(self):
        return OpenAILLMClient()
    
    def create_embedding_client(self):
        return OpenAIEmbeddingClient()
    
    def create_image_client(self):
        return OpenAIImageClient()

# Concrete implementations for Anthropic
class AnthropicLLMClient(LLMClient):
    def generate(self, prompt, **kwargs):
        return anthropic.Completion.create(
            model="claude-3-opus-20240229",
            prompt=f"Human: {prompt}\n\nAssistant:",
            **kwargs
        )
    
    def stream_generate(self, prompt, **kwargs):
        return anthropic.Completion.create(
            model="claude-3-opus-20240229",
            prompt=f"Human: {prompt}\n\nAssistant:",
            stream=True,
            **kwargs
        )

class AnthropicFactory(AIProviderFactory):
    def create_llm_client(self):
        return AnthropicLLMClient()
    
    def create_embedding_client(self):
        # Anthropic doesn't have embeddings, use OpenAI
        return OpenAIEmbeddingClient()
    
    def create_image_client(self):
        # Anthropic doesn't have image generation, use OpenAI
        return OpenAIImageClient()

# Usage
class AIApplication:
    def __init__(self, factory: AIProviderFactory):
        self.llm = factory.create_llm_client()
        self.embeddings = factory.create_embedding_client()
        self.images = factory.create_image_client()
    
    def process_multimodal_request(self, text_prompt, image_url=None):
        if image_url:
            image_analysis = self.images.analyze_image(image_url)
            combined_prompt = f"{text_prompt}\n\nImage analysis: {image_analysis}"
        else:
            combined_prompt = text_prompt
        
        return self.llm.generate(combined_prompt)

# Easy provider switching
openai_app = AIApplication(OpenAIFactory())
anthropic_app = AIApplication(AnthropicFactory())

Benefits:

  • Consistent interface across all AI providers

  • Easy switching between providers

  • Family of related clients work together

  • Centralized provider configuration

2. Environment-Based Configuration Factory

Different configurations for different environments:

class AIConfigFactory(ABC):
    @abstractmethod
    def create_llm_config(self):
        pass
    
    @abstractmethod
    def create_embedding_config(self):
        pass
    
    @abstractmethod
    def create_storage_config(self):
        pass

class DevelopmentConfigFactory(AIConfigFactory):
    def create_llm_config(self):
        return {
            "model": "gpt-3.5-turbo",  # Cheaper for development
            "max_tokens": 100,
            "temperature": 0.7,
            "timeout": 30
        }
    
    def create_embedding_config(self):
        return {
            "model": "text-embedding-ada-002",
            "batch_size": 10,  # Smaller batches for dev
            "cache_enabled": False
        }
    
    def create_storage_config(self):
        return {
            "type": "memory",  # In-memory for quick dev cycles
            "persistence": False
        }

class ProductionConfigFactory(AIConfigFactory):
    def create_llm_config(self):
        return {
            "model": "gpt-4",  # Best model for production
            "max_tokens": 2000,
            "temperature": 0.3,  # More deterministic
            "timeout": 120,
            "retry_attempts": 3
        }
    
    def create_embedding_config(self):
        return {
            "model": "text-embedding-ada-002",
            "batch_size": 100,  # Optimized batching
            "cache_enabled": True,
            "cache_ttl": 3600
        }
    
    def create_storage_config(self):
        return {
            "type": "postgresql",  # Persistent storage
            "connection_pool": 20,
            "backup_enabled": True
        }

class TestConfigFactory(AIConfigFactory):
    def create_llm_config(self):
        return {
            "model": "mock",  # Mock for fast tests
            "deterministic": True,
            "response_delay": 0
        }
    
    def create_embedding_config(self):
        return {
            "model": "mock",
            "fixed_dimensions": 1536
        }
    
    def create_storage_config(self):
        return {
            "type": "memory",
            "clear_on_start": True  # Clean slate for each test
        }

# Environment-based factory selection
def get_ai_factory(environment):
    factories = {
        "development": DevelopmentConfigFactory(),
        "production": ProductionConfigFactory(),
        "test": TestConfigFactory()
    }
    return factories.get(environment, DevelopmentConfigFactory())

Benefits:

  • Environment-specific optimizations

  • Consistent configuration patterns

  • Easy environment switching

  • Centralized configuration management

3. RAG System Component Factory

Creating complete RAG systems with consistent component families:

class RAGComponentFactory(ABC):
    @abstractmethod
    def create_vectorstore(self):
        pass
    
    @abstractmethod
    def create_retriever(self):
        pass
    
    @abstractmethod
    def create_reranker(self):
        pass
    
    @abstractmethod
    def create_generator(self):
        pass

class ScientificRAGFactory(RAGComponentFactory):
    def create_vectorstore(self):
        return ChromaVectorStore(
            collection_name="scientific_papers",
            embedding_model="all-mpnet-base-v2",  # Good for scientific text
            metadata_schema={
                "paper_id": str,
                "authors": list,
                "journal": str,
                "citation_count": int
            }
        )
    
    def create_retriever(self):
        return ScientificRetriever(
            similarity_threshold=0.7,
            citation_weight=0.3,  # Favor highly cited papers
            recency_weight=0.2,
            max_results=10
        )
    
    def create_reranker(self):
        return ScientificReranker(
            model="cross-encoder/ms-marco-MiniLM-L-12-v2",
            citation_boost=True,
            peer_review_filter=True
        )
    
    def create_generator(self):
        return ScientificGenerator(
            model="gpt-4",
            system_prompt="""You are a scientific research assistant. 
            Provide accurate, well-cited responses based on peer-reviewed sources.""",
            citation_format="academic"
        )

class LegalRAGFactory(RAGComponentFactory):
    def create_vectorstore(self):
        return PineconeVectorStore(
            index_name="legal_documents",
            embedding_model="law-embedding-model",  # Specialized legal embeddings
            metadata_schema={
                "case_id": str,
                "jurisdiction": str,
                "court_level": str,
                "date": str,
                "legal_area": str
            }
        )
    
    def create_retriever(self):
        return LegalRetriever(
            jurisdiction_filter=True,
            precedent_ranking=True,
            statutory_priority=True,
            max_results=15
        )
    
    def create_reranker(self):
        return LegalReranker(
            model="legal-reranker-v1",
            jurisdiction_boost=True,
            recency_penalty=False  # Older cases can be very relevant
        )
    
    def create_generator(self):
        return LegalGenerator(
            model="gpt-4",
            system_prompt="""You are a legal research assistant. 
            Provide legally accurate information with proper case citations.
            Always include appropriate disclaimers.""",
            citation_format="legal"
        )

class GeneralRAGFactory(RAGComponentFactory):
    def create_vectorstore(self):
        return FAISSVectorStore(
            embedding_model="text-embedding-ada-002",
            index_type="flat",
            normalize_embeddings=True
        )
    
    def create_retriever(self):
        return SemanticRetriever(
            similarity_threshold=0.75,
            max_results=5
        )
    
    def create_reranker(self):
        return CrossEncoderReranker(
            model="cross-encoder/ms-marco-MiniLM-L-6-v2"
        )
    
    def create_generator(self):
        return GeneralGenerator(
            model="gpt-3.5-turbo",
            system_prompt="You are a helpful assistant.",
            max_tokens=1000
        )

# RAG System using factory
class RAGSystem:
    def __init__(self, factory: RAGComponentFactory):
        self.vectorstore = factory.create_vectorstore()
        self.retriever = factory.create_retriever()
        self.reranker = factory.create_reranker()
        self.generator = factory.create_generator()
    
    def query(self, question):
        # Retrieve relevant documents
        docs = self.retriever.retrieve(question, self.vectorstore)
        
        # Rerank for relevance
        ranked_docs = self.reranker.rerank(question, docs)
        
        # Generate response
        return self.generator.generate(question, ranked_docs)

# Domain-specific RAG systems
scientific_rag = RAGSystem(ScientificRAGFactory())
legal_rag = RAGSystem(LegalRAGFactory())
general_rag = RAGSystem(GeneralRAGFactory())

Benefits:

  • Domain-optimized RAG components

  • Consistent component interfaces

  • Easy domain switching

  • Specialized configurations per use case

4. Testing and Evaluation Factory

Creating comprehensive testing suites for different scenarios:

class TestSuiteFactory(ABC):
    @abstractmethod
    def create_test_data_generator(self):
        pass
    
    @abstractmethod
    def create_evaluator(self):
        pass
    
    @abstractmethod
    def create_metrics_collector(self):
        pass
    
    @abstractmethod
    def create_reporter(self):
        pass

class PerformanceTestFactory(TestSuiteFactory):
    def create_test_data_generator(self):
        return PerformanceTestDataGenerator(
            query_count=1000,
            concurrent_users=[1, 10, 50, 100],
            query_complexity_distribution={
                "simple": 0.4,
                "medium": 0.4,
                "complex": 0.2
            }
        )
    
    def create_evaluator(self):
        return PerformanceEvaluator(
            timeout_threshold=30,  # seconds
            memory_limit=1024,     # MB
            cpu_threshold=80       # %
        )
    
    def create_metrics_collector(self):
        return PerformanceMetricsCollector(
            metrics=["response_time", "throughput", "error_rate", "resource_usage"],
            sampling_interval=1    # second
        )
    
    def create_reporter(self):
        return PerformanceReporter(
            format="dashboard",
            real_time_updates=True,
            alert_thresholds={
                "response_time": 5.0,
                "error_rate": 0.05
            }
        )

class QualityTestFactory(TestSuiteFactory):
    def create_test_data_generator(self):
        return QualityTestDataGenerator(
            golden_dataset="human_evaluated_qa.json",
            adversarial_examples=True,
            domain_specific_cases=True
        )
    
    def create_evaluator(self):
        return QualityEvaluator(
            metrics=["accuracy", "relevance", "coherence", "factuality"],
            human_evaluation_sample_rate=0.1
        )
    
    def create_metrics_collector(self):
        return QualityMetricsCollector(
            automated_scoring=True,
            llm_as_judge=True,
            judge_model="gpt-4"
        )
    
    def create_reporter(self):
        return QualityReporter(
            format="detailed_analysis",
            include_examples=True,
            failure_analysis=True
        )

# Testing framework using factories
class AITestingFramework:
    def __init__(self, factory: TestSuiteFactory):
        self.data_generator = factory.create_test_data_generator()
        self.evaluator = factory.create_evaluator()
        self.metrics = factory.create_metrics_collector()
        self.reporter = factory.create_reporter()
    
    def run_test_suite(self, model):
        # Generate test data
        test_data = self.data_generator.generate()
        
        # Run evaluation
        results = self.evaluator.evaluate(model, test_data)
        
        # Collect metrics
        metrics = self.metrics.collect(results)
        
        # Generate report
        return self.reporter.generate_report(metrics, results)

# Different testing approaches
performance_testing = AITestingFramework(PerformanceTestFactory())
quality_testing = AITestingFramework(QualityTestFactory())

Benefits:

  • Comprehensive testing capabilities

  • Consistent testing interfaces

  • Easy test type switching

  • Specialized testing for different concerns

Implementation Advantages

1. Provider Independence

  • Switch between AI providers without code changes

  • Consistent interfaces across different services

  • Easy migration between providers

  • Vendor lock-in prevention

2. Configuration Management

  • Environment-specific optimizations

  • Centralized configuration control

  • Easy deployment across environments

  • Consistent component relationships

3. Domain Specialization

  • Optimized component families for specific domains

  • Domain-specific configurations and behaviors

  • Easy domain switching and comparison

  • Specialized feature sets per domain

4. Testing and Quality Assurance

  • Comprehensive testing frameworks

  • Different testing strategies for different concerns

  • Consistent evaluation approaches

  • Easy comparison of different configurations

Real-World Impact

The Abstract Factory pattern in LLM applications provides:

  • Flexibility: Easy switching between providers, environments, and configurations

  • Consistency: Guaranteed compatible component families

  • Maintainability: Centralized creation logic and configuration management

  • Scalability: Easy extension to new providers and environments

This pattern is crucial for production LLM systems where you need to support multiple AI providers, different deployment environments, and domain-specific optimizations while maintaining consistent interfaces and behavior.

Last updated