| """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": {"<group>": [{"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, <em>per span</em>", |
| "subtitle": "Document classification labels the whole review. GLiNER 2.5 attaches <strong>attributes to every entity</strong> 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") |
|
|