不用 LLM 抽 JSON?GLiNER2 机制与工程落地

0 阅读19分钟

信源:论文 arXiv:2507.18546,代码 github.com/fastino-ai/GLiNER2。下文机制与 API 以仓库实现为准;论文表格数字仅作零样本参考。


先摆问题:信息抽取为什么常常做成pipeline

从一段非结构化文本里拿出结构化字段——人名地名、情感标签、发票上的金额和日期、实体之间的关系——在 NLP 里叫信息抽取(IE)。工程里常见做法是:NER 一个模型、分类又一个、抽 JSON 再调 LLM 或专用模型、关系抽取再挂 GLiREL 一类东西。每加一种任务,就多一套部署、多一轮延迟,标签稍微变一下还可能要重训或重配。

第一代 GLiNER(2024)已经说明:用小型 encoder(双向 Transformer),把「实体类型」写进输入里,可以在 CPU 上做零样本 NER,效果和当时不少 LLM 提示词方案接近,体量大约一两亿参数。但 GLiNER 只管 NER;后面社区又长出 GLiClass(分类)、GLiREL(关系)等分支,能力散了。

GLiNER2 要做的事很直白:在保持「小模型 + CPU 能跑」的前提下,把 实体识别、文本分类、层级结构化抽取、关系抽取 收进 同一个模型、同一套 Schema 接口,并且允许 一次前向传播 里组合多个任务。论文把它叫 schema-driven:你先声明「要抽什么」,模型按声明好的 prompt 模板去读文本,而不是每次写一大段自然语言指令去赌 LLM 格式稳定。


模型在干什么

先说一句容易误解的:GLiNER2 没有 GPT 那种自回归 decoder,不会一个字一个字「生成 JSON」。它是 Encoder + 若干任务头:把 schema 和正文拼成一条序列,过 DeBERTa 编码;NER/结构化/关系在 候选 span 上打分匹配;分类在 标签 embedding 上出 logit。下面按任务拆开,参考论文 Appendix A 和源码。

1. 统一输入:前半段是「任务说明书」,后半段是正文

论文抽象写法:

[Task Prompt] ⊕ [SEP] ⊕ [Input Text]

落到仓库里,SchemaTransformer._format_input_with_mapping 实际拼的是:

[schema_1 的 token 串] [SEP_STRUCT] [schema_2 的 token 串] [SEP_STRUCT][SEP_TEXT] [正文按词切开后的 token 串]
  • 每个子任务(entities、某个 classification、某个 structure、某个 relation)各自占一段 schema token
  • [SEP_STRUCT] / [SEP_TEXT] 是库内常量(processor.py 里 SEP_STRUCTSEP_TEXT),作用和论文的 [SEP] 一样:别让任务说明和正文在注意力里糊成一团

例子(概念上) :NER + 情感,正文 Tim Cook unveiled iPhone 15. Analysts are optimistic.

[P] entities [E] person [E] company [E] product [SEP_STRUCT]
[P] sentiment [L] positive [L] negative [L] neutral [SEP_TEXT]
tim cook unveiled iphone 15 analysts are optimistic

(真实字符串会再经 HF tokenizer 切成 subword;特殊 token 会进词表。)

2. 特殊 token:任务侧的「锚点」

库内 / 论文出现在哪模型拿来干什么
[P]每个子任务开头结构化/关系:count 头读它的 hidden state,预测有几个实例(0–19)
[E]NER 每个类型前该位置 hidden → 实体类型向量,去和正文 span 打分
[L]分类每个标签前该位置 hidden → 标签向量,过 classifier MLP 得 logit
[C]结构化每个字段前字段向量;配合 count_embed 变成「第 k 个实例的第 j 个字段」
[R]关系每个槽位前与 [C] 类似,常见 head / tail 两槽,走同一套 span 匹配

这些 token 会 add_special_tokens 进 DeBERTa 词表,和 encoder 一起训。

3. NER:在正文「词」上滑窗,和每个 [E] 比相似度

