frondori-sdk 0.4.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Melvine Nargeot
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,271 @@
1
+ Metadata-Version: 2.4
2
+ Name: frondori-sdk
3
+ Version: 0.4.0
4
+ Summary: Python SDK for the Frondori AI competition platform: play any environment online, or locally in competition conditions.
5
+ Author: Melvine Nargeot
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://frondori.com
8
+ Project-URL: Documentation, https://frondori.com/documentation/sdks
9
+ Project-URL: Source, https://github.com/Melvin-klein/frondori-sdk-python
10
+ Project-URL: Issues, https://github.com/Melvin-klein/frondori-sdk-python/issues
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Intended Audience :: Education
14
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Operating System :: OS Independent
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: websockets<13,>=12
25
+ Requires-Dist: msgpack<2,>=1.0
26
+ Requires-Dist: numpy>=1.24
27
+ Requires-Dist: gymnasium>=1.0
28
+ Provides-Extra: local
29
+ Requires-Dist: frondori-engine>=0.4; extra == "local"
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=7; extra == "dev"
32
+ Requires-Dist: frondori-engine>=0.4; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # frondori-sdk
36
+
37
+ SDK Python pour connecter un modèle à la plateforme de compétition Frondori.
38
+ Il gère la connexion WebSocket, l'authentification et le protocole réseau :
39
+ il ne reste qu'à écrire une fonction `observation -> action`.
40
+
41
+ Le SDK ne connaît aucun jeu en particulier : tu choisis l'environnement à
42
+ jouer (football, cuisine coopérative...) à chaque connexion, et le serveur
43
+ décrit ses observations et ses actions au début de chaque match. Un même
44
+ agent (un même token) peut jouer à plusieurs environnements ; son classement
45
+ est tenu séparément pour chacun.
46
+
47
+ Pour s'entraîner en local, sans serveur, utiliser `frondori-engine` et les
48
+ paquets des environnements voulus (`frondori-kitchen`, `frondori-football`...) :
49
+ ce sont les mêmes environnements, et **les observations reçues en compétition
50
+ ont exactement la même forme qu'en local**. Une politique entraînée en local
51
+ se branche donc telle quelle ici — et le SDK sait aussi jouer un match complet
52
+ en local (`local=True`).
53
+
54
+ ## Installation
55
+
56
+ ```bash
57
+ pip install frondori-sdk
58
+
59
+ # Pour jouer aussi en local : frondori-engine et les environnements voulus
60
+ pip install "frondori-sdk[local]" frondori-kitchen
61
+ ```
62
+
63
+ Dépendances : Python >= 3.10, `websockets`, `msgpack`, `numpy`, `gymnasium`.
64
+
65
+ ## Démarrage rapide
66
+
67
+ ```python
68
+ from frondori import Agent
69
+
70
+ agent = Agent(token="frd_…", environment="kitchen-v0") # sans url : le serveur Frondori
71
+
72
+ def act(observation):
73
+ return my_policy(observation) # une action de agent.action_space
74
+
75
+ result = agent.run(act)
76
+ print(result.own_return, result.returns)
77
+ ```
78
+
79
+ `act` est rappelée une fois par pas avec l'observation la plus récente ;
80
+ tout le reste (handshake, `Ping`, boucle réseau) est géré en interne. Dès le
81
+ début du match, `agent.observation_space` et `agent.action_space` (spaces
82
+ Gymnasium) décrivent ce que reçoit et doit renvoyer `act`. Voir
83
+ [`examples/random_agent.py`](examples/random_agent.py).
84
+
85
+ ### Depuis un notebook Jupyter (ou du code déjà `async`)
86
+
87
+ `Agent.run()` démarre sa propre boucle asyncio, ce qui échoue si une boucle
88
+ tourne déjà. Utiliser `play()` à la place :
89
+
90
+ ```python
91
+ result = await agent.play(act)
92
+ ```
93
+
94
+ ## Jouer en local : évaluer une politique entraînée
95
+
96
+ Trois étapes, trois outils :
97
+
98
+ | Étape | Outil | Pour |
99
+ |---|---|---|
100
+ | 1. Entraîner | `frondori_engine.make("kitchen-v0")` (API PettingZoo) | Apprendre : chaque pas, chaque récompense, tous les agents sous ton contrôle |
101
+ | 2. Évaluer | `Agent(environment="kitchen-v0", local=True)` | Vérifier une politique entraînée en conditions de compétition |
102
+ | 3. Concourir | `Agent(token="frd_…", environment="kitchen-v0")` | Jouer contre les autres participants, entrer au classement |
103
+
104
+ `Agent` ne sert pas à entraîner : `act` ne reçoit que l'observation, jamais
105
+ la récompense, et le résultat n'arrive qu'en fin de match. Pour les étapes 2
106
+ et 3, le code est le même : seuls les paramètres d'`Agent` changent.
107
+
108
+ ```python
109
+ # Sur le serveur Frondori (wss://play.frondori.com/agent par défaut, surchargeable par FRONDORI_URL)
110
+ agent = Agent(token="frd_…", environment="kitchen-v0")
111
+
112
+ # En local, avec l'environnement installé : pas de token
113
+ agent = Agent(environment="kitchen-v0", local=True)
114
+
115
+ result = agent.run(act)
116
+ ```
117
+
118
+ Le match local reproduit les conditions de la compétition : mêmes
119
+ observations (mêmes types), budget de calcul appliqué (au-delà : action
120
+ neutre, `actions_too_slow`), actions invalides remplacées, même
121
+ `MatchResult`. Ton siège est tiré au hasard, comme l'ordre d'arrivée en
122
+ ligne. Pas de réseau, donc jamais d'action manquante, et le match va aussi
123
+ vite que tes politiques.
124
+
125
+ Les autres sièges sont joués par ta propre politique (self-play), ou par
126
+ celles que tu fournis dans `others`, une par autre siège :
127
+
128
+ ```python
129
+ result = Agent(environment="football-v0", local=True, others=[baseline.act], seed=0).run(act)
130
+ ```
131
+
132
+ `seed` rend l'épisode et le tirage du siège reproductibles. En self-play, le
133
+ même appelable joue tous les sièges : si ta politique garde un état, donne
134
+ aux autres sièges leurs propres instances via `others`. En local, `run()`
135
+ fonctionne partout, notebooks compris.
136
+
137
+ ## Un agent écrit comme une classe
138
+
139
+ `act` peut être n'importe quel objet appelable, pas seulement une fonction :
140
+ le SDK se contente d'appeler `act(observation)` à chaque pas. Crée ton
141
+ instance une seule fois, avant le match, et passe sa méthode
142
+ (`agent.run(policy.act)`) — ou définis `__call__` et passe l'instance
143
+ elle-même (`agent.run(policy)`).
144
+
145
+ ```python
146
+ from frondori import Agent
147
+
148
+ class MyPolicy:
149
+ def __init__(self, model):
150
+ self.model = model # chargé une seule fois, hors du match
151
+ self.memory = None # conservé d'un pas à l'autre
152
+
153
+ def act(self, observation):
154
+ action, self.memory = self.model(observation, self.memory)
155
+ return action
156
+
157
+ model = load_model("weights.pt")
158
+ policy = MyPolicy(model)
159
+
160
+ agent = Agent(token="frd_…", environment="kitchen-v0")
161
+ result = agent.run(policy.act) # la même instance joue tout le match
162
+ ```
163
+
164
+ - **Le temps de chargement n'est pas compté.** Seul chaque appel à `act`
165
+ entre dans le budget de calcul : charger le modèle dans `__init__`, avant
166
+ `run()`, ne coûte rien.
167
+ - **Échauffe ton modèle.** Le premier appel peut être bien plus lent que les
168
+ suivants (initialisation paresseuse, compilation, allocation GPU) et
169
+ dépasser le budget (30 ms au football) : cette action serait jouée en
170
+ neutre. Appelle `act` une fois avant, sur une observation locale de même
171
+ forme :
172
+
173
+ ```python
174
+ import frondori_engine
175
+
176
+ env = frondori_engine.make("kitchen-v0")
177
+ observations, _ = env.reset(seed=0)
178
+ policy.act(observations["chef_0"])
179
+ ```
180
+
181
+ - **Réinitialise toi-même l'état propre à un match.** `act` ne reçoit que
182
+ l'observation, sans signal de début de match. Si ton agent garde un état
183
+ pour la durée du match (mémoire récurrente, historique), donne à chaque
184
+ match une nouvelle instance — le modèle, lui, reste partagé :
185
+
186
+ ```python
187
+ for _ in range(10):
188
+ agent = Agent(token="frd_…", environment="kitchen-v0")
189
+ result = agent.run(MyPolicy(model).act)
190
+ print(result.own_return)
191
+ ```
192
+
193
+ ## Résultat d'un match
194
+
195
+ `run()`/`play()` renvoient un `MatchResult` :
196
+
197
+ - `returns` : somme des récompenses de chaque agent du match ;
198
+ `own_return` : la tienne. Gagner, perdre, réussir ensemble... : c'est à
199
+ toi d'interpréter selon l'environnement (compétitif ou coopératif).
200
+ - `agent_name` : l'agent que tu contrôlais (`team_0`, `chef_1`...).
201
+ - `forfeited` : agents dont le participant s'est déconnecté en cours de match.
202
+ - `actions_applied`, `actions_rejected`, `actions_too_slow`,
203
+ `actions_missing` : le sort de tes actions, tel que rapporté par le serveur.
204
+ - `compute_budget_ms` : le temps de calcul accordé par action ;
205
+ `compute_ms` (et `mean_compute_ms`, `max_compute_ms`) : le temps mesuré
206
+ pour chacune de tes actions.
207
+
208
+ ## Temps de calcul : ta latence réseau ne compte pas
209
+
210
+ Les matchs se jouent en pas-à-pas : le serveur attend l'action de chaque
211
+ agent avant d'avancer d'un pas. Être loin du serveur ne te coûte donc rien
212
+ (ça rallonge seulement la durée du match). Ce qui est limité, c'est le temps
213
+ de calcul de ton agent : le SDK le mesure, de la réception de l'observation
214
+ à l'envoi de ton action (décodage, `act`, encodage), et l'envoie avec elle.
215
+ Chaque environnement fixe son budget (`agent.compute_budget_ms`, connu dès le
216
+ début du match) : 30 ms au football, 200 ms en cuisine.
217
+
218
+ Le serveur confronte ce temps déclaré à ses propres mesures (temps de
219
+ réponse, aller-retour réseau) et signale les déclarations incohérentes. Tous
220
+ ces temps sont enregistrés avec le match : ils font partie des données de
221
+ recherche téléchargeables.
222
+
223
+ ## Actions refusées, trop lentes ou manquantes
224
+
225
+ Une action hors de l'`action_space`, calculée en plus que le budget, ou
226
+ jamais arrivée (client bloqué, connexion coupée), ne fait pas perdre le
227
+ match : le serveur joue à la place l'action neutre de l'environnement (ne
228
+ rien faire). Mais tu en es informé : le SDK logue un avertissement à la
229
+ première occurrence de chaque cas (logger `frondori`), et le total apparaît
230
+ dans `MatchResult`.
231
+
232
+ ## Erreurs
233
+
234
+ - `AuthenticationError` : token refusé, ou environnement demandé
235
+ indisponible sur ce serveur.
236
+ - `ConnectionLostError` : connexion perdue de façon inattendue avant la fin
237
+ du match. Une fin de match normale ne lève jamais cette erreur.
238
+ - `ProtocolError` : message qui ne respecte pas le protocole (bug serveur,
239
+ ou version du SDK trop ancienne).
240
+
241
+ Pas de reconnexion : une déconnexion en cours de match est un forfait.
242
+
243
+ ## Développer / tester le SDK
244
+
245
+ ```bash
246
+ python -m venv .venv && source .venv/bin/activate
247
+ pip install -e ../frondori-engine -e ".[dev]" # frondori-engine : pas encore sur PyPI
248
+ python -m pytest
249
+ ```
250
+
251
+ Les tests sont isolés : aucun ne nécessite le serveur réel, ni aucun paquet
252
+ d'environnement (le mode local est testé sur l'environnement d'exemple
253
+ `tests/rps.py`, enregistré à la main). Les vecteurs de
254
+ `tests/test_messages.py` sont des octets réellement produits par le serveur
255
+ Rust (`protocol::encode`) ; s'ils cassent, le protocole a changé côté serveur.
256
+
257
+ ## Publier une version
258
+
259
+ 1. Mettre à jour `version` dans `pyproject.toml` et commiter.
260
+ 2. Pousser un tag du même numéro : `git tag v0.1.0 && git push origin v0.1.0`.
261
+
262
+ La CI (`.github/workflows/ci.yml`) teste, construit et publie sur PyPI ; elle
263
+ refuse un tag qui ne correspond pas à la version. Publication par *Trusted
264
+ Publishing*, sans token : à configurer une fois sur PyPI (projet `frondori-sdk` >
265
+ Publishing > trusted publisher GitHub : ce dépôt, workflow `ci.yml`,
266
+ environnement `pypi`).
267
+
268
+ `frondori-engine` doit être publié AVANT ce paquet (il en dépend, et la CI
269
+ l'installe depuis PyPI).
270
+
271
+ Licence : MIT.
@@ -0,0 +1,237 @@
1
+ # frondori-sdk
2
+
3
+ SDK Python pour connecter un modèle à la plateforme de compétition Frondori.
4
+ Il gère la connexion WebSocket, l'authentification et le protocole réseau :
5
+ il ne reste qu'à écrire une fonction `observation -> action`.
6
+
7
+ Le SDK ne connaît aucun jeu en particulier : tu choisis l'environnement à
8
+ jouer (football, cuisine coopérative...) à chaque connexion, et le serveur
9
+ décrit ses observations et ses actions au début de chaque match. Un même
10
+ agent (un même token) peut jouer à plusieurs environnements ; son classement
11
+ est tenu séparément pour chacun.
12
+
13
+ Pour s'entraîner en local, sans serveur, utiliser `frondori-engine` et les
14
+ paquets des environnements voulus (`frondori-kitchen`, `frondori-football`...) :
15
+ ce sont les mêmes environnements, et **les observations reçues en compétition
16
+ ont exactement la même forme qu'en local**. Une politique entraînée en local
17
+ se branche donc telle quelle ici — et le SDK sait aussi jouer un match complet
18
+ en local (`local=True`).
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ pip install frondori-sdk
24
+
25
+ # Pour jouer aussi en local : frondori-engine et les environnements voulus
26
+ pip install "frondori-sdk[local]" frondori-kitchen
27
+ ```
28
+
29
+ Dépendances : Python >= 3.10, `websockets`, `msgpack`, `numpy`, `gymnasium`.
30
+
31
+ ## Démarrage rapide
32
+
33
+ ```python
34
+ from frondori import Agent
35
+
36
+ agent = Agent(token="frd_…", environment="kitchen-v0") # sans url : le serveur Frondori
37
+
38
+ def act(observation):
39
+ return my_policy(observation) # une action de agent.action_space
40
+
41
+ result = agent.run(act)
42
+ print(result.own_return, result.returns)
43
+ ```
44
+
45
+ `act` est rappelée une fois par pas avec l'observation la plus récente ;
46
+ tout le reste (handshake, `Ping`, boucle réseau) est géré en interne. Dès le
47
+ début du match, `agent.observation_space` et `agent.action_space` (spaces
48
+ Gymnasium) décrivent ce que reçoit et doit renvoyer `act`. Voir
49
+ [`examples/random_agent.py`](examples/random_agent.py).
50
+
51
+ ### Depuis un notebook Jupyter (ou du code déjà `async`)
52
+
53
+ `Agent.run()` démarre sa propre boucle asyncio, ce qui échoue si une boucle
54
+ tourne déjà. Utiliser `play()` à la place :
55
+
56
+ ```python
57
+ result = await agent.play(act)
58
+ ```
59
+
60
+ ## Jouer en local : évaluer une politique entraînée
61
+
62
+ Trois étapes, trois outils :
63
+
64
+ | Étape | Outil | Pour |
65
+ |---|---|---|
66
+ | 1. Entraîner | `frondori_engine.make("kitchen-v0")` (API PettingZoo) | Apprendre : chaque pas, chaque récompense, tous les agents sous ton contrôle |
67
+ | 2. Évaluer | `Agent(environment="kitchen-v0", local=True)` | Vérifier une politique entraînée en conditions de compétition |
68
+ | 3. Concourir | `Agent(token="frd_…", environment="kitchen-v0")` | Jouer contre les autres participants, entrer au classement |
69
+
70
+ `Agent` ne sert pas à entraîner : `act` ne reçoit que l'observation, jamais
71
+ la récompense, et le résultat n'arrive qu'en fin de match. Pour les étapes 2
72
+ et 3, le code est le même : seuls les paramètres d'`Agent` changent.
73
+
74
+ ```python
75
+ # Sur le serveur Frondori (wss://play.frondori.com/agent par défaut, surchargeable par FRONDORI_URL)
76
+ agent = Agent(token="frd_…", environment="kitchen-v0")
77
+
78
+ # En local, avec l'environnement installé : pas de token
79
+ agent = Agent(environment="kitchen-v0", local=True)
80
+
81
+ result = agent.run(act)
82
+ ```
83
+
84
+ Le match local reproduit les conditions de la compétition : mêmes
85
+ observations (mêmes types), budget de calcul appliqué (au-delà : action
86
+ neutre, `actions_too_slow`), actions invalides remplacées, même
87
+ `MatchResult`. Ton siège est tiré au hasard, comme l'ordre d'arrivée en
88
+ ligne. Pas de réseau, donc jamais d'action manquante, et le match va aussi
89
+ vite que tes politiques.
90
+
91
+ Les autres sièges sont joués par ta propre politique (self-play), ou par
92
+ celles que tu fournis dans `others`, une par autre siège :
93
+
94
+ ```python
95
+ result = Agent(environment="football-v0", local=True, others=[baseline.act], seed=0).run(act)
96
+ ```
97
+
98
+ `seed` rend l'épisode et le tirage du siège reproductibles. En self-play, le
99
+ même appelable joue tous les sièges : si ta politique garde un état, donne
100
+ aux autres sièges leurs propres instances via `others`. En local, `run()`
101
+ fonctionne partout, notebooks compris.
102
+
103
+ ## Un agent écrit comme une classe
104
+
105
+ `act` peut être n'importe quel objet appelable, pas seulement une fonction :
106
+ le SDK se contente d'appeler `act(observation)` à chaque pas. Crée ton
107
+ instance une seule fois, avant le match, et passe sa méthode
108
+ (`agent.run(policy.act)`) — ou définis `__call__` et passe l'instance
109
+ elle-même (`agent.run(policy)`).
110
+
111
+ ```python
112
+ from frondori import Agent
113
+
114
+ class MyPolicy:
115
+ def __init__(self, model):
116
+ self.model = model # chargé une seule fois, hors du match
117
+ self.memory = None # conservé d'un pas à l'autre
118
+
119
+ def act(self, observation):
120
+ action, self.memory = self.model(observation, self.memory)
121
+ return action
122
+
123
+ model = load_model("weights.pt")
124
+ policy = MyPolicy(model)
125
+
126
+ agent = Agent(token="frd_…", environment="kitchen-v0")
127
+ result = agent.run(policy.act) # la même instance joue tout le match
128
+ ```
129
+
130
+ - **Le temps de chargement n'est pas compté.** Seul chaque appel à `act`
131
+ entre dans le budget de calcul : charger le modèle dans `__init__`, avant
132
+ `run()`, ne coûte rien.
133
+ - **Échauffe ton modèle.** Le premier appel peut être bien plus lent que les
134
+ suivants (initialisation paresseuse, compilation, allocation GPU) et
135
+ dépasser le budget (30 ms au football) : cette action serait jouée en
136
+ neutre. Appelle `act` une fois avant, sur une observation locale de même
137
+ forme :
138
+
139
+ ```python
140
+ import frondori_engine
141
+
142
+ env = frondori_engine.make("kitchen-v0")
143
+ observations, _ = env.reset(seed=0)
144
+ policy.act(observations["chef_0"])
145
+ ```
146
+
147
+ - **Réinitialise toi-même l'état propre à un match.** `act` ne reçoit que
148
+ l'observation, sans signal de début de match. Si ton agent garde un état
149
+ pour la durée du match (mémoire récurrente, historique), donne à chaque
150
+ match une nouvelle instance — le modèle, lui, reste partagé :
151
+
152
+ ```python
153
+ for _ in range(10):
154
+ agent = Agent(token="frd_…", environment="kitchen-v0")
155
+ result = agent.run(MyPolicy(model).act)
156
+ print(result.own_return)
157
+ ```
158
+
159
+ ## Résultat d'un match
160
+
161
+ `run()`/`play()` renvoient un `MatchResult` :
162
+
163
+ - `returns` : somme des récompenses de chaque agent du match ;
164
+ `own_return` : la tienne. Gagner, perdre, réussir ensemble... : c'est à
165
+ toi d'interpréter selon l'environnement (compétitif ou coopératif).
166
+ - `agent_name` : l'agent que tu contrôlais (`team_0`, `chef_1`...).
167
+ - `forfeited` : agents dont le participant s'est déconnecté en cours de match.
168
+ - `actions_applied`, `actions_rejected`, `actions_too_slow`,
169
+ `actions_missing` : le sort de tes actions, tel que rapporté par le serveur.
170
+ - `compute_budget_ms` : le temps de calcul accordé par action ;
171
+ `compute_ms` (et `mean_compute_ms`, `max_compute_ms`) : le temps mesuré
172
+ pour chacune de tes actions.
173
+
174
+ ## Temps de calcul : ta latence réseau ne compte pas
175
+
176
+ Les matchs se jouent en pas-à-pas : le serveur attend l'action de chaque
177
+ agent avant d'avancer d'un pas. Être loin du serveur ne te coûte donc rien
178
+ (ça rallonge seulement la durée du match). Ce qui est limité, c'est le temps
179
+ de calcul de ton agent : le SDK le mesure, de la réception de l'observation
180
+ à l'envoi de ton action (décodage, `act`, encodage), et l'envoie avec elle.
181
+ Chaque environnement fixe son budget (`agent.compute_budget_ms`, connu dès le
182
+ début du match) : 30 ms au football, 200 ms en cuisine.
183
+
184
+ Le serveur confronte ce temps déclaré à ses propres mesures (temps de
185
+ réponse, aller-retour réseau) et signale les déclarations incohérentes. Tous
186
+ ces temps sont enregistrés avec le match : ils font partie des données de
187
+ recherche téléchargeables.
188
+
189
+ ## Actions refusées, trop lentes ou manquantes
190
+
191
+ Une action hors de l'`action_space`, calculée en plus que le budget, ou
192
+ jamais arrivée (client bloqué, connexion coupée), ne fait pas perdre le
193
+ match : le serveur joue à la place l'action neutre de l'environnement (ne
194
+ rien faire). Mais tu en es informé : le SDK logue un avertissement à la
195
+ première occurrence de chaque cas (logger `frondori`), et le total apparaît
196
+ dans `MatchResult`.
197
+
198
+ ## Erreurs
199
+
200
+ - `AuthenticationError` : token refusé, ou environnement demandé
201
+ indisponible sur ce serveur.
202
+ - `ConnectionLostError` : connexion perdue de façon inattendue avant la fin
203
+ du match. Une fin de match normale ne lève jamais cette erreur.
204
+ - `ProtocolError` : message qui ne respecte pas le protocole (bug serveur,
205
+ ou version du SDK trop ancienne).
206
+
207
+ Pas de reconnexion : une déconnexion en cours de match est un forfait.
208
+
209
+ ## Développer / tester le SDK
210
+
211
+ ```bash
212
+ python -m venv .venv && source .venv/bin/activate
213
+ pip install -e ../frondori-engine -e ".[dev]" # frondori-engine : pas encore sur PyPI
214
+ python -m pytest
215
+ ```
216
+
217
+ Les tests sont isolés : aucun ne nécessite le serveur réel, ni aucun paquet
218
+ d'environnement (le mode local est testé sur l'environnement d'exemple
219
+ `tests/rps.py`, enregistré à la main). Les vecteurs de
220
+ `tests/test_messages.py` sont des octets réellement produits par le serveur
221
+ Rust (`protocol::encode`) ; s'ils cassent, le protocole a changé côté serveur.
222
+
223
+ ## Publier une version
224
+
225
+ 1. Mettre à jour `version` dans `pyproject.toml` et commiter.
226
+ 2. Pousser un tag du même numéro : `git tag v0.1.0 && git push origin v0.1.0`.
227
+
228
+ La CI (`.github/workflows/ci.yml`) teste, construit et publie sur PyPI ; elle
229
+ refuse un tag qui ne correspond pas à la version. Publication par *Trusted
230
+ Publishing*, sans token : à configurer une fois sur PyPI (projet `frondori-sdk` >
231
+ Publishing > trusted publisher GitHub : ce dépôt, workflow `ci.yml`,
232
+ environnement `pypi`).
233
+
234
+ `frondori-engine` doit être publié AVANT ce paquet (il en dépend, et la CI
235
+ l'installe depuis PyPI).
236
+
237
+ Licence : MIT.
@@ -0,0 +1,29 @@
1
+ """SDK Python pour la plateforme de compétition d'IA Frondori.
2
+
3
+ Usage minimal :
4
+
5
+ from frondori import Agent
6
+
7
+ def act(observation):
8
+ return policy(observation) # une action de agent.action_space
9
+
10
+ agent = Agent(token="mon-token", environment="kitchen-v0") # wss://play.frondori.com
11
+ result = agent.run(act)
12
+ print(result.own_return)
13
+
14
+ Un même agent peut jouer à n'importe quel environnement du serveur. Les
15
+ observations arrivent sous la même forme qu'avec `frondori-engine` en local.
16
+ """
17
+
18
+ from .client import Agent, MatchResult
19
+ from .errors import AuthenticationError, ConnectionLostError, ProtocolError
20
+ from .messages import ActionStatus
21
+
22
+ __all__ = [
23
+ "ActionStatus",
24
+ "Agent",
25
+ "AuthenticationError",
26
+ "ConnectionLostError",
27
+ "MatchResult",
28
+ "ProtocolError",
29
+ ]