LangExtract 后端 Provider 指南:Gemini / OpenAI / Ollama 路由机制、ModelConfig 进阶配置与自定义 Provider 插件开发

原创2026-09-08 14:31:291,835 阅读
文章标签:人工智能大模型NLPAI 应用

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 选择:

  1. 依次执行 providers.load_builtins_once() 与 providers.load_plugins_once(),保证内置 Provider 和第三方插件的注册表都已就绪;
  2. 若 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 的批处理模式(通过 batch kwarg 或 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 的自动路由存在两个限制:

  1. 它只自动匹配 GPT 风格模型 ID,即 ^gpt-4、^gpt4\.、^gpt-5、^gpt5\. 四个模式;
  2. 工厂层做环境变量默认值补齐时,是按 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_alive 5 分钟、上下文 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"

关于插件生命周期,参考文档点出了两个容易被忽略的事实:

  1. 插件加载是惰性的。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 对该开关有专门用例)。
  2. 旧导入路径仅作兼容。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 能力。

登录后查看全文
langextract