# LangExtract Factory Pattern: How to Create Language Models Programmatically

> Discover how LangExtract uses the factory pattern to programmatically create language models via the create_model function. Learn about ModelConfig and dynamic provider resolution.

- Repository: [Google/langextract](https://github.com/google/langextract)
- Tags: how-to-guide
- Published: 2026-02-16

---

**LangExtract implements a classic factory pattern in [`langextract/factory.py`](https://github.com/google/langextract/blob/main/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`](https://github.com/google/langextract/blob/main/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`](https://github.com/google/langextract/blob/main/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`](https://github.com/google/langextract/blob/main/langextract/providers/__init__.py) (lines 49-84), two critical functions execute **lazy loading**:

- `providers.load_builtins_once()`: Registers built-in providers (Gemini, OpenAI, Ollama) via `router.register_lazy()`
- `providers.load_plugins_once()`: Discovers and registers plugin providers from entry points

The actual resolution happens in [`langextract/providers/router.py`](https://github.com/google/langextract/blob/main/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 class
- `router.resolve_provider(name)`: Retrieves a provider by explicit name when the user specifies the `provider` field in `ModelConfig`

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`](https://github.com/google/langextract/blob/main/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()`:

```python
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`](https://github.com/google/langextract/blob/main/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:

```python
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`](https://github.com/google/langextract/blob/main/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:

```bash
export GEMINI_API_KEY=env-gemini-key
export OPENAI_API_KEY=env-openai-key

```

```python
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:

```python
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`](https://github.com/google/langextract/blob/main/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:

```python
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`](https://github.com/google/langextract/blob/main/langextract/factory.py) | Core factory implementation containing `ModelConfig`, `create_model()`, and environment default handling. |
| [`langextract/providers/router.py`](https://github.com/google/langextract/blob/main/langextract/providers/router.py) | Runtime registry that maps regex patterns to provider classes; handles resolution via `resolve()` and `resolve_provider()`. |
| [`langextract/providers/__init__.py`](https://github.com/google/langextract/blob/main/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`](https://github.com/google/langextract/blob/main/tests/factory_test.py) | Concrete test cases demonstrating factory usage patterns and provider registration. |
| [`langextract/core/base_model.py`](https://github.com/google/langextract/blob/main/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.py`](https://github.com/google/langextract/blob/main/langextract/factory.py) using a classic factory pattern that decouples configuration from implementation.
- The **`ModelConfig`** dataclass encapsulates model identity, provider hints, and provider-specific arguments, ensuring type-safe configuration transport.
- **Lazy provider registration** via `load_builtins_once()` and `load_plugins_once()` prevents circular imports while populating the router registry in [`langextract/providers/router.py`](https://github.com/google/langextract/blob/main/langextract/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`](https://github.com/google/langextract/blob/main/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`](https://github.com/google/langextract/blob/main/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`](https://github.com/google/langextract/blob/main/langextract/core/base_model.py) and register it with `router.register()` in [`langextract/providers/router.py`](https://github.com/google/langextract/blob/main/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`](https://github.com/google/langextract/blob/main/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.