How LangExtract Handles Schema Constraints with Gemini Models: A Deep Dive into Structured Output

LangExtract automatically generates JSON schemas from example extractions and injects them into Gemini API calls via response_schema and response_mime_type="application/json", while validating that output formatting options remain compatible with Gemini's native structured output capabilities.

When working with structured data extraction using Google's LangExtract library, understanding how the framework enforces LangExtract schema constraints with Gemini models ensures your LLM outputs adhere to predictable, type-safe formats. The library bridges the gap between Python type definitions and Gemini's native JSON schema support through a three-stage pipeline involving schema generation, provider configuration, and runtime validation.

Architecture Overview

The constraint handling system consists of three integrated components that work together to ensure schema compliance:

Component Role Key Implementation
GeminiSchema Generates JSON schemas from example data and validates format compatibility. langextract/providers/schemas/gemini.py
GeminiLanguageModel Injects schemas into API requests and enforces JSON output requirements. langextract/providers/gemini.py
BaseSchema / FormatHandler Define shared contracts for schema conversion and output formatting. langextract/core/schema.py and langextract/core/format_handler.py

Building Gemini-Compatible Schemas

Generating Schemas from Examples

The GeminiSchema.from_examples classmethod in langextract/providers/schemas/gemini.py (lines 98-123) analyzes your example extractions to build a JSON schema that mirrors your data structure:

@classmethod
def from_examples(cls, examples_data, attribute_suffix=data.ATTRIBUTE_SUFFIX) -> GeminiSchema:
    # Collect attribute types per extraction class

    extraction_categories = {}
    for example in examples_data:
        for extraction in example.extractions:
            category = extraction.extraction_class
            extraction_categories.setdefault(category, {})
            if extraction.attributes:
                for attr_name, attr_value in extraction.attributes.items():
                    extraction_categories[category].setdefault(attr_name, set()).add(type(attr_value))

    # Build JSON schema objects for each class and its attribute sub-object

    extraction_properties = {}
    for category, attrs in extraction_categories.items():
        extraction_properties[category] = {"type": "string"}
        attr_field = f"{category}{attribute_suffix}"
        # Schema construction continues...

        extraction_properties[attr_field] = {"type": "object", "properties": attr_properties, "nullable": True}

This method inspects Python types from your examples and maps them to JSON schema types, ensuring the Gemini model understands the expected output structure.

Provider Configuration Conversion

Once built, the to_provider_config method (lines 50-58) converts the internal schema representation into Gemini-specific API parameters:

def to_provider_config(self) -> dict[str, Any]:
    """Return Gemini-specific kwargs."""
    return {
        "response_schema": self._schema_dict,
        "response_mime_type": "application/json",
    }

These parameters tell the Gemini API to constrain its output to valid JSON matching the provided schema.

Format Validation

The validate_format method (lines 66-96) ensures your output formatting choices don't conflict with Gemini's native JSON capabilities:

def validate_format(self, format_handler: fh.FormatHandler) -> None:
    # Gemini must not use fences because it already outputs raw JSON

    if format_handler.use_fences:
        warnings.warn(
            "Gemini outputs native JSON via response_mime_type='application/json'. "
            "Using fence_output=True may cause parsing issues. Set fence_output=False.",
            UserWarning,
        )
    # Gemini expects the wrapper key to be the library-wide EXTRACTIONS_KEY

    if (not format_handler.use_wrapper
        or format_handler.wrapper_key != data.EXTRACTIONS_KEY):
        warnings.warn(
            f"Gemini's response_schema expects wrapper_key='{data.EXTRACTIONS_KEY}'. "
            f"Current settings: use_wrapper={format_handler.use_wrapper}, "
            f"wrapper_key='{format_handler.wrapper_key}'",
            UserWarning,
        )

This validation prevents common configuration errors that would cause parsing failures.

Provider-Side Schema Injection

Request Configuration

The GeminiLanguageModel class in langextract/providers/gemini.py handles the actual API integration. During each request in _process_single_prompt (lines 12-16), it injects the schema:

def _process_single_prompt(self, prompt: str, config: dict) -> core_types.ScoredOutput:
    # Attach any extra kwargs that weren't already in the config

    for key, value in self._extra_kwargs.items():
        if key not in config and value is not None:
            config[key] = value

    if self.gemini_schema:
        self._validate_schema_config()                     # ensure JSON format

        config.setdefault('response_mime_type', 'application/json')
        config.setdefault('response_schema', self.gemini_schema.schema_dict)

This ensures every request includes the schema constraints when gemini_schema is present.

Format-Type Guard

The _validate_schema_config method (lines 90-99) enforces that structured output only works with JSON:

def _validate_schema_config(self) -> None:
    if self.gemini_schema and self.format_type != data.FormatType.JSON:
        raise exceptions.InferenceConfigError(
            'Gemini structured output only supports JSON format. '
            'Set format_type=JSON or use_schema_constraints=False.'
        )

Attempting to use YAML or other formats with schema constraints triggers an immediate InferenceConfigError.

Batch API Handling

When using the Gemini Batch API, the provider strips schema fields from the generation config because the batch helper injects them separately:


# langextract/providers/gemini.py#L67-L71

batch_config = dict(config)
batch_config.pop('response_mime_type', None)
batch_config.pop('response_schema', None)

# …later pass schema_dict to gemini_batch.infer_batch(...)

This maintains compatibility with the batch processing workflow while preserving schema constraints.

Core Abstractions

GeminiSchema inherits from BaseSchema defined in langextract/core/schema.py. The abstract base enforces three methods that every provider-specific schema must implement:

  • from_examples – create a schema from example extractions.
  • to_provider_config – turn the internal representation into provider-specific kwargs.
  • requires_raw_output – indicate whether the provider outputs raw JSON/YAML (Gemini returns True).

The FormatHandler (in langextract/core/format_handler.py) reads the requires_raw_output flag to decide whether to wrap model output in markdown fences. Because Gemini's schema sets requires_raw_output=True, the library automatically omits fences, matching Gemini's native JSON response format.

Practical Implementation Example

import langextract as le

# 1️⃣ Define example data (normally produced by le.Extractions objects)

examples = le.load_examples("my_examples.json")   # ← any source that yields ExampleData

# 2️⃣ Create a Gemini-compatible schema from the examples

schema = le.providers.schemas.gemini.GeminiSchema.from_examples(examples)

# 3️⃣ Build a Gemini model that will respect the schema

gemini = le.providers.gemini.GeminiLanguageModel(
    api_key="YOUR_API_KEY",               # (or set LANGEXTRACT_API_KEY env var)

    gemini_schema=schema,                 # ← enable structured output

    format_type=le.data.FormatType.JSON,  # required for Gemini

    fence_output=False,                   # optional – warnings if True

)

# 4️⃣ Run inference

for result in gemini.infer(["Extract entities from the following text: …"]):
    print(result[0].output)   # → JSON adhering to the schema

If you accidentally set format_type=le.data.FormatType.YAML while passing gemini_schema, the constructor raises an InferenceConfigError to prevent invalid configurations.

Summary

  • Schema Generation: GeminiSchema.from_examples in langextract/providers/schemas/gemini.py analyzes example extractions to build JSON schemas that map Python types to JSON schema definitions.
  • API Integration: The GeminiLanguageModel class injects schemas via response_schema and response_mime_type="application/json" parameters, ensuring Gemini returns strictly conformant JSON.
  • Validation Guards: Runtime checks in validate_format and _validate_schema_config prevent incompatible settings like YAML output or markdown fences when using structured output.
  • Raw Output Handling: Because Gemini returns native JSON, requires_raw_output=True ensures the FormatHandler skips markdown fencing and uses the correct wrapper key.

Frequently Asked Questions

How does LangExtract convert Python types to JSON schema definitions?

The GeminiSchema.from_examples method inspects the extraction_class and attributes of your example data, collecting Python types (like str, int, float) into sets. It then maps these to JSON schema type strings (e.g., "type": "string", "type": "number") when constructing the extraction_properties dictionary, ensuring the generated schema accurately reflects your data structure.

What happens if I try to use YAML format with Gemini schema constraints?

The library raises an InferenceConfigError with the message: "Gemini structured output only supports JSON format. Set format_type=JSON or use_schema_constraints=False." This validation occurs in GeminiLanguageModel._validate_schema_config within langextract/providers/gemini.py, preventing API calls that would fail or return malformed data.

Why does LangExtract warn about markdown fences when using Gemini?

Because Gemini's API returns raw JSON when response_mime_type="application/json" is set, wrapping the output in markdown fences (like ```json) would cause parsing failures. The GeminiSchema.validate_format method checks format_handler.use_fences and emits a UserWarning if fences are enabled, recommending fence_output=False to ensure clean JSON parsing.

Can I use schema constraints with the Gemini Batch API?

Yes, but with a specific implementation detail. When using batch processing, the GeminiLanguageModel strips response_mime_type and response_schema from the generation config before sending the batch request, as shown in lines 67-71 of langextract/providers/gemini.py. The schema constraints are instead passed directly to the batch helper (gemini_batch.infer_batch), ensuring structured output compliance without breaking the batch API payload structure.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →