trustgraph-span 2.11.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trustgraph_span-2.11.1/PKG-INFO +19 -0
- trustgraph_span-2.11.1/pyproject.toml +38 -0
- trustgraph_span-2.11.1/setup.cfg +4 -0
- trustgraph_span-2.11.1/trustgraph/__init__.py +1 -0
- trustgraph_span-2.11.1/trustgraph/model/__init__.py +1 -0
- trustgraph_span-2.11.1/trustgraph/model/field_extraction/__init__.py +1 -0
- trustgraph_span-2.11.1/trustgraph/model/field_extraction/span/__init__.py +2 -0
- trustgraph_span-2.11.1/trustgraph/model/field_extraction/span/__main__.py +6 -0
- trustgraph_span-2.11.1/trustgraph/model/field_extraction/span/engine.py +157 -0
- trustgraph_span-2.11.1/trustgraph/model/field_extraction/span/service.py +136 -0
- trustgraph_span-2.11.1/trustgraph/span_version.py +1 -0
- trustgraph_span-2.11.1/trustgraph_span.egg-info/PKG-INFO +19 -0
- trustgraph_span-2.11.1/trustgraph_span.egg-info/SOURCES.txt +15 -0
- trustgraph_span-2.11.1/trustgraph_span.egg-info/dependency_links.txt +1 -0
- trustgraph_span-2.11.1/trustgraph_span.egg-info/entry_points.txt +2 -0
- trustgraph_span-2.11.1/trustgraph_span.egg-info/requires.txt +9 -0
- trustgraph_span-2.11.1/trustgraph_span.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: trustgraph-span
|
|
3
|
+
Version: 2.11.1
|
|
4
|
+
Summary: Span-extraction field extraction service for TrustGraph.
|
|
5
|
+
Author-email: "trustgraph.ai" <security@trustgraph.ai>
|
|
6
|
+
Project-URL: Homepage, https://github.com/trustgraph-ai/trustgraph
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: trustgraph-base<2.12,>=2.11
|
|
12
|
+
Requires-Dist: trustgraph-flow<2.12,>=2.11
|
|
13
|
+
Requires-Dist: onnxruntime
|
|
14
|
+
Requires-Dist: tokenizers
|
|
15
|
+
Requires-Dist: huggingface-hub
|
|
16
|
+
Requires-Dist: numpy
|
|
17
|
+
Requires-Dist: pulsar-client
|
|
18
|
+
Requires-Dist: pyyaml
|
|
19
|
+
Requires-Dist: prometheus-client
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "trustgraph-span"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
authors = [{name = "trustgraph.ai", email = "security@trustgraph.ai"}]
|
|
9
|
+
description = "Span-extraction field extraction service for TrustGraph."
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"trustgraph-base>=2.11,<2.12",
|
|
14
|
+
"trustgraph-flow>=2.11,<2.12",
|
|
15
|
+
"onnxruntime",
|
|
16
|
+
"tokenizers",
|
|
17
|
+
"huggingface-hub",
|
|
18
|
+
"numpy",
|
|
19
|
+
"pulsar-client",
|
|
20
|
+
"pyyaml",
|
|
21
|
+
"prometheus-client",
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Operating System :: OS Independent",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/trustgraph-ai/trustgraph"
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
field-extraction-span = "trustgraph.model.field_extraction.span:run"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
include = ["trustgraph*"]
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.dynamic]
|
|
38
|
+
version = {attr = "trustgraph.span_version.__version__"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
|
|
2
|
+
"""
|
|
3
|
+
ONNX inference engine for BIO span extraction.
|
|
4
|
+
Loads a span-extraction model from HuggingFace Hub and runs
|
|
5
|
+
instruction-based field extraction using BIO tagging.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any, Dict, List, Optional, Union
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
import onnxruntime as ort
|
|
14
|
+
from tokenizers import Tokenizer
|
|
15
|
+
from huggingface_hub import hf_hub_download
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
DEFAULT_REPO = "tasksource/UltraBERT-150M-clairic-extraction"
|
|
20
|
+
DEFAULT_MAX_LENGTH = 512
|
|
21
|
+
|
|
22
|
+
O, B, I = 0, 1, 2
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _serialize_state(state):
|
|
26
|
+
if isinstance(state, str):
|
|
27
|
+
return state
|
|
28
|
+
return json.dumps(state, ensure_ascii=False)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _decode_spans(text, tags, offsets, seq_ids):
|
|
32
|
+
"""Extract text spans from BIO tags.
|
|
33
|
+
|
|
34
|
+
Only tokens in the text sequence (seq_id=1) are considered.
|
|
35
|
+
"""
|
|
36
|
+
spans = []
|
|
37
|
+
start = None
|
|
38
|
+
|
|
39
|
+
for i, tag in enumerate(tags):
|
|
40
|
+
in_text = (seq_ids[i] == 1)
|
|
41
|
+
|
|
42
|
+
if start is not None and (not in_text or tag != I):
|
|
43
|
+
char_start = offsets[start][0]
|
|
44
|
+
char_end = offsets[i - 1][1]
|
|
45
|
+
span = text[char_start:char_end].strip().rstrip(",;:")
|
|
46
|
+
if span:
|
|
47
|
+
spans.append(span)
|
|
48
|
+
start = None
|
|
49
|
+
|
|
50
|
+
if in_text and tag in (B, I) and start is None:
|
|
51
|
+
start = i
|
|
52
|
+
|
|
53
|
+
if start is not None:
|
|
54
|
+
char_start = offsets[start][0]
|
|
55
|
+
last = len(tags) - 1
|
|
56
|
+
while last > start and seq_ids[last] != 1:
|
|
57
|
+
last -= 1
|
|
58
|
+
char_end = offsets[last][1]
|
|
59
|
+
span = text[char_start:char_end].strip().rstrip(",;:")
|
|
60
|
+
if span:
|
|
61
|
+
spans.append(span)
|
|
62
|
+
|
|
63
|
+
return spans
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class OnnxEngine:
|
|
67
|
+
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
repo_id: str = DEFAULT_REPO,
|
|
71
|
+
local_dir: Optional[str] = None,
|
|
72
|
+
max_length: int = DEFAULT_MAX_LENGTH,
|
|
73
|
+
):
|
|
74
|
+
local_dir = local_dir or "/opt/models/span-extraction"
|
|
75
|
+
|
|
76
|
+
logger.info(
|
|
77
|
+
"Downloading span-extraction model from %s ...", repo_id,
|
|
78
|
+
)
|
|
79
|
+
model_path = hf_hub_download(
|
|
80
|
+
repo_id=repo_id, filename="onnx/model_quantized.onnx",
|
|
81
|
+
local_dir=local_dir,
|
|
82
|
+
)
|
|
83
|
+
tokenizer_path = hf_hub_download(
|
|
84
|
+
repo_id=repo_id, filename="tokenizer.json",
|
|
85
|
+
local_dir=local_dir,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
logger.info("Loading ONNX session...")
|
|
89
|
+
self.session = ort.InferenceSession(
|
|
90
|
+
model_path, providers=["CPUExecutionProvider"],
|
|
91
|
+
)
|
|
92
|
+
self.tokenizer = Tokenizer.from_file(tokenizer_path)
|
|
93
|
+
self.tokenizer.no_padding()
|
|
94
|
+
|
|
95
|
+
self.max_length = max_length
|
|
96
|
+
self.repo_id = repo_id
|
|
97
|
+
|
|
98
|
+
logger.info(
|
|
99
|
+
"Span-extraction engine loaded (max_length=%d)", max_length,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def predict(
|
|
103
|
+
self,
|
|
104
|
+
state: Union[str, dict, list],
|
|
105
|
+
fields: Dict[str, str],
|
|
106
|
+
) -> Dict[str, Any]:
|
|
107
|
+
|
|
108
|
+
text = _serialize_state(state)
|
|
109
|
+
|
|
110
|
+
field_names = list(fields.keys())
|
|
111
|
+
instructions = list(fields.values())
|
|
112
|
+
|
|
113
|
+
encodings = [
|
|
114
|
+
self.tokenizer.encode(inst, text) for inst in instructions
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
if self.max_length:
|
|
118
|
+
for enc in encodings:
|
|
119
|
+
enc.truncate(self.max_length)
|
|
120
|
+
|
|
121
|
+
max_len = max(len(enc.ids) for enc in encodings)
|
|
122
|
+
pad_id = 0
|
|
123
|
+
|
|
124
|
+
all_input_ids = []
|
|
125
|
+
all_attention_mask = []
|
|
126
|
+
|
|
127
|
+
for enc in encodings:
|
|
128
|
+
ids = list(enc.ids)
|
|
129
|
+
pad_len = max_len - len(ids)
|
|
130
|
+
all_input_ids.append(ids + [pad_id] * pad_len)
|
|
131
|
+
all_attention_mask.append([1] * len(ids) + [0] * pad_len)
|
|
132
|
+
|
|
133
|
+
input_ids = np.array(all_input_ids, dtype=np.int64)
|
|
134
|
+
attention_mask = np.array(all_attention_mask, dtype=np.int64)
|
|
135
|
+
|
|
136
|
+
outputs = self.session.run(None, {
|
|
137
|
+
"input_ids": input_ids,
|
|
138
|
+
"attention_mask": attention_mask,
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
tags = outputs[0].argmax(axis=-1)
|
|
142
|
+
|
|
143
|
+
answers = {}
|
|
144
|
+
for i, name in enumerate(field_names):
|
|
145
|
+
enc = encodings[i]
|
|
146
|
+
n_tokens = len(enc.ids)
|
|
147
|
+
answers[name] = _decode_spans(
|
|
148
|
+
text,
|
|
149
|
+
tags[i][:n_tokens].tolist(),
|
|
150
|
+
enc.offsets,
|
|
151
|
+
enc.sequence_ids,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
"model": self.repo_id,
|
|
156
|
+
"answers": answers,
|
|
157
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
|
|
2
|
+
"""
|
|
3
|
+
Field extraction service using BIO span extraction via ONNX Runtime.
|
|
4
|
+
Extracts verbatim text spans from input based on field instructions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
from .... base import FlowProcessor, ConsumerSpec, ProducerSpec, ParameterSpec
|
|
11
|
+
from .... schema import FieldExtractionRequest, FieldExtractionResponse
|
|
12
|
+
from .... schema import Error
|
|
13
|
+
|
|
14
|
+
from . engine import OnnxEngine, DEFAULT_REPO, DEFAULT_MAX_LENGTH
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
default_ident = "field-extraction"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Processor(FlowProcessor):
|
|
22
|
+
|
|
23
|
+
def __init__(self, **params):
|
|
24
|
+
|
|
25
|
+
super(Processor, self).__init__(**params)
|
|
26
|
+
|
|
27
|
+
self.repo_id = params.get("model") or DEFAULT_REPO
|
|
28
|
+
self.max_length = int(
|
|
29
|
+
params.get("max-length") or DEFAULT_MAX_LENGTH
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
self.register_specification(
|
|
33
|
+
ParameterSpec(
|
|
34
|
+
name="model",
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
self.register_specification(
|
|
39
|
+
ParameterSpec(
|
|
40
|
+
name="max-length",
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
self.register_specification(
|
|
45
|
+
ConsumerSpec(
|
|
46
|
+
name="request",
|
|
47
|
+
schema=FieldExtractionRequest,
|
|
48
|
+
handler=self.on_request,
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
self.register_specification(
|
|
53
|
+
ProducerSpec(
|
|
54
|
+
name="response",
|
|
55
|
+
schema=FieldExtractionResponse,
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
self.engine = None
|
|
60
|
+
|
|
61
|
+
async def start(self):
|
|
62
|
+
|
|
63
|
+
logger.info("Loading span-extraction ONNX engine...")
|
|
64
|
+
loop = asyncio.get_event_loop()
|
|
65
|
+
self.engine = await loop.run_in_executor(
|
|
66
|
+
None,
|
|
67
|
+
lambda: OnnxEngine(
|
|
68
|
+
repo_id=self.repo_id, max_length=self.max_length,
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
logger.info("Span-extraction ONNX engine loaded successfully")
|
|
72
|
+
|
|
73
|
+
await super().start()
|
|
74
|
+
|
|
75
|
+
async def on_request(self, msg, consumer, flow):
|
|
76
|
+
|
|
77
|
+
request = msg.value()
|
|
78
|
+
id = msg.properties()["id"]
|
|
79
|
+
producer = flow("response")
|
|
80
|
+
|
|
81
|
+
if self.engine is None:
|
|
82
|
+
response = FieldExtractionResponse(
|
|
83
|
+
error=Error(
|
|
84
|
+
type="not-ready",
|
|
85
|
+
message="Span-extraction engine is still loading",
|
|
86
|
+
),
|
|
87
|
+
)
|
|
88
|
+
await producer.send(response, properties={"id": id})
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
|
|
93
|
+
result = self.engine.predict(
|
|
94
|
+
request.state, request.fields,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
response = FieldExtractionResponse(
|
|
98
|
+
answers=result.get("answers", {}),
|
|
99
|
+
model=result.get("model", ""),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
except Exception as e:
|
|
103
|
+
logger.error(f"Field extraction error: {e}")
|
|
104
|
+
response = FieldExtractionResponse(
|
|
105
|
+
error=Error(
|
|
106
|
+
type="field-extraction-error",
|
|
107
|
+
message=str(e),
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
await producer.send(response, properties={"id": id})
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def add_args(parser):
|
|
115
|
+
|
|
116
|
+
FlowProcessor.add_args(parser)
|
|
117
|
+
|
|
118
|
+
parser.add_argument(
|
|
119
|
+
'--model',
|
|
120
|
+
default=None,
|
|
121
|
+
help='HuggingFace repo for ONNX model (default: %s)'
|
|
122
|
+
% DEFAULT_REPO,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
parser.add_argument(
|
|
126
|
+
'--max-length',
|
|
127
|
+
default=None,
|
|
128
|
+
type=int,
|
|
129
|
+
help='Max token sequence length (default: %d)'
|
|
130
|
+
% DEFAULT_MAX_LENGTH,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def run():
|
|
135
|
+
|
|
136
|
+
Processor.launch(default_ident, __doc__)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.11.1"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: trustgraph-span
|
|
3
|
+
Version: 2.11.1
|
|
4
|
+
Summary: Span-extraction field extraction service for TrustGraph.
|
|
5
|
+
Author-email: "trustgraph.ai" <security@trustgraph.ai>
|
|
6
|
+
Project-URL: Homepage, https://github.com/trustgraph-ai/trustgraph
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: trustgraph-base<2.12,>=2.11
|
|
12
|
+
Requires-Dist: trustgraph-flow<2.12,>=2.11
|
|
13
|
+
Requires-Dist: onnxruntime
|
|
14
|
+
Requires-Dist: tokenizers
|
|
15
|
+
Requires-Dist: huggingface-hub
|
|
16
|
+
Requires-Dist: numpy
|
|
17
|
+
Requires-Dist: pulsar-client
|
|
18
|
+
Requires-Dist: pyyaml
|
|
19
|
+
Requires-Dist: prometheus-client
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
pyproject.toml
|
|
2
|
+
trustgraph/__init__.py
|
|
3
|
+
trustgraph/span_version.py
|
|
4
|
+
trustgraph/model/__init__.py
|
|
5
|
+
trustgraph/model/field_extraction/__init__.py
|
|
6
|
+
trustgraph/model/field_extraction/span/__init__.py
|
|
7
|
+
trustgraph/model/field_extraction/span/__main__.py
|
|
8
|
+
trustgraph/model/field_extraction/span/engine.py
|
|
9
|
+
trustgraph/model/field_extraction/span/service.py
|
|
10
|
+
trustgraph_span.egg-info/PKG-INFO
|
|
11
|
+
trustgraph_span.egg-info/SOURCES.txt
|
|
12
|
+
trustgraph_span.egg-info/dependency_links.txt
|
|
13
|
+
trustgraph_span.egg-info/entry_points.txt
|
|
14
|
+
trustgraph_span.egg-info/requires.txt
|
|
15
|
+
trustgraph_span.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
trustgraph
|