输入示例

  • Schema:personcompanyproduct
  • 正文:Apple CEO Tim Cook announced iPhone 15.

做法(推理时,engine.py → _extract_entities

  1. 正文先被 WhitespaceTokenSplitter 切成 appleceotimcook, …),每个词再切成 subword,聚成 一词一向量(默认取该词第一个 subword 的 hidden,token_pooling="first")。
  2. compute_span_rep 在所有 连续词片段 上滑窗,宽度 ≤ max_width(配置里多为 8 个词),得到每个候选 span 的向量(SpanRepLayer,来自 GLiNER 家族的 markerV0)。
  3. 每个 [E] person / [E] company … 在 encoder 输出里取对应位置的 hidden,当作 类型向量
  4. 算 sigmoid(span_emb · type_emb)(实现里是 einsum 批量点积),超过 threshold 的 span 保留。
  5. 用 start_mapping / end_mapping 把 词级 span 映回 字符 start/endinclude_spans=True 时)。

这一条例子里期望发生什么(零样本,不保证全对)

类型可能抽到的 span对应词级片段
personTim Cooktim + cook(连续两词)
companyAppleapple
productiPhone 15iphone + 15

NER 没有「先找 mention 再分类」的两阶段流水线,而是 所有候选 span 同时对每种类型打分;同一 span 理论上可多种类型都过阈值(靠描述和训练压这种情况)。

4. 文本分类:每个 [L] 一个 logit,不碰 span

输入示例

  • 任务 sentiment,标签 positive / negative / neutral
  • 正文:This laptop has amazing performance but terrible battery life.

做法(engine.py → _extract_classification_result

  1. encoder 跑完后,在 schema 段找到每个 [L] 后面的标签名位置,取出 hidden → cls_embeds
  2. 共享的 classifiermodel.py 里两层 MLP,hidden → 2×hidden → 1)对每个标签出一个 标量 logit
  3. 单标签:softmax,取 argmax(上例可能偏 negative)。
  4. 多标签:sigmoid,保留 ≥ cls_threshold 的标签(可多个)。

分类 只看整段正文在 encoder 里的上下文,不要求标签对应某个字符 span;所以中文连续无空格时,分类往往比 NER 稳(见下文「中文与 span」)。

5. 结构化抽取:先数有几「行」,再每行填列

输入示例

  • Structure product,字段 name::strprice
  • 正文:iPhone 15 costs $999. Galaxy costs $899.

序列形状(概念)

[P] product [C] name [C] price [SEP_TEXT] iphone 15 costs $999 galaxy costs $899

做法(三步,论文 Appendix + model.py / engine.py

  1. 数实例count_pred(MLP,[P] hidden → 20 类)预测 K,例如 K=2。推理用 argmax;训练用 gold count 的交叉熵。
  2. 造 K 套字段向量count_embedlayers.py 里 CountLSTM / CountLSTMv2 等)把每个 [C] 的 embedding 扩展成 K 份——第 1 份偏向「第一个 product」,第 2 份偏向「第二个」,以此类推,靠 GRU + 位置 embedding 区分实例,而不是靠 decoder 逐字段生成。
  3. 每字段对齐 span:对第 k 个实例的第 j 个字段,用与 NER 相同的 span_rep 和字段向量点积,在正文词上找最高分片段 → nameiPhone 15 / Galaxyprice$999 / $899

字段带 choices(枚举)时,在推理侧当成分类子问题处理(cls_fields 映射),仍落在 span/标签打分框架里。

6. 关系抽取:同一套 span 机,两个(或多个)槽位填 head/tail

输入示例

  • 关系 works_for
  • 正文:John works for Apple Inc.

序列形状

[P] works_for [R] head [R] tail [SEP_TEXT] john works for apple inc

做法

  1. count_pred 预测有几条关系实例(常为 1)。
  2. count_embed 为 headtail 各生成实例 k 下的向量。
  3. 分别在正文 span 上为 headtail 找最佳片段 → ('John', 'Apple Inc.')
  4. _extract_relations 把两槽拼成元组;include_spans 时 head/tail 各带字符坐标。

