File size: 9,000 Bytes
53395c6 d538019 493b437 d538019 493b437 55f70b7 493b437 99fea15 493b437 99412aa 493b437 25e6476 f9b1c76 25e6476 f9b1c76 25e6476 f9b1c76 25e6476 493b437 d538019 7bb3ae6 d538019 f9b1c76 d538019 f9b1c76 d538019 493b437 d538019 b3f34c3 25e6476 b3f34c3 25e6476 b3f34c3 d538019 b3f34c3 d538019 b3f34c3 d538019 b3f34c3 d538019 b3f34c3 493b437 d538019 493b437 55f70b7 493b437 55f70b7 d538019 55f70b7 7bb3ae6 55f70b7 d538019 55f70b7 d538019 55f70b7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 | """Span attributes demo: entities carry per-span attribute groups (sentiment,
assertion, role), single- or multi-label, scoped with applies_to.
Two examples: a clinical note (conditions with assertion status, medications,
procedures) and a mixed product review (products with sentiment, people with
role). Free-typed text falls back to the review schema.
Response contract:
{"spans": [{"start", "end", "text", "label", "score",
"attributes": {"<group>": [{"label": str, "score": float}]}}]}
"""
import json
import os
MOCK = os.environ.get("GLINER_MOCK", "1") == "1"
MODEL_ID = os.environ.get("MODEL_ID", "fastino/gliner2.5-multi-v1")
DEMO = {
"title": "Span attribute extraction",
"subtitle": "Extract descriptive labels alongside spans for richer context.",
"model_name": "GLiNER 2.5",
"model_variant": "0.3B parameters, f32, CPU",
"examples": [
{"chip": "Clinical note", "fixture": "clinical.json",
"code": (
'from gliner2 import AutoExtractor, AttributeGroup\n'
'\n'
'extractor = AutoExtractor.from_pretrained("fastino/gliner2.5-multi-v1", map_location="cpu")\n'
'extractor.float()\n'
'\n'
'note = open("chart_note.txt").read()\n'
'\n'
'schema = (extractor.create_schema()\n'
' .entities({"medication": "A medication or drug mentioned in the note",\n'
' "condition": "A medical condition, diagnosis, or symptom",\n'
' "procedure": "A medical procedure or test"})\n'
' .entity_attributes({\n'
' "assertion": AttributeGroup(\n'
' labels=["present", "absent", "possible", "historical"],\n'
' applies_to=["condition"])}))\n'
'\n'
'result = extractor.extract(note, schema, include_spans=True, include_confidence=True)\n'
'# -> {"entities": {"condition": [{"text": "chest pain", "assertion": {"label": "present"}, ...}], ...}}'
)},
{"chip": "Mixed review", "fixture": "review.json",
"code": (
'from gliner2 import AutoExtractor, AttributeGroup\n'
'\n'
'extractor = AutoExtractor.from_pretrained("fastino/gliner2.5-multi-v1", map_location="cpu")\n'
'extractor.float()\n'
'\n'
'review = open("product_review.txt").read()\n'
'\n'
'schema = (extractor.create_schema()\n'
' .entities({"feature_mention": "A feature, component, or aspect of a product mentioned in the review"})\n'
' .entity_attributes({\n'
' "sentiment": AttributeGroup(\n'
' labels=["positive", "neutral", "negative"],\n'
' applies_to=["feature_mention"])}))\n'
'\n'
'result = extractor.extract(review, schema, include_spans=True, include_confidence=True)\n'
'# -> {"entities": {"feature_mention": [{"text": "keyboard", "sentiment": {"label": "positive"}, ...},\n'
'# {"text": "docking station", "sentiment": {"label": "negative"}, ...}], ...}}'
)},
],
}
# Per-example schemas: entity types + attribute groups differ by domain.
# Keys match the fixture .txt basenames.
EXAMPLE_SCHEMAS = {
"clinical": {
"entities": {
"medication": "A medication or drug mentioned in the note",
"condition": "A medical condition, diagnosis, or symptom",
"procedure": "A medical procedure or test",
},
"attributes": {
"assertion": (["present", "absent", "possible", "historical"], ["condition"]),
},
},
"review": {
"threshold": 0.15, # features in complaint clauses score below the 0.5 default
"entities": {
"feature_mention": "A feature, component, or aspect of a product mentioned in the review",
},
"attributes": {
"sentiment": (["positive", "neutral", "negative"], ["feature_mention"]),
},
},
}
_HERE = os.path.dirname(os.path.abspath(__file__))
def config():
"""DEMO for /api/config, with result previews baked from fixtures."""
out = {k: v for k, v in DEMO.items() if k != "code"}
# per-example code lives on each example dict
out["mock"] = MOCK
out["examples"] = []
for ex in DEMO["examples"]:
ex2 = {**ex, "text": _text(ex)}
ex2["result"] = _result_preview(ex["fixture"])
out["examples"].append(ex2)
return out
def warmup():
if MOCK:
for ex in DEMO["examples"]:
_fixture(ex["fixture"], _text(ex))
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 _text(ex):
txt = ex.get("text")
if txt is None:
with open(os.path.join(_HERE, "fixtures", ex["fixture"].replace(".json", ".txt"))) as f:
txt = f.read().rstrip("\n")
return txt
def _schema_key(text):
for ex in DEMO["examples"]:
if _text(ex) == text:
return ex["fixture"].replace(".json", "")
return None
def _result_preview(fixture_name, per_type=2):
"""Notebook-style Out[1] preview: the API response shape, truncated per label."""
with open(os.path.join(_HERE, "fixtures", fixture_name)) as f:
out = json.load(f)
entities = {}
for sp in out["spans"]:
entities.setdefault(sp["label"], [])
if len(entities[sp["label"]]) < per_type:
entities[sp["label"]].append(sp)
counts = {}
for sp in out["spans"]:
counts[sp["label"]] = counts.get(sp["label"], 0) + 1
body = []
for label, entries in entities.items():
entry_strs = []
for e in entries:
attrs = {g: [{"label": a["label"], "score": a["score"]} for a in es]
for g, es in e.get("attributes", {}).items() if es}
base = (f'{{"text": {json.dumps(e["text"])}, "confidence": {e["score"]}, '
f'"start": {e["start"]}, "end": {e["end"]}')
if attrs:
base += f', "attributes": {json.dumps(attrs)}'
base += "}"
entry_strs.append(base)
more = counts[label] - len(entries)
parts = ", ".join(entry_strs)
if more > 0:
parts += f",\n # ... {more} more {label} span{'' if more == 1 else 's'}"
body.append(f' {json.dumps(label)}: [\n {parts}\n ]')
return f'{{\n "entities": {{\n' + ",\n".join(body) + "\n }\n}"
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 _text(ex) == text:
return _fixture(ex["fixture"])
return {"spans": []}
_model = None
def _load_real():
global _model
from gliner2 import AutoExtractor
_model = AutoExtractor.from_pretrained(MODEL_ID, map_location="cpu")
_model.float()
def _infer_real(text):
from gliner2 import AttributeGroup
key = _schema_key(text)
cfg = EXAMPLE_SCHEMAS.get(key, EXAMPLE_SCHEMAS["review"])
schema = _model.create_schema().entities(cfg["entities"])
attr_groups = {}
for group, (labels, applies_to) in cfg["attributes"].items():
kwargs = {"applies_to": applies_to} if applies_to else {}
attr_groups[group] = AttributeGroup(labels=labels, **kwargs)
if attr_groups:
schema = schema.entity_attributes(attr_groups)
result = _model.extract(text, schema, threshold=cfg.get("threshold", 0.5),
include_spans=True, include_confidence=True)
spans = []
for label, entries in result.get("entities", {}).items():
for e in entries:
span = {
"start": e["start"], "end": e["end"], "text": e["text"],
"label": label, "score": e.get("confidence", 1.0),
"attributes": {},
}
for k, v in e.items():
if k not in ("text", "start", "end", "confidence"):
if isinstance(v, dict) and "label" in v:
span["attributes"][k] = [{"label": v["label"], "score": v.get("confidence", 1.0)}]
elif isinstance(v, list):
span["attributes"][k] = [{"label": x["label"], "score": x.get("confidence", 1.0)} for x in v]
spans.append(span)
spans.sort(key=lambda s: s["start"])
return {"spans": spans}
|