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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,2 @@
1
+
2
+ from . service import *
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from . service import run
4
+
5
+ if __name__ == '__main__':
6
+ run()
@@ -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,2 @@
1
+ [console_scripts]
2
+ field-extraction-span = trustgraph.model.field_extraction.span:run
@@ -0,0 +1,9 @@
1
+ trustgraph-base<2.12,>=2.11
2
+ trustgraph-flow<2.12,>=2.11
3
+ onnxruntime
4
+ tokenizers
5
+ huggingface-hub
6
+ numpy
7
+ pulsar-client
8
+ pyyaml
9
+ prometheus-client