关系在实现里 不是 单独的图神经网络,而是 多槽位的结构化 span 匹配

7. 多任务组合:多个 schema 段 + 一次 encoder

create_schema().entities(...).classification(...).structure(...) 会在 schema_tokens_list 里得到 多段 prompt,中间用 [SEP_STRUCT] 连接,正文只出现 一次

encoder 只对 input_idsforward 一次extract_embeddings_from_batch 再按位置切出:

  • 正文各  的向量列表 → 共用一份 span_rep
  • 每个子任务 schema 上 [P]/[E]/[L]/[C]/[R] 的向量 → 分别走分类头或 span 匹配。

因此组合任务的算力 ≈ 一次编码 + 多个轻量头,而不是「任务数个完整模型」。


代码里对应什么:从 schema 到字典结果

整体可以看成 五条流水线(没有独立「解码器」模块,解码 = 在候选 span / 标签上取 argmax 或阈值过滤):

图片

模块对照表

阶段文件 / 类做什么
Schema 构建inference/schema.py → Schema链式 .entities().classification().structure(),产出 JSON 形任务声明
词切分processor.py → WhitespaceTokenSplitter正则切词 + 字符起止;中文连续串易并成一词(见「中文与 span」)
序列拼装SchemaTransformer._format_input_with_mappingschema + [SEP_TEXT] + 正文词 → subword → input_ids
批处理PreprocessedBatch / collatepadding、text_word_indices 供 fast gather
编码器Extractor.encoderAutoModel,默认 microsoft/deberta-v3-base;可选 FlashDeberta
词级特征extract_embeddings_from_batch从 subword hidden 汇聚到词(first/mean/max)
Schema 特征同上,读 schema_special_indices每个 [P][E][L][C][R] 处的 hidden
Span 特征compute_span_rep + SpanRepLayer词序列上所有长度 ≤8 的片段 → (num_spans, hidden)
分类头Extractor.classifier每个标签 hidden → 1 维 logit
计数头Extractor.count_pred[P] hidden → 20 类(实例数 0–19)
多实例展开layers.CountLSTM* → count_embed字段 embedding × 预测个数 K → (K, num_fields, hidden)
训练损失forward → _compute_sample_loss分类 BCE;结构 BCE on span 网格;count CE
推理入口inference/engine.py → GLiNER2extract / batch_extract 调 _extract_from_batch
后处理RegexValidator(可选)在 span 已抽出后做正则过滤(见「Schema 与 RegexValidator」)

分词与「特征」两层含义

第一层:词(word) ——WhitespaceTokenSplitter 产出,是 NER/结构/关系的 span 单位max_len 限制的是词数。

第二层:子词(subword) ——DeBERTa tokenizer.tokenize(每个词),encoder 实际吃的是 subword 序列。text_word_first_positions 记录「这个词的第一个 subword 在序列里的下标」,后面用 gather 一次取出各词代表向量。

因此:不是 jieba 之类的分词;中文要自己预处理时,应在进模型 之前 用空格把词隔开,且训推一致(见「中文与 span」)。

Span 怎么从 hidden 变出来(compute_span_rep

对长度为 T 的词序列,枚举所有 (start, end) 且 end - start < max_width 的片段;SpanRepLayer 把片段内各词向量合成 一个 span 向量(GLiNER markerV0:以边界词为主的 MLP 组合,不引入跨样本注意力)。

训练时 compute_struct_loss 在 (实例, 字段, start, width) 四维网格上打 BCE;推理时 _find_spans 在得分张量上找峰值并映射回字符串。

「解码器」在本项目里指什么

常见含义GLiNER2 有没有
自回归 Transformer decoder(GPT 式)
把 hidden 变成 JSON 字符串,直接出 Python dict
分类 softmax / 多标签 sigmoidclassifier + _extract_classification_result
Span 解码(选 start/width)_find_spans + 字符映射

走读示例:一次 extract_entities 调用了谁

extractor.extract_entities(text, ["company""person"])
  1. engine.pycreate_schema().entities(...) → extract → batch_extract(batch_size=1)
  2. processor.transform_and_format:schema → schema_tokens_list;正文 → text_tokens + input_ids
  3. model._encode_batchencoder(input_ids) → extract_embeddings_from_batch
  4. compute_span_rep(token_embs):正文词向量 → span_info
  5. _extract_span_resultcount_pred 对 entities 常预测 ≥1;对每个 [E] 类型跑 span 打分
  6. _format_spans:组装 {'entities': {'company': [...], 'person': [...]}}

训练时同一条 input_ids 走 Extractor.forward(PreprocessedBatch),用 gold 标注算 classification_loss / structure_loss / count_loss 之和。

用户 API 与核心类的关系

  • GLiNER2engine.py):继承 Extractormodel.py),对外暴露 extract_*batch_*load_adapter
  • SchemaTransformerprocessor.py):训练/推理共用的「schema → 张量」编译器;Extractor 构造时挂 self.processor,编码后由 processor 负责 拆词向量 / schema 向量

能做什么(按任务说)

论文和 README 对齐的能力可以收成四块;关系抽取在库里有 API,训练数据里也有,但论文 zero-shot 基准主要报的是 分类 + NER(层级结构在论文里写明缺标准 zero-shot benchmark,未做同口径横评)。

  1. 命名实体识别(NER)
    标签可以只是一串字符串,也可以是 {类型: "自然语言描述"},用描述收窄语义(医疗、法务场景常用)。可选返回 置信度字符级 span
  2. 文本分类
    单标签或多标签;多标签时调 cls_threshold。多个分类任务可以在一次 extract 里并列声明。
  3. 层级 / 结构化抽取
    用字段语法 字段名::类型::描述,类型 str 或 list;可用 [选项1|选项2] 做枚举约束。适合「一张表多行」「一个公司块 + 多个产品块」这类半结构化输出。
  4. 关系抽取
    输出 (head, tail) 元组列表,关系名可带描述。README 示例:works_forlocated_in 等。
  5. 组合
    create_schema() 链式挂上 entity、classification、structure、relations,一次 extract(text, schema) 返回多键结果。适合日志解析、工单分拣、合规字段预审等 字段集合固定 的场景。

怎么用(最短路径)

安装

# 不跑本地模型:schema 校验、训练数据工具、云 API 客户端
pip install gliner2

# 本地推理 / 训练(会拉 torch、transformers)
pip install gliner2[local]

gliner2/__init__.py 对 GLiNER2 做了懒加载:没装 [local] 时 import 不会立刻拖进 torch。

本地推理

from gliner2 import GLiNER2

extractor = GLiNER2.from_pretrained("fastino/gliner2-base-v1")

text = "Apple CEO Tim Cook announced iPhone 15 in Cupertino yesterday."
result = extractor.extract_entities(
    text,
    ["company""person""product""location"],
)
# {'entities': {'company': ['Apple'], 'person': ['Tim Cook'], ...}}

带描述、置信度、span 的参数见仓库 tutorial/ 下各篇;批量场景用 batch_extract_entities / batch_extract,可开 num_workers 做预处理并行。

Schema 组合(多任务一次调用)

schema = (
    extractor.create_schema()
    .entities(["person""organization"])
    .classification("sentiment", ["positive""negative""neutral"])
)
result = extractor.extract(text, schema)

结构化字段用 extract_json 或 schema builder 的 structure 段;关系用 relations(...)

云 API

GLiNER2.from_api() 走 Pioneer 托管的大模型(如 XL 1B),适合本机不想下权重、也没有 GPU 的情况;和「205M 本地小模型」是不同部署选项,不是替代关系。

微调

仓库带 training/ 与 GLiNER2Trainertutorial/9-training.md)。垂直场景建议 全量微调 base 权重,让 encoder 与任务头一起对齐域内标注;论文训练集约 25 万 条,混合真实文档与 GPT-4o 标注/合成数据,多任务一起训。仓库也支持 LoRA 挂多域 adapter(tutorial/10-lora_adapters.md),本篇不展开。


