LangExtract Factory Pattern: How to Create Language Models Programmatically
LangExtract implements a classic factory pattern in langextract/factory.py that centralizes language model instantiation through the create_model() function, using ModelConfig objects to resolve providers dynamically while handling environment variables and schema constraints automatically.
The LangExtract factory pattern provides a clean, extensible API for creating language model instances without hard-coding provider-specific logic throughout the codebase. By centralizing instantiation logic in langextract/factory.py, the library separates configuration concerns from model usage, allowing developers to switch between Gemini, OpenAI, Ollama, or custom providers using a unified interface.
Core Components of the LangExtract Factory Pattern
The ModelConfig Dataclass
The factory accepts an immutable configuration object defined in langextract/factory.py at lines 35-51. The ModelConfig dataclass encapsulates three critical pieces of information:
- model_id: The identifier string (e.g.,
"gemini-pro","gpt-4o-mini") - provider: Optional explicit provider name to bypass pattern matching
- provider_kwargs: Dictionary of provider-specific arguments like API keys or base URLs
This encapsulation ensures that all model creation parameters travel through a single, type-safe conduit.
Provider Resolution and Registration
Before instantiation occurs, the factory ensures all available providers are registered without triggering circular imports. In langextract/providers/__init__.py (lines 49-84), two critical functions execute lazy loading:
providers.load_builtins_once(): Registers built-in providers (Gemini, OpenAI, Ollama) viarouter.register_lazy()providers.load_plugins_once(): Discovers and registers plugin providers from entry points
The actual resolution happens in langextract/providers/router.py (lines 38-66). The router maintains a registry of regex patterns mapped to provider classes:
router.resolve(model_id): Matches the model ID against registered regex patterns to find the appropriate provider classrouter.resolve_provider(name): Retrieves a provider by explicit name when the user specifies theproviderfield inModelConfig
This pattern-matching approach allows new providers to declare which model IDs they can handle without modifying the factory code.
Environment Variable Integration
The factory automatically merges sensitive configuration from environment variables to keep credentials out of source code. The helper function _kwargs_with_environment_defaults() in langextract/factory.py (lines 53-99) inspects the requested model_id and injects defaults for:
- Gemini:
GEMINI_API_KEY - OpenAI:
OPENAI_API_KEY - Generic:
LANGEXTRACT_API_KEY - Ollama:
OLLAMA_BASE_URL
Environment values are only injected if the caller has not already supplied the same key in provider_kwargs, ensuring explicit parameters always take precedence.
Implementing Model Creation with the Factory Pattern
Basic Model Creation
To create a model instance, construct a ModelConfig and pass it to create_model():
from langextract import factory
# Build a configuration for a Gemini model
cfg = factory.ModelConfig(
model_id="gemini-pro",
provider_kwargs={"api_key": "my-gemini-key"},
)
model = factory.create_model(cfg)
print(model) # → <GeminiLanguageModel …>
print(model.model_id) # → gemini-pro
print(model.api_key) # → my-gemini-key
The create_model() function in langextract/factory.py (lines 103-178) handles registration, resolution, environment default merging, and instantiation. Any InferenceConfigError raised by the provider's __init__ is wrapped in a user-friendly exception.
Convenience Helper Method
For simple use cases, create_model_from_id() provides a streamlined interface:
from langextract import factory
# One-liner for a typical OpenAI model
model = factory.create_model_from_id(
"gpt-4o-mini", api_key="openai-key"
)
print(model.model_id) # gpt-4o-mini
This helper, located at langextract/factory.py lines 179-197, constructs a ModelConfig internally and forwards to create_model(), reducing boilerplate for standard configurations.
Environment-Based Configuration
The factory automatically detects API keys from the environment, eliminating hard-coded credentials:
export GEMINI_API_KEY=env-gemini-key
export OPENAI_API_KEY=env-openai-key
from langextract import factory
# No explicit key – the factory will read GEMINI_API_KEY automatically
model = factory.create_model_from_id("gemini-1.5-flash")
print(model.api_key) # → env-gemini-key
The implementation in _kwargs_with_environment_defaults() checks for provider-specific environment variables and injects them only when the caller hasn't provided explicit values.
Registering Custom Providers
The factory pattern supports extension through the provider router, enabling custom model implementations:
from langextract.providers import router
from langextract.core import base_model
from langextract import types
# Simple echo provider for demonstration
class EchoProvider(base_model.BaseLanguageModel):
def __init__(self, model_id="echo", **kwargs):
self.model_id = model_id
super().__init__()
def infer(self, batch_prompts, **kwargs):
return [[types.ScoredOutput(score=1.0, output=p)] for p in batch_prompts]
# Register with a custom pattern
router.register(r"^echo", priority=100)(EchoProvider)
# Now the factory can resolve it
from langextract import factory
cfg = factory.ModelConfig(model_id="echo")
model = factory.create_model(cfg)
print(model.infer(["hello", "world"]))
This pattern, demonstrated in tests/factory_test.py, allows the factory to resolve custom providers without modifying the core library code.
Schema-Constrained Model Creation
For structured output, the factory supports schema-based constraints through an internal helper:
from langextract import factory, extraction
examples = [
{"input": "Take 2 tablets daily.", "output": {"dose": "2 tablets", "frequency": "daily"}},
# … more examples …
]
cfg = factory.ModelConfig(
model_id="gemini-pro",
provider_kwargs={"api_key": "my-key"},
)
model = factory.create_model(
cfg,
examples=examples,
use_schema_constraints=True, # generate schema from examples
)
# The model now validates outputs against the inferred schema
result = extraction.extract(model, "Take 1 pill twice a day.")
print(result) # → {'dose': '1 pill', 'frequency': 'twice a day'}
When use_schema_constraints=True, create_model delegates to _create_model_with_schema(), which builds the schema via provider_class.get_schema_class(), syncs the schema with provider kwargs, and calls model.set_fence_output().
Key Files in the Factory Architecture
| File | Role |
|---|---|
langextract/factory.py |
Core factory implementation containing ModelConfig, create_model(), and environment default handling. |
langextract/providers/router.py |
Runtime registry that maps regex patterns to provider classes; handles resolution via resolve() and resolve_provider(). |
langextract/providers/__init__.py |
Entry points for lazy loading of built-in and plugin providers through load_builtins_once() and load_plugins_once(). |
tests/factory_test.py |
Concrete test cases demonstrating factory usage patterns and provider registration. |
langextract/core/base_model.py |
Abstract base class that all provider classes inherit from; the factory returns instances of subclasses of this type. |
These files collectively demonstrate how LangExtract separates configuration, provider discovery, and instantiation, embodying the factory pattern to provide a simple, extensible API for creating any supported language model.
Summary
- LangExtract centralizes model instantiation in
langextract/factory.pyusing a classic factory pattern that decouples configuration from implementation. - The
ModelConfigdataclass encapsulates model identity, provider hints, and provider-specific arguments, ensuring type-safe configuration transport. - Lazy provider registration via
load_builtins_once()andload_plugins_once()prevents circular imports while populating the router registry inlangextract/providers/router.py. - Automatic environment variable injection through
_kwargs_with_environment_defaults()securely handles API keys for Gemini, OpenAI, and Ollama without hard-coding credentials. - Extensibility is supported through the router's
register()method, allowing custom providers to integrate with the factory without modifying core library code.
Frequently Asked Questions
What is the difference between create_model and create_model_from_id?
The create_model() function accepts a ModelConfig dataclass instance, giving you full control over provider selection, model ID, and provider-specific kwargs. The create_model_from_id() convenience helper (located at langextract/factory.py lines 179-197) constructs the ModelConfig internally from simple string arguments, reducing boilerplate when you only need to specify a model ID and API key.
How does LangExtract handle API keys securely?
Rather than requiring hard-coded credentials, the factory automatically reads API keys from environment variables through the _kwargs_with_environment_defaults() function in langextract/factory.py (lines 53-99). It checks for GEMINI_API_KEY, OPENAI_API_KEY, LANGEXTRACT_API_KEY, and OLLAMA_BASE_URL, injecting these values only when the caller hasn't explicitly provided them in provider_kwargs.
Can I use the factory pattern with custom model providers?
Yes, the factory supports custom providers through the router's registration system. You can create a subclass of BaseLanguageModel from langextract/core/base_model.py and register it with router.register() in langextract/providers/router.py, specifying a regex pattern to match your custom model IDs. Once registered, create_model() can instantiate your custom provider just like any built-in implementation.
What happens if the model_id doesn't match any registered provider?
If the factory cannot resolve the model_id to a registered provider via router.resolve() or router.resolve_provider() in langextract/providers/router.py, the factory raises a resolution error indicating that no provider matches the given model identifier. This ensures that configuration errors are caught at instantiation time rather than during inference, with clear messaging about available providers or pattern mismatches.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →