You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -162,20 +162,23 @@ pour vous familiariser avec le modèle de lignes et colonnes de `flet`, **ajoute
162
162
163
163
+++ {"slideshow": {"slide_type": ""}, "tags": []}
164
164
165
-
## v03: avec un peu de classe
165
+
## v03: avec un peu de classe: `ChatbotApp`
166
166
167
167
ceci est une étape **totalement optionnelle**, mais je vous recommande de **créer une classe**, qui pourrait s'appeler **`ChatbotApp`**, pour regrouper la logique de notre application, et éviter de mettre tout notre code en vrac dans le `main`
168
168
169
169
- on pourrait envisager par exemple que `ChatbotApp` hérite de `ft.Column`
170
170
- de cette façon on se retrouverait avec un `main` qui ne fait plus que
171
-
```{literalinclude} chatbot-10.py
172
-
:start-after: def main
171
+
```{literalinclude} chatbot-03a.py
172
+
:start-after: show the code in the instructions
173
173
```
174
-
````{admonition} page.update()
175
-
:class: tip
176
-
on choisit de passer `page` au constructeur de l'objet, car avec *flet* il faut penser à *flush* les changements avec un `page.update()` - sinon les changements que l'on fait en mémoire ne sont pas répercutés dans l'affichage
177
-
et donc il faut qu'on puisse accéder à cette `page` depuis la classe `ChatbotApp` !
178
-
````
174
+
175
+
Je vous propose de procéder en deux temps
176
+
177
+
- étape 3a: on crée la classe `ChatbotApp`;
178
+
le code de `main` se retrouve essentiellement dans le constructeur de `ChatbotApp`
179
+
(et souvenez-vous comment on utilise `super()` pour initialiser la superclasse, ici `Column`
180
+
- étape 3b: la fonction `send_request` devient une méthode de la classe
181
+
(au lieu d'être une fontion incluse dans le constructeur)
179
182
180
183
+++
181
184
@@ -186,75 +189,125 @@ ceci est une étape **totalement optionnelle**, mais je vous recommande de **cr
186
189
:align: right
187
190
```
188
191
189
-
toujours pour éviter de finir avec un gros paquet de spaguettis, on pourrait imaginer à ce stade **écrire une classe `History`** (tout ceci est totalement indicatif...) qui:
192
+
toujours pour éviter de finir avec un gros paquet de spaguettis, on va imaginer à ce stade d'**écrire une classe `History`** ( nouveau <!---->tout ceci est totalement indicatif...) qui:
190
193
191
194
- hérite, là encore de `ft.Column`
192
-
- et est responsable de la partie "dialogue" entre humain et robot
193
-
- et qui du coup crée la zone de prompt,
194
-
- et possède une méthode `current_prompt()` qui renvoie le prompt tapé par l'utilisateur
195
-
- et une méthode `add_message()` pour insérer les questions et les réponses au fur et à mesure
196
-
(à ce stade on ne fait pas encore la différence entre prompt et réponse)
195
+
- c'est elle qui est responsable de **créer la zone de prompt**
196
+
- et d'afficher au fur et à mesure, et au bon endroit, **les échanges avec le robot**
197
+
- de cette façon on pourra l'insérer simplement en bas dans l'objet `ChatbotApp`
198
+
ainsi cet objet - qui rappelons-le est une `Column` - va voir maintenant 3 fils:
197
199
198
-
````{admonition} alternance de questions / réponses
200
+
- le titre
201
+
- la `Row` avec les différents réglages
202
+
- et une instance de `History()`
203
+
204
+
`````{admonition} la logique de la classe History
199
205
:class: tip
206
+
pour fixer les idées, disons qu'à ce stade cette classe possède les méthodes
200
207
201
-
comme la logique du dialogue c'est d'alterner les questions et les réponses, on peut tout à fait considérer que c'est un fait acquis, et du coup admettre que:
202
-
- le dernier élément dans la colonne History est toujours le dernier prompt;
203
-
- et les autres éléments sont alternativement, en commençant du début: prompt, réponse, prompt, réponse, etc...
204
-
````
208
+
- `current_prompt()` qui renvoie le prompt tapé par l'utilisateur
209
+
- `add_message(some_text)` pour insérer les questions et les réponses au fur et à mesure
210
+
211
+
l'idée est que l'objet `History` possède:
212
+
213
+
- en dernier (tout en bas donc) un objet de type `ft.TextField` (qui est éditable); dans lequel on va taper notre prompt
214
+
- et au dessus on va conserver la trace des échanges: question1, réponse1, etc...
215
+
et pour cela on utilisera `add_message(some_text)`, dont le job donc est d'insérer un objet `ft.Text`
216
+
(non modifiable par l'utilisateur cette fois)
217
+
**en avant-dernière position** - c'est-à-dire juste au dessus du prompt
218
+
`````
219
+
220
+
pour être bien clair, à ce stade on ne fait pas encore usage du réseau pour quoi que ce soit, on veut juste mettre en place la structure de l'UI
205
221
206
-
pour être bien clair, à ce stade on ne fait pas encore usage du réseau pour quoi que ce soit
222
+
ici encore je vous conseille de procéder par petites étapes:
223
+
224
+
- 4a: la trame de la classe `History`
225
+
- 4b: faites en sorte que le fait de taper "Entrée" dans la zone de prompt fasse le même effet que le bouton "Send"
207
226
208
227
+++
209
228
210
229
## v05: un peu de réseau
211
230
212
231
c'est seulement maintenant que l'on va effectivement **interagir via le réseau avec les serveurs** ollama
213
-
je vous propose pour commencer de simplement fabriquer la requête, et pour commencer de simplement afficher la réponse sur le terminal
232
+
je vous propose pour commencer de simplement:
233
+
234
+
- fabriquer la requête,
235
+
- et simplement afficher la réponse **dans le terminal**
214
236
215
237
quelques indices:
216
238
217
239
- la librairie qu'on va utiliser pour cela s'appelle `requests`;
218
240
- vous pouvez commencer par regarder ceci pour quelques exemples <https://requests.readthedocs.io/en/latest/user/quickstart/>
219
-
- je vous recommande de vous concentrer pour l'instant sur le serveur CPU, ce qui vous évite pour l'instant de vous embêter avec les authentifications
220
241
- notre objectif ici et de bien comprendre la structure de la réponse
221
242
posez-vous notamment la question de savoir quand est-ce que c'est terminé, et regardez bien la fin de la réponse
222
243
- pour l'instant aussi, on ignore le flag *streaming*: on poste une requête et on attend le retour
223
244
245
+
à nouveau on pourra procéder par étapes:
246
+
247
+
- 5a: en commençant par le serveur CPU uniquement
248
+
- 5b: ajouter l'authentification lorsque c'est nécessaire, de façon à pouvoir utiliser indifféremment les deux serveurs
249
+
224
250
+++
225
251
226
252
````{admonition} un petit exemple
227
253
:class: dropdown tip
228
254
229
-
voici comment on pourrait dire bonjour au modèle `gemma2:2b`
255
+
voici comment on pourrait dire `hey` au modèle `gemma2:2b`
256
+
ce code peut s'exécuter par exemple directement dans ipython
dans cette version, on utilise la réponse du serveur pour *afficher le dialogue **dans notre application*** et non plus dans le terminal
271
324
272
-
pour cela on va devoir faire quelques modifications à la classe `History`; en effet vous devez avoir observé à ce stade que la réponse vient "en petits morceaux", ce qui fait qu'on pourrait avoir envie de modifier un peu la classe `History` de sorte qu'elle expose à présent les méthodes
325
+
pour cela on va devoir faire quelques modifications à la classe `History`;
326
+
en effet vous devez avoir observé à ce stade que la réponse vient "en petits morceaux", ce que l'on n'a pas encore prévu
327
+
328
+
du coup pour aboutir à une version à peu près fonctionnelle il devrait vous suffire de
273
329
274
-
-`add_prompt()` et `add_answer()` pour distinguer entre les deux types d'entrée
275
-
- et surtout `add_chunk()` qui permet d'ajouter *juste un mot* dans la réponse du robot, pour nous ajuster avec le format de la réponse
330
+
-ajouter à la classe `History` une méthode `add_chunk(token)`, qui permet d'ajouter *juste un mot* dans la réponse du robot
331
+
- et au lieu d'afficher la réponse du robot en bloc, de la traiter proprement pour en extraire les différents petits morceaux, puis les afficher dans l'interface grâce donc à `add_chunk()`
276
332
277
-
````{admonition} le scrolling
278
-
:class: tip dropdown
333
+
````{admonition} update()
334
+
:class: tip
335
+
336
+
avec *flet* il faut penser à *flush* les changements avec un `flet_object.update()`
337
+
car sinon les changements que l'on fait en mémoire ne sont pas répercutés dans l'affichage
338
+
(si vous avez le TP sur le snake, c'est la même logique ici avec `flet` que ça l'était avec `pygame`)
339
+
**il faut rafraichir explicitement** la page pour que vos modifications se voient à l'écran
340
+
341
+
pour faire ça `flet` fournit sur tous ses objets une méthode `update()`
342
+
et comme notre `History` hérite de `ft.Column`, vous pouvez simplement lui envoyer la méthode `update()`
343
+
````
344
+
345
+
+++
346
+
347
+
## v07: un peu de cosmétique
348
+
349
+
```{image} media/chatbot-07.png
350
+
:width: 400px
351
+
:align: right
352
+
```
353
+
354
+
ici on va simplement ajouter un peu de relief pour qu'on s'y retrouve entre les questions et les réponses
355
+
356
+
ici aussi on peut imaginer procéder en deux étapes
357
+
358
+
- 7a: juste la cosmétique: montrer de manière plus distinte les 3 groupes (questions, réponses, et prompt)
359
+
- 7b: faire en sorte que la fenêtre *scroll* automatiquement vers le bas, lorsque le dialogue remplit toute la page
360
+
- 7c: faire en sorte qu'on ne puisse pas envoyer plusieurs requêtes en parallèle
361
+
362
+
+++
363
+
364
+
````{admonition} pour 7a: de la couleur
365
+
:class: dropdown tip
366
+
367
+
- mettre un fond de couleur à notre `TextField` (le prompt)
368
+
- enrichir un peu l'interface de `History`, et remplacer l'unique méthode `add_message()`
369
+
par deux méthodes différentes `add_prompt(text)` et `add_answer(text)`
370
+
````
371
+
372
+
+++
373
+
374
+
````{admonition} pour 7b: le scrolling
375
+
:class: dropdown tip
376
+
377
+
j'ai eu un peu du mal avec cette partie; (revenez dessus plus tard si nécessaire), mais il est important que notre chatbot *scroll* correctement:
378
+
c'est-à-dire qu'après plusieurs questions/réponses on voie toujours **le bas du dialogue**
279
379
280
-
peut-être un peu prématuré (revenez dessus plus tard si nécessaire), mais il est important que notre chatbot *scroll* correctement:
281
-
c'est-à-dire qu'après plusisurs questions/réponses on voie toujours le bas du dialogue
282
380
et pour ça sachez qu'il faut procéder comme ceci
283
381
```python
284
382
cl = ft.Column(
@@ -294,19 +392,25 @@ cl = ft.Column(
294
392
295
393
enfin, remarquez qu'on peut avoir envie d'activer le scrolling
296
394
297
-
- sur la `Column` principale (notre `ChatbotApp`), mais dans ce cas les widgets de mode (streaming, server...) vont scroller aussi
395
+
- sur la `Column` principale (notre `ChatbotApp`), mais dans ce cas les widgets de mode (streaming, server...) vont scroller aussi...
298
396
c'est mieux que pas de scroll, mais pas forcément idéal encore
299
397
- sur la `History`, et dans ce cas les widgets de mode vont rester fixes;
300
398
dans ce cas-là toutefois, pensez à mettre tout de même `expand=True` sur la `ChatbotApp` pour que les changements de la taille de l'app se propagent jusqu'à l'`History`
301
-
302
399
````
303
400
304
401
+++
305
402
306
-
## v07: pas de multiples requêtes
403
+
````{admonition} pour 7c: éviter plusieurs Send en parallèle
404
+
:class: dropdown tip
405
+
406
+
le sujet c'est que si le code ne fait rien de particulier, rien n'empêche l'utilisateur de cliquer 3 fois de suite sur le bouton Send, et que ça envoie 3 requêtes essentiellement *en même temps*
307
407
308
-
à ce stade il est utile d'ajouter un peu de logique pour éviter que l'on puisse poster deux requêtes "en même temps": on **rend l'UI inactive** jusqu'à réception de la réponse
309
-
pour cela voyez dans `flet` l'attribut `disabled`
408
+
pour éviter ça, vous faites en sorte de *disable* les deux moyens d'envoyer la requête (le bouton *Send* et la touche *Entrée* dans le prompt)
409
+
410
+
je vous recommande du coup d'ajouter les méthodes `enable_prompt()` et `disable_prompt()` dans la classe `History`
411
+
412
+
et ensuite d'implémenter une logique dans la méthode `send_request()` pour désactiver / réactiver l'interface au bon moment; c'est peut-être d'ailleurs le moment de couper cette méthode en plus petits morceaux..
413
+
````
310
414
311
415
+++
312
416
@@ -338,16 +442,9 @@ dans mon code j'ai conservé les deux modes (streaming et non-streaming) pour po
338
442
339
443
+++
340
444
341
-
## v09: authentification
342
-
343
-
à ce stade il est temps d'ajouter du code pour pouvoir s'**authentifier avec le login/password** auprès du serveur qui en a besoin
344
-
c'est juste une question d'ajouter, dans l'appel à `requests.post`, un paramètre `auth=(user, password)`
0 commit comments