Schema:接口怎么写

Schema 会直接编成输入里的 Task Prompt。改标签名、改字段,等于改这次 forward 在找什么。Schema 类不依赖 torch,可在没装 [local] 的机器上先写好、校验。

三种写法,选一种主路径:

extractor.extract_entities(text, ["person""company"])
extractor.classify_text(text, {"sentiment": ["positive""negative""neutral"]})
extractor.extract_json(text, {"contact": ["name::str""email::str""phone"]})

schema = (
    extractor.create_schema()
    .entities({"person""人名""organization""机构名"})
    .classification("sentiment", ["positive""negative""neutral"])
    .structure("contact")
        .field("name", dtype="str")
        .field("email", dtype="str")
        .field("phone")
    .relations(["works_for"])
)
result = extractor.extract(text, schema)

字典 / JSON 用 Schema.from_dict() / from_json(),适合配置中心;to_dict() 导出,与模型权重 同版本发布

结构化字段串:字段名::类型::描述,或 字段名::[选项A|选项B]::类型str 单值,list 可多段,choices 做枚举。同一 structure 多实例:训练 JSONL 里用 多个同名 parent 的列表,每个 dict 一行实例。

组合任务共享正文编码,建议从最小 schema 起,指标够了再加字段。include_confidence / include_spans 对组合调用全局生效;批量用 batch_extract(texts, schema, batch_size=..., num_workers=...)num_workers 只并行预处理。

RegexValidator

挂在 structure 字段上,span 抽出 之后 做正则过滤,不训练进网络:

from gliner2 import RegexValidator

email_v = RegexValidator(r"^[\w.-]+@[\w.-]+.\w+$")
schema = (
    extractor.create_schema()
    .structure("contact")
        .field("email", dtype="str", validators=[email_v])
)

适用邮箱、电话、编号形态;不能替代 Luhn、身份证校验位等业务规则。


中文与 span

NER、结构、关系都要字符级 span。库内 WhitespaceTokenSplitter 先切 ,再 HF tokenizer 切 子词;span 在词上滑窗,再映回字符坐标。batch_extract 的 max_len 限制 词数,超出静默丢弃尾部。

连续汉字 无空格 时,正则常把整段并成一个「词」,词级 span 很难标人名/地名。分类受影响小,NER / structure / 关系 / 中文 PII 受影响大。

import jieba
from gliner2 import GLiNER2

extractor = GLiNER2.from_pretrained("fastino/gliner2-multi-v1")

def zh_for_gliner(text: str) -> str:
    return " ".join(jieba.cut(text))

spaced = zh_for_gliner("张三在北京的阿里巴巴工作")
result = extractor.extract_entities(spaced, ["人名""地名""机构"])

gliner2-multi-v1(mDeBERTa)对中文子词更合适,但切词器逻辑未变,仍建议预分词。include_spans 坐标相对 传入字符串(含空格);JSONL 的 input 须与推理用同一套分词,标注写在 spaced 文本上。字级插空格是权宜:每字一词,但 span 窗口约 8 词,长机构名吃力。


PII 场景

GLiNER2 做 PII 是 可声明类型的 span 检测,典型链路:

原文 → GLiNER2(PII schema)→ 掩码/替换/审计 → 存储或 LLM

适合姓名、电话、邮箱、证件号、银行卡片段、地址等类型相对固定;数据不出内网、CPU 批量扫;与规则引擎并存(模型候选 + 正则确认)。

pii_schema = extractor.create_schema().entities({
    "person_name""自然人姓名,含中文与英文名",
    "phone""手机号或座机",
    "email""电子邮件",
    "id_number""身份证、护照等证件号",
    "bank_card""银行卡或信用卡号",
    "address""可定位到个人的详细地址",
})

零样本能跑,合规口径(什么算地址、是否含公司名)往往要靠 描述 + 少量全量微调。Presidio 等可把 GLiNER2 当自定义 NER 后端,业务侧把 extract 转成 (start, end, type);仓库不带 Presidio 适配器。

