datafog-core 0.4.5b2__tar.gz → 0.4.5b4__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.
Files changed (69) hide show
  1. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/Cargo.lock +4 -4
  2. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/PKG-INFO +7 -7
  3. {datafog_core-0.4.5b2/crates/core → datafog_core-0.4.5b4}/README.md +6 -6
  4. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/bindings/python/Cargo.toml +2 -2
  5. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/bindings/python/src/models.rs +48 -4
  6. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/Cargo.toml +1 -1
  7. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4/crates/core}/README.md +6 -6
  8. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/src/models/gliner2.rs +106 -19
  9. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/src/models/tests.rs +270 -3
  10. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/src/models.rs +275 -13
  11. datafog_core-0.4.5b4/crates/core/tests/model_datafog.rs +134 -0
  12. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/pyproject.toml +1 -1
  13. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/python/datafog_core/__init__.pyi +2 -1
  14. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/python/datafog_core/_gliner2_worker.py +1 -1
  15. datafog_core-0.4.5b2/crates/core/tests/model_datafog.rs +0 -44
  16. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/Cargo.toml +0 -0
  17. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/bindings/python/src/lib.rs +0 -0
  18. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/bindings/python/tests/api_key_conformance.py +0 -0
  19. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/bindings/python/tests/bearer_token_conformance.py +0 -0
  20. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/bindings/python/tests/capabilities_conformance.py +0 -0
  21. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/bindings/python/tests/credential_overlap_conformance.py +0 -0
  22. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/bindings/python/tests/credential_uri_conformance.py +0 -0
  23. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/bindings/python/tests/email_context_conformance.py +0 -0
  24. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/bindings/python/tests/german_conformance.py +0 -0
  25. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/bindings/python/tests/jwt_conformance.py +0 -0
  26. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/bindings/python/tests/npi_conformance.py +0 -0
  27. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/bindings/python/tests/private_key_conformance.py +0 -0
  28. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/bindings/python/tests/requirements-typing.txt +0 -0
  29. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/bindings/python/tests/synthetic_fixtures.py +0 -0
  30. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/bindings/python/tests/test_installed.py +0 -0
  31. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/bindings/python/tests/test_python49_integration.py +0 -0
  32. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/bindings/python/tests/test_typing.py +0 -0
  33. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/bindings/python/tests/us_routing_number_conformance.py +0 -0
  34. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/bindings/python/tests/uuid_conformance.py +0 -0
  35. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/examples/german_benchmark.rs +0 -0
  36. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/examples/scan_benchmark.rs +0 -0
  37. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/src/api_key.rs +0 -0
  38. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/src/bearer_token.rs +0 -0
  39. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/src/capabilities.rs +0 -0
  40. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/src/credential_uri.rs +0 -0
  41. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/src/email_context.rs +0 -0
  42. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/src/german.rs +0 -0
  43. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/src/jwt.rs +0 -0
  44. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/src/lib.rs +0 -0
  45. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/src/models/gliner2-lock.json +0 -0
  46. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/src/models/process.rs +0 -0
  47. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/src/npi.rs +0 -0
  48. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/src/offsets.rs +0 -0
  49. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/src/private_key.rs +0 -0
  50. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/src/scan_selection_tests.rs +0 -0
  51. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/src/selection_tests.rs +0 -0
  52. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/src/structured.rs +0 -0
  53. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/src/us_routing_number.rs +0 -0
  54. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/src/uuid.rs +0 -0
  55. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/tests/api_key.rs +0 -0
  56. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/tests/bearer_token.rs +0 -0
  57. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/tests/capabilities.rs +0 -0
  58. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/tests/credential_overlaps.rs +0 -0
  59. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/tests/credential_uri.rs +0 -0
  60. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/tests/email_context.rs +0 -0
  61. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/tests/german.rs +0 -0
  62. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/tests/jwt.rs +0 -0
  63. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/tests/npi.rs +0 -0
  64. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/tests/private_key.rs +0 -0
  65. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/tests/support/synthetic_fixtures.rs +0 -0
  66. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/tests/us_routing_number.rs +0 -0
  67. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/crates/core/tests/uuid.rs +0 -0
  68. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/python/datafog_core/__init__.py +0 -0
  69. {datafog_core-0.4.5b2 → datafog_core-0.4.5b4}/python/datafog_core/py.typed +0 -0
@@ -80,7 +80,7 @@ checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d"
80
80
 
81
81
  [[package]]
82
82
  name = "datafog-core"
83
- version = "0.4.5-beta.2"
83
+ version = "0.4.5-beta.4"
84
84
  dependencies = [
85
85
  "base64",
86
86
  "futures",
@@ -94,7 +94,7 @@ dependencies = [
94
94
 
95
95
  [[package]]
96
96
  name = "datafog-core-python"
97
- version = "0.4.5-beta.2"
97
+ version = "0.4.5-beta.4"
98
98
  dependencies = [
99
99
  "datafog-core",
100
100
  "pyo3",
@@ -105,7 +105,7 @@ dependencies = [
105
105
 
106
106
  [[package]]
107
107
  name = "datafog-node"
108
- version = "0.4.5-beta.2"
108
+ version = "0.4.5-beta.4"
109
109
  dependencies = [
110
110
  "datafog-core",
111
111
  "napi",
@@ -116,7 +116,7 @@ dependencies = [
116
116
 
117
117
  [[package]]
118
118
  name = "datafog-wasm"
119
- version = "0.4.5-beta.2"
119
+ version = "0.4.5-beta.4"
120
120
  dependencies = [
121
121
  "datafog-core",
122
122
  "serde",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datafog-core
3
- Version: 0.4.5b2
3
+ Version: 0.4.5b4
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: License :: OSI Approved :: MIT License
6
6
  Classifier: Programming Language :: Python :: 3
@@ -59,7 +59,7 @@ DataFog Core is a Rust library with Python, Node.js, and browser WebAssembly bin
59
59
 
60
60
  Rule-based scanning runs locally without model weights or downloads. Optional local model sessions add DataFog and GLiNER2 support in Rust, Python, and Node.js.
61
61
 
62
- **Stable:** [0.4.4](https://github.com/DataFog/datafog-core/releases/tag/v0.4.4) · **Model beta:** [0.4.5-beta.1](https://github.com/DataFog/datafog-core/releases/tag/v0.4.5-beta.1)
62
+ **Stable:** [0.4.4](https://github.com/DataFog/datafog-core/releases/tag/v0.4.4) · **Model beta:** [0.4.5-beta.4](https://github.com/DataFog/datafog-core/releases/tag/v0.4.5-beta.4)
63
63
 
64
64
  ## Quickstart
65
65
 
@@ -198,23 +198,23 @@ Keep scan settings under `scan` and transformation policy under `transform`. A d
198
198
 
199
199
  ## Local models (beta)
200
200
 
201
- [0.4.5-beta.1](https://github.com/DataFog/datafog-core/releases/tag/v0.4.5-beta.1) adds local sessions for the DataFog PII model and GLiNER2 in Rust, Python, and Node.js on all five native platforms.
201
+ [0.4.5-beta.4](https://github.com/DataFog/datafog-core/releases/tag/v0.4.5-beta.4) adds local sessions for the DataFog PII model and GLiNER2 in Rust, Python, and Node.js on all five native platforms.
202
202
 
203
203
  Install the beta for your runtime:
204
204
 
205
205
  ```bash
206
- python -m pip install 'datafog-core==0.4.5b1'
206
+ python -m pip install 'datafog-core==0.4.5b4'
207
207
  npm install @datafog/node@beta
208
- cargo add datafog-core@0.4.5-beta.1 --features models
208
+ cargo add datafog-core@0.4.5-beta.4 --features models
209
209
  ```
210
210
 
211
211
  For GLiNER2, install its optional dependencies in the Python environment used by the runner:
212
212
 
213
213
  ```bash
214
- python -m pip install 'datafog-core[gliner2]==0.4.5b1'
214
+ python -m pip install 'datafog-core[gliner2]==0.4.5b4'
215
215
  ```
216
216
 
217
- The GLiNER2 runner ships in the Python package and also serves Node.js and Rust callers. Download and verify model files separately, then use `ModelSession.open`, text `scan`, and `close`. Reuse sessions across calls. Both backends return consistent results and errors, with cancellation and cleanup support.
217
+ GLiNER2 supports the PII checkpoint and compatible general-purpose span models with caller-defined labels. The GLiNER2 runner ships in the Python package and also serves Node.js and Rust callers. Download and verify model files separately, then use `ModelSession.open`, text `scan`, and `close`. Reuse sessions across calls. Both backends return consistent results and errors, with cancellation and cleanup support.
218
218
 
219
219
  [DataFog setup and session API →](docs/model-sessions.md) · [GLiNER2 setup →](docs/gliner2-model.md)
220
220
 
@@ -26,7 +26,7 @@ DataFog Core is a Rust library with Python, Node.js, and browser WebAssembly bin
26
26
 
27
27
  Rule-based scanning runs locally without model weights or downloads. Optional local model sessions add DataFog and GLiNER2 support in Rust, Python, and Node.js.
28
28
 
29
- **Stable:** [0.4.4](https://github.com/DataFog/datafog-core/releases/tag/v0.4.4) · **Model beta:** [0.4.5-beta.1](https://github.com/DataFog/datafog-core/releases/tag/v0.4.5-beta.1)
29
+ **Stable:** [0.4.4](https://github.com/DataFog/datafog-core/releases/tag/v0.4.4) · **Model beta:** [0.4.5-beta.4](https://github.com/DataFog/datafog-core/releases/tag/v0.4.5-beta.4)
30
30
 
31
31
  ## Quickstart
32
32
 
@@ -165,23 +165,23 @@ Keep scan settings under `scan` and transformation policy under `transform`. A d
165
165
 
166
166
  ## Local models (beta)
167
167
 
168
- [0.4.5-beta.1](https://github.com/DataFog/datafog-core/releases/tag/v0.4.5-beta.1) adds local sessions for the DataFog PII model and GLiNER2 in Rust, Python, and Node.js on all five native platforms.
168
+ [0.4.5-beta.4](https://github.com/DataFog/datafog-core/releases/tag/v0.4.5-beta.4) adds local sessions for the DataFog PII model and GLiNER2 in Rust, Python, and Node.js on all five native platforms.
169
169
 
170
170
  Install the beta for your runtime:
171
171
 
172
172
  ```bash
173
- python -m pip install 'datafog-core==0.4.5b1'
173
+ python -m pip install 'datafog-core==0.4.5b4'
174
174
  npm install @datafog/node@beta
175
- cargo add datafog-core@0.4.5-beta.1 --features models
175
+ cargo add datafog-core@0.4.5-beta.4 --features models
176
176
  ```
177
177
 
178
178
  For GLiNER2, install its optional dependencies in the Python environment used by the runner:
179
179
 
180
180
  ```bash
181
- python -m pip install 'datafog-core[gliner2]==0.4.5b1'
181
+ python -m pip install 'datafog-core[gliner2]==0.4.5b4'
182
182
  ```
183
183
 
184
- The GLiNER2 runner ships in the Python package and also serves Node.js and Rust callers. Download and verify model files separately, then use `ModelSession.open`, text `scan`, and `close`. Reuse sessions across calls. Both backends return consistent results and errors, with cancellation and cleanup support.
184
+ GLiNER2 supports the PII checkpoint and compatible general-purpose span models with caller-defined labels. The GLiNER2 runner ships in the Python package and also serves Node.js and Rust callers. Download and verify model files separately, then use `ModelSession.open`, text `scan`, and `close`. Reuse sessions across calls. Both backends return consistent results and errors, with cancellation and cleanup support.
185
185
 
186
186
  [DataFog setup and session API →](docs/model-sessions.md) · [GLiNER2 setup →](docs/gliner2-model.md)
187
187
 
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "datafog-core-python"
3
- version = "0.4.5-beta.2"
3
+ version = "0.4.5-beta.4"
4
4
  edition = "2024"
5
5
  rust-version = "1.88"
6
6
  description = "Python bindings for datafog-core"
@@ -12,7 +12,7 @@ name = "datafog_core"
12
12
  crate-type = ["cdylib"]
13
13
 
14
14
  [dependencies]
15
- datafog-core = { version = "0.4.5-beta.2", path = "../../crates/core", features = ["models"] }
15
+ datafog-core = { version = "0.4.5-beta.4", path = "../../crates/core", features = ["models"] }
16
16
  pyo3 = { version = "0.29", features = ["abi3-py310"] }
17
17
  pyo3-async-runtimes = { version = "0.29", features = ["tokio-runtime"] }
18
18
  serde_json = "1"
@@ -91,8 +91,49 @@ impl ModelSession {
91
91
  .map(|session| Self { session })
92
92
  .map_err(|error| model_error(py, error))
93
93
  }
94
- #[pyo3(signature = (text, *, cancellation=None))]
94
+ /// Rule detectors handle the entity types they support and the model runs
95
+ /// only for requested types it alone provides. Omitting `config` equals
96
+ /// `{}`: every enabled rule detector plus the model's remaining labels.
97
+ #[pyo3(signature = (text, config=None, *, cancellation=None))]
95
98
  fn scan(
99
+ &self,
100
+ py: Python<'_>,
101
+ text: String,
102
+ config: Option<&Bound<'_, PyAny>>,
103
+ cancellation: Option<PyRef<'_, ModelCancellation>>,
104
+ ) -> PyResult<ModelScanResult> {
105
+ let routed = match config {
106
+ Some(config) => {
107
+ let value = py_to_json(py, config, "")?;
108
+ self.session
109
+ .routed_config(&value)
110
+ .map_err(|error| model_error(py, error))?
111
+ }
112
+ None => self.session.default_routed_config(),
113
+ };
114
+ // Rule-only routing never reserves the model worker.
115
+ let request = if routed.needs_model() {
116
+ Some(
117
+ self.session
118
+ .prepare_request()
119
+ .map_err(|error| model_error(py, error))?,
120
+ )
121
+ } else {
122
+ None
123
+ };
124
+ let cancellation = cancellation
125
+ .map(|v| Arc::clone(&v.cancellation))
126
+ .unwrap_or_default();
127
+ py.detach(|| {
128
+ self.session
129
+ .scan_routed_request(request.as_ref(), &text, &routed, &cancellation)
130
+ })
131
+ .map(ModelScanResult::from)
132
+ .map_err(|error| model_error(py, error))
133
+ }
134
+ /// Raw model output: every model label, with no rule detectors.
135
+ #[pyo3(signature = (text, *, cancellation=None))]
136
+ fn scan_model(
96
137
  &self,
97
138
  py: Python<'_>,
98
139
  text: String,
@@ -105,9 +146,12 @@ impl ModelSession {
105
146
  let cancellation = cancellation
106
147
  .map(|v| Arc::clone(&v.cancellation))
107
148
  .unwrap_or_default();
108
- py.detach(|| self.session.scan_request(&request, &text, &cancellation))
109
- .map(ModelScanResult::from)
110
- .map_err(|error| model_error(py, error))
149
+ py.detach(|| {
150
+ self.session
151
+ .scan_model_request(&request, &text, &cancellation)
152
+ })
153
+ .map(ModelScanResult::from)
154
+ .map_err(|error| model_error(py, error))
111
155
  }
112
156
  fn close(&self, py: Python<'_>) -> PyResult<()> {
113
157
  py.detach(|| self.session.close())
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "datafog-core"
3
- version = "0.4.5-beta.2"
3
+ version = "0.4.5-beta.4"
4
4
  edition = "2024"
5
5
  rust-version = "1.88"
6
6
  description = "Fast structured PII detection library"
@@ -26,7 +26,7 @@ DataFog Core is a Rust library with Python, Node.js, and browser WebAssembly bin
26
26
 
27
27
  Rule-based scanning runs locally without model weights or downloads. Optional local model sessions add DataFog and GLiNER2 support in Rust, Python, and Node.js.
28
28
 
29
- **Stable:** [0.4.4](https://github.com/DataFog/datafog-core/releases/tag/v0.4.4) · **Model beta:** [0.4.5-beta.1](https://github.com/DataFog/datafog-core/releases/tag/v0.4.5-beta.1)
29
+ **Stable:** [0.4.4](https://github.com/DataFog/datafog-core/releases/tag/v0.4.4) · **Model beta:** [0.4.5-beta.4](https://github.com/DataFog/datafog-core/releases/tag/v0.4.5-beta.4)
30
30
 
31
31
  ## Quickstart
32
32
 
@@ -165,23 +165,23 @@ Keep scan settings under `scan` and transformation policy under `transform`. A d
165
165
 
166
166
  ## Local models (beta)
167
167
 
168
- [0.4.5-beta.1](https://github.com/DataFog/datafog-core/releases/tag/v0.4.5-beta.1) adds local sessions for the DataFog PII model and GLiNER2 in Rust, Python, and Node.js on all five native platforms.
168
+ [0.4.5-beta.4](https://github.com/DataFog/datafog-core/releases/tag/v0.4.5-beta.4) adds local sessions for the DataFog PII model and GLiNER2 in Rust, Python, and Node.js on all five native platforms.
169
169
 
170
170
  Install the beta for your runtime:
171
171
 
172
172
  ```bash
173
- python -m pip install 'datafog-core==0.4.5b1'
173
+ python -m pip install 'datafog-core==0.4.5b4'
174
174
  npm install @datafog/node@beta
175
- cargo add datafog-core@0.4.5-beta.1 --features models
175
+ cargo add datafog-core@0.4.5-beta.4 --features models
176
176
  ```
177
177
 
178
178
  For GLiNER2, install its optional dependencies in the Python environment used by the runner:
179
179
 
180
180
  ```bash
181
- python -m pip install 'datafog-core[gliner2]==0.4.5b1'
181
+ python -m pip install 'datafog-core[gliner2]==0.4.5b4'
182
182
  ```
183
183
 
184
- The GLiNER2 runner ships in the Python package and also serves Node.js and Rust callers. Download and verify model files separately, then use `ModelSession.open`, text `scan`, and `close`. Reuse sessions across calls. Both backends return consistent results and errors, with cancellation and cleanup support.
184
+ GLiNER2 supports the PII checkpoint and compatible general-purpose span models with caller-defined labels. The GLiNER2 runner ships in the Python package and also serves Node.js and Rust callers. Download and verify model files separately, then use `ModelSession.open`, text `scan`, and `close`. Reuse sessions across calls. Both backends return consistent results and errors, with cancellation and cleanup support.
185
185
 
186
186
  [DataFog setup and session API →](docs/model-sessions.md) · [GLiNER2 setup →](docs/gliner2-model.md)
187
187
 
@@ -1,11 +1,10 @@
1
- //! Pinned official GLiNER2 span runtime, executed in an explicitly installed Python environment.
1
+ //! Official GLiNER2 span runtime, executed in an explicitly installed Python environment.
2
2
  use super::*;
3
3
  use std::process::Command;
4
4
 
5
5
  pub const MODEL: &str = "fastino/gliner2-privacy-filter-PII-multi";
6
6
  const ADAPTER: &str = "gliner2-worker-v1";
7
7
  const SCALE: &str = "normalized_span_sigmoid";
8
- const LABELS: &str = "person full_name first_name middle_name last_name date_of_birth email phone_number address street_address city state_or_region postal_code country government_id national_id_number passport_number drivers_license_number license_number tax_id tax_number bank_account account_number routing_number iban payment_card card_number card_expiry card_cvv username ip_address account_id sensitive_account_id password secret api_key access_token recovery_code sensitive_date document_date expiration_date transaction_date";
9
8
 
10
9
  #[derive(Clone, Debug, Serialize, Deserialize)]
11
10
  #[serde(deny_unknown_fields)]
@@ -64,9 +63,13 @@ fn validate_config(config: &Gliner2Config) -> Result<(), ModelError> {
64
63
  return Err(ModelError::config("empty_value", "/gliner2/labels"));
65
64
  }
66
65
  let mut seen = std::collections::BTreeSet::new();
66
+ let mut entity_types = std::collections::BTreeSet::new();
67
67
  for (index, label) in config.labels.iter().enumerate() {
68
68
  let path = format!("/gliner2/labels/{index}");
69
- if !LABELS.split_whitespace().any(|name| name == label.label) || !seen.insert(&label.label)
69
+ if label.label.trim().is_empty()
70
+ || label.label.trim() != label.label
71
+ || !seen.insert(&label.label)
72
+ || !entity_types.insert(mapping(&label.label))
70
73
  {
71
74
  return Err(ModelError::config(
72
75
  "invalid_value",
@@ -142,24 +145,50 @@ pub(super) fn open(
142
145
  return Err(invalid());
143
146
  }
144
147
  let manifest: BundleManifest = serde_json::from_slice(&bytes).map_err(|_| invalid())?;
145
- let lock: serde_json::Value =
146
- serde_json::from_str(include_str!("gliner2-lock.json")).map_err(|_| invalid())?;
147
- let expected: BTreeMap<String, AssetIdentity> =
148
- serde_json::from_value(lock["files"].clone()).map_err(|_| invalid())?;
149
- if manifest.platform != "portable-cpu" || manifest.files.len() != expected.len() {
148
+ let identity = validate_model_identity(&manifest, "gliner2")?;
149
+ let model_name = identity.map_or_else(|| MODEL.to_owned(), |model| model.id.clone());
150
+ let adapter_version = identity.map_or_else(
151
+ || ADAPTER.to_owned(),
152
+ |model| format!("{ADAPTER}:{}", model.revision),
153
+ );
154
+ if manifest.platform != "portable-cpu" || manifest.files.is_empty() {
150
155
  return Err(invalid());
151
156
  }
152
- for (name, expected) in expected {
153
- let identity = manifest.files.get(&name).ok_or_else(invalid)?;
154
- if identity.sha256 != expected.sha256 || identity.bytes != expected.bytes {
157
+ if identity.is_none() {
158
+ let lock: serde_json::Value =
159
+ serde_json::from_str(include_str!("gliner2-lock.json")).map_err(|_| invalid())?;
160
+ let expected: BTreeMap<String, AssetIdentity> =
161
+ serde_json::from_value(lock["files"].clone()).map_err(|_| invalid())?;
162
+ if manifest.files.len() != expected.len() {
155
163
  return Err(invalid());
156
164
  }
165
+ for (name, expected) in expected {
166
+ let asset = manifest.files.get(&name).ok_or_else(invalid)?;
167
+ if asset.sha256 != expected.sha256 || asset.bytes != expected.bytes {
168
+ return Err(invalid());
169
+ }
170
+ }
171
+ }
172
+ // The existing worker loads this local, single-file GLiNER2 span-model layout.
173
+ for required in [
174
+ "config.json",
175
+ "encoder_config/config.json",
176
+ "tokenizer.json",
177
+ "tokenizer_config.json",
178
+ "model.safetensors",
179
+ ] {
180
+ if !manifest.files.contains_key(required) {
181
+ return Err(invalid());
182
+ }
183
+ }
184
+ for (name, asset) in &manifest.files {
185
+ validate_asset_name(name)?;
157
186
  let path = bundle.join(name).canonicalize().map_err(|_| invalid())?;
158
187
  if !path.starts_with(&bundle) || !path.is_file() {
159
188
  return Err(invalid());
160
189
  }
161
190
  let (hash, size) = file_digest(&path, deadline)?;
162
- if hash != expected.sha256 || size != expected.bytes {
191
+ if hash != asset.sha256 || size != asset.bytes {
163
192
  return Err(invalid());
164
193
  }
165
194
  }
@@ -178,8 +207,8 @@ pub(super) fn open(
178
207
  let session = ModelSession {
179
208
  descriptor: ModelSessionDescriptor {
180
209
  backend: "gliner2_worker".into(),
181
- model: MODEL.into(),
182
- adapter_version: ADAPTER.into(),
210
+ model: model_name,
211
+ adapter_version,
183
212
  labels: options
184
213
  .labels
185
214
  .iter()
@@ -255,7 +284,7 @@ pub(super) fn decode(bytes: &[u8], max_findings: usize) -> Result<BackendResult,
255
284
  serde_json::from_value(value).map_err(|_| ModelError::invalid_output())?;
256
285
  let expected = runtime();
257
286
  if output.runtime != expected
258
- || output.worker_version != "0.4.5b2"
287
+ || output.worker_version != "0.4.5b4"
259
288
  || output.findings.len() > max_findings
260
289
  {
261
290
  return Err(ModelError::invalid_output());
@@ -303,13 +332,71 @@ mod tests {
303
332
  bad.labels.push(bad.labels[0].clone());
304
333
  assert!(validate_config(&bad).is_err());
305
334
  let mut bad = parsed.clone();
306
- bad.labels[0].label = "not_a_checkpoint_label".into();
307
- assert!(validate_config(&bad).is_err());
335
+ bad.labels[0].label = "current_employer".into();
336
+ bad.labels[0].description = Some("The caller's current employer".into());
337
+ assert!(validate_config(&bad).is_ok());
338
+ for label in ["", " ", " employer", "employer "] {
339
+ bad.labels[0].label = label.into();
340
+ assert!(validate_config(&bad).is_err());
341
+ }
308
342
  let mut bad = parsed;
309
343
  bad.chunk_overlap = bad.chunk_size;
310
344
  assert!(validate_config(&bad).is_err());
311
345
  }
312
346
  #[test]
347
+ fn labels_must_have_distinct_output_types() {
348
+ for (first, second) in [("employer", "EMPLOYER"), ("phone_number", "phone")] {
349
+ let config = Gliner2Config {
350
+ python: std::env::current_exe().unwrap(),
351
+ labels: [first, second]
352
+ .into_iter()
353
+ .map(|label| Gliner2Label {
354
+ label: label.into(),
355
+ threshold: 0.5,
356
+ description: None,
357
+ })
358
+ .collect(),
359
+ intra_threads: 1,
360
+ chunk_size: 384,
361
+ chunk_overlap: 64,
362
+ };
363
+ assert!(validate_config(&config).is_err());
364
+ }
365
+ }
366
+
367
+ #[test]
368
+ fn custom_model_labels_keep_types_offsets_and_transformation_support() {
369
+ let text = "👋 Cedar Labs";
370
+ let mut descriptor = super::super::tests::descriptor();
371
+ descriptor.backend = "gliner2_worker".into();
372
+ descriptor.model = "fastino/gliner2-base-v1".into();
373
+ descriptor.labels = [("current_employer".into(), "CURRENT_EMPLOYER".into())].into();
374
+ descriptor.thresholds = [("current_employer".into(), 0.5)].into();
375
+ descriptor.score_scale = SCALE.into();
376
+ let response = serde_json::json!({
377
+ "worker_version": "0.4.5b4", "runtime": runtime(),
378
+ "findings": [{"label": "current_employer", "start": 2, "end": 12,
379
+ "text": "Cedar Labs", "raw_score": 0.9}]
380
+ })
381
+ .to_string();
382
+ let entries = convert(text, decode(response.as_bytes(), 1).unwrap(), &descriptor).unwrap();
383
+ let findings: Vec<_> = entries.into_iter().map(|(finding, _)| finding).collect();
384
+ assert_eq!(findings[0].entity_type, "CURRENT_EMPLOYER");
385
+ assert_eq!(findings[0].byte_range, TextRange { start: 5, end: 15 });
386
+ assert_eq!(findings[0].codepoint_range, TextRange { start: 2, end: 12 });
387
+ assert_eq!(findings[0].confidence, None);
388
+ let transformed = crate::transform(
389
+ text,
390
+ &findings,
391
+ &crate::TransformationConfig::new(crate::TransformationStrategy::Redact),
392
+ )
393
+ .unwrap();
394
+ assert_eq!(transformed.text, "👋 [CURRENT_EMPLOYER]");
395
+ let mut unexpected = descriptor;
396
+ unexpected.labels.clear();
397
+ assert!(convert(text, decode(response.as_bytes(), 1).unwrap(), &unexpected).is_err());
398
+ }
399
+ #[test]
313
400
  fn uncalibrated_scores_are_validated_before_thresholding_and_rebased() {
314
401
  let mut descriptor = super::super::tests::descriptor();
315
402
  descriptor.backend = "gliner2_worker".into();
@@ -318,7 +405,7 @@ mod tests {
318
405
  descriptor.thresholds = [("street_address".into(), 0.5)].into();
319
406
  descriptor.score_scale = SCALE.into();
320
407
  let response = |score: f64| {
321
- serde_json::json!({"worker_version":"0.4.5b2","runtime":runtime(),
408
+ serde_json::json!({"worker_version":"0.4.5b4","runtime":runtime(),
322
409
  "findings":[{"label":"street_address","start":2,"end":17,"text":"123 Main Street","raw_score":score}]}).to_string()
323
410
  };
324
411
  let valid = decode(response(0.9).as_bytes(), 100).unwrap();
@@ -346,7 +433,7 @@ mod tests {
346
433
  .is_empty()
347
434
  );
348
435
  let corrupt =
349
- serde_json::json!({"worker_version":"0.4.5b2","runtime":{},"findings":[]}).to_string();
436
+ serde_json::json!({"worker_version":"0.4.5b4","runtime":{},"findings":[]}).to_string();
350
437
  assert!(decode(corrupt.as_bytes(), 100).is_err());
351
438
  }
352
439
  }