LangExtract 后端 Provider 指南:Gemini / OpenAI / Ollama 路由机制、ModelConfig 进阶配置与自定义 Provider 插件开发
LangExtract 后端 Provider 指南:Gemini / OpenAI / Ollama 路由机制、ModelConfig 进阶配置与自定义 Provider 插件开发
LangExtract 是面向 LLM 的非结构化文本抽取库,其“抽取能力落在哪个模型后端上”由一套松耦合的 Provider 体系决定。本文以 Provider 参考文档 为主体,结合仓库内的路由注册、工厂解析与插件加载源码,系统讲解内置的 Gemini、OpenAI、Ollama 三个 Provider 的接入方式与各自行为差异、ModelConfig 的高级用法,以及如何通过 @router.register + 入口点(entry point)注册一个可被 LangExtract 自动发现的自定义 Provider 插件。读完本文,你将能够在多后端之间按 model_id 自动路由、为私有端点与兼容接口显式指定 Provider,并独立开发、发布一个第三方 Provider。
Provider 体系一览:三个内置后端与插件扩展
LangExtract 默认内置三个 Provider,均通过“正则模式 → Provider 类”的映射被路由选中:
| Provider | 代表模型 | 是否内置依赖 | 鉴权方式 |
|---|---|---|---|
| Gemini | gemini-* 系列 |
是(google-genai 为常驻依赖) |
GEMINI_API_KEY,回退 LANGEXTRACT_API_KEY |
| OpenAI | gpt-4*、gpt-5* |
否,需 pip install langextract[openai] |
OPENAI_API_KEY,回退 LANGEXTRACT_API_KEY |
| Ollama | gemma2:2b、llama3.2、qwen 等本地模型 |
是(通过 HTTP 访问本地服务) | 无需 API Key,需运行中的 Ollama 服务 |
在代码层面,三种 Provider 都继承自 langextract/core/base_model.py 中的抽象基类 BaseLanguageModel,通过 infer() 把批量 prompt 转换为 ScoredOutput,随后由 resolver/annotation 层解析成带字符级定位的结构化结果。因此无论是公有云、本地模型还是自定义后端,对上层 lx.extract() 暴露的都是同一套接口。
一个值得注意的实现细节是:模型的“自动路由规则”并不散落在 Provider 代码里,而是集中在 langextract/providers/patterns.py 中集中定义,再由 langextract/providers/builtin_registry.py 统一注册。内置注册表把每个 Provider 绑定为一组正则模式和优先级(priority),例如:
GEMINI_PATTERNS = (r'^gemini',),即任何以gemini开头的model_id都会命中 Gemini;OPENAI_PATTERNS = (r'^gpt-4', r'^gpt4\.', r'^gpt-5', r'^gpt5\.');OLLAMA_PATTERNS覆盖gemma、llama、mistral、mixtral、phi、qwen、deepseek、command-r等常见本地模型前缀,以及meta-llama/Llama-3.2-1B-Instruct这类 Hugging Face 风格 ID。
三个内置 Provider 的注册优先级相同(均为 10)。当优先级相同时,langextract/providers/router.py 中的 resolve() 会按注册顺序取第一个模式命中的 Provider 类;如果模型 ID 同时命中多个 Provider 或需要强制指定某个后端,就应使用下文的 ModelConfig(provider=...) 显式选择。
从 lx.extract() 到 Provider:一次调用内部的解析链路
理解 Provider 的用法前,先看它如何被创建。以最典型的写法为例:
result = lx.extract(
text_or_documents=text,
prompt_description=prompt,
examples=examples,
model_id="gemini-2.5-flash", # 以 gemini 开头 → 自动路由到 Gemini Provider
)
在 langextract/extraction.py 中,extract() 会把顶层参数整理成一个 factory.ModelConfig,然后调用 factory.create_model();而 langextract/factory.py 的 create_model() 内部分两步完成 Provider 选择:
- 依次执行
providers.load_builtins_once()与providers.load_plugins_once(),保证内置 Provider 和第三方插件的注册表都已就绪; - 若
config.provider为空,调用router.resolve(model_id),按优先级对model_id做正则搜索,返回第一个命中的 Provider 类;随后把model_id、provider_kwargs、以及从环境变量补齐的默认值一起传入该类完成实例化。
这一步解释了参考文档反复强调的“让 extract() 与 factory/provider 层自动决定”的含义:只要 model_id 命中某个 Provider 的模式,你并不需要手动导入 Provider 模块,也不需要关心 provider 内部的初始化细节,LangExtract 的工厂会自动完成“路由 → 实例化 → 注入配置”的全过程。
Gemini Provider:默认且推荐的后端
Gemini 是 lx.extract() 的默认 Provider——在 langextract/extraction.py 中 model_id 的默认值即 gemini-3.5-flash,而 langextract/providers/gemini.py 的类默认值 _DEFAULT_MODEL_ID = 'gemini-3.5-flash' 与此一致。参考文档中的示例(gemini-2.5-flash / gemini-2.5-pro)属于面向不同任务的推荐取值:日常任务用 flash 类模型,复杂推理场景改用 pro 类模型。只要 ID 以 gemini 开头,路由都会命中 GeminiLanguageModel。
result = lx.extract(
text_or_documents=text,
prompt_description=prompt,
examples=examples,
model_id="gemini-2.5-flash", # 复杂推理可换 "gemini-2.5-pro"
)
鉴权方面,GEMINI_API_KEY 为第一优先,未设置时回退到 LANGEXTRACT_API_KEY。这一回退逻辑由 langextract/factory.py 的 _kwargs_with_environment_defaults() 实现:它按 model_id 中是否包含 "gemini" 前缀决定查找哪组环境变量;当 GEMINI_API_KEY 与 LANGEXTRACT_API_KEY 同时存在时,会发出一个 UserWarning 并采用前者。
从源码结构看,Gemini Provider 还内置了丰富的结构化输出能力与容错参数:get_schema_class() 返回 schemas.gemini.GeminiSchema,会把 few-shot examples 转换成 Gemini 原生支持的 response_schema 与 response_mime_type='application/json',从而保证模型直接输出合法 JSON(langextract/providers/schemas/gemini.py);同时支持 max_retries、retry_delay、max_retry_delay 等重试参数(默认 3 次重试、指数退避),并可通过 language_model_params={"http_options": ...} 传入 SDK 级选项。批量 prompt 在 max_workers > 1 时同样使用 ThreadPoolExecutor 并行发送(langextract/providers/gemini.py)。
OpenAI Provider:JSON 模式、并行批处理与自动路由边界
基础用法与鉴权
OpenAI Provider 需要额外安装 SDK(pip install langextract[openai],见 pyproject.toml 的 extras),然后即可直接用 GPT 系列模型:
result = lx.extract(
text_or_documents=text,
examples=examples,
prompt_description=prompt,
model_id="gpt-4o",
)
环境变量解析与 Gemini 对称:先查 OPENAI_API_KEY,未设置再回退 LANGEXTRACT_API_KEY。
JSON mode、fence 自动推断与并行度
参考文档明确说明了 OpenAI Provider 的几个实现特征,它们都能在源码中得到印证:
- JSON mode:当输出格式为 JSON 且未配置结构化 schema 时,Provider 会向 Chat Completions API 发送
response_format={"type": "json_object"}(见 langextract/providers/openai.py 的_build_chat_completions_params()),并要求模型以“You are a helpful assistant that responds in JSON format.”作为系统提示。 requires_fence_output=False:由于 OpenAI 原生返回裸 JSON 而非```json围栏包裹,Provider 覆盖了requires_fence_output属性,在 JSON 格式下恒为False。因此参考文档建议不要手动设置fence_output——extract()会读取该属性自动配置 resolver,显式传值反而可能破坏自动推断。- 并行批处理:当
max_workers > 1且批量 prompt 数大于 1 时,OpenAI Provider 用ThreadPoolExecutor并行发送请求(langextract/providers/openai.py 中infer()的实现),并行度取min(max_workers, len(prompts))。此外它还支持 OpenAI Batch API 的批处理模式(通过batchkwarg 或BatchConfig开启),此处不再展开。
关于 use_schema_constraints 的版本说明
参考文档提到“OpenAI Provider 不暴露 schema 类,因此 use_schema_constraints 是 no-op”。需要指出,这句话与当前仓库源码(版本 1.6.0,见 pyproject.toml)并不完全一致:当前 OpenAILanguageModel.get_schema_class() 实际返回 langextract/providers/schemas/openai.py 中的 OpenAISchema,tests/provider_schema_test.py 也明确断言了这一行为。也就是说,当调用 extract() 时若保持默认的 use_schema_constraints=True 并传入 examples,OpenAI 实际会走 strict JSON Schema(response_format={"type": "json_schema", ...})的结构化输出路径,且 requires_raw_output=True;只有在显式关闭 schema 或未传入 examples 时,才退回到上文最朴素的 json_object 模式。这一差异并不影响参考文档给出的推荐写法——两者都无需你手动设置 fence_output 或 use_schema_constraints——但在排查“为什么 OpenAI 输出比预期更严格”时,理解这条底层路径会很有帮助。
自动路由边界:OpenAI 兼容端点与非 GPT 模型
内置 OpenAI Provider 的自动路由存在两个限制:
- 它只自动匹配 GPT 风格模型 ID,即
^gpt-4、^gpt4\.、^gpt-5、^gpt5\.四个模式; - 工厂层做环境变量默认值补齐时,是按
model_id中是否出现"gpt"(而非"openai")来决定查找OPENAI_API_KEY的。
因此,如果你接入的是 OpenAI 兼容端点(LiteLLM、本地服务器、自定义 base_url)或 非 GPT 命名的模型,model_id 不会命中内置 OpenAI Provider 的模式,直接传 model_id="my-model" 会得到“No provider registered”类的 InferenceConfigError。正确做法是通过 ModelConfig 显式指定 Provider 并透传端点参数:
from langextract.factory import ModelConfig
result = lx.extract(
text_or_documents=text,
examples=examples,
prompt_description=prompt,
config=ModelConfig(
model_id="my-openai-compatible-model",
provider="openai",
provider_kwargs={"api_key": "sk-...", "base_url": "https://..."},
),
)
Ollama Provider:本地模型,无 Key 推理
Ollama Provider 面向完全本地化的场景,无需任何 API Key,前置条件仅仅是运行中的 Ollama 服务,以及已 ollama pull 到本地的模型:
result = lx.extract(
text_or_documents=text,
examples=examples,
prompt_description=prompt,
model_id="gemma2:2b",
model_url="http://localhost:11434",
)
其中 model_url 指定 Ollama 服务地址(默认即 http://localhost:11434)。从 langextract/providers/ollama.py 的源码可见,extract() 的 model_url 参数最终以 base_url 别名传入 Provider 构造器,因此两种写法等价;同时 Ollama Provider 也支持 base_url 参数,方便与其它 Provider 保持一致的调用习惯。
Ollama Provider 与另外两个 Provider 的关键差异包括:
- Schema 能力不同:它的
get_schema_class()返回FormatModeSchema(langextract/core/schema.py)。这类 schema 不约束字段级结构,只在生成时请求format='json',让 Ollama 原生保证输出是合法 JSON——对无法做严格 schema 约束的本地模型而言这是最务实的选择。同理,requires_raw_output在 JSON 下为True,所以fence_output与use_schema_constraints同样应保持默认、交由上层根据 Provider schema 自动决定。 - 批处理是串行的:参考文档特别提醒“
max_workers > 1不会带来并行”。核对源码确实如此——infer()用for prompt in batch_prompts:逐个请求,没有线程池(langextract/providers/ollama.py)。 - 推理参数默认值:Ollama 请求默认
temperature=0.1、超时 120 秒、keep_alive5 分钟、上下文num_ctx=2048;并默认以think=False发起请求,避免思维链模型只返回 reasoning trace 而导致空响应。
由于 Ollama 支持大量开源模型 ID(如 llama3.2:1b、mistral:7b、qwen2.5:7b 甚至 meta-llama/Llama-3.2-1B-Instruct 这样的 HF 风格命名),当某个本地模型名与其它 Provider 的模式冲突时,推荐用 ModelConfig(provider="OllamaLanguageModel") 显式指定。仓库中的 examples/ollama 目录还提供了可运行的演示脚本与 docker-compose 配置,适合快速做端到端验证。
ModelConfig 进阶:多 Provider 歧义消解与参数透传
当需要为 Provider 传专属 kwargs(自定义 base_url、显式 api_key 等)、或者 model_id 可能命中多个 Provider 时,lx.extract() 支持通过 config 参数传入 ModelConfig:
from langextract.factory import ModelConfig
result = lx.extract(
text_or_documents=text,
examples=examples,
config=ModelConfig(
model_id="gpt-4o",
provider_kwargs={"api_key": "your_key", "base_url": "https://..."},
),
)
参考文档总结了 ModelConfig 的三个字段,对应 langextract/factory.py 中 dataclass 的声明:
model_id:目标模型标识,如"gemini-3.5-flash"、"gpt-4o"。当只传provider而不传model_id时,会使用 Provider 自身的默认模型。provider:可选。用于在多个 Provider 的模式都命中同一个model_id时做歧义消解,取值既可以是类名("OpenAILanguageModel"),也可以是"gemini"、"openai"、"ollama"这类大小写不敏感的名称——router.resolve_provider()会先尝试精确模式,再按类名做子串匹配。provider_kwargs:Provider 专属关键字参数,例如自定义端点的base_url、显式传入的api_key、Ollama 的model_url,以及生成参数temperature、max_output_tokens等。
需要注意的是,config 与 model(已实例化的 Provider)都传入时,model 优先级最高;而 config 又优先于 model_id 等顶层参数。另外参考文档中“自定义 base URL 就需要手动传 provider_kwargs”的提示,对应的是工厂层环境变量补齐逻辑的边界:当 model_id 里既不含 gemini 也不含 gpt 时,LangExtract 不会自动从 OPENAI_API_KEY / GEMINI_API_KEY 读取凭据,因此访问兼容端点必须像上文那样显式提供 api_key。
自定义 Provider 插件:让任意模型后端接入 LangExtract
当内置 Provider 覆盖不了你的后端时,参考文档给出的方案是:继承 BaseLanguageModel,并用 @router.register 把一段正则模式绑定到你自己的 Provider 类上:
from langextract.core.base_model import BaseLanguageModel
from langextract.providers import router
@router.register(r"^my-model")
class MyProvider(BaseLanguageModel):
...
这样,任何以 my-model 开头的 model_id 在被 router.resolve() 命中时,都会实例化你的 Provider。如果插件要发布为独立的第三方包,则需要在 pyproject.toml 中声明入口点,让 LangExtract 在模型创建时自动发现它:
[project.entry-points."langextract.providers"]
my-model = "my_package.provider:MyProvider"
关于插件生命周期,参考文档点出了两个容易被忽略的事实:
- 插件加载是惰性的。
load_plugins_once()只在第一次通过工厂创建模型时运行(langextract/providers/init.py 与 langextract/factory.py 都会调用它),而不是在import langextract时执行。该函数基于importlib.metadata枚举langextract.providers组的全部入口点并逐个加载——模块顶层的@router.register装饰器正是在这一刻触发、把模式写入路由器。加载完成后,凡是模式命中的model_id都会被自动路由到该插件。若想彻底关闭插件发现机制,可以设置环境变量LANGEXTRACT_DISABLE_PLUGINS=1(tests/provider_plugin_test.py 对该开关有专门用例)。 - 旧导入路径仅作兼容。
langextract.inference与langextract.providers.registry仍然存在,作为向后兼容别名;新代码应如上例所示,从langextract.core.base_model与langextract.providers.router导入。
仓库在 examples/custom_provider_plugin 提供了一个可直接运行的最小示例插件,完整覆盖了 Provider 实现、schema 集成与测试脚本三部分:
- provider.py:
CustomGeminiProvider继承BaseLanguageModel并用@router.register(r'^gemini')注册(与内置 Gemini 模式相同,因此使用时必须显式provider="CustomGeminiProvider"),实现了__init__、get_schema_class()、apply_schema()与infer(); - schema.py:
CustomProviderSchema继承core.schema.BaseSchema,通过from_examples()把 few-shot examples 分析成 JSON Schema,由to_provider_config()转换为 Provider 构造参数,并用requires_raw_output告诉上层输出是否为无围栏裸 JSON; - pyproject.toml:声明
[project.entry-points."langextract.providers"]入口点,使插件能被自动发现; - test_example_provider.py:插件安装(
pip install -e .)后运行python test_example_provider.py即可验证。
如果你不想从零手抄模板,仓库还提供了脚手架脚本 scripts/create_provider_plugin.py,一条命令即可生成带 schema 支持的插件骨架:
python scripts/create_provider_plugin.py MyProvider --with-schema
为自定义 Provider 添加 schema 约束支持的完整方法链是:实现 get_schema_class() 返回自己的 BaseSchema 子类 → 在 schema 中实现 from_examples()(从 examples 学习结构)与 to_provider_config()(转成 Provider kwargs)→ 在 Provider 的 __init__ / apply_schema() 中接收并消费这些 kwargs → 实现 requires_raw_output 以配合 fence_output 的自动推断。可参考 langextract/providers/README.md 中“Adding Schema Support”一节的完整清单。
小结与进一步阅读
Provider 层的核心设计可概括为三条:模式驱动路由(model_id 正则命中即选定 Provider)、优先级 + 显式指定(ModelConfig.provider 用于歧义消解)、惰性插件发现(入口点 + 首次工厂调用时加载)。内置 Gemini / OpenAI / Ollama 分别覆盖默认云推理、GPT 系 JSON 结构化输出与完全本地的推理场景,三者都可保持 fence_output、use_schema_constraints 默认状态,由 extract() 依据 Provider schema 自动配置;自定义后端则只需实现 BaseLanguageModel、注册模式并声明入口点即可无缝融入。
仓库内可继续深入的材料包括:Provider 参考文档(本文主体)、Provider 系统架构说明(含模式注册时序图与完整开发 checklist)、用法技能文档(覆盖多 Provider 的选择建议)、自定义插件完整示例、路由与插件测试用例 router 解析、插件发现 以及 Provider schema 能力。