pyannotators-entityfishing 1.6.64__tar.gz → 1.6.71__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.
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/PKG-INFO +17 -7
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/README.md +16 -6
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/src/pyannotators_entityfishing/__init__.py +1 -1
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/vars/pythonPipeline.groovy +35 -25
- pyannotators_entityfishing-1.6.71/tools/el_jev.py +118 -0
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/.claude/skills/ef-mapping/SKILL.md +0 -0
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/.gitignore +0 -0
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/.gitmodules +0 -0
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/.python-version +0 -0
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/Taskfile.yml +0 -0
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/pyproject.toml +0 -0
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/src/pyannotators_entityfishing/ef_client.py +0 -0
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/src/pyannotators_entityfishing/entityfishing.py +0 -0
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/.claude-plugin/marketplace.json +0 -0
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/.claude-plugin/plugin.json +0 -0
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/HOWTO.md +0 -0
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/README.md +0 -0
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/resources/Stages.yml +0 -0
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/resources/Taskfile.yml +0 -0
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/skills/new-plugin/SKILL.md +0 -0
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/skills/new-plugin/references/types.md +0 -0
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/tools/check-marketplace.py +0 -0
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/tools/eval_mappings.py +0 -0
- {pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/tools/wdfilter.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pyannotators-entityfishing
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.71
|
|
4
4
|
Summary: Annotator based on entity-fishing
|
|
5
5
|
Project-URL: Homepage, https://github.com/oterrier/pyannotators_entityfishing/
|
|
6
6
|
Author-email: Olivier Terrier <olivier.terrier@kairntech.com>
|
|
@@ -89,24 +89,34 @@ for ann in docs[0].annotations:
|
|
|
89
89
|
The build is driven by [Task](https://taskfile.dev) and [uv](https://docs.astral.sh/uv/),
|
|
90
90
|
with the shared stages coming from the `python-archetype` submodule.
|
|
91
91
|
|
|
92
|
-
###
|
|
92
|
+
### Getting started
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
Python interpreter install themselves on demand (every task that runs uv depends on an
|
|
96
|
-
internal `install-python` task), but the thing that runs them does not.
|
|
94
|
+
The stages live in a Git submodule, so **clone with `--recurse-submodules`**:
|
|
97
95
|
|
|
98
96
|
```bash
|
|
97
|
+
git clone --recurse-submodules git@bitbucket.org:kairntech/pyannotators_entityfishing.git
|
|
98
|
+
cd pyannotators_entityfishing
|
|
99
99
|
sh -c "$(curl -sSL https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin
|
|
100
|
+
task
|
|
100
101
|
```
|
|
101
102
|
|
|
102
|
-
|
|
103
|
+
Already cloned without it? The submodule directory is empty, and `task` fails on:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
task: No Taskfile found at ".../submodules/python-archetype/resources/Taskfile.yml"
|
|
107
|
+
```
|
|
103
108
|
|
|
104
|
-
|
|
109
|
+
which means exactly that, and nothing worse:
|
|
105
110
|
|
|
106
111
|
```bash
|
|
107
112
|
git submodule update --init
|
|
108
113
|
```
|
|
109
114
|
|
|
115
|
+
**Task is the only manual prerequisite.** An archetype cannot bootstrap itself: uv and the
|
|
116
|
+
Python interpreter install themselves on demand (every task that runs uv depends on an
|
|
117
|
+
internal `install-python` task), but the thing that runs them does not. Make sure
|
|
118
|
+
`~/.local/bin` is on your `PATH` — that is where `task` and `uv` both land.
|
|
119
|
+
|
|
110
120
|
### Running the pipeline
|
|
111
121
|
|
|
112
122
|
```bash
|
|
@@ -41,24 +41,34 @@ for ann in docs[0].annotations:
|
|
|
41
41
|
The build is driven by [Task](https://taskfile.dev) and [uv](https://docs.astral.sh/uv/),
|
|
42
42
|
with the shared stages coming from the `python-archetype` submodule.
|
|
43
43
|
|
|
44
|
-
###
|
|
44
|
+
### Getting started
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
Python interpreter install themselves on demand (every task that runs uv depends on an
|
|
48
|
-
internal `install-python` task), but the thing that runs them does not.
|
|
46
|
+
The stages live in a Git submodule, so **clone with `--recurse-submodules`**:
|
|
49
47
|
|
|
50
48
|
```bash
|
|
49
|
+
git clone --recurse-submodules git@bitbucket.org:kairntech/pyannotators_entityfishing.git
|
|
50
|
+
cd pyannotators_entityfishing
|
|
51
51
|
sh -c "$(curl -sSL https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin
|
|
52
|
+
task
|
|
52
53
|
```
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
Already cloned without it? The submodule directory is empty, and `task` fails on:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
task: No Taskfile found at ".../submodules/python-archetype/resources/Taskfile.yml"
|
|
59
|
+
```
|
|
55
60
|
|
|
56
|
-
|
|
61
|
+
which means exactly that, and nothing worse:
|
|
57
62
|
|
|
58
63
|
```bash
|
|
59
64
|
git submodule update --init
|
|
60
65
|
```
|
|
61
66
|
|
|
67
|
+
**Task is the only manual prerequisite.** An archetype cannot bootstrap itself: uv and the
|
|
68
|
+
Python interpreter install themselves on demand (every task that runs uv depends on an
|
|
69
|
+
internal `install-python` task), but the thing that runs them does not. Make sure
|
|
70
|
+
`~/.local/bin` is on your `PATH` — that is where `task` and `uv` both land.
|
|
71
|
+
|
|
62
72
|
### Running the pipeline
|
|
63
73
|
|
|
64
74
|
```bash
|
|
@@ -211,6 +211,28 @@ def call(Map config = [:]) {
|
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
stage('py:sbom') {
|
|
215
|
+
steps {
|
|
216
|
+
sh 'task py:sbom'
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
stage('py:check-vulnerabilities') {
|
|
221
|
+
steps {
|
|
222
|
+
script {
|
|
223
|
+
// pip-audit ne voit que les paquets Python, jamais une couche OS : ce
|
|
224
|
+
// stage ne remplace pas un scan d'image, il couvre les dépendances.
|
|
225
|
+
if (env._BLOCK_ON_VULNS == 'true') {
|
|
226
|
+
sh 'task py:check-vulnerabilities'
|
|
227
|
+
} else {
|
|
228
|
+
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
|
|
229
|
+
sh 'task py:check-vulnerabilities'
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
214
236
|
stage('images:base') {
|
|
215
237
|
when { expression { return stageDeclared('images:base') } }
|
|
216
238
|
steps {
|
|
@@ -254,28 +276,6 @@ def call(Map config = [:]) {
|
|
|
254
276
|
}
|
|
255
277
|
}
|
|
256
278
|
|
|
257
|
-
stage('py:sbom') {
|
|
258
|
-
steps {
|
|
259
|
-
sh 'task py:sbom'
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
stage('py:check-vulnerabilities') {
|
|
264
|
-
steps {
|
|
265
|
-
script {
|
|
266
|
-
// pip-audit ne voit que les paquets Python, jamais une couche OS : ce
|
|
267
|
-
// stage ne remplace pas un scan d'image, il couvre les dépendances.
|
|
268
|
-
if (env._BLOCK_ON_VULNS == 'true') {
|
|
269
|
-
sh 'task py:check-vulnerabilities'
|
|
270
|
-
} else {
|
|
271
|
-
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
|
|
272
|
-
sh 'task py:check-vulnerabilities'
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
279
|
stage('version') {
|
|
280
280
|
when {
|
|
281
281
|
allOf {
|
|
@@ -545,7 +545,17 @@ Either add it here, or remove it from STAGES — not two lists drifting in silen
|
|
|
545
545
|
"""
|
|
546
546
|
}
|
|
547
547
|
|
|
548
|
-
/**
|
|
548
|
+
/**
|
|
549
|
+
* The stages this pipeline runs, in order. Kept next to checkStageDrift so both change together.
|
|
550
|
+
*
|
|
551
|
+
* L'ordre suit une seule regle : tout ce qui peut dire non passe avant ce qu'on ne peut pas
|
|
552
|
+
* defaire. 'version' pousse un commit et 'py:publish' envoie sur PyPI, qui est immuable -- ces
|
|
553
|
+
* deux-la sont les seuls a avoir un effet hors de l'agent, donc ils restent en queue, apres
|
|
554
|
+
* les portes. Les portes Python (py:sbom, py:check-vulnerabilities) et les portes d'image
|
|
555
|
+
* (images:scan) n'ont aucune dependance entre elles : aucun depot ne declare a la fois une roue
|
|
556
|
+
* et des images. Les premieres sont donc groupees avec py:test, pour que les stages py:* d'un
|
|
557
|
+
* depot a roue soient contigus et que les colonnes images:*, vides chez lui, ne les coupent pas.
|
|
558
|
+
*/
|
|
549
559
|
@groovy.transform.Field
|
|
550
560
|
List<String> JENKINS_STAGES = [
|
|
551
561
|
'init',
|
|
@@ -553,12 +563,12 @@ List<String> JENKINS_STAGES = [
|
|
|
553
563
|
'py:lock-check',
|
|
554
564
|
'py:lint',
|
|
555
565
|
'py:test',
|
|
566
|
+
'py:sbom',
|
|
567
|
+
'py:check-vulnerabilities',
|
|
556
568
|
'images:base',
|
|
557
569
|
'images:flavors',
|
|
558
570
|
'backport-proof',
|
|
559
571
|
'images:scan',
|
|
560
|
-
'py:sbom',
|
|
561
|
-
'py:check-vulnerabilities',
|
|
562
572
|
'version',
|
|
563
573
|
'py:build',
|
|
564
574
|
'py:publish',
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Prototype : NER amont -> candidats entity-fishing -> désambiguïsation Jev.
|
|
2
|
+
|
|
3
|
+
Clé lue dans JEV_API_KEY (comme tests/.env) ou TYPESAFE_API_KEY ; sans clé, affiche la
|
|
4
|
+
requête Jev au lieu de l'envoyer. Voir docs/entity-fishing-disambiguation-candidates_fr.md.
|
|
5
|
+
|
|
6
|
+
.venv/bin/python tools/el_jev.py "Macron à Washington" "Macron:PERSON,Washington:LOCATION" fr
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
import urllib.parse
|
|
13
|
+
import urllib.request
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
import mongoquery
|
|
17
|
+
|
|
18
|
+
EF = "https://entityfishing.kairntech.com/service"
|
|
19
|
+
API_KEY = os.getenv("JEV_API_KEY") or os.getenv("TYPESAFE_API_KEY")
|
|
20
|
+
JEV = "https://api.typesafe.ai/v1/systemone"
|
|
21
|
+
TOP_K = 8
|
|
22
|
+
NONE = "NONE"
|
|
23
|
+
|
|
24
|
+
# Filtres de type : les mappings mongoquery de l'annotateur (tests/data_v2/<Label>.json),
|
|
25
|
+
# appliqués au JSON de /kb/concept. Un type NER sans mapping (EVENT) n'est pas filtré.
|
|
26
|
+
MAPPINGS = Path(__file__).resolve().parent.parent / "tests" / "data_v2"
|
|
27
|
+
NER_TO_LABEL = {"PERSON": "Person", "LOCATION": "Location", "ORGANIZATION": "Organization"}
|
|
28
|
+
FILTERS = {ner: mongoquery.Query(json.loads((MAPPINGS / f"{label}.json").read_text())) for ner, label in NER_TO_LABEL.items()}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get(url):
|
|
32
|
+
with urllib.request.urlopen(url, timeout=60) as r:
|
|
33
|
+
return json.load(r)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def senses(term, lang="en"):
|
|
37
|
+
return get(f"{EF}/kb/term/{urllib.parse.quote(term)}?lang={lang}").get("senses", [])
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def concept(pageid, lang="en"):
|
|
41
|
+
return get(f"{EF}/kb/concept/{pageid}?lang={lang}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def first_sentence(wikitext):
|
|
45
|
+
t = re.sub(r"\[\[(?:[^|\]]*\|)?([^\]]*)\]\]", r"\1", wikitext)
|
|
46
|
+
t = re.sub(r"'''?|\{\{[^}]*\}\}|\([^)]*\)", "", t)
|
|
47
|
+
return re.split(r"(?<=\.)\s", re.sub(r"\s+", " ", t).strip())[0]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def candidates(mention, ner_type, lang="en"):
|
|
51
|
+
out = []
|
|
52
|
+
for s in senses(mention, lang)[:TOP_K]:
|
|
53
|
+
c = concept(s["pageid"], lang)
|
|
54
|
+
st = c.get("statements", [])
|
|
55
|
+
if ner_type in FILTERS and not FILTERS[ner_type].match(c):
|
|
56
|
+
continue
|
|
57
|
+
facts = {}
|
|
58
|
+
for x in st:
|
|
59
|
+
if x["propertyId"] in ("P31", "P106", "P27", "P641", "P585") and x.get("valueName"):
|
|
60
|
+
facts.setdefault(x["propertyName"], []).append(x["valueName"])
|
|
61
|
+
if x["propertyId"] == "P569":
|
|
62
|
+
facts["date of birth"] = [x["value"]["time"][1:11]]
|
|
63
|
+
defs = c.get("definitions") or [{}]
|
|
64
|
+
out.append(
|
|
65
|
+
{
|
|
66
|
+
"qid": c["wikidataId"],
|
|
67
|
+
"label": s["preferred"],
|
|
68
|
+
"prior": s["prob_c"],
|
|
69
|
+
"description": first_sentence(defs[0].get("definition", "")),
|
|
70
|
+
"facts": {k: v[:3] for k, v in facts.items()},
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
return out
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def jev_request(text, mentions):
|
|
77
|
+
"""Une Choice par mention, toutes dans le même appel (évaluées en parallèle)."""
|
|
78
|
+
questions = {}
|
|
79
|
+
for i, m in enumerate(mentions):
|
|
80
|
+
criteria = {c["qid"]: {"name": c["label"], "description": c["description"], "facts": c["facts"]} for c in m["candidates"]}
|
|
81
|
+
criteria[NONE] = "None of the listed entities is the one meant by the mention"
|
|
82
|
+
questions[f"m{i}"] = {
|
|
83
|
+
"type": "choice",
|
|
84
|
+
"instructions": {
|
|
85
|
+
"mention": {"surface": m["surface"], "ner_type": m["type"]},
|
|
86
|
+
"question": "In `text`, which knowledge-base entity does `mention.surface` refer to?",
|
|
87
|
+
},
|
|
88
|
+
"criteria": criteria,
|
|
89
|
+
}
|
|
90
|
+
return {"model": "jev-latest", "state": {"text": text}, "questions": questions}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def call_jev(payload):
|
|
94
|
+
req = urllib.request.Request(JEV, data=json.dumps(payload).encode(), headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"})
|
|
95
|
+
with urllib.request.urlopen(req, timeout=60) as r:
|
|
96
|
+
return json.load(r)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
if __name__ == "__main__":
|
|
100
|
+
import sys
|
|
101
|
+
|
|
102
|
+
text = sys.argv[1] if len(sys.argv) > 1 else "Ronaldo at the World Cup"
|
|
103
|
+
spec = sys.argv[2] if len(sys.argv) > 2 else "Ronaldo:PERSON,World Cup:EVENT"
|
|
104
|
+
lang = sys.argv[3] if len(sys.argv) > 3 else "en"
|
|
105
|
+
ner = [m.rsplit(":", 1) for m in spec.split(",")]
|
|
106
|
+
mentions = [{"surface": s, "type": t, "candidates": candidates(s, t, lang)} for s, t in ner]
|
|
107
|
+
payload = jev_request(text, mentions)
|
|
108
|
+
if not API_KEY:
|
|
109
|
+
print(json.dumps(payload, indent=1, ensure_ascii=False))
|
|
110
|
+
sys.exit()
|
|
111
|
+
answers = call_jev(payload)["answers"]
|
|
112
|
+
for i, m in enumerate(mentions):
|
|
113
|
+
a = answers[f"m{i}"]
|
|
114
|
+
prior = {c["qid"]: c["prior"] for c in m["candidates"]}
|
|
115
|
+
print(f"{m['surface']} -> {a['choice']} (conf {a['confidence']:.2f})")
|
|
116
|
+
for qid, p in sorted(a["probabilities"].items(), key=lambda kv: -kv[1]):
|
|
117
|
+
label = {c["qid"]: c["label"] for c in m["candidates"]}.get(qid, "")
|
|
118
|
+
print(f" {qid:10} jev={p:.3f} prior={prior.get(qid, 0):.3f} {label}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyannotators_entityfishing-1.6.64 → pyannotators_entityfishing-1.6.71}/tools/eval_mappings.py
RENAMED
|
File without changes
|
|
File without changes
|