76d991c447
Auto Update PR / update-prs (push) Has been cancelled
CI / format-check (push) Has been cancelled
CI / test (3.10) (push) Has been cancelled
CI / test (3.11) (push) Has been cancelled
CI / test (3.12) (push) Has been cancelled
CI / live-api-tests (push) Has been cancelled
CI / plugin-integration-test (push) Has been cancelled
CI / ollama-integration-test (push) Has been cancelled
CI / test-fork-pr (push) Has been cancelled
223 lines
6.9 KiB
Markdown
223 lines
6.9 KiB
Markdown
# Custom Provider Plugin Example
|
|
|
|
This example demonstrates how to create a custom provider plugin that extends LangExtract with your own model backend.
|
|
|
|
**Note**: This is an example included in the LangExtract repository for reference. It is not part of the LangExtract package and won't be installed when you `pip install langextract`.
|
|
|
|
**Automated Creation**: Instead of manually copying this example, use the [provider plugin generator script](../../scripts/create_provider_plugin.py):
|
|
```bash
|
|
python scripts/create_provider_plugin.py MyProvider --with-schema
|
|
```
|
|
This will create a complete plugin structure with all boilerplate code ready for customization.
|
|
|
|
## Structure
|
|
|
|
```
|
|
custom_provider_plugin/
|
|
├── pyproject.toml # Package configuration and metadata
|
|
├── README.md # This file
|
|
├── langextract_provider_example/ # Package directory
|
|
│ ├── __init__.py # Package initialization
|
|
│ ├── provider.py # Custom provider implementation
|
|
│ └── schema.py # Custom schema implementation (optional)
|
|
└── test_example_provider.py # Test script
|
|
```
|
|
|
|
## Key Components
|
|
|
|
### Provider Implementation (`provider.py`)
|
|
|
|
```python
|
|
from langextract.core import base_model
|
|
from langextract.providers import router
|
|
|
|
@router.register(
|
|
r'^gemini', # Pattern for model IDs this provider handles
|
|
)
|
|
class CustomGeminiProvider(base_model.BaseLanguageModel):
|
|
def __init__(self, model_id: str, **kwargs):
|
|
# Initialize your backend client
|
|
|
|
def infer(self, batch_prompts, **kwargs):
|
|
# Call your backend API and return results
|
|
```
|
|
|
|
### Package Configuration (`pyproject.toml`)
|
|
|
|
```toml
|
|
[project.entry-points."langextract.providers"]
|
|
custom_gemini = "langextract_provider_example:CustomGeminiProvider"
|
|
```
|
|
|
|
This entry point allows LangExtract to automatically discover your provider.
|
|
|
|
### Custom Schema Support (`schema.py`)
|
|
|
|
Providers can optionally implement custom schemas for structured output:
|
|
|
|
**Flow:** Examples → `from_examples()` → `to_provider_config()` → Provider kwargs → Inference
|
|
|
|
```python
|
|
from langextract.core import schema as core_schema
|
|
|
|
class CustomProviderSchema(core_schema.BaseSchema):
|
|
@classmethod
|
|
def from_examples(cls, examples_data, attribute_suffix="_attributes"):
|
|
# Analyze examples to find patterns
|
|
# Build schema based on extraction classes and attributes seen
|
|
return cls(schema_dict)
|
|
|
|
def to_provider_config(self):
|
|
# Convert schema to provider kwargs
|
|
return {
|
|
"response_schema": self._schema_dict,
|
|
"enable_structured_output": True
|
|
}
|
|
|
|
@property
|
|
def requires_raw_output(self):
|
|
# True = provider emits raw JSON, no markdown fences needed
|
|
return True
|
|
```
|
|
|
|
Then in your provider:
|
|
|
|
```python
|
|
class CustomProvider(base_model.BaseLanguageModel):
|
|
@classmethod
|
|
def get_schema_class(cls):
|
|
return CustomProviderSchema # Tell LangExtract about your schema
|
|
|
|
def __init__(self, **kwargs):
|
|
# Receive schema config in kwargs when use_schema_constraints=True
|
|
self.response_schema = kwargs.get('response_schema')
|
|
|
|
def infer(self, batch_prompts, **kwargs):
|
|
# Use schema during API calls
|
|
if self.response_schema:
|
|
config['response_schema'] = self.response_schema
|
|
```
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
# Navigate to this example directory first
|
|
cd examples/custom_provider_plugin
|
|
|
|
# Install in development mode
|
|
pip install -e .
|
|
|
|
# Test the provider (must be run from this directory)
|
|
python test_example_provider.py
|
|
```
|
|
|
|
## Usage
|
|
|
|
Since this example registers the same pattern as the default Gemini provider, you must explicitly specify it:
|
|
|
|
```python
|
|
import langextract as lx
|
|
|
|
# Option A: build a model explicitly and pass it to extract()
|
|
config = lx.factory.ModelConfig(
|
|
model_id="gemini-3.5-flash",
|
|
provider="CustomGeminiProvider",
|
|
provider_kwargs={"api_key": "your-api-key"},
|
|
)
|
|
model = lx.factory.create_model(config)
|
|
|
|
result = lx.extract(
|
|
text_or_documents="Your text here",
|
|
model=model,
|
|
prompt_description="Extract key information",
|
|
examples=[...],
|
|
)
|
|
|
|
# Option B: let extract() build the model from a ModelConfig
|
|
result = lx.extract(
|
|
text_or_documents="Your text here",
|
|
config=lx.factory.ModelConfig(
|
|
model_id="gemini-3.5-flash",
|
|
provider="CustomGeminiProvider",
|
|
provider_kwargs={"api_key": "your-api-key"},
|
|
),
|
|
prompt_description="Extract key information",
|
|
examples=[...],
|
|
)
|
|
```
|
|
|
|
## Creating Your Own Provider - Step by Step
|
|
|
|
### 1. Copy and Rename
|
|
```bash
|
|
# Copy this example directory
|
|
cp -r examples/custom_provider_plugin/ ~/langextract-myprovider/
|
|
|
|
# Rename the package directory
|
|
cd ~/langextract-myprovider/
|
|
mv langextract_provider_example langextract_myprovider
|
|
```
|
|
|
|
### 2. Update Package Configuration
|
|
Edit `pyproject.toml`:
|
|
- Change `name = "langextract-myprovider"`
|
|
- Update description and author information
|
|
- Change entry point: `myprovider = "langextract_myprovider:MyProvider"`
|
|
|
|
### 3. Modify Provider Implementation
|
|
Edit `provider.py`:
|
|
- Change class name from `CustomGeminiProvider` to `MyProvider`
|
|
- Update `@router.register(...)` patterns to match your model IDs
|
|
- Replace Gemini API calls with your backend
|
|
- Add any provider-specific parameters
|
|
|
|
### 4. Add Schema Support (Optional)
|
|
Edit `schema.py`:
|
|
- Rename to `MyProviderSchema`
|
|
- Customize `from_examples()` for your extraction format
|
|
- Update `to_provider_config()` for your API requirements
|
|
- Implement `requires_raw_output` (abstract in `BaseSchema`) based on whether your provider emits raw JSON/YAML or fenced output
|
|
|
|
### 5. Install and Test
|
|
```bash
|
|
# Install in development mode
|
|
pip install -e .
|
|
|
|
# Test your provider
|
|
python -c "
|
|
from langextract.providers import load_plugins_once, router
|
|
load_plugins_once()
|
|
print('Provider registered:', any('myprovider' in str(e) for e in router.list_entries()))
|
|
"
|
|
```
|
|
|
|
### 6. Write Tests
|
|
- Test that your provider loads and handles basic inference
|
|
- Verify schema support works (if implemented)
|
|
- Test error handling for your specific API
|
|
|
|
### 7. Publish to PyPI and Share with Community
|
|
```bash
|
|
# Build package
|
|
python -m build
|
|
|
|
# Upload to PyPI
|
|
twine upload dist/*
|
|
```
|
|
|
|
**Share with the community:**
|
|
- Submit a PR to add your provider to the [Community Providers Registry](../../COMMUNITY_PROVIDERS.md)
|
|
- Open an issue on [LangExtract GitHub](https://github.com/google/langextract/issues) to announce your provider and get feedback
|
|
|
|
## Common Pitfalls to Avoid
|
|
|
|
1. **Forgetting to trigger plugin loading** - Plugins load lazily, use `load_plugins_once()` in tests
|
|
2. **Pattern conflicts** - Avoid patterns that conflict with built-in providers
|
|
3. **Missing dependencies** - List all requirements in `pyproject.toml`
|
|
4. **Schema mismatches** - Test schema generation with real examples
|
|
5. **Not handling None schema** - Provider must clear schema when `apply_schema(None)` is called (see provider.py for implementation)
|
|
|
|
## License
|
|
|
|
Apache License 2.0
|