"""Span attributes demo — entities carry per-span attribute groups (sentiment, role, assertion, impact), single- or multi-label, scoped with applies_to. MOCK until GLiNER 2.5 ships. Response contract: {"spans": [{"start", "end", "text", "label", "score", "attributes": {"": [{"label": str, "score": float}]}}]} Multi-label groups return several entries in the group list. """ import json import os MOCK = os.environ.get("GLINER_MOCK", "1") == "1" MODEL_ID = os.environ.get("MODEL_ID", "fastino/gliner2.5") MODEL_URL = os.environ.get("MODEL_URL", f"https://huggingface.co/{MODEL_ID}") DEMO = { "title": "Sentiment and role, per span", "subtitle": "Document classification labels the whole review. GLiNER 2.5 attaches attributes to every entity it extracts.", "model_name": "GLiNER 2.5", "model_variant": "0.3B parameters · f32 · CPU", "code": ( 'schema = (model.create_schema()\n' ' .entities({"product": "A product mentioned in the text",\n' ' "person": "A person by name",\n' ' "condition": "A medical condition"})\n' ' .entity_attributes({\n' ' "sentiment": AttributeGroup(\n' ' labels=["positive", "neutral", "negative"],\n' ' applies_to=["product"], qualify_labels=True),\n' ' "role": AttributeGroup(\n' ' labels=["executive", "employee", "customer", "analyst"],\n' ' applies_to=["person"]),\n' ' "assertion": AttributeGroup(\n' ' labels=["present", "absent", "possible", "historical"],\n' ' applies_to=["condition"]),\n' ' "impact": AttributeGroup(\n' ' labels=["blocks_work", "data_loss", "security_risk"],\n' ' multi_label=True, threshold=0.40)}))\n' 'model.extract(text, schema)' ), "examples": [ {"chip": "Mixed review", "text": "The screen is gorgeous and the keyboard feels great, but the battery is disappointing and the fan noise is unacceptable.", "fixture": "review.json"}, {"chip": "Org announcement", "text": "CEO Maya Chen announced that CFO Daniel Okafor will lead the acquisition, while analyst Priya Nair briefed reporters.", "fixture": "org.json"}, {"chip": "Clinical note", "text": "Patient denies chest pain. No evidence of pneumonia. Possible mild anemia; history of hypertension noted.", "fixture": "clinical.json"}, {"chip": "Support ticket", "text": "The export button silently deletes rows, which blocked our quarterly report and risks losing audited data.", "fixture": "support.json"}, ], } _HERE = os.path.dirname(os.path.abspath(__file__)) def warmup(): if MOCK: for ex in DEMO["examples"]: _fixture(ex["fixture"], ex["text"]) else: _load_real() def infer(data): text = (data.get("text") or "").replace("\r", "") if not text.strip(): return {"spans": []} if MOCK: return _infer_mock(text) return _infer_real(text) def _fixture(name, expect_text=None): with open(os.path.join(_HERE, "fixtures", name)) as f: out = json.load(f) if expect_text is not None: for sp in out.get("spans", []): assert expect_text[sp["start"]:sp["end"]] == sp["text"], \ f"{name}: bad offsets for {sp['text']!r}" return out def _infer_mock(text): for ex in DEMO["examples"]: if ex["text"] == text: return _fixture(ex["fixture"]) return {"spans": []} _model = None def _load_real(): global _model from gliner import GLiNER _model = GLiNER.from_pretrained(MODEL_ID, token=os.environ.get("HF_TOKEN")) def _infer_real(text): raise NotImplementedError("wire up GLiNER 2.5 here, keep the response shape")