不是 DLP 全套;长文超 max_len 须分段;多数 PII 管线只需 NER + 正则,不必四类任务全开。


微调:JSONL 与全量训练

零样本不够时,用 JSONL + GLiNER2Trainer全量微调(encoder 与任务头一起训)。一行一条:

{"input": "待分析正文", "output": { ... 标注 ... }}

output 常用键:entitiesclassificationsjson_structuresrelations,与推理 schema 语义对齐。关系标注硬约束:同一种关系名,全文实例字段名须与第一次出现一致(不能第一条 head/tail,后面改成 from/to)。json_structures 多实例用列表里多个同名 parent;允许某类为空,但不能所有任务都空。

from gliner2.training.data import InputExample
from gliner2.training.trainer import GLiNER2Trainer, TrainingConfig

examples = [
    InputExample(
        text="John works at Google in California.",
        entities={"person": ["John"], "company": ["Google"], "location": ["California"]},
    ),
]
model = GLiNER2.from_pretrained("fastino/gliner2-base-v1")
config = TrainingConfig(
    output_dir="./output",
    num_epochs=10,
    batch_size=8,
    encoder_lr=1e-5,
    task_lr=5e-4,
)
GLiNER2Trainer(model, config).train(train_data=examples)
# trainer.train(train_data="train.jsonl")

中文微调:input 用与推理相同的预分词;entities 字符串须能在 input 中字面找到。建议顺序:10~20 条零样本 → 难字段加描述或 RegexValidator → 几百~几千条 JSONL 全量微调 → holdout 对比。


批量推理与上线

大队列别逐条 extract 硬循环:

results = extractor.batch_extract(
    texts,
    schema,
    batch_size=32,
    num_workers=4,
    threshold=0.5,
    include_confidence=True,
)

batch_size 按显存/CPU 调;max_len 超出静默截断,长文须分段并处理 span 偏移。GPU 可试 quantize=Truecompile=True;CPU 生产以 base-v1 + 合理 batch 为主。论文 CPU 分类约百毫秒级/次(标签 5~50),实测为准。

部署适合
from_pretrained 本地不出网、可控延迟、schema 固定
from_api()不想管权重与机器
全量微调权重单域垂直、标注口径稳定

在 RAG / Agent 里放哪

GLiNER2 管「从文本里抠字段」;LLM 管「用字段和上下文做决策」。常见落点:

原文/用户消息 → 分段 → GLiNER2(PII 或元数据)→ 掩码或打标 → 索引 / 再送 LLM

入库前扫 PII;RAG chunk 打实体、时间、产品名做过滤或重排;工单用分类路由;工具返回的大段文本用固定 structure schema 抽 JSON 再进 LLM。不适合替代检索、规划,或单次超长上下文(须分段)。

闭集、高频、要 span → GLiNER2;开放域偶尔抽 → LLM;不要让 LLM 再抽一遍已确定的字段

图片


论文里的效果与效率(怎么读这些数)

分类(Table 2) :七个公开集上,GLiNER2(205M)平均准确率 0.72,高于 GLiClass(0.63)和 DeBERTa-v3 zero-shot(0.69),低于 GPT-4o(0.84)。意图类(如 Banking77)涨得明显;个别集仍略输 DeBERTa。

NER(Table 3,CrossNER) :平均 F1 0.590,接近 GPT-4o(0.599),略低于专注 NER 的 GLiNER-M(0.615)——论文的解释是:GLiNER2 是多任务通用模型,NER 略让一点算预期内的 trade-off。

延迟(Table 4,CPU 分类) :标签数从 5 增到 50,GLiNER2 大约 130–208 ms 量级;DeBERTa 按「每标签一次 forward」线性涨,20 标签时论文报 约 6.8× 更慢。GPT-4o 走 API,同一表里大约 2.6× 慢于 GLiNER2/GLiClass 的 CPU 路径。

读表时注意三点:零样本设定(不是你在域内微调后的上限);上下文 论文对比里 GLiNER2 为 2048 tokens(初代 GLiNER 512);层级结构 缺统一公开基准,别拿「没表」当「一定更强或更弱」。


