Diffuser les réponses en continu
- Leçon 15
- 45 min
- Module 5 : Conversations et diffusion en continu
Leçon 15 sur 19 · Créer des applications fiables avec les API de LLMDiffuser les réponses en continu
1. Commencer par une seule requête au modèle
2. La rédaction de requêtes, une tâche d'ingénierie
3. Sorties structurées et validation
4. Appeler des outils en toute sécurité
5. Conversations et diffusion en continu
6. Contrôles de fiabilité et de coût
Ce que vous saurez faire
Affichez le brouillon de l’assistant à mesure qu’il arrive. L’agent peut ainsi suivre la progression et l’arrêter. Assurez-vous que seule une réponse complète, vérifiée par les deux barrières, peut devenir des données.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- Les deux barrières et le
Outcome(résultat) deanalyse_ticket(valid: valide,rejected: rejeté,failed: échec), dans Valider le sens. finish_reason: "length"(motif de fin : limite de longueur) et le JSON mal formé, dans Traiter les résultats incomplets.- Comment le simulateur trouve un enregistrement à partir de la requête exacte, dans Un premier essai sans compte.
Ce qu’il vous faut sur votre ordinateur
- Le projet après Gérer l’état de la conversation :
python -m pytestaffiche62 passed, 1 deselected(62 tests réussis, 1 désélectionné). L’environnement virtuel est actif. - L’instantané
end-of-m05du dépôt des exercices, pour copier deux fichiers.
Un numéro de téléphone, chiffre par chiffre
Section intitulée « Un numéro de téléphone, chiffre par chiffre »Un collègue vous dicte un numéro de téléphone sur une mauvaise ligne : « Zéro, deux, zéro, sept… ». Vous notez chaque chiffre à mesure qu’il arrive. C’est utile : vous êtes prêt plus tôt et vous voyez que l’appel se déroule bien. Mais vous ne composez pas le numéro après quatre chiffres. « 0207 » n’est pas un numéro de téléphone court. C’est une partie d’un numéro plus long, que vous ne connaîtrez qu’à la fin.
La réponse d’un modèle peut arriver de la même façon, morceau par morceau. Cette leçon porte sur les deux volets de cette idée : montrer les morceaux tôt, mais attendre la fin avant d’agir.
Le problème : une longue attente sans rien à l’écran
Section intitulée « Le problème : une longue attente sans rien à l’écran »Jusqu’ici, analyse attendait toute la réponse, puis l’affichait. Pour T-64704 (une bouilloire dégageant une odeur de brûlé), la réponse structurée de Termechat-smallLe nom que le code de Larkfield donne à un petit modèle hébergé peu coûteux : gpt-6-luna sur Azure. Le code utilise ce nom, donc le modèle peut changer sans modifier le code.En savoir plus Ce qu'est une API de LLMCréer des applications fiables avec les API de LLM a pris 1,45 s. Pour d’autres billets de soutien, elle prenait jusqu’à 4 ou 5 secondes. La latence Termep95Le 95e centile : le temps sous lequel restent 95 appels sur 100. Il montre la durée des appels lents, que la médiane cache.En savoir plus Mesurer la performance en pratiqueLes LLM : comment ils fonctionnent et comment en choisir un de chat-small avec la requête (prompt) v2 était de 4,34 s. Pendant ce temps, l’écran du service de soutien n’affiche rien. TermeOmarLe gestionnaire de produit de Larkfield. Il pose les questions d’affaires : ce qu’un changement coûte et ce qu’il apporte à la boutique. craint qu’un agent pense que l’écran est figé et clique de nouveau. Un deuxième clic signifie une deuxième requête et une deuxième facture.
La diffusion en continu répond à ce problème. Dans une requête diffusée en continu, le fournisseur envoie la réponse en petits morceaux, aussi appelés segments. Il les envoie à mesure que le modèle les écrit, sur une seule connexion ouverte. L’application peut afficher chaque morceau immédiatement. Vous demandez ce mode avec un seul champ dans la requête : "stream": true.
TermeAmiraLa développeuse de Larkfield. Elle écrit le code des outils du service d’assistance dans les cours. a enregistré trois flux réels, avec le moment d’arrivée de chaque morceau. Voici quand le texte de chacun est arrivé :
- chat-small, T-64704
- chat-strong, T-64704
- chat-small, T-80005
Description textuelle du graphique
Un graphique avec trois lignes. L’axe horizontal représente le temps depuis l’envoi de la requête, de 0 à 2,6 secondes. L’axe vertical représente le nombre de caractères reçus. Chaque ligne reste longtemps à zéro, puis monte rapidement. chat-small, T-64704: 106 pieces and 453 characters; the first piece at 1.275 s, the last at 1.554 s, the stream closed at 1.597 s. chat-strong, T-64704: 87 pieces and 408 characters; the first piece at 1.883 s, the last at 2.379 s, the stream closed at 2.42 s. chat-small, T-80005: 90 pieces and 382 characters; the first piece at 2.329 s, the last at 2.44 s, the stream closed at 2.506 s.
Regardez la partie plate. Pour chat-small sur T-64704, le premier morceau est arrivé à 1.275 s, et le dernier à 1.554 s. Donc, environ 80 % de l’attente précédait le premier morceau. Pour T-80005, c’était 93 %. Ces modèles raisonnent avant d’écrire, et ce raisonnement n’est pas diffusé en continu. La diffusion en continu n’accélère pas la réponse. Elle montre la réponse dès que le modèle commence à écrire, et indique que la requête est toujours en cours.
Les morceaux ne sont ni des mots ni des données
Section intitulée « Les morceaux ne sont ni des mots ni des données »Voici les neuf premiers morceaux de ce flux, exactement comme ils sont arrivés :
| Morceau | Temps (s) | Texte |
|---|---|---|
| 1 | 1.275 | {" |
| 2 | 1.275 | language |
| 3 | 1.275 | ":" |
| 4 | 1.275 | en |
| 5 | 1.275 | "," |
| 6 | 1.275 | team |
| 7 | 1.275 | ":" |
| 8 | 1.275 | w |
| 9 | 1.276 | arranty |
Les morceaux suivent les jetons (tokens) du modèle, pas les mots ni les champs. Après le morceau 8, l’équipe est w. Après le morceau 9, c’est warranty (garantie). Un morceau peut se terminer au milieu d’un mot, d’un nombre ou d’un identifiant de commande. Le flux de T-80005 a envoyé LK, -, 266 et 415 en quatre morceaux.
Le texte qui arrive encore est donc du texte partiel : une partie d’une réponse dont vous ne connaissez pas encore la fin. Ce ne sont pas des données. Vous ne pouvez pas l’analyser, et même si vous le pouviez, vous ne pourriez pas vous y fier. Vous avez la réponse complète seulement quand le flux se termine normalement. Alors, et seulement alors, elle passe par les deux barrières du module 3.
L’idée : afficher tôt, vérifier à la fin
Section intitulée « L’idée : afficher tôt, vérifier à la fin »Le schéma suit une réponse diffusée en continu. Chaque chemin mène à l’un de quatre états : valide, rejetée, annulée ou interrompue.
Description textuelle du diagramme
- La requête est envoyée avec stream : true. L’écran attend : aucun morceau reçu.
- Un morceau arrive. L’écran l’affiche dans un brouillon portant la mention « non vérifié ».
- Comment le flux s’est-il terminé? Si d’autres morceaux arrivent, revenez à l’étape 2.
- Fin normale : le texte est complet. Il passe par les deux barrières, le schéma puis les règles. Si les deux réussissent, la réponse est valide et peut être stockée. Si une barrière échoue, la réponse est rejetée et une personne vérifie le billet.
- L’agent a appuyé sur Stop (arrêter) : le flux est annulé.
- La connexion a été perdue, ou le flux s’est terminé avec finish_reason « length » : le flux est interrompu.
- Les réponses annulées et interrompues sont du texte partiel. Elles ne sont pas vérifiées et ne sont jamais stockées.
Deux de ces fins méritent quelques précisions. Annuler signifie que l’agent a appuyé sur Stop. L’application cesse de lire et ferme la connexion. Un fournisseur peut quand même facturer les jetons que le modèle a écrits avant l’arrêt; consultez sa documentation. Un flux interrompu se termine sans fin normale : le réseau coupe ou le modèle atteint la limite de jetons (finish_reason: "length"). Le projet traite les deux cas de la même façon. Le texte reçu reste à l’écran comme brouillon incomplet et ne va jamais plus loin.
Un petit exemple : le même flux, trois fins
Section intitulée « Un petit exemple : le même flux, trois fins »La fonction stream_answer() du projet lit les morceaux, affiche chacun et vérifie une fonction cancel() avant chaque morceau. Elle retourne un StreamResult avec le texte, les morceaux et la façon dont le flux s’est terminé. La voici sur le flux enregistré, exécuté jusqu’à la fin :
python -m assistant.stream T-64704La sortie (le flux réel de chat-small, rejoué avec les délais enregistrés) :
{"language":"en","team":"warranty","needs_human":true,"reason":"A problem developed during use, so this is a warranty issue; the heat, burning smell and scorch mark are safety concerns (H1).","confidence":"high","order":null,"reply":"I’m sorry this happened, and you did the right thing by stopping use of the kettle. Please keep it unplugged and do not use it again. A member of our team will contact you about the next steps and your safety concern."}
106 pieces, first after 1.27 s, all after 1.55 scomplete | validLe texte apparaît morceau par morceau dans votre terminal. Vos temps peuvent varier de quelques centièmes de seconde. Maintenant, l’agent appuie sur Stop après 20 morceaux :
python -m assistant.stream T-64704 --cancel-after 20{"language":"en","team":"warranty","needs_human":true,"reason":"A problem
20 pieces, first after 1.27 s, all after 1.29 scancelled | not checked: partial text is not dataEt la connexion coupe après 40 morceaux. Cette coupure est simulée : l’option --cut-after arrête volontairement le flux enregistré.
python -m assistant.stream T-64704 --cut-after 40{"language":"en","team":"warranty","needs_human":true,"reason":"A problem developed during use, so this is a warranty issue; the heat, burning smell and scorch mark are
40 pieces, first after 1.27 s, all after 1.40 sinterrupted: Connection lost (simulated). | not checked: partial text is not dataChaque exécution se termine par une ligne d’état. Seule la première indique valid. Les deux autres indiquent ce qui s’est passé et que rien n’a été vérifié.
Ajouter la diffusion en continu au projet
Section intitulée « Ajouter la diffusion en continu au projet »-
Ouvrez
assistant/providers.py. Ajoutezfrom typing import Iteratoraux importations en haut du fichier. -
Dans
MockProvider, ajoutez un paramètrespeedà__init__, puis une méthodestream()souscomplete():assistant/providers.py (MockProvider) def __init__(self, folder: Path = RECORDINGS, speed: float = 0.0):self.speed = speed # 1.0 replays a stream with its recorded timing; 0 replays it at onceself.recordings = {}# ... the rest of __init__ stays the samedef stream(self, request: dict) -> Iterator[tuple[float, str]]:"""Yield (seconds since the request, text piece) as the recorded stream did."""entry = self._entry({**request, "stream": True})start = time.monotonic()for at, piece in entry["chunks"]:if self.speed:time.sleep(max(0.0, at / self.speed - (time.monotonic() - start)))yield at, pieceLe simulateur cherche la requête avec
"stream": true, car une requête diffusée en continu est une requête différente. -
Dans
OpenAICompatibleProvider, ajoutez une méthodestream()souscomplete():assistant/providers.py (OpenAICompatibleProvider) def stream(self, request: dict) -> Iterator[tuple[float, str]]:start = time.monotonic()with translate_errors():for chunk in self.client.chat.completions.create(**request, stream=True):if chunk.choices and chunk.choices[0].delta.content:yield round(time.monotonic() - start, 3), chunk.choices[0].delta.contentif chunk.choices and chunk.choices[0].finish_reason not in (None, "stop"):# for example "length": the stream ended normally, but the answer was cutraise ProviderError(f"The stream ended with finish_reason: {chunk.choices[0].finish_reason}.")La dernière vérification est importante. Un flux coupé par la limite de jetons se ferme comme un flux normal. Sans cette vérification, le code considérerait une réponse coupée comme complète.
-
Créez le fichier
assistant/stream.py:assistant/stream.py """Show the answer as it arrives, allow cancel, and check only the complete answer.python -m assistant.stream T-64704 replays a recorded stream with its real timingpython -m assistant.stream T-64704 --cancel-after 20 the agent presses Stop after 20 piecespython -m assistant.stream T-64704 --cut-after 40 the connection breaks after 40 pieces (simulated)"""import argparseimport sysimport timefrom dataclasses import dataclass, fieldfrom typing import Callablefrom .providers import Completion, ProviderError@dataclassclass StreamResult:text: str = ""complete: bool = False # True only when the stream ended normallycancelled: bool = Falseerror: str = ""pieces: list[tuple[float, str]] = field(default_factory=list)@propertydef first_piece_s(self) -> float | None:return self.pieces[0][0] if self.pieces else None@propertydef status(self) -> str:"""How the stream ended: complete, cancelled, interrupted (cut after some pieces) or failed (no piece)."""if self.complete:return "complete"if self.cancelled:return "cancelled"return "interrupted" if self.pieces else "failed"def stream_answer(provider, request: dict, show: Callable[[str], None] = lambda piece: None,cancel: Callable[[], bool] = lambda: False) -> StreamResult:result = StreamResult()try:for at, piece in provider.stream(request):if cancel():result.cancelled = Truereturn resultresult.pieces.append((at, piece))result.text += pieceshow(piece)except ProviderError as error:result.error = str(error) # what arrived before the error stays partial textreturn resultresult.complete = Truereturn resultdef as_completion(result: StreamResult) -> Completion:"""Only a complete stream becomes a Completion that validate() may check."""if not result.complete:raise ValueError("Partial text is not data: the stream did not finish.")data = {"choices": [{"message": {"content": result.text}, "finish_reason": "stop"}]}return Completion.from_response(data, result.pieces[-1][0] if result.pieces else 0.0)class CutAfter:"""Simulated: the connection breaks after `pieces` pieces of a real (or recorded) stream."""def __init__(self, inner, pieces: int):self.inner, self.pieces = inner, piecesdef stream(self, request: dict):for i, item in enumerate(self.inner.stream(request)):if i == self.pieces:raise ProviderError("Connection lost (simulated).")yield itemdef main(argv: list[str] | None = None) -> int:from .config import Settings, make_providerfrom .context import build_requestfrom .data import load_ticketfrom .orders import OrderBookfrom .schema import response_formatfrom .validate import validateparser = argparse.ArgumentParser(prog="python -m assistant.stream")parser.add_argument("ticket_id", nargs="?", default="T-64704")parser.add_argument("--cancel-after", type=int, metavar="N", help="press Stop after N pieces")parser.add_argument("--cut-after", type=int, metavar="N", help="break the connection after N pieces (simulated)")args = parser.parse_args(argv)settings = Settings.from_env()provider = make_provider(settings)if hasattr(provider, "speed"):provider.speed = 1.0 # replay the recorded timingif args.cut_after is not None:provider = CutAfter(provider, args.cut_after)try:ticket = load_ticket(args.ticket_id)except KeyError as error:print(error.args[0], file=sys.stderr)return 2request = build_request(ticket, settings.model, "v2", response_format())shown = []def show(piece: str) -> None:print(piece, end="", flush=True)shown.append(piece)def stop() -> bool: # the agent's Stop button, pressed after N piecesreturn args.cancel_after is not None and len(shown) >= args.cancel_afterstart = time.monotonic()result = stream_answer(provider, request, show=show, cancel=stop)first = f"first after {result.first_piece_s:.2f} s, " if result.pieces else ""print(f"\n\n{len(result.pieces)} pieces, {first}all after {time.monotonic() - start:.2f} s")if not result.complete:reason = f": {result.error}" if result.error else ""checked = "not checked: partial text is not data" if result.pieces else "no answer"print(f"{result.status}{reason} | {checked}")return 1verdict = validate(as_completion(result), ticket, OrderBook())checked = "valid" if verdict.valid else "rejected: " + ", ".join(p.code for p in verdict.problems)print(f"complete | {checked}")return 0 if verdict.valid else 1if __name__ == "__main__":sys.exit(main()) -
Copiez
recordings/streams.jsonlde l’instantanéend-of-m05dans votre dossierrecordings, comme dans la leçon précédente. -
Exécutez les trois commandes du petit exemple et comparez votre sortie.
-
Copiez
tests/test_stream.pyde l’instantanéend-of-m05dans votre dossiertests. Exécutezpython -m pytest.
L’étape 7 affiche 65 passed, 1 deselected (65 tests réussis, 1 désélectionné). Les trois nouveaux tests rejouent le flux enregistré jusqu’à la fin, l’annulent après 20 morceaux et le coupent après 10 morceaux (simulation). Les deux derniers vérifient que as_completion() refuse le texte partiel.
Cette conception place la règle à un seul endroit. as_completion() est le seul passage d’un flux à un Completion, et validate() accepte seulement un Completion. Aucun chemin dans le code ne peut donc vérifier ou stocker le texte d’un flux qui n’est pas terminé.
Pour regarder les autres flux enregistrés, définissez le modèle avant la commande. Sur macOS ou Linux : ASSISTANT_MODEL=chat-strong python -m assistant.stream T-64704. Sur Windows, dans PowerShell : $env:ASSISTANT_MODEL="chat-strong", puis la commande. Le flux de Termechat-strongLe nom que le code de Larkfield donne à un modèle hébergé plus grand : gpt-6.1-sol sur Azure. Il est souvent plus exact que chat-small et coûte environ 14 à 18 fois plus par appel.En savoir plus Les éléments d'une requêteCréer des applications fiables avec les API de LLM commence à 1,88 s et se termine par complete | valid (complet | valide). Retirez ensuite ce paramètre (Remove-Item Env:ASSISTANT_MODEL dans PowerShell).
Quand diffuser en continu, et à quel coût
Section intitulée « Quand diffuser en continu, et à quel coût »Diffusez en continu quand une personne attend et regarde : l’écran du service de soutien, une conversation. Ne le faites pas pour un travail que personne ne regarde, comme l’eval nocturne sur 69 billets. Dans ce cas, la diffusion en continu ajoute seulement du code.
La diffusion en continu a ses propres coûts :
- Les jetons coûtent le même prix. Le modèle écrit la même réponse. L’annulation économise seulement la partie qu’il n’avait pas encore écrite, si votre fournisseur cesse alors de facturer.
- L’utilisation doit être demandée. Un flux ordinaire ne rapporte pas l’utilisation des jetons. Plusieurs API compatibles avec OpenAI l’envoient à la fin seulement si la requête la demande. Le flux du projet ne la demande pas. Mesurer l’utilisation et le coût consigne donc l’utilisation des appels normaux.
- Plus d’états à gérer. Un appel normal réussit ou échoue. Un flux peut aussi s’arrêter en plein milieu. La prochaine leçon définit ce que l’agent voit dans chaque cas.
- La TermeSortie structuréeUne requête qui envoie au fournisseur le schéma de la réponse, pour que le modèle ne puisse écrire qu’une réponse de cette forme. Elle contrôle la forme, pas la vérité : l’application vérifie toujours chaque réponse.En savoir plus Définir un schémaCréer des applications fiables avec les API de LLM s’affiche mal. L’agent ne veut pas lire
{"language":"en","team":"w. Un véritable écran afficherait seulement le champreply(réponse) à mesure qu’il se remplit. Dans le flux de T-64704, la réponse commençait à 1.395 s, seulement 0.20 s avant la fin. Pour les réponses structurées courtes d’un TermeModèle de raisonnementUn modèle qui écrit des jetons de raisonnement cachés avant sa réponse. chat-small en est un. Un tel modèle accepte souvent seulement sa température par défaut, et ses jetons cachés comptent dans la limite de sortie.En savoir plus Raisonnement et multimodalitéLes LLM : comment ils fonctionnent et comment en choisir un, un état clair « Rédaction d’un brouillon… » peut aider autant que le texte.
À vous : quand pourriez-vous vérifier T-80005?
Section intitulée « À vous : quand pourriez-vous vérifier T-80005? »Le flux enregistré de T-80005 compte 90 morceaux. Exécutez-le avec --cut-after 45. Avant de l’exécuter, prédisez deux choses : dans quel champ le flux coupe, et ce que dit la ligne d’état. Répondez ensuite : y a-t-il un numéro de morceau, avant la fin, après lequel l’analyse pourrait être vérifiée sans risque?
Afficher la réponse
{"language":"fr","team":"delivery","needs_human":false,"reason":"Le suivi est bloqué et la commande n’est pas arrivée : c’est une demande de livraison.","confidence":"high","order":{"order_id
45 pieces, first after 2.33 s, all after 2.40 sinterrupted: Connection lost (simulated). | not checked: partial text is not dataLe flux a coupé dans l’objet order (commande), avant l’identifiant de commande. Et non, aucun morceau précédent n’est sûr. Le JSON n’est pas complet avant le dernier morceau, donc la barrière 1 (le schéma) ne peut pas s’exécuter. Même un champ qui semble terminé, comme "team":"delivery" (équipe : livraison), est sûr seulement si rien ensuite ne change le sens. Le champ reply et la commande viennent plus tard, et les règles les vérifient ensemble. Le seul moment sûr est la fin normale du flux.
Erreur courante
« Fermez simplement les accolades et utilisez ce qu'on a »
Symptôme : un flux s’arrête trop tôt. TermeTomásL’ingénieur en IA de Larkfield. Il propose souvent un raccourci rapide, et la leçon montre pourquoi il échoue. a une idée : « Le JSON est presque complet. Ajoutez le "} qui manque et analysez-le. » Amira essaie sur le flux enregistré de T-64704, arrêté après 76 morceaux. C’est une simulation à partir de l’enregistrement réel : le flux comptait en fait 106 morceaux.
from assistant.context import build_requestfrom assistant.data import load_ticketfrom assistant.orders import OrderBookfrom assistant.providers import Completion, MockProviderfrom assistant.schema import response_formatfrom assistant.stream import stream_answerfrom assistant.validate import validate
ticket = load_ticket("T-64704")request = build_request(ticket, "chat-small", "v2", response_format())count = iter(range(1000))result = stream_answer(MockProvider(), request, cancel=lambda: next(count) >= 76) # Stop after 76 piecesprint(result.status, "after", len(result.pieces), "pieces")
repaired = result.text + '"}' # Tomás: "just close the quote and the bracket"data = {"choices": [{"message": {"content": repaired}, "finish_reason": "stop"}]}verdict = validate(Completion.from_response(data, 0.0), ticket, OrderBook())print("valid:", verdict.valid, "| needs_human:", verdict.analysis.needs_human)print("reply:", verdict.analysis.reply)cancelled after 76 piecesvalid: True | needs_human: Truereply: I’m sorry this happened, and you did the right thing by stopping use of the kettle.Les deux barrières réussissent. La réponse ressemble à une phrase terminée. Mais la réponse complète continuait : « Gardez la bouilloire débranchée et ne l’utilisez plus. Un membre de notre équipe vous contactera au sujet des prochaines étapes et de votre inquiétude concernant la sécurité. » Le brouillon réparé omet le conseil de sécurité et la promesse de contact, pour un billet concernant une odeur de brûlé.
Cause : le texte partiel peut sembler complet. Le schéma vérifie la forme, et les règles vérifient ce pour quoi elles ont été écrites. Aucun des deux ne peut savoir ce que le modèle aurait écrit ensuite. Une réparation invente une fin : ici, la fin « rien de plus ».
Solution : ne réparez, n’analysez et ne stockez jamais le texte partiel. Affichez-le comme brouillon incomplet, avec la mention « non vérifié », et proposez un nouvel essai ou une réponse manuelle. Dans le projet, as_completion() refuse un flux qui n’est pas terminé. Le seul chemin vers validate() est donc une réponse complète.
Ce que cela signifie pour un véritable service d’IA : « presque complet » n’est pas une forme de complet. Toute donnée qui guide une décision doit provenir d’une réponse terminée normalement et ayant réussi toutes les vérifications.
Vérification des connaissances
Section intitulée « Vérification des connaissances »Vérifiez votre compréhension
Dans le flux enregistré de chat-small pour T-64704, le premier morceau est arrivé à 1,275 s et le flux s'est fermé à 1,597 s. Qu'est-ce que la diffusion en continu a amélioré, et qu'est-ce qu'elle n'a pas amélioré?
Afficher la réponse
Elle a amélioré ce que l’agent voit. Le premier texte apparaît à 1,28 s. L’agent sait donc que la requête est toujours en cours et peut l’arrêter. Elle n’a pas accéléré la réponse. Environ 80 % de l’attente précédait le premier morceau, pendant le raisonnement du modèle. La réponse entière prenait toujours environ 1,6 s. La réponse utilisable et vérifiée est prête seulement à la fin.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Un flux se termine avec finish_reason « length ». La connexion s'est fermée normalement. Pourquoi le projet le traite-t-il comme interrompu, et quel serait le problème autrement?
Afficher la réponse
« length » signifie que le modèle a atteint la limite de jetons et que la réponse a été coupée. Le texte est partiel, même si la connexion s’est fermée normalement. Sans la vérification dans OpenAICompatibleProvider.stream(), stream_answer() marquerait le résultat comme complet. as_completion() le transmettrait alors avec finish_reason: "stop" (motif de fin : arrêt normal). Avec une sortie structurée, le JSON coupé échouerait généralement à la barrière 1 (le schéma). Mais une réponse coupée en texte brut pourrait sembler terminée, comme le montre le cas d’échec.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Prédisez : vous exécutez python -m assistant.stream T-80008 avec le simulateur. Que se passe-t-il, et pourquoi?
Afficher la réponse
Le simulateur n’a aucun flux enregistré pour T-80008. Il lève donc RecordingNotFound avant le premier morceau. stream_answer() intercepte cette exception comme une ProviderError. Le résultat n’a aucun morceau, donc son état est failed. La commande affiche 0 pieces (0 morceau), puis failed: No recording for this request (key …) (échec : aucun enregistrement pour cette requête, clé …) et no answer (aucune réponse). Elle se termine avec le code de sortie 1. Rien n’est vérifié ni stocké.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Récapitulatif
- La diffusion en continu envoie la réponse par morceaux à mesure que le modèle l’écrit (
"stream": true). Elle montre la progression et permet à l’agent d’arrêter. Elle n’accélère pas la réponse : dans les flux enregistrés, 78 % à 93 % de l’attente précédait le premier morceau. - Les morceaux suivent les jetons, pas les champs : l’équipe est arrivée sous la forme
w, puisarranty. Le texte qui arrive encore est du texte partiel, et le texte partiel n’est pas une donnée. - Chaque flux se termine dans un état : complet, annulé (l’agent a appuyé sur Stop), interrompu (connexion coupée ou
finish_reasonétait « length ») ou échec (aucun morceau). - Seul un flux complet passe par les deux barrières. Dans le projet,
as_completion()refuse tout autre résultat. Aucun texte partiel ne peut donc être vérifié ni stocké. - Une réponse partielle réparée peut réussir les deux barrières et rester incorrecte. La réponse de T-64704, coupée après 76 morceaux (simulation), a perdu son conseil de sécurité.
Ensuite, décidez ce que l’agent voit dans chacun de ces états, avec des mots utiles et sans détails internes.
Leçon suivante : Communiquer un échec