pyannotators-entityfishing 1.6.67__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.67 → pyannotators_entityfishing-1.6.71}/PKG-INFO +17 -7
- {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/README.md +16 -6
- {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/src/pyannotators_entityfishing/__init__.py +1 -1
- pyannotators_entityfishing-1.6.71/tools/el_jev.py +118 -0
- {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/.claude/skills/ef-mapping/SKILL.md +0 -0
- {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/.gitignore +0 -0
- {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/.gitmodules +0 -0
- {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/.python-version +0 -0
- {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/Taskfile.yml +0 -0
- {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/pyproject.toml +0 -0
- {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/src/pyannotators_entityfishing/ef_client.py +0 -0
- {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/src/pyannotators_entityfishing/entityfishing.py +0 -0
- {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/.claude-plugin/marketplace.json +0 -0
- {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/.claude-plugin/plugin.json +0 -0
- {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/HOWTO.md +0 -0
- {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/README.md +0 -0
- {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/resources/Stages.yml +0 -0
- {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/resources/Taskfile.yml +0 -0
- {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/skills/new-plugin/SKILL.md +0 -0
- {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/skills/new-plugin/references/types.md +0 -0
- {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/tools/check-marketplace.py +0 -0
- {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/submodules/python-archetype/vars/pythonPipeline.groovy +0 -0
- {pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/tools/eval_mappings.py +0 -0
- {pyannotators_entityfishing-1.6.67 → 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
|
|
@@ -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
|
|
File without changes
|
{pyannotators_entityfishing-1.6.67 → pyannotators_entityfishing-1.6.71}/tools/eval_mappings.py
RENAMED
|
File without changes
|
|
File without changes
|