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.
- frondori_sdk-0.4.0/LICENSE +21 -0
- frondori_sdk-0.4.0/PKG-INFO +271 -0
- frondori_sdk-0.4.0/README.md +237 -0
- frondori_sdk-0.4.0/frondori/__init__.py +29 -0
- frondori_sdk-0.4.0/frondori/client.py +330 -0
- frondori_sdk-0.4.0/frondori/errors.py +20 -0
- frondori_sdk-0.4.0/frondori/local.py +133 -0
- frondori_sdk-0.4.0/frondori/messages.py +172 -0
- frondori_sdk-0.4.0/frondori/spaces.py +63 -0
- frondori_sdk-0.4.0/frondori_sdk.egg-info/PKG-INFO +271 -0
- frondori_sdk-0.4.0/frondori_sdk.egg-info/SOURCES.txt +18 -0
- frondori_sdk-0.4.0/frondori_sdk.egg-info/dependency_links.txt +1 -0
- frondori_sdk-0.4.0/frondori_sdk.egg-info/requires.txt +11 -0
- frondori_sdk-0.4.0/frondori_sdk.egg-info/top_level.txt +1 -0
- frondori_sdk-0.4.0/pyproject.toml +46 -0
- frondori_sdk-0.4.0/setup.cfg +4 -0
- frondori_sdk-0.4.0/tests/test_client.py +169 -0
- frondori_sdk-0.4.0/tests/test_local.py +122 -0
- frondori_sdk-0.4.0/tests/test_messages.py +119 -0
- frondori_sdk-0.4.0/tests/test_spaces.py +66 -0
|
@@ -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
|
+
]
|