contexte 0.1.0__py3-none-any.whl
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.
contexte/__init__.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""contexte : client Python de ContexteTech : charger une fiche (dataset, prompt, contexte…) en une ligne.
|
|
2
|
+
|
|
3
|
+
import contexte
|
|
4
|
+
|
|
5
|
+
ds = contexte.load("contextetech/anonymisation-rgpd-fr") # dataset → lignes (dict)
|
|
6
|
+
p = contexte.load("contextetech/fiche-produit-ecommerce") # prompt → p.fill(produit=…)
|
|
7
|
+
c = contexte.load("contextetech/anonymisation-rgpd") # contexte → c.messages("texte")
|
|
8
|
+
contexte.search("rgpd", kind="dataset") # recherche dans le catalogue
|
|
9
|
+
contexte.cost("contextetech/anonymisation-rgpd") # coût estimé par modèle
|
|
10
|
+
|
|
11
|
+
Aucune dépendance : seulement la bibliothèque standard. `Dataset.to_hf()` utilise `datasets` s'il est installé.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
import urllib.error
|
|
19
|
+
import urllib.parse
|
|
20
|
+
import urllib.request
|
|
21
|
+
|
|
22
|
+
__version__ = "0.1.0"
|
|
23
|
+
__all__ = ["Client", "Resource", "Dataset", "Prompt", "Context", "load", "get", "search", "cost", "ContexteTechError"]
|
|
24
|
+
|
|
25
|
+
BASE_URL = os.environ.get("CONTEXTETECH_URL", "https://contextetech.com").rstrip("/")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ContexteTechError(Exception):
|
|
29
|
+
"""Erreur renvoyée par l'API (fiche introuvable, réseau…)."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _to_id(ref: str) -> str:
|
|
33
|
+
"""« auteur/nom » → identifiant « auteur--nom » ; un identifiant est laissé tel quel."""
|
|
34
|
+
ref = ref.strip().strip("/")
|
|
35
|
+
if "/" in ref:
|
|
36
|
+
author, name = ref.split("/", 1)
|
|
37
|
+
return f"{author}--{name}"
|
|
38
|
+
return ref
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Resource(dict):
|
|
42
|
+
"""Fiche brute renvoyée par l'API (dictionnaire), avec quelques raccourcis."""
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def id(self) -> str:
|
|
46
|
+
return self["id"]
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def kind(self) -> str:
|
|
50
|
+
return self["kind"]
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def name(self) -> str:
|
|
54
|
+
return self["name"]
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def license(self) -> str:
|
|
58
|
+
return self.get("license", "")
|
|
59
|
+
|
|
60
|
+
def __repr__(self) -> str:
|
|
61
|
+
return f"<{type(self).__name__} {self.get('authorHandle', '')}/{self.get('name', '')}>"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Dataset(Resource):
|
|
65
|
+
"""Dataset : `rows` (liste de dict), itérable, `len()`, `to_hf()`."""
|
|
66
|
+
|
|
67
|
+
rows: list
|
|
68
|
+
|
|
69
|
+
def __iter__(self):
|
|
70
|
+
return iter(self.rows)
|
|
71
|
+
|
|
72
|
+
def __len__(self) -> int:
|
|
73
|
+
return len(self.rows)
|
|
74
|
+
|
|
75
|
+
def __getitem__(self, key):
|
|
76
|
+
return self.rows[key] if isinstance(key, (int, slice)) else super().__getitem__(key)
|
|
77
|
+
|
|
78
|
+
def to_hf(self):
|
|
79
|
+
"""Dataset `datasets.Dataset` (nécessite `pip install datasets`)."""
|
|
80
|
+
from datasets import Dataset as HFDataset
|
|
81
|
+
return HFDataset.from_list(self.rows)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class Prompt(Resource):
|
|
85
|
+
"""Prompt à trous {{variable}} : `variables`, `fill(**valeurs)`."""
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def template(self) -> str:
|
|
89
|
+
return self.get("template", "")
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def variables(self) -> list[str]:
|
|
93
|
+
return list(dict.fromkeys(re.findall(r"\{\{\s*([\w-]+)\s*\}\}", self.template)))
|
|
94
|
+
|
|
95
|
+
def fill(self, **values) -> str:
|
|
96
|
+
missing = [v for v in self.variables if v not in values]
|
|
97
|
+
if missing:
|
|
98
|
+
raise ValueError(f"Variables manquantes : {', '.join(missing)}")
|
|
99
|
+
return re.sub(r"\{\{\s*([\w-]+)\s*\}\}", lambda m: str(values[m.group(1)]), self.template)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class Context(Resource):
|
|
103
|
+
"""Contexte few-shot : consigne système, exemples, connaissances ; `messages(texte)` pour une API de chat."""
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def system(self) -> str:
|
|
107
|
+
return self.get("system", "")
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def examples(self) -> list[dict]:
|
|
111
|
+
return self.get("examples") or []
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def knowledge(self) -> str:
|
|
115
|
+
return self.get("knowledge", "")
|
|
116
|
+
|
|
117
|
+
def system_prompt(self) -> str:
|
|
118
|
+
"""Consigne système complète (consigne + connaissances)."""
|
|
119
|
+
return self.system + (f"\n\n{self.knowledge}" if self.knowledge else "")
|
|
120
|
+
|
|
121
|
+
def messages(self, user_input: str) -> list[dict]:
|
|
122
|
+
"""Messages prêts pour une API de chat : exemples en tours user/assistant, puis votre texte.
|
|
123
|
+
La consigne système est à passer à part (`system_prompt()`), comme le demandent la plupart des API."""
|
|
124
|
+
out = []
|
|
125
|
+
for ex in self.examples:
|
|
126
|
+
out += [{"role": "user", "content": ex.get("input", "")}, {"role": "assistant", "content": ex.get("output", "")}]
|
|
127
|
+
return out + [{"role": "user", "content": user_input}]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
_KINDS = {"dataset": Dataset, "prompt": Prompt, "context": Context}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class Client:
|
|
134
|
+
"""Accès à l'API publique de ContexteTech (lecture seule, sans compte)."""
|
|
135
|
+
|
|
136
|
+
def __init__(self, base_url: str = BASE_URL, timeout: float = 30):
|
|
137
|
+
self.base_url = base_url.rstrip("/")
|
|
138
|
+
self.timeout = timeout
|
|
139
|
+
|
|
140
|
+
def _get(self, path: str, raw: bool = False):
|
|
141
|
+
req = urllib.request.Request(self.base_url + path, headers={"User-Agent": f"contexte-python/{__version__}", "Accept": "application/json"})
|
|
142
|
+
try:
|
|
143
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as r:
|
|
144
|
+
body = r.read().decode("utf-8")
|
|
145
|
+
except urllib.error.HTTPError as e:
|
|
146
|
+
raise ContexteTechError(f"{e.code} sur {path}" + (" : fiche introuvable" if e.code == 404 else "")) from None
|
|
147
|
+
except urllib.error.URLError as e:
|
|
148
|
+
raise ContexteTechError(f"Réseau : {e.reason}") from None
|
|
149
|
+
return body if raw else json.loads(body)
|
|
150
|
+
|
|
151
|
+
def get(self, ref: str) -> Resource:
|
|
152
|
+
"""Fiche brute (dictionnaire) : « auteur/nom » ou identifiant."""
|
|
153
|
+
data = self._get(f"/api/resources/{urllib.parse.quote(_to_id(ref))}")
|
|
154
|
+
return _KINDS.get(data.get("kind"), Resource)(data)
|
|
155
|
+
|
|
156
|
+
def load(self, ref: str) -> Resource:
|
|
157
|
+
"""Fiche prête à l'emploi : Dataset (lignes chargées), Prompt, Context ou Resource."""
|
|
158
|
+
res = self.get(ref)
|
|
159
|
+
if isinstance(res, Dataset):
|
|
160
|
+
text = res.get("jsonl") or self._get(f"/api/resources/{urllib.parse.quote(res.id)}/download", raw=True)
|
|
161
|
+
res.rows = [json.loads(line) for line in text.splitlines() if line.strip()]
|
|
162
|
+
return res
|
|
163
|
+
|
|
164
|
+
def search(self, q: str = "", kind: str = "", lang: str = "", page: int = 1) -> list[Resource]:
|
|
165
|
+
"""Recherche dans le catalogue ; renvoie des fiches résumées (sans le contenu)."""
|
|
166
|
+
params = {k: v for k, v in {"q": q, "kind": kind, "lang": lang, "page": page}.items() if v}
|
|
167
|
+
data = self._get("/api/resources?" + urllib.parse.urlencode(params))
|
|
168
|
+
return [_KINDS.get(it.get("kind"), Resource)(it) for it in data.get("items", [])]
|
|
169
|
+
|
|
170
|
+
def cost(self, ref: str) -> dict:
|
|
171
|
+
"""Coût estimé en euros : par modèle d'API (tokens) ou, pour un modèle ouvert, location de GPU."""
|
|
172
|
+
return self._get(f"/api/resources/{urllib.parse.quote(_to_id(ref))}/cost")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
_default = Client()
|
|
176
|
+
get, load, search, cost = _default.get, _default.load, _default.search, _default.cost
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: contexte
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Load ContexteTech resources (datasets, prompts, contexts) in one line of Python.
|
|
5
|
+
Author-email: ContexteTech <coucou@contextetech.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://contextetech.com/en/
|
|
8
|
+
Project-URL: Documentation, https://contextetech.com/api/docs
|
|
9
|
+
Keywords: llm,prompts,datasets,fine-tuning,few-shot,contextetech,contexte
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Provides-Extra: hf
|
|
17
|
+
Requires-Dist: datasets; extra == "hf"
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# contexte
|
|
21
|
+
|
|
22
|
+
Load resources from [ContexteTech](https://contextetech.com/en/) — datasets, prompts, few-shot contexts — in one line of Python. No dependencies.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install contexte
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import contexte
|
|
30
|
+
|
|
31
|
+
# Dataset: rows as dicts (JSONL messages)
|
|
32
|
+
ds = contexte.load("contextetech/anonymisation-rgpd-fr")
|
|
33
|
+
print(len(ds), ds[0]["messages"][1]["content"])
|
|
34
|
+
|
|
35
|
+
# Prompt with {{variables}}
|
|
36
|
+
p = contexte.load("contextetech/fiche-produit-ecommerce")
|
|
37
|
+
print(p.variables) # ['produit', 'caracteristiques', 'public']
|
|
38
|
+
text = p.fill(produit="Casque audio", caracteristiques="Bluetooth, 30 h", public="étudiants")
|
|
39
|
+
|
|
40
|
+
# Few-shot context, ready for any chat API
|
|
41
|
+
c = contexte.load("contextetech/anonymisation-rgpd")
|
|
42
|
+
system = c.system_prompt()
|
|
43
|
+
messages = c.messages("Bonjour, je suis Jean Martin, 06 11 22 33 44.")
|
|
44
|
+
|
|
45
|
+
# Search and cost
|
|
46
|
+
contexte.search("rgpd", kind="dataset")
|
|
47
|
+
contexte.cost("contextetech/anonymisation-rgpd") # estimated cost per model, in euros
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
A resource is referenced as `author/name`, as shown on its page. Read-only, no account needed.
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
|
|
54
|
+
MIT. The ContexteTech name and logos are trademarks and are not covered by this license.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
contexte/__init__.py,sha256=mY0kMEkmE_KAGuIKMs1k0YUqNzljQTREXnMXNunFAbg,6768
|
|
2
|
+
contexte-0.1.0.dist-info/licenses/LICENSE,sha256=8K65Ea34j3rWRhAWv_VLJbCFo965q2n5oTGDZE5LfiM,1082
|
|
3
|
+
contexte-0.1.0.dist-info/METADATA,sha256=RUKJUTanP7k4cyCCA-6tN6_TXyHx2kbvZd_6BiN9NsY,1916
|
|
4
|
+
contexte-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
5
|
+
contexte-0.1.0.dist-info/top_level.txt,sha256=Mv-esQ7S67YhTVEoU-fq0_9SmCERX66EQyacQI0nWRk,9
|
|
6
|
+
contexte-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ContexteTech (Tonydevweb)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
contexte
|