From 38771987f9ccae5a800843fd9c6cb6b26b489ca0 Mon Sep 17 00:00:00 2001 From: Raphael Mitsch Date: Mon, 11 May 2026 16:02:01 +0200 Subject: [PATCH] fix: Support gliner2 1.3 by resolving Schema/StructureBuilder via package root. gliner2 1.3 removed the `gliner2.inference.engine` submodule. Resolve the `Schema` and `StructureBuilder` types via `gliner_.Schema` / `gliner_.StructureBuilder`, which prefers the 1.3+ top-level re-exports and falls back to the engine submodule for 1.2. Co-Authored-By: Claude Opus 4.7 --- sieves/model_wrappers/gliner_.py | 14 +++++++++++++- sieves/tasks/predictive/gliner_bridge.py | 7 +++---- sieves/tasks/predictive/schemas/classification.py | 7 +------ .../predictive/schemas/information_extraction.py | 8 +------- sieves/tasks/predictive/schemas/ner.py | 8 +------- .../predictive/schemas/relation_extraction.py | 8 +------- sieves/tasks/predictive/utils.py | 7 ++++--- .../tests/tasks/predictive/test_signature_utils.py | 10 +++++----- .../predictive/test_task_signature_migration.py | 12 ++++++------ 9 files changed, 35 insertions(+), 46 deletions(-) diff --git a/sieves/model_wrappers/gliner_.py b/sieves/model_wrappers/gliner_.py index 1cc8859f..89374cb6 100644 --- a/sieves/model_wrappers/gliner_.py +++ b/sieves/model_wrappers/gliner_.py @@ -12,7 +12,19 @@ from sieves.model_wrappers.core import Executable, ModelWrapper from sieves.model_wrappers.types import TokenUsage -PromptSignature = gliner2.inference.engine.Schema | gliner2.inference.engine.StructureBuilder +# gliner2 1.3 removed the `gliner2.inference.engine` submodule and re-exports +# `Schema`/`StructureBuilder` at the package root. Resolve them in a way that +# works for both 1.2 (engine submodule) and 1.3+ (top-level re-export). +try: + Schema = gliner2.Schema + StructureBuilder = gliner2.StructureBuilder +except AttributeError: # pragma: no cover - gliner2 < 1.3 fallback + from gliner2.inference import engine as _gliner2_engine + + Schema = _gliner2_engine.Schema + StructureBuilder = _gliner2_engine.StructureBuilder + +PromptSignature = Schema | StructureBuilder Model = gliner2.GLiNER2 Result = dict[str, str | list[str | dict[str, Any]]] diff --git a/sieves/tasks/predictive/gliner_bridge.py b/sieves/tasks/predictive/gliner_bridge.py index 61c90a59..162a8306 100644 --- a/sieves/tasks/predictive/gliner_bridge.py +++ b/sieves/tasks/predictive/gliner_bridge.py @@ -8,7 +8,6 @@ from collections.abc import Sequence from typing import Any, Literal, override -import gliner2 import pydantic from sieves.data import Doc @@ -36,7 +35,7 @@ from sieves.tasks.predictive.utils import convert_to_signature -class GliNERBridge(Bridge[gliner2.inference.engine.Schema, gliner_.Result, gliner_.InferenceMode]): +class GliNERBridge(Bridge[gliner_.Schema, gliner_.Result, gliner_.InferenceMode]): """Bridge for GLiNER2 models.""" def __init__( @@ -78,7 +77,7 @@ def model_type(self) -> ModelType: @override @property - def prompt_signature(self) -> gliner2.inference.engine.Schema | gliner2.inference.engine.StructureBuilder: + def prompt_signature(self) -> gliner_.Schema | gliner_.StructureBuilder: # Map internal inference mode to GliNER utility mode. mode_map = { gliner_.InferenceMode.classification: "classification", @@ -117,7 +116,7 @@ def prompt_signature(self) -> gliner2.inference.engine.Schema | gliner2.inferenc model_type=ModelType.gliner, **kwargs, ) - assert isinstance(prompt_signature, gliner2.inference.engine.Schema | gliner2.inference.engine.StructureBuilder) + assert isinstance(prompt_signature, gliner_.Schema | gliner_.StructureBuilder) return prompt_signature diff --git a/sieves/tasks/predictive/schemas/classification.py b/sieves/tasks/predictive/schemas/classification.py index cb4f0013..821fce5d 100644 --- a/sieves/tasks/predictive/schemas/classification.py +++ b/sieves/tasks/predictive/schemas/classification.py @@ -3,7 +3,6 @@ from __future__ import annotations import dspy -import gliner2.inference.engine import pydantic from sieves.model_wrappers import dspy_, gliner_, huggingface_, langchain_, outlines_ @@ -98,10 +97,6 @@ class ResultMultiLabel(pydantic.BaseModel): TaskModel = dspy_.Model | gliner_.Model | langchain_.Model | huggingface_.Model | outlines_.Model TaskPromptSignature = ( - type[dspy.Signature] - | type[pydantic.BaseModel] - | gliner2.inference.engine.Schema - | gliner2.inference.engine.StructureBuilder - | list[str] + type[dspy.Signature] | type[pydantic.BaseModel] | gliner_.Schema | gliner_.StructureBuilder | list[str] ) TaskResult = ResultSingleLabel | ResultMultiLabel diff --git a/sieves/tasks/predictive/schemas/information_extraction.py b/sieves/tasks/predictive/schemas/information_extraction.py index 1e45bb34..9a23aebe 100644 --- a/sieves/tasks/predictive/schemas/information_extraction.py +++ b/sieves/tasks/predictive/schemas/information_extraction.py @@ -3,7 +3,6 @@ from __future__ import annotations import dspy -import gliner2.inference.engine import pydantic from sieves.model_wrappers import dspy_, gliner_, langchain_, outlines_ @@ -73,10 +72,5 @@ class ResultMulti(pydantic.BaseModel): TaskModel = dspy_.Model | gliner_.Model | langchain_.Model | outlines_.Model -TaskPromptSignature = ( - type[dspy.Signature] - | type[pydantic.BaseModel] - | gliner2.inference.engine.Schema - | gliner2.inference.engine.StructureBuilder -) +TaskPromptSignature = type[dspy.Signature] | type[pydantic.BaseModel] | gliner_.Schema | gliner_.StructureBuilder TaskResult = ResultSingle | ResultMulti diff --git a/sieves/tasks/predictive/schemas/ner.py b/sieves/tasks/predictive/schemas/ner.py index a5d1c719..e1db987f 100644 --- a/sieves/tasks/predictive/schemas/ner.py +++ b/sieves/tasks/predictive/schemas/ner.py @@ -3,7 +3,6 @@ from __future__ import annotations import dspy -import gliner2.inference.engine import pydantic from sieves.model_wrappers import dspy_, gliner_, langchain_, outlines_ @@ -111,10 +110,5 @@ def target_fields(self) -> tuple[str, ...]: TaskModel = dspy_.Model | gliner_.Model | langchain_.Model | outlines_.Model -TaskPromptSignature = ( - type[dspy.Signature] - | type[pydantic.BaseModel] - | gliner2.inference.engine.Schema - | gliner2.inference.engine.StructureBuilder -) +TaskPromptSignature = type[dspy.Signature] | type[pydantic.BaseModel] | gliner_.Schema | gliner_.StructureBuilder TaskResult = Result diff --git a/sieves/tasks/predictive/schemas/relation_extraction.py b/sieves/tasks/predictive/schemas/relation_extraction.py index 33abb01e..570a0091 100644 --- a/sieves/tasks/predictive/schemas/relation_extraction.py +++ b/sieves/tasks/predictive/schemas/relation_extraction.py @@ -3,7 +3,6 @@ from __future__ import annotations import dspy -import gliner2.inference.engine import pydantic from sieves.model_wrappers import dspy_, gliner_, langchain_, outlines_ @@ -102,10 +101,5 @@ def target_fields(self) -> tuple[str, ...]: TaskModel = dspy_.Model | gliner_.Model | langchain_.Model | outlines_.Model -TaskPromptSignature = ( - type[dspy.Signature] - | type[pydantic.BaseModel] - | gliner2.inference.engine.Schema - | gliner2.inference.engine.StructureBuilder -) +TaskPromptSignature = type[dspy.Signature] | type[pydantic.BaseModel] | gliner_.Schema | gliner_.StructureBuilder TaskResult = Result diff --git a/sieves/tasks/predictive/utils.py b/sieves/tasks/predictive/utils.py index be4e00ea..302ad7bc 100644 --- a/sieves/tasks/predictive/utils.py +++ b/sieves/tasks/predictive/utils.py @@ -8,10 +8,11 @@ from typing import Any, Literal, TypeVar, Union import dspy -import gliner2.inference.engine import pydantic from sieves.model_wrappers import ModelType, dspy_, gliner_, huggingface_, outlines_ +from sieves.model_wrappers.gliner_ import Schema as _GlinerSchema +from sieves.model_wrappers.gliner_ import StructureBuilder as _GlinerStructureBuilder _EntityType = TypeVar("_EntityType", bound=pydantic.BaseModel) @@ -54,7 +55,7 @@ def _extract_labels_from_model(model_cls: type[pydantic.BaseModel]) -> list[str] def _pydantic_to_gliner( model_cls: type[pydantic.BaseModel], mode: str, **kwargs: Any -) -> gliner2.inference.engine.Schema | gliner2.inference.engine.StructureBuilder: +) -> _GlinerSchema | _GlinerStructureBuilder: """Convert Pydantic model to GliNER2 signature. :param model_cls: Pydantic model to convert. @@ -62,7 +63,7 @@ def _pydantic_to_gliner( :param kwargs: Additional arguments for GliNER2 schema methods. :return: GliNER2 schema or structure builder. """ - schema = gliner2.inference.engine.Schema() + schema = _GlinerSchema() if mode in ("classification", "entities", "relations"): labels = _extract_labels_from_model(model_cls) diff --git a/sieves/tests/tasks/predictive/test_signature_utils.py b/sieves/tests/tasks/predictive/test_signature_utils.py index e868e0c2..436656b2 100644 --- a/sieves/tests/tasks/predictive/test_signature_utils.py +++ b/sieves/tests/tasks/predictive/test_signature_utils.py @@ -2,7 +2,7 @@ from typing import Literal, Union, Any import dspy -import gliner2.inference.engine +from sieves.model_wrappers.gliner_ import Schema as _GlinerSchema import pydantic import pytest @@ -70,7 +70,7 @@ class ExpectedSig(dspy.Signature): def test_convert_to_gliner_structure_full(): # Expected manually constructed GliNER2 structure - expected = gliner2.inference.engine.Schema().structure("SimpleExtraction") + expected = _GlinerSchema().structure("SimpleExtraction") expected.field("name", dtype="str") expected.field("age", dtype="str") @@ -80,7 +80,7 @@ def test_convert_to_gliner_structure_full(): def test_convert_to_gliner_classification_full(): # Expected manually constructed GliNER2 classification - expected = gliner2.inference.engine.Schema().classification( + expected = _GlinerSchema().classification( task="classification", labels=["A", "B", "C"] ) @@ -106,7 +106,7 @@ def test_convert_to_outlines_choice_full(): assert actual == expected def test_convert_to_gliner_entities_full(): - expected = gliner2.inference.engine.Schema().entities( + expected = _GlinerSchema().entities( entity_types=["science", "politics", "sports"] ) @@ -118,7 +118,7 @@ def test_convert_to_gliner_structure_choices_full(): class ChoiceModel(pydantic.BaseModel): category: Literal["X", "Y"] - expected = gliner2.inference.engine.Schema().structure("ChoiceModel") + expected = _GlinerSchema().structure("ChoiceModel") expected.field("category", dtype="str", choices=["X", "Y"]) actual = convert_to_signature(ChoiceModel, ModelType.gliner, mode="structure") diff --git a/sieves/tests/tasks/predictive/test_task_signature_migration.py b/sieves/tests/tasks/predictive/test_task_signature_migration.py index d021d2d0..7920fea0 100644 --- a/sieves/tests/tasks/predictive/test_task_signature_migration.py +++ b/sieves/tests/tasks/predictive/test_task_signature_migration.py @@ -2,7 +2,7 @@ from typing import Literal, Union, Any import dspy -import gliner2.inference.engine +from sieves.model_wrappers.gliner_ import Schema as _GlinerSchema import pydantic import pytest @@ -55,7 +55,7 @@ class ExpectedDSPy(dspy.Signature): score = dspy.OutputField(desc="Confidence score") # GliNER (classification mode) - expected_gliner = gliner2.inference.engine.Schema().classification( + expected_gliner = _GlinerSchema().classification( task="classification", labels=["science", "politics", "sports"] ) @@ -92,7 +92,7 @@ class ExpectedDSPy(dspy.Signature): sports = dspy.OutputField(desc="Score for sports category") # GliNER (classification mode) - expected_gliner = gliner2.inference.engine.Schema().classification( + expected_gliner = _GlinerSchema().classification( task="classification", labels=["science", "politics", "sports"] ) @@ -127,7 +127,7 @@ class ExpectedDSPy(dspy.Signature): age = dspy.OutputField(desc="Age in years") # GliNER (structure mode) - expected_gliner = gliner2.inference.engine.Schema().structure("Person") + expected_gliner = _GlinerSchema().structure("Person") expected_gliner.field("name", dtype="str") expected_gliner.field("age", dtype="str") @@ -145,7 +145,7 @@ class NEROutput(pydantic.BaseModel): # 2. Define Expected Output Signatures # GliNER (entities mode) - expected_gliner = gliner2.inference.engine.Schema().entities( + expected_gliner = _GlinerSchema().entities( entity_types=["PER", "LOC", "ORG"] ) @@ -204,7 +204,7 @@ class RelationOutput(pydantic.BaseModel): # 2. Define Expected Output Signatures # GliNER (relations mode) - expected_gliner = gliner2.inference.engine.Schema().relations( + expected_gliner = _GlinerSchema().relations( relation_types=["head", "relation", "tail"] # Per instruction: all but score )