pommes-assistant 0.1.0__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.
- pommes_assistant-0.1.0/LICENSE +21 -0
- pommes_assistant-0.1.0/MANIFEST.in +5 -0
- pommes_assistant-0.1.0/PKG-INFO +157 -0
- pommes_assistant-0.1.0/README.md +101 -0
- pommes_assistant-0.1.0/pyproject.toml +83 -0
- pommes_assistant-0.1.0/setup.cfg +4 -0
- pommes_assistant-0.1.0/src/pommes_assistant/__init__.py +13 -0
- pommes_assistant-0.1.0/src/pommes_assistant/assistant.py +179 -0
- pommes_assistant-0.1.0/src/pommes_assistant/audit/__init__.py +5 -0
- pommes_assistant-0.1.0/src/pommes_assistant/audit/audit.py +146 -0
- pommes_assistant-0.1.0/src/pommes_assistant/audit/references.py +111 -0
- pommes_assistant-0.1.0/src/pommes_assistant/audit/thresholds.yaml +21 -0
- pommes_assistant-0.1.0/src/pommes_assistant/bench/__init__.py +9 -0
- pommes_assistant-0.1.0/src/pommes_assistant/bench/backend.py +306 -0
- pommes_assistant-0.1.0/src/pommes_assistant/bench/dialogues.py +496 -0
- pommes_assistant-0.1.0/src/pommes_assistant/bench/rejudge.py +115 -0
- pommes_assistant-0.1.0/src/pommes_assistant/bench/report.py +247 -0
- pommes_assistant-0.1.0/src/pommes_assistant/bench/runner.py +259 -0
- pommes_assistant-0.1.0/src/pommes_assistant/bench/score.py +309 -0
- pommes_assistant-0.1.0/src/pommes_assistant/catalogue/__init__.py +1 -0
- pommes_assistant-0.1.0/src/pommes_assistant/catalogue/card.py +110 -0
- pommes_assistant-0.1.0/src/pommes_assistant/catalogue/cards/sltyndp_2024.yaml +775 -0
- pommes_assistant-0.1.0/src/pommes_assistant/catalogue/loaders/__init__.py +1 -0
- pommes_assistant-0.1.0/src/pommes_assistant/catalogue/loaders/sltyndp.py +177 -0
- pommes_assistant-0.1.0/src/pommes_assistant/catalogue/rules.py +334 -0
- pommes_assistant-0.1.0/src/pommes_assistant/catalogue/search.py +111 -0
- pommes_assistant-0.1.0/src/pommes_assistant/cli.py +1586 -0
- pommes_assistant-0.1.0/src/pommes_assistant/client/__init__.py +11 -0
- pommes_assistant-0.1.0/src/pommes_assistant/client/agent.py +508 -0
- pommes_assistant-0.1.0/src/pommes_assistant/client/llm.py +332 -0
- pommes_assistant-0.1.0/src/pommes_assistant/client/toolbox.py +196 -0
- pommes_assistant-0.1.0/src/pommes_assistant/compile/__init__.py +18 -0
- pommes_assistant-0.1.0/src/pommes_assistant/compile/assemble.py +146 -0
- pommes_assistant-0.1.0/src/pommes_assistant/compile/build.py +221 -0
- pommes_assistant-0.1.0/src/pommes_assistant/compile/compare.py +114 -0
- pommes_assistant-0.1.0/src/pommes_assistant/compile/craft.py +289 -0
- pommes_assistant-0.1.0/src/pommes_assistant/compile/errors.py +18 -0
- pommes_assistant-0.1.0/src/pommes_assistant/compile/overrides.py +50 -0
- pommes_assistant-0.1.0/src/pommes_assistant/compile/table_transforms.py +223 -0
- pommes_assistant-0.1.0/src/pommes_assistant/compile/write.py +149 -0
- pommes_assistant-0.1.0/src/pommes_assistant/dialogue/__init__.py +5 -0
- pommes_assistant-0.1.0/src/pommes_assistant/dialogue/policy.py +432 -0
- pommes_assistant-0.1.0/src/pommes_assistant/dialogue/quotes.py +159 -0
- pommes_assistant-0.1.0/src/pommes_assistant/estimate/__init__.py +21 -0
- pommes_assistant-0.1.0/src/pommes_assistant/estimate/journal.py +98 -0
- pommes_assistant-0.1.0/src/pommes_assistant/estimate/machine.py +61 -0
- pommes_assistant-0.1.0/src/pommes_assistant/estimate/size.py +172 -0
- pommes_assistant-0.1.0/src/pommes_assistant/estimate/time.py +95 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/__init__.py +46 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/adapters/__init__.py +27 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/adapters/battery.py +238 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/adapters/dispatchable.py +165 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/adapters/electrolyser_hub.py +156 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/adapters/h2_cavity.py +286 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/adapters/hydrogen.py +209 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/adapters/shiftable_demand.py +117 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/brent.py +108 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/context.py +300 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/conventions.py +72 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/curves.py +244 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/domain.py +51 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/engine.py +152 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/errors.py +22 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/finance.py +91 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/inputs.py +622 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/prices.py +391 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/report.py +351 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/rules/__init__.py +27 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/rules/battery.py +252 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/rules/dispatchable.py +75 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/rules/electrolyser.py +522 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/rules/h2_cavity.py +234 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/rules/shiftable_demand.py +242 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/settings.yaml +279 -0
- pommes_assistant-0.1.0/src/pommes_assistant/guess/validation.yaml +1279 -0
- pommes_assistant-0.1.0/src/pommes_assistant/mcp/__init__.py +6 -0
- pommes_assistant-0.1.0/src/pommes_assistant/mcp/prompts.py +61 -0
- pommes_assistant-0.1.0/src/pommes_assistant/mcp/server.py +135 -0
- pommes_assistant-0.1.0/src/pommes_assistant/mcp/skill/SKILL.md +113 -0
- pommes_assistant-0.1.0/src/pommes_assistant/mcp/tools.py +1062 -0
- pommes_assistant-0.1.0/src/pommes_assistant/results/__init__.py +10 -0
- pommes_assistant-0.1.0/src/pommes_assistant/results/checks.py +268 -0
- pommes_assistant-0.1.0/src/pommes_assistant/results/compare.py +106 -0
- pommes_assistant-0.1.0/src/pommes_assistant/results/load.py +228 -0
- pommes_assistant-0.1.0/src/pommes_assistant/results/report.py +674 -0
- pommes_assistant-0.1.0/src/pommes_assistant/run/__init__.py +25 -0
- pommes_assistant-0.1.0/src/pommes_assistant/run/guards.py +36 -0
- pommes_assistant-0.1.0/src/pommes_assistant/run/llm.py +355 -0
- pommes_assistant-0.1.0/src/pommes_assistant/run/llm_data/relay.py +321 -0
- pommes_assistant-0.1.0/src/pommes_assistant/run/llm_data/serve.sbatch +77 -0
- pommes_assistant-0.1.0/src/pommes_assistant/run/local.py +147 -0
- pommes_assistant-0.1.0/src/pommes_assistant/run/profile.py +126 -0
- pommes_assistant-0.1.0/src/pommes_assistant/run/remote.py +272 -0
- pommes_assistant-0.1.0/src/pommes_assistant/run/runner.py +185 -0
- pommes_assistant-0.1.0/src/pommes_assistant/sketch/__init__.py +19 -0
- pommes_assistant-0.1.0/src/pommes_assistant/sketch/errors.py +16 -0
- pommes_assistant-0.1.0/src/pommes_assistant/sketch/flexibility.py +258 -0
- pommes_assistant-0.1.0/src/pommes_assistant/sketch/l0.py +220 -0
- pommes_assistant-0.1.0/src/pommes_assistant/sketch/l1.py +148 -0
- pommes_assistant-0.1.0/src/pommes_assistant/sketch/reduce.py +341 -0
- pommes_assistant-0.1.0/src/pommes_assistant/sketch/regret.py +514 -0
- pommes_assistant-0.1.0/src/pommes_assistant/sketch/summary.py +427 -0
- pommes_assistant-0.1.0/src/pommes_assistant/sketch/thresholds.yaml +23 -0
- pommes_assistant-0.1.0/src/pommes_assistant/sketch/worlds.py +392 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/__init__.py +0 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/edit.py +1017 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/expected_units.py +106 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/findings.py +199 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/generate.py +116 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/importer.py +179 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/inherit.py +247 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/io.py +132 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/model.py +268 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/paths.py +45 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/pommes_schema.py +110 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/replay.py +257 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/rules.py +646 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/schema_data/expected_units.yaml +107 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/schema_data/pommes-0.5.0.json +1759 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/schema_data/pommes-0.6.0.json +1945 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/sources.py +135 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/tables.py +105 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/transforms.py +302 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/units.py +236 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/validate.py +72 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/values.py +145 -0
- pommes_assistant-0.1.0/src/pommes_assistant/spec/walk.py +144 -0
- pommes_assistant-0.1.0/src/pommes_assistant/views/__init__.py +95 -0
- pommes_assistant-0.1.0/src/pommes_assistant/views/areas.yaml +40 -0
- pommes_assistant-0.1.0/src/pommes_assistant/views/chart.py +126 -0
- pommes_assistant-0.1.0/src/pommes_assistant/views/data.py +667 -0
- pommes_assistant-0.1.0/src/pommes_assistant/views/palette.py +116 -0
- pommes_assistant-0.1.0/src/pommes_assistant/views/palette.yaml +89 -0
- pommes_assistant-0.1.0/src/pommes_assistant/views/render_mpl.py +221 -0
- pommes_assistant-0.1.0/src/pommes_assistant/views/render_plotly.py +190 -0
- pommes_assistant-0.1.0/src/pommes_assistant/views/standard.py +572 -0
- pommes_assistant-0.1.0/src/pommes_assistant.egg-info/PKG-INFO +157 -0
- pommes_assistant-0.1.0/src/pommes_assistant.egg-info/SOURCES.txt +140 -0
- pommes_assistant-0.1.0/src/pommes_assistant.egg-info/dependency_links.txt +1 -0
- pommes_assistant-0.1.0/src/pommes_assistant.egg-info/entry_points.txt +2 -0
- pommes_assistant-0.1.0/src/pommes_assistant.egg-info/requires.txt +44 -0
- pommes_assistant-0.1.0/src/pommes_assistant.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Robin Girard, MINES Paris – PSL
|
|
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,157 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pommes-assistant
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Deterministic backbone of a conversational assistant for pommes energy-system studies: StudySpec with provenance, validator, catalogue, tools
|
|
5
|
+
Author-email: Robin Girard <robin.girard@minesparis.psl.eu>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://git.persee.minesparis.psl.eu/energy-alternatives/pommes-assistant
|
|
8
|
+
Keywords: energy,energy system modelling,pommes,provenance,LLM tools,MCP
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering
|
|
14
|
+
Classifier: Natural Language :: French
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: pydantic<3,>=2.7
|
|
19
|
+
Requires-Dist: pyyaml<7,>=6
|
|
20
|
+
Provides-Extra: pommes
|
|
21
|
+
Requires-Dist: pommes<0.7,>=0.5; extra == "pommes"
|
|
22
|
+
Provides-Extra: craft
|
|
23
|
+
Requires-Dist: pommes_craft>=0.5.1; extra == "craft"
|
|
24
|
+
Requires-Dist: polars>=0.20; extra == "craft"
|
|
25
|
+
Provides-Extra: data
|
|
26
|
+
Requires-Dist: polars>=0.20; extra == "data"
|
|
27
|
+
Requires-Dist: xarray>=2024.1; extra == "data"
|
|
28
|
+
Requires-Dist: h5netcdf>=1.3; extra == "data"
|
|
29
|
+
Requires-Dist: numpy>=1.26; extra == "data"
|
|
30
|
+
Provides-Extra: mcp
|
|
31
|
+
Requires-Dist: mcp<3,>=2.2; extra == "mcp"
|
|
32
|
+
Provides-Extra: views
|
|
33
|
+
Requires-Dist: plotly>=6; extra == "views"
|
|
34
|
+
Requires-Dist: matplotlib>=3.8; extra == "views"
|
|
35
|
+
Requires-Dist: xarray>=2024.1; extra == "views"
|
|
36
|
+
Requires-Dist: h5netcdf>=1.3; extra == "views"
|
|
37
|
+
Requires-Dist: numpy>=1.26; extra == "views"
|
|
38
|
+
Requires-Dist: hdf5plugin>=4; extra == "views"
|
|
39
|
+
Provides-Extra: all
|
|
40
|
+
Requires-Dist: pommes<0.7,>=0.6; extra == "all"
|
|
41
|
+
Requires-Dist: pommes_craft>=0.5.1; extra == "all"
|
|
42
|
+
Requires-Dist: linopy>=0.8; extra == "all"
|
|
43
|
+
Requires-Dist: highspy>=1.7; extra == "all"
|
|
44
|
+
Requires-Dist: polars>=0.20; extra == "all"
|
|
45
|
+
Requires-Dist: xarray>=2024.1; extra == "all"
|
|
46
|
+
Requires-Dist: h5netcdf>=1.3; extra == "all"
|
|
47
|
+
Requires-Dist: numpy>=1.26; extra == "all"
|
|
48
|
+
Requires-Dist: mcp<3,>=2.2; extra == "all"
|
|
49
|
+
Requires-Dist: plotly>=6; extra == "all"
|
|
50
|
+
Requires-Dist: matplotlib>=3.8; extra == "all"
|
|
51
|
+
Requires-Dist: hdf5plugin>=4; extra == "all"
|
|
52
|
+
Provides-Extra: dev
|
|
53
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
54
|
+
Requires-Dist: anyio>=4; extra == "dev"
|
|
55
|
+
Dynamic: license-file
|
|
56
|
+
|
|
57
|
+
# pommes-assistant
|
|
58
|
+
|
|
59
|
+
Ossature déterministe d'un assistant conversationnel pour les études
|
|
60
|
+
[pommes](https://git.persee.minesparis.psl.eu/energy-alternatives/pommes) (planification de
|
|
61
|
+
systèmes multi-énergies, MINES Paris – PSL / PERSEE). Le LLM n'est qu'une interface
|
|
62
|
+
remplaçable ; la valeur est dans les outils typés, utilisables sans lui.
|
|
63
|
+
|
|
64
|
+
Vous décrivez votre étude en français ; l'assistant remplit le fichier d'étude (le
|
|
65
|
+
*StudySpec*), pose les questions qui manquent, esquisse le modèle, estime son coût de calcul, le
|
|
66
|
+
résout et lit les résultats. **Aucun nombre n'est inventé** : toute valeur vient de vos mots
|
|
67
|
+
(cités), d'un jeu de données du catalogue, d'un document cité ou d'un défaut de pommes marqué
|
|
68
|
+
comme tel. L'outil refuse toute écriture qui ne cite pas sa source.
|
|
69
|
+
|
|
70
|
+
**Mode d'emploi complet : `docs/guide.md`** (dans le dépôt).
|
|
71
|
+
|
|
72
|
+
## Installation
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install "pommes-assistant[all]" # l'assistant, pommes, pommes_craft, HiGHS, les vues
|
|
76
|
+
pip install pommes-assistant # le cœur seul : lire et valider un StudySpec
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Python 3.12 au moins. Pour un modèle local : [Ollama](https://ollama.com), puis
|
|
80
|
+
`ollama pull gemma4:26b` (≈ 20 Go de mémoire).
|
|
81
|
+
|
|
82
|
+
## Trois façons de s'en servir
|
|
83
|
+
|
|
84
|
+
- **Claude Code** (VS Code, PyCharm, terminal ; abonnement Claude), la voie la plus sûre (29 dialogues
|
|
85
|
+
de test sur 30) :
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
claude mcp add pommes-assistant -- pommes-assistant mcp # dans votre projet
|
|
89
|
+
pommes-assistant skill --install . # la marche de l'assistant
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- **Python ou un notebook**, avec un modèle ouvert servi par Ollama (gemma4:26b : 28 à 30 sur 30) :
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from pommes_assistant import Assistant
|
|
96
|
+
|
|
97
|
+
assistant = Assistant()
|
|
98
|
+
assistant("Mon étude est le fichier mon_etude.study.yaml. Dis-moi ce qui manque.")
|
|
99
|
+
assistant("esquisse-la")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- **Le terminal** : `pommes-assistant chat --spec mon_etude.study.yaml`.
|
|
103
|
+
|
|
104
|
+
Au banc, aucun modèle n'a jamais écrit de nombre inventé : c'est l'ossature qui le refuse, pas le
|
|
105
|
+
modèle qui s'en abstient.
|
|
106
|
+
|
|
107
|
+
## Ce que fait l'ossature
|
|
108
|
+
|
|
109
|
+
- le **StudySpec** porte une source par valeur numérique ; le **validateur** rend des écarts typés,
|
|
110
|
+
et les questions de l'assistant viennent de lui ;
|
|
111
|
+
- le **catalogue** de jeux certifiés (SL-TYNDP 2024 et PECD) et l'**audit** d'ordres de grandeur ;
|
|
112
|
+
- le **compilateur** produit une étude pommes ordinaire (le cas TYNDP 8 nœuds à l'identique) ;
|
|
113
|
+
- l'**esquisse** (une heure ou quelques semaines représentatives, en secondes), l'**estimation par
|
|
114
|
+
règle** (électrolyseur, cavité d'hydrogène, batterie, demande déplaçable, pilotable) et le
|
|
115
|
+
**regret** d'une règle dans son monde, sous le bandeau « pas un résultat » ;
|
|
116
|
+
- l'**estimateur** de taille, de temps et de mémoire ; l'**exécution** locale ou sur un serveur
|
|
117
|
+
(soumettre, suivre, rapatrier, sans shell libre) ; le modèle de langage servi par un job GPU
|
|
118
|
+
Slurm (`run llm`) ;
|
|
119
|
+
- les **vues** (neuf, en HTML ou PDF) et le **rapport** Quarto ;
|
|
120
|
+
- le **serveur MCP**, une fonction par action ; le **client** local (Ollama ou compatible OpenAI) ;
|
|
121
|
+
le **banc** de 30 dialogues, qui mesure les inventions et les questions superflues.
|
|
122
|
+
|
|
123
|
+
## Développement
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
git clone https://git.persee.minesparis.psl.eu/energy-alternatives/pommes-assistant.git
|
|
127
|
+
pip install -e ".[all,dev]"
|
|
128
|
+
pytest -q # sans données ni LLM ; les tests « data » lisent la racine DATA
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Commandes
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
pommes-assistant validate mon_etude.study.yaml # écarts typés (--json)
|
|
135
|
+
pommes-assistant questions mon_etude.study.yaml # les questions du prochain tour
|
|
136
|
+
pommes-assistant spec set-user mon_etude.study.yaml "components[Carbon/carbon].tax" 100 \
|
|
137
|
+
--who "Camille Martin" --quote "une taxe carbone de 100 €/t"
|
|
138
|
+
pommes-assistant sketch mon_etude.study.yaml # esquisse L0 (ou --level L1)
|
|
139
|
+
pommes-assistant guess mon_etude.study.yaml # estimation par règle, sans résolution
|
|
140
|
+
pommes-assistant regret mon_etude.study.yaml # regret d'une règle dans son monde
|
|
141
|
+
pommes-assistant estimate mon_etude.study.yaml # taille, temps et RAM
|
|
142
|
+
pommes-assistant build mon_etude.study.yaml -o etude # une étude pommes ordinaire
|
|
143
|
+
pommes-assistant run local etude --dry-run # ou run submit etude --host …
|
|
144
|
+
pommes-assistant results etude/output/<run> --checks --view all --format html,pdf
|
|
145
|
+
pommes-assistant import-craft modele_sauve -o mon_etude.study.yaml
|
|
146
|
+
pommes-assistant chat --spec mon_etude.study.yaml # l'assistant au terminal (Ollama)
|
|
147
|
+
pommes-assistant mcp # le serveur MCP sur stdio
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Documentation dans le dépôt : `docs/guide.md` (mode d'emploi), `docs/studyspec.md` (format),
|
|
151
|
+
`docs/compile.md`, `docs/sketch.md`, `docs/run.md`, `docs/views.md`, `docs/mcp.md`,
|
|
152
|
+
`docs/client.md`, `docs/bench.md` ; exemples dans `examples/` (TYNDP 8 nœuds,
|
|
153
|
+
heat_electrification, hub H2 de Fos, dorsale H2, notebook `assistant.ipynb`).
|
|
154
|
+
|
|
155
|
+
## Licence
|
|
156
|
+
|
|
157
|
+
MIT — Robin Girard, MINES Paris – PSL.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# pommes-assistant
|
|
2
|
+
|
|
3
|
+
Ossature déterministe d'un assistant conversationnel pour les études
|
|
4
|
+
[pommes](https://git.persee.minesparis.psl.eu/energy-alternatives/pommes) (planification de
|
|
5
|
+
systèmes multi-énergies, MINES Paris – PSL / PERSEE). Le LLM n'est qu'une interface
|
|
6
|
+
remplaçable ; la valeur est dans les outils typés, utilisables sans lui.
|
|
7
|
+
|
|
8
|
+
Vous décrivez votre étude en français ; l'assistant remplit le fichier d'étude (le
|
|
9
|
+
*StudySpec*), pose les questions qui manquent, esquisse le modèle, estime son coût de calcul, le
|
|
10
|
+
résout et lit les résultats. **Aucun nombre n'est inventé** : toute valeur vient de vos mots
|
|
11
|
+
(cités), d'un jeu de données du catalogue, d'un document cité ou d'un défaut de pommes marqué
|
|
12
|
+
comme tel. L'outil refuse toute écriture qui ne cite pas sa source.
|
|
13
|
+
|
|
14
|
+
**Mode d'emploi complet : `docs/guide.md`** (dans le dépôt).
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install "pommes-assistant[all]" # l'assistant, pommes, pommes_craft, HiGHS, les vues
|
|
20
|
+
pip install pommes-assistant # le cœur seul : lire et valider un StudySpec
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Python 3.12 au moins. Pour un modèle local : [Ollama](https://ollama.com), puis
|
|
24
|
+
`ollama pull gemma4:26b` (≈ 20 Go de mémoire).
|
|
25
|
+
|
|
26
|
+
## Trois façons de s'en servir
|
|
27
|
+
|
|
28
|
+
- **Claude Code** (VS Code, PyCharm, terminal ; abonnement Claude), la voie la plus sûre (29 dialogues
|
|
29
|
+
de test sur 30) :
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
claude mcp add pommes-assistant -- pommes-assistant mcp # dans votre projet
|
|
33
|
+
pommes-assistant skill --install . # la marche de l'assistant
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- **Python ou un notebook**, avec un modèle ouvert servi par Ollama (gemma4:26b : 28 à 30 sur 30) :
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from pommes_assistant import Assistant
|
|
40
|
+
|
|
41
|
+
assistant = Assistant()
|
|
42
|
+
assistant("Mon étude est le fichier mon_etude.study.yaml. Dis-moi ce qui manque.")
|
|
43
|
+
assistant("esquisse-la")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- **Le terminal** : `pommes-assistant chat --spec mon_etude.study.yaml`.
|
|
47
|
+
|
|
48
|
+
Au banc, aucun modèle n'a jamais écrit de nombre inventé : c'est l'ossature qui le refuse, pas le
|
|
49
|
+
modèle qui s'en abstient.
|
|
50
|
+
|
|
51
|
+
## Ce que fait l'ossature
|
|
52
|
+
|
|
53
|
+
- le **StudySpec** porte une source par valeur numérique ; le **validateur** rend des écarts typés,
|
|
54
|
+
et les questions de l'assistant viennent de lui ;
|
|
55
|
+
- le **catalogue** de jeux certifiés (SL-TYNDP 2024 et PECD) et l'**audit** d'ordres de grandeur ;
|
|
56
|
+
- le **compilateur** produit une étude pommes ordinaire (le cas TYNDP 8 nœuds à l'identique) ;
|
|
57
|
+
- l'**esquisse** (une heure ou quelques semaines représentatives, en secondes), l'**estimation par
|
|
58
|
+
règle** (électrolyseur, cavité d'hydrogène, batterie, demande déplaçable, pilotable) et le
|
|
59
|
+
**regret** d'une règle dans son monde, sous le bandeau « pas un résultat » ;
|
|
60
|
+
- l'**estimateur** de taille, de temps et de mémoire ; l'**exécution** locale ou sur un serveur
|
|
61
|
+
(soumettre, suivre, rapatrier, sans shell libre) ; le modèle de langage servi par un job GPU
|
|
62
|
+
Slurm (`run llm`) ;
|
|
63
|
+
- les **vues** (neuf, en HTML ou PDF) et le **rapport** Quarto ;
|
|
64
|
+
- le **serveur MCP**, une fonction par action ; le **client** local (Ollama ou compatible OpenAI) ;
|
|
65
|
+
le **banc** de 30 dialogues, qui mesure les inventions et les questions superflues.
|
|
66
|
+
|
|
67
|
+
## Développement
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
git clone https://git.persee.minesparis.psl.eu/energy-alternatives/pommes-assistant.git
|
|
71
|
+
pip install -e ".[all,dev]"
|
|
72
|
+
pytest -q # sans données ni LLM ; les tests « data » lisent la racine DATA
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Commandes
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pommes-assistant validate mon_etude.study.yaml # écarts typés (--json)
|
|
79
|
+
pommes-assistant questions mon_etude.study.yaml # les questions du prochain tour
|
|
80
|
+
pommes-assistant spec set-user mon_etude.study.yaml "components[Carbon/carbon].tax" 100 \
|
|
81
|
+
--who "Camille Martin" --quote "une taxe carbone de 100 €/t"
|
|
82
|
+
pommes-assistant sketch mon_etude.study.yaml # esquisse L0 (ou --level L1)
|
|
83
|
+
pommes-assistant guess mon_etude.study.yaml # estimation par règle, sans résolution
|
|
84
|
+
pommes-assistant regret mon_etude.study.yaml # regret d'une règle dans son monde
|
|
85
|
+
pommes-assistant estimate mon_etude.study.yaml # taille, temps et RAM
|
|
86
|
+
pommes-assistant build mon_etude.study.yaml -o etude # une étude pommes ordinaire
|
|
87
|
+
pommes-assistant run local etude --dry-run # ou run submit etude --host …
|
|
88
|
+
pommes-assistant results etude/output/<run> --checks --view all --format html,pdf
|
|
89
|
+
pommes-assistant import-craft modele_sauve -o mon_etude.study.yaml
|
|
90
|
+
pommes-assistant chat --spec mon_etude.study.yaml # l'assistant au terminal (Ollama)
|
|
91
|
+
pommes-assistant mcp # le serveur MCP sur stdio
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Documentation dans le dépôt : `docs/guide.md` (mode d'emploi), `docs/studyspec.md` (format),
|
|
95
|
+
`docs/compile.md`, `docs/sketch.md`, `docs/run.md`, `docs/views.md`, `docs/mcp.md`,
|
|
96
|
+
`docs/client.md`, `docs/bench.md` ; exemples dans `examples/` (TYNDP 8 nœuds,
|
|
97
|
+
heat_electrification, hub H2 de Fos, dorsale H2, notebook `assistant.ipynb`).
|
|
98
|
+
|
|
99
|
+
## Licence
|
|
100
|
+
|
|
101
|
+
MIT — Robin Girard, MINES Paris – PSL.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
# setuptools>=77: the [project] table uses the PEP 639 licence form (SPDX string +
|
|
3
|
+
# license-files), which older setuptools rejects.
|
|
4
|
+
requires = ["setuptools>=77", "wheel"]
|
|
5
|
+
build-backend = "setuptools.build_meta"
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "pommes-assistant"
|
|
9
|
+
# Single source of truth: `pommes_assistant.__version__`, read by setuptools without import.
|
|
10
|
+
dynamic = ["version"]
|
|
11
|
+
description = "Deterministic backbone of a conversational assistant for pommes energy-system studies: StudySpec with provenance, validator, catalogue, tools"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.12"
|
|
14
|
+
license = "MIT"
|
|
15
|
+
license-files = ["LICENSE"]
|
|
16
|
+
authors = [{ name = "Robin Girard", email = "robin.girard@minesparis.psl.eu" }]
|
|
17
|
+
keywords = ["energy", "energy system modelling", "pommes", "provenance", "LLM tools", "MCP"]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 3 - Alpha",
|
|
20
|
+
"Intended Audience :: Science/Research",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Scientific/Engineering",
|
|
24
|
+
"Natural Language :: French",
|
|
25
|
+
]
|
|
26
|
+
# The core (spec + validate) must stay light and import neither pommes nor linopy:
|
|
27
|
+
# the CLI answers in well under 2 s. pommes and pommes_craft are optional extras.
|
|
28
|
+
dependencies = [
|
|
29
|
+
"pydantic>=2.7,<3",
|
|
30
|
+
"pyyaml>=6,<7",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
# Regenerate the bundled parameter schema from an installed pommes.
|
|
35
|
+
# Snapshots are bundled for 0.5.0 and 0.6.0 ; a StudySpec is validated against its own.
|
|
36
|
+
pommes = ["pommes>=0.5,<0.7"]
|
|
37
|
+
# Import a pommes_craft model.yaml. pommes_craft 0.3.7 pins linopy<0.8, which conflicts with
|
|
38
|
+
# pommes>=0.5 (install it with `pip install --no-deps pommes_craft`); 0.3.8 lifts the pin
|
|
39
|
+
# (pommes_craft!11, 2026-10-05).
|
|
40
|
+
craft = ["pommes_craft>=0.5.1", "polars>=0.20"]
|
|
41
|
+
# Read data: `validate --tables`, catalogue loaders, audit of a pommes Dataset.
|
|
42
|
+
data = ["polars>=0.20", "xarray>=2024.1", "h5netcdf>=1.3", "numpy>=1.26"]
|
|
43
|
+
# The MCP server (``pommes-assistant mcp``); the tools themselves need no mcp.
|
|
44
|
+
mcp = ["mcp>=2.2,<3"]
|
|
45
|
+
# The views (S7): HTML with plotly, PDF/SVG/PNG with matplotlib; zstd result files need
|
|
46
|
+
# hdf5plugin.
|
|
47
|
+
views = ["plotly>=6", "matplotlib>=3.8", "xarray>=2024.1", "h5netcdf>=1.3", "numpy>=1.26",
|
|
48
|
+
"hdf5plugin>=4"]
|
|
49
|
+
# Everything a user needs : the assistant (MCP tools in process, Ollama or an OpenAI-compatible
|
|
50
|
+
# server), compilation, sketch, guess, regret, runs, views.
|
|
51
|
+
all = ["pommes>=0.6,<0.7", "pommes_craft>=0.5.1", "linopy>=0.8", "highspy>=1.7", "polars>=0.20",
|
|
52
|
+
"xarray>=2024.1", "h5netcdf>=1.3", "numpy>=1.26", "mcp>=2.2,<3", "plotly>=6",
|
|
53
|
+
"matplotlib>=3.8", "hdf5plugin>=4"]
|
|
54
|
+
dev = ["pytest>=8", "anyio>=4"]
|
|
55
|
+
|
|
56
|
+
[project.scripts]
|
|
57
|
+
pommes-assistant = "pommes_assistant.cli:main"
|
|
58
|
+
|
|
59
|
+
[project.urls]
|
|
60
|
+
Repository = "https://git.persee.minesparis.psl.eu/energy-alternatives/pommes-assistant"
|
|
61
|
+
|
|
62
|
+
[tool.setuptools.dynamic]
|
|
63
|
+
version = { attr = "pommes_assistant.__version__" }
|
|
64
|
+
|
|
65
|
+
[tool.setuptools.packages.find]
|
|
66
|
+
where = ["src"]
|
|
67
|
+
|
|
68
|
+
[tool.setuptools.package-data]
|
|
69
|
+
"pommes_assistant.spec" = ["schema_data/*.json", "schema_data/*.yaml"]
|
|
70
|
+
"pommes_assistant.catalogue" = ["cards/*.yaml"]
|
|
71
|
+
"pommes_assistant.audit" = ["thresholds.yaml"]
|
|
72
|
+
"pommes_assistant.sketch" = ["thresholds.yaml"]
|
|
73
|
+
"pommes_assistant.run" = ["llm_data/*"]
|
|
74
|
+
"pommes_assistant.mcp" = ["skill/SKILL.md"]
|
|
75
|
+
"pommes_assistant.guess" = ["settings.yaml", "validation.yaml"]
|
|
76
|
+
"pommes_assistant.views" = ["*.yaml"]
|
|
77
|
+
|
|
78
|
+
[tool.pytest.ini_options]
|
|
79
|
+
testpaths = ["tests"]
|
|
80
|
+
addopts = "-ra"
|
|
81
|
+
markers = [
|
|
82
|
+
"data: reads the real datasets under ${DATA} (skipped when the root is not configured)",
|
|
83
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""pommes-assistant: deterministic backbone for describing, building, running and
|
|
2
|
+
exploiting pommes energy-system studies, with the provenance of every number."""
|
|
3
|
+
|
|
4
|
+
__version__ = "0.1.0"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def __getattr__(name: str):
|
|
8
|
+
"""``from pommes_assistant import Assistant`` without loading the client at import time."""
|
|
9
|
+
if name in ("Assistant", "Answer"):
|
|
10
|
+
from pommes_assistant import assistant
|
|
11
|
+
|
|
12
|
+
return getattr(assistant, name)
|
|
13
|
+
raise AttributeError(f"module 'pommes_assistant' has no attribute {name!r}")
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""The assistant from Python : a notebook, a script, an IDE console (S35).
|
|
2
|
+
|
|
3
|
+
.. code-block:: python
|
|
4
|
+
|
|
5
|
+
from pommes_assistant import Assistant
|
|
6
|
+
|
|
7
|
+
assistant = Assistant(host="slurm") # gemma4:26b served from a GPU job (run llm)
|
|
8
|
+
assistant("Mon étude est le fichier etude.study.yaml. Esquisse-la.")
|
|
9
|
+
assistant("combien de temps prendrait le modèle complet ?")
|
|
10
|
+
assistant.close() # the job it submitted is cancelled
|
|
11
|
+
|
|
12
|
+
The same tool loop as ``pommes-assistant chat`` and the bench (:class:`~pommes_assistant.
|
|
13
|
+
client.Agent`), with the same policy and the same guards : no number without a source, the
|
|
14
|
+
questions from the validator. A local model by default (Ollama on this machine), or one served
|
|
15
|
+
from a host of the profile (``host=``, :class:`~pommes_assistant.run.llm.Serving` : the job is
|
|
16
|
+
submitted on the first call, held, and cancelled by :meth:`Assistant.close`, by a ``with``
|
|
17
|
+
block, or at the end of the process), or any OpenAI-compatible server (``backend="openai"``,
|
|
18
|
+
``base_url=``). Claude itself is reached through Claude Code (VS Code, PyCharm : the MCP
|
|
19
|
+
server of ``.mcp.json``), not from here : no API key (decision of 2026-09-27).
|
|
20
|
+
|
|
21
|
+
Each call returns an :class:`Answer`, which a notebook shows as Markdown : the answer, then the
|
|
22
|
+
tools called (refused ones marked), the time and, if any, the wait for an unreachable server.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import atexit
|
|
28
|
+
import html
|
|
29
|
+
import json
|
|
30
|
+
from dataclasses import dataclass, field
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
#: as each model was measured (``cli.DEFAULT_THINK``) : the 26B thinks, the 12B does not
|
|
35
|
+
DEFAULT_THINK = {"gemma4:26b": True}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class Answer:
|
|
40
|
+
"""One answer of the assistant."""
|
|
41
|
+
|
|
42
|
+
text: str
|
|
43
|
+
tool_uses: list[dict[str, Any]] = field(default_factory=list)
|
|
44
|
+
error: str | None = None
|
|
45
|
+
duration_s: float = 0.0
|
|
46
|
+
waited_s: float = 0.0
|
|
47
|
+
|
|
48
|
+
def __str__(self) -> str:
|
|
49
|
+
return self.text + (f"\n\n[erreur : {self.error}]" if self.error else "")
|
|
50
|
+
|
|
51
|
+
def _repr_markdown_(self) -> str:
|
|
52
|
+
parts = [self.text or "*(pas de réponse)*"]
|
|
53
|
+
if self.error:
|
|
54
|
+
parts.append(f"> **Erreur** : {self.error}")
|
|
55
|
+
if self.tool_uses:
|
|
56
|
+
rows = []
|
|
57
|
+
for u in self.tool_uses:
|
|
58
|
+
args = {k: v for k, v in (u.get("args") or {}).items() if k != "spec_file"} \
|
|
59
|
+
if isinstance(u.get("args"), dict) else u.get("args")
|
|
60
|
+
shown = html.escape(json.dumps(args, ensure_ascii=False, default=str))
|
|
61
|
+
if len(shown) > 160:
|
|
62
|
+
shown = shown[:157] + "…"
|
|
63
|
+
rows.append(f"<li><code>{u['tool']}</code>{'' if u.get('ok') else ' — refusé'}"
|
|
64
|
+
f" <small>{shown}</small></li>")
|
|
65
|
+
parts.append(f"<details><summary>{len(self.tool_uses)} appel(s) d'outil</summary>"
|
|
66
|
+
f"<ul>{''.join(rows)}</ul></details>")
|
|
67
|
+
tail = f"{self.duration_s:.0f} s"
|
|
68
|
+
if self.waited_s:
|
|
69
|
+
tail += f", dont {self.waited_s:.0f} s d'attente du serveur"
|
|
70
|
+
parts.append(f"<small>{tail}</small>")
|
|
71
|
+
return "\n\n".join(parts)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class Assistant:
|
|
75
|
+
"""A conversation with the assistant (see the module's docstring).
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
model: the model's name on the server (``gemma4:26b`` by default, the measured one).
|
|
79
|
+
host: a host of the profile that serves the model from a GPU job (``run llm``) ; the
|
|
80
|
+
job is submitted on the first call if none runs, held, and cancelled on close.
|
|
81
|
+
base_url: the server's address (default : Ollama on this machine, or the tunnel of
|
|
82
|
+
``host``).
|
|
83
|
+
backend: ``ollama`` (native API : context length and thinking per request) or
|
|
84
|
+
``openai`` (any OpenAI-compatible server).
|
|
85
|
+
think: thinking on or off ; default : as the model was measured.
|
|
86
|
+
workdir: the folder relative paths are read from (default : the current one).
|
|
87
|
+
wait_minutes: how long a call waits for an unreachable server (VPN, a job starting).
|
|
88
|
+
verbose: print each tool call as it happens.
|
|
89
|
+
chat: a chat model already made (tests) ; ``model``, ``backend`` and ``base_url`` are
|
|
90
|
+
then not used.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def __init__(self, model: str = "gemma4:26b", *, host: str | None = None,
|
|
94
|
+
base_url: str | None = None, backend: str = "ollama",
|
|
95
|
+
think: bool | str | None = None, workdir: str | Path | None = None,
|
|
96
|
+
wait_minutes: float = 10.0, max_tokens: int | None = 8192,
|
|
97
|
+
num_ctx: int = 65536, job_time: str | None = None, verbose: bool = False,
|
|
98
|
+
chat: Any = None) -> None:
|
|
99
|
+
self.model, self.host, self.base_url, self.backend = model, host, base_url, backend
|
|
100
|
+
self.think = DEFAULT_THINK.get(model, False) if think is None else think
|
|
101
|
+
self.workdir = Path(workdir or Path.cwd()).resolve()
|
|
102
|
+
self.wait_minutes, self.max_tokens, self.num_ctx = wait_minutes, max_tokens, num_ctx
|
|
103
|
+
self.job_time, self.verbose = job_time, verbose
|
|
104
|
+
self.answers: list[Answer] = []
|
|
105
|
+
self._chat, self._agent, self._serving = chat, None, None
|
|
106
|
+
self._closed = False
|
|
107
|
+
atexit.register(self.close)
|
|
108
|
+
|
|
109
|
+
# -------------------------------------------------------------------- lifecycle
|
|
110
|
+
def _serve(self) -> str | None:
|
|
111
|
+
"""The base URL of the host's tunnel, the job submitted and held if needed."""
|
|
112
|
+
if self.host is None:
|
|
113
|
+
return None
|
|
114
|
+
if self._serving is None:
|
|
115
|
+
from pommes_assistant.run.llm import Serving
|
|
116
|
+
from pommes_assistant.run.profile import host as host_of
|
|
117
|
+
|
|
118
|
+
serving = Serving(host_of(self.host), time_limit=self.job_time,
|
|
119
|
+
log=lambda m: print(f"[run llm] {m}", flush=True))
|
|
120
|
+
serving.__enter__()
|
|
121
|
+
self._serving = serving
|
|
122
|
+
return self._serving.base_url
|
|
123
|
+
|
|
124
|
+
def _make_agent(self) -> Any:
|
|
125
|
+
from pommes_assistant.client import Agent, ToolBox, chat_model
|
|
126
|
+
|
|
127
|
+
chat = self._chat
|
|
128
|
+
if chat is None:
|
|
129
|
+
url = self.base_url or self._serve()
|
|
130
|
+
options: dict[str, Any] = {"max_tokens": self.max_tokens,
|
|
131
|
+
"wait_s": 60.0 * self.wait_minutes,
|
|
132
|
+
"on_wait": lambda m: print(f"[serveur] {m}", flush=True)}
|
|
133
|
+
if self.backend == "ollama":
|
|
134
|
+
options.update(num_ctx=self.num_ctx, think=self.think)
|
|
135
|
+
chat = chat_model(self.backend, self.model, url, **options)
|
|
136
|
+
elif self.host is not None:
|
|
137
|
+
self._serve()
|
|
138
|
+
|
|
139
|
+
def show(kind: str, data: dict[str, Any]) -> None:
|
|
140
|
+
if kind == "tool":
|
|
141
|
+
state = "ok" if data["ok"] else "refusé"
|
|
142
|
+
print(f" · {data['name']} → {state}", flush=True)
|
|
143
|
+
|
|
144
|
+
return Agent(chat, ToolBox(self.workdir), on_event=show if self.verbose else None)
|
|
145
|
+
|
|
146
|
+
def close(self) -> None:
|
|
147
|
+
"""End the conversation ; the job this assistant submitted is cancelled."""
|
|
148
|
+
if self._closed:
|
|
149
|
+
return
|
|
150
|
+
self._closed = True
|
|
151
|
+
if self._serving is not None:
|
|
152
|
+
self._serving.__exit__(None, None, None)
|
|
153
|
+
self._serving = None
|
|
154
|
+
|
|
155
|
+
def __enter__(self) -> Assistant:
|
|
156
|
+
return self
|
|
157
|
+
|
|
158
|
+
def __exit__(self, *exc: Any) -> None:
|
|
159
|
+
self.close()
|
|
160
|
+
|
|
161
|
+
# -------------------------------------------------------------------- conversation
|
|
162
|
+
def __call__(self, message: str) -> Answer:
|
|
163
|
+
"""Send one message ; the answer comes back when the model has finished its turn."""
|
|
164
|
+
if self._closed:
|
|
165
|
+
raise RuntimeError("cet assistant est fermé : en créer un nouveau")
|
|
166
|
+
if self._agent is None:
|
|
167
|
+
self._agent = self._make_agent()
|
|
168
|
+
turn = self._agent.send(message)
|
|
169
|
+
answer = Answer(turn.text, list(turn.tool_uses), turn.error, turn.duration_s,
|
|
170
|
+
getattr(turn, "waited_s", 0.0))
|
|
171
|
+
self.answers.append(answer)
|
|
172
|
+
return answer
|
|
173
|
+
|
|
174
|
+
ask = __call__
|
|
175
|
+
|
|
176
|
+
def __repr__(self) -> str:
|
|
177
|
+
where = self.host or self.base_url or "Ollama local"
|
|
178
|
+
return (f"Assistant({self.model}, {where}, {len(self.answers)} réponse(s)"
|
|
179
|
+
+ (", fermé" if self._closed else "") + ")")
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"""Audit of the inputs (F10): indicators on an assembled pommes Dataset, compared to sourced
|
|
2
|
+
references with sourced thresholds. Never a correction: error / warning / info lines only.
|
|
3
|
+
|
|
4
|
+
Needs the ``data`` extra (xarray; polars for references read from the catalogue).
|
|
5
|
+
"""
|