|
|
""" |
|
|
T9 Oracle GLiNER Entity Extractor - HF Space Deployment |
|
|
Gradio API endpoint for zero-shot NER with 70 medical device labels |
|
|
|
|
|
Deployed on: Persistent T4 GPU |
|
|
Model: urchade/gliner_large-v2.1 (1.7GB) |
|
|
Cost: $0.60/hour |
|
|
""" |
|
|
|
|
|
import gradio as gr |
|
|
import json |
|
|
import logging |
|
|
from typing import List, Dict |
|
|
from gliner import GLiNER |
|
|
|
|
|
logging.basicConfig(level=logging.INFO) |
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
ENTITY_LABELS = [ |
|
|
|
|
|
"part_number", "component_name", "manufacturer", "model_number", |
|
|
|
|
|
"pressure", "temperature", "voltage", "current", "material", |
|
|
"dimension", "weight", "volume", "flow_rate", "power", |
|
|
"diameter", "length", "thickness", |
|
|
|
|
|
"standard_reference", "certification", "compliance", "safety_class", |
|
|
|
|
|
"thread_standard", "pipe_size", "tubing_size", "connector_type", |
|
|
"surface_finish", "surface_treatment", "width", "height", |
|
|
"tolerance", "hardness", "torque", |
|
|
|
|
|
"diagram_reference", "drawing_number", "procedure_number", |
|
|
"test_protocol", "revision", "sku_number", "part_label", |
|
|
|
|
|
"accuracy", "speed", "frequency", "resistance", |
|
|
"operating_temperature", "supply_voltage", "response_time", |
|
|
"duty_cycle", "operating_range", |
|
|
|
|
|
"operator_id", "tool_number", "gauge_id", "fixture_number", |
|
|
"machine_id", "lot_number", "serial_number", "batch_id", |
|
|
|
|
|
"medical_device", "scope_manufacturer", "channel_type", |
|
|
"port_type", "hub_type", "color_code", "leak_test", |
|
|
|
|
|
"diagram_type", "technical_annotation", |
|
|
|
|
|
"calibration_interval", "service_interval", "mtbf", |
|
|
"warranty", "expiration_date", "production_date", "inspection_report" |
|
|
] |
|
|
|
|
|
|
|
|
logger.info("Loading GLiNER Large model (1.7GB)...") |
|
|
model = GLiNER.from_pretrained("urchade/gliner_large-v2.1") |
|
|
logger.info(f"✓ GLiNER loaded with {len(ENTITY_LABELS)} labels") |
|
|
|
|
|
|
|
|
def extract_entities(text: str, max_length: int = 10000) -> str: |
|
|
""" |
|
|
Extract entities from text using GLiNER zero-shot NER |
|
|
|
|
|
Args: |
|
|
text: Input text (max 10,000 characters recommended) |
|
|
max_length: Maximum text length per prediction |
|
|
|
|
|
Returns: |
|
|
JSON string with extracted entities |
|
|
""" |
|
|
if not text or not text.strip(): |
|
|
return json.dumps({"entities": [], "error": "Empty text provided"}) |
|
|
|
|
|
|
|
|
if len(text) > max_length: |
|
|
logger.warning(f"Text truncated from {len(text)} to {max_length} chars") |
|
|
text = text[:max_length] |
|
|
|
|
|
try: |
|
|
|
|
|
predictions = model.predict_entities(text, ENTITY_LABELS) |
|
|
|
|
|
|
|
|
entities = [] |
|
|
for pred in predictions: |
|
|
entities.append({ |
|
|
"text": pred.get("text", ""), |
|
|
"label": pred.get("label", ""), |
|
|
"start": pred.get("start", 0), |
|
|
"end": pred.get("end", 0), |
|
|
"score": float(pred.get("score", 0.0)) |
|
|
}) |
|
|
|
|
|
logger.info(f"Extracted {len(entities)} entities from {len(text)} chars") |
|
|
|
|
|
return json.dumps({ |
|
|
"entities": entities, |
|
|
"input_length": len(text), |
|
|
"entity_count": len(entities), |
|
|
"labels_used": len(ENTITY_LABELS) |
|
|
}, indent=2) |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"Extraction failed: {e}") |
|
|
return json.dumps({"entities": [], "error": str(e)}) |
|
|
|
|
|
|
|
|
def batch_extract(text_batch: str) -> str: |
|
|
""" |
|
|
Extract entities from multiple texts (newline-separated) |
|
|
|
|
|
Args: |
|
|
text_batch: Multiple texts separated by double newlines |
|
|
|
|
|
Returns: |
|
|
JSON string with results for each text |
|
|
""" |
|
|
texts = [t.strip() for t in text_batch.split("\n\n") if t.strip()] |
|
|
|
|
|
results = [] |
|
|
for i, text in enumerate(texts): |
|
|
result_json = extract_entities(text) |
|
|
result = json.loads(result_json) |
|
|
result["text_index"] = i |
|
|
results.append(result) |
|
|
|
|
|
return json.dumps({"results": results, "batch_size": len(texts)}, indent=2) |
|
|
|
|
|
|
|
|
|
|
|
demo = gr.Interface( |
|
|
fn=extract_entities, |
|
|
inputs=[ |
|
|
gr.Textbox( |
|
|
lines=10, |
|
|
placeholder="Enter technical text here (max 10,000 chars)...", |
|
|
label="Input Text" |
|
|
) |
|
|
], |
|
|
outputs=gr.JSON(label="Extracted Entities"), |
|
|
title="T9 Oracle Entity Extractor (GLiNER Large)", |
|
|
description=f""" |
|
|
**Zero-shot NER for Medical Device Technical Documentation** |
|
|
|
|
|
Extracts **{len(ENTITY_LABELS)} entity types** across 10 tiers: |
|
|
- Part numbers, dimensions, materials, standards |
|
|
- Electrical specs, pressure, temperature, flow rates |
|
|
- Thread standards, tolerances, surface treatments |
|
|
- Medical device specific (scopes, channels, colors) |
|
|
- Quality & maintenance data |
|
|
|
|
|
**Model:** GLiNER Large v2.1 (1.7GB) |
|
|
**Hardware:** NVIDIA T4 GPU (16GB VRAM) |
|
|
**Max input:** 10,000 characters per request |
|
|
""", |
|
|
examples=[ |
|
|
["Part Number: A70002-2, Material: SS316L, Pressure: 60 psi, Thread: 1/4\" NPT"], |
|
|
["Standard: ISO 1179-2, ASTM A112, Temperature: -40 to 85°C, Dimension: 6mm x 35mm"], |
|
|
["Manufacturer: Olympus, Channel: Biopsy, Color: Orange Tubing, Serial: SN-123456"] |
|
|
], |
|
|
api_name="extract", |
|
|
allow_flagging="never" |
|
|
) |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch( |
|
|
server_name="0.0.0.0", |
|
|
server_port=7860, |
|
|
share=False |
|
|
) |
|
|
|