LLM 时代为什么还要 GLiNER2(不是二选一)

大模型用提示词也能抽字段,很多团队已经这么干了。GLiNER2 存在的理由,论文和工程实践里其实是 分工,不是「谁取代谁」:

  1. 成本和时延
    高 QPS、短文本、标签集合固定的抽取(客服意图、PII 扫描、日志字段),用小 encoder 在 CPU 上跑,单条毫秒级、无按 token 计费,和「每条都调 7B+ API」账不一样。
  2. 数据不出域
    医疗、金融、政务常见要求是 原文不出内网。205M 模型本地加载即可推理;论文 Table 1 把「CPU 部署 / 无 API 费用 / 隐私」和 7B 级开放 LLM 的差异写得很直白。
  3. 输出形态稳定
    LLM 抽取往往要再解析 JSON、修格式、对 hallucination 做护栏。GLiNER2 的输出是 schema 绑定的字典结构(实体列表、标签、字段槽位),适合下游直接进规则引擎、检索索引或数据库列——不是万能,但在「字段 closed-set」任务上路径更短。
  4. 和 LLM 配合,而不是对立
    常见拆法:GLiNER2 做第一道结构化(实体、分类、固定槽位),LLM 做开放推理、长文档摘要、需要世界知识的步骤
  5. GLiNER 生态的收口
    以前 NER / 分类 / 关系可能要三套权重;GLiNER2 用一套接口和一次 forward 组合,运维面更简单——这是产品向的「独特之处」,不是声称全面超越 GPT-4o。

不适合硬上的情况也要说清楚:开放式抽取、schema 每周大变、超长文档、强推理型 IE(需要多跳阅读与世界知识),仍更适合 LLM 或专门 pipeline;GLiNER2 的强项是 声明式 schema + 高效 encoder + 本地可部署


和相近方案比一眼(避免捧杀)

路线特点和 GLiNER2 的差别(简述)
spaCy / 传统 NLP成熟、快,标签多为训练时固定零样本换标签能力弱,多任务要多套模型
LLM 提示词抽取灵活、懂开放域GPU/API 成本、格式与幻觉要额外治理
初代 GLiNERNER 强、CPU 友好只有 NER;上下文 512
GLiNER2多任务 + schema 组合参数 ~205M–340M;NER 专模略强仍可能,但省管线

读源码时建议看的文件

路径内容
gliner2/inference/engine.py对外 GLiNER2、batch 推理、create_schema
gliner2/inference/schema.pySchema 构建与校验
gliner2/processor.pySchemaTransformer、prompt 编解码、PreprocessedBatch
gliner2/model.pyExtractor 网络结构、loss、forward
gliner2/layers.pycount / span 相关层
tutorial/*.md官方按任务拆的用法说明
gliner2/api_client.py云端 API 客户端

包版本以仓库 __init__.py 的 __version__ 为准(撰写时看到为 1.3.1)。


收束

GLiNER2 是 Encoder + 任务头:schema 与正文一次编码,分类走 logit,NER/结构/关系走词级 span 匹配,没有自回归 decoder,直接出 Python dict。闭集字段、要本地跑、要稳定 JSON 结构时值得试;开放域和长推理仍靠 LLM。中文 NER 依赖 multi-v1 与训推一致的预分词;PII 是 NER + 规则复核;垂直场景用 JSONL 全量微调比裸零样本稳得多。

字段名基本固定、量大、要低延迟或不能出网,可先装 gliner2[local] 用十几条业务句跑 schema;开放式理解长报告,GLiNER2 更适合前置过滤器,而不是唯一大脑。


信源

  • Zaratiana, U., Pasternak, G., Hurn-Maloney, O. B. G., & Lewis, A. GLiNER2: An Efficient Multi-Task Information Extraction System with Schema-Driven Interface.  arXiv:2507.18546, 2025.
  • 代码仓库:github.com/fastino-ai/GLiNER2