docs: refresh translations for recent English changes (#3379)
This commit is contained in:
parent
d8b638304e
commit
c5d7d0b6d0
1 changed files with 3149 additions and 1047 deletions
|
|
@ -1,13 +1,13 @@
|
|||
---
|
||||
translation:
|
||||
sections: [e33d441f12d50535, 7099694c603e0f5f, c1df4cf9673433e6, c9cd294541422e6e, 6cec073617bfd037, efa92b8f99e908c8, 6a22a29e27fb4601]
|
||||
sections: [7be05607887e6853, e7375894888d9750, c36f73fc7e3af13b, 2fec2d7e129e62fe, 809b0e0a7c27295a, b4395a04d2a5d906, 1a436007f5f54779, c6b2078ed1e63ba5]
|
||||
tool: 1
|
||||
---
|
||||
# Gérer les erreurs {#handling-errors}
|
||||
|
||||
Un outil (tool) peut échouer de deux manières, et le SDK les traite très différemment.
|
||||
Un outil (tool) peut échouer de trois manières, et le SDK traite chacune différemment.
|
||||
|
||||
Levez une exception ordinaire et c’est le **modèle** qui la voit. Levez `MCPError` et c’est le **protocole** qui la voit.
|
||||
Levez `ToolError` et c’est le **modèle** qui voit votre message. Levez `MCPError` et c’est le **protocole** qui le voit. Levez quoi que ce soit d’autre et c’est un plantage : le modèle apprend seulement que l’appel a échoué, et votre journal reçoit le traceback.
|
||||
|
||||
Cette page vous aide à choisir.
|
||||
|
||||
|
|
@ -15,11 +15,11 @@ Cette page vous aide à choisir.
|
|||
|
||||
Prenez un outil qui effectue une recherche, et laissez cette recherche échouer :
|
||||
|
||||
```python title="server.py" hl_lines="11-12"
|
||||
```python title="server.py" hl_lines="2 12-13"
|
||||
--8<-- "docs_src/handling_errors/tutorial001.py"
|
||||
```
|
||||
|
||||
Ces deux lignes n’ont rien de spécifique à MCP. `get_author` lève une simple `ValueError`, comme le ferait n’importe quelle fonction Python.
|
||||
`ToolError`, qui vient de `mcp.server.mcpserver.exceptions`, est le moyen pour un outil de dire au modèle que quelque chose s’est mal passé.
|
||||
|
||||
Appelez-le avec un titre absent du catalogue et regardez le résultat :
|
||||
|
||||
|
|
@ -30,13 +30,15 @@ result.structured_content # None
|
|||
```
|
||||
|
||||
* La requête a **réussi**. Il y a un résultat ; rien n’a été levé côté appelant.
|
||||
* `is_error` vaut `True`, et le message de votre exception (préfixé du nom de l’outil) se trouve dans `content`, exactement là où le modèle lit.
|
||||
* `is_error` vaut `True`, et votre message (préfixé du nom de l’outil) se trouve dans `content`, exactement là où le modèle lit.
|
||||
* `structured_content` vaut `None`. Un appel en échec n’a aucune valeur de retour à structurer.
|
||||
|
||||
C’est une **erreur d’outil** (tool error), et c’est le comportement par défaut pour *toute* exception que lève votre outil. C’est aussi presque toujours ce que vous voulez.
|
||||
C’est une **erreur d’outil** (tool error), et c’est presque toujours ce que vous voulez.
|
||||
|
||||
C’est le modèle qui appelle votre outil. C’est lui qui a choisi les arguments. Une erreur d’outil est donc un tour de conversation : le modèle lit *« No book titled 'Nothing' in the catalog. »*, comprend qu’il s’est trompé de titre et rappelle l’outil avec un meilleur. Vous avez écrit un seul `raise` et obtenu un agent qui se corrige tout seul.
|
||||
|
||||
Côté serveur, une `ToolError` se résume à une ligne `INFO` dans le journal, sans traceback. Vous l’aviez vue venir, il n’y a donc rien à examiner.
|
||||
|
||||
!!! tip
|
||||
N’utilisez jamais `return` pour renvoyer un message d’erreur depuis un outil. Une chaîne renvoyée a
|
||||
`is_error=False` : pour le modèle (et pour toute interface cliente), l’outil semble avoir
|
||||
|
|
@ -44,7 +46,7 @@ C’est le modèle qui appelle votre outil. C’est lui qui a choisi les argumen
|
|||
|
||||
## Une erreur que le modèle ne peut pas corriger {#an-error-the-model-cannot-fix}
|
||||
|
||||
Remplacez maintenant `ValueError` par `MCPError`.
|
||||
Remplacez maintenant `ToolError` par `MCPError`.
|
||||
|
||||
```python title="server.py" hl_lines="1 3 14"
|
||||
--8<-- "docs_src/handling_errors/tutorial002.py"
|
||||
|
|
@ -77,10 +79,10 @@ Remplacez maintenant `ValueError` par `MCPError`.
|
|||
|
||||
Les deux voies répondent à deux questions différentes.
|
||||
|
||||
* **Levez n’importe quelle exception** pour un échec d’*exécution* : ce que votre outil a tenté de faire n’a pas fonctionné. Le modèle a choisi l’appel, il devrait donc en voir la conséquence et avoir une chance de se rattraper. Un titre mal orthographié, une API amont qui a expiré, une ligne qui n’existe pas : autant d’erreurs d’outil.
|
||||
* **Levez `ToolError`** pour un échec d’*exécution* : ce que votre outil a tenté de faire n’a pas fonctionné. Le modèle a choisi l’appel, il devrait donc en voir la conséquence et avoir une chance de se rattraper. Un titre mal orthographié, une API amont qui a expiré, une ligne qui n’existe pas : autant d’erreurs d’outil.
|
||||
* **Levez `MCPError`** quand c’est la *requête elle-même* qui doit être rejetée : il manque au client une capacité dont dépend votre outil, le serveur n’est pas en état de servir qui que ce soit, l’appelant a sauté une étape obligatoire. Aucune nouvelle tentative du modèle ne corrige cela, il n’y a donc rien à gagner à lui transmettre le message.
|
||||
|
||||
Une seule question tranche : **un modèle plus malin aurait-il pu éviter cela ?** Oui -> exception ordinaire. Non -> `MCPError`.
|
||||
Une seule question tranche : **un modèle plus malin aurait-il pu éviter cela ?** Oui -> `ToolError`. Non -> `MCPError`.
|
||||
|
||||
Selon ce critère, la seconde version de `get_author` a fait le mauvais choix : un meilleur titre règle le problème, le modèle méritait donc de voir le message. Elle est là pour vous montrer le mécanisme, pas pour le recommander.
|
||||
|
||||
|
|
@ -89,6 +91,25 @@ Selon ce critère, la seconde version de `get_author` a fait le mauvais choix :
|
|||
utile `data` facultative. Ce que vous y mettez est ce que le client reçoit : le SDK transmet telle
|
||||
quelle une `MCPError` levée au lieu de l’assainir.
|
||||
|
||||
## Toute autre exception {#any-other-exception}
|
||||
|
||||
Retirez maintenant la vérification et laissez la recherche dans le dictionnaire échouer d’elle-même :
|
||||
|
||||
```python title="server.py" hl_lines="11"
|
||||
--8<-- "docs_src/handling_errors/tutorial004.py"
|
||||
```
|
||||
|
||||
`CATALOG[title]` lève `KeyError`. Vous ne l’aviez pas prévue, le SDK la traite donc comme un plantage :
|
||||
|
||||
```python
|
||||
result.is_error # True
|
||||
result.content # [TextContent(text="Error executing tool get_author")]
|
||||
```
|
||||
|
||||
L’appel renvoie toujours `is_error=True`, le modèle sait donc qu’il a échoué et peut passer à autre chose. Ce qu’il n’obtient pas, c’est le texte de l’exception : une `KeyError` venue de votre code, ou une pile de SQL remontée d’un pilote trois bibliothèques plus bas, peut décrire les entrailles de votre serveur, si bien que ce texte ne quitte jamais le serveur.
|
||||
|
||||
C’est vous qui le recevez. Le serveur journalise le plantage au niveau `ERROR` avec le traceback complet, sous l’intitulé `Tool 'get_author' raised an unexpected exception`. Un journal de production réglé sur `WARNING` reste donc silencieux à chaque `ToolError` et se manifeste dès que quelque chose est réellement cassé.
|
||||
|
||||
## Une ressource qui n’existe pas {#a-resource-that-doesnt-exist}
|
||||
|
||||
Les ressources tracent la même frontière, et fournissent une exception dédiée pour le cas courant.
|
||||
|
|
@ -109,7 +130,7 @@ Quand elle ne le peut pas, levez `ResourceNotFoundError`. Le SDK la transforme e
|
|||
}
|
||||
```
|
||||
|
||||
Remarquez qu’il n’y a pas ici de demi-résultat `is_error=True`. La lecture d’une ressource renvoie un contenu ou échoue : les ressources n’ont que la voie du protocole. Les modèles et tout ce qui concerne les ressources se trouvent dans **[Ressources](resources.md)**.
|
||||
Remarquez qu’il n’y a pas ici de demi-résultat `is_error=True`. La lecture d’une ressource renvoie un contenu ou échoue : les ressources n’ont que la voie du protocole. `ResourceError` est l’équivalent pour un échec qui n’est pas « introuvable » (`-32603`, votre message), et les deux se résument à une ligne `INFO` dans votre journal. Toute autre exception hormis `MCPError` est un plantage : le client reçoit `-32603` ne mentionnant que l’URI, et le traceback va dans votre journal au niveau `ERROR`. Les modèles et tout ce qui concerne les ressources se trouvent dans **[Ressources](resources.md)**.
|
||||
|
||||
## Les erreurs que vous ne levez jamais {#errors-you-never-raise}
|
||||
|
||||
|
|
@ -120,19 +141,21 @@ Envoyez à `get_author` un `title` qui n’est pas une chaîne et le SDK le reje
|
|||
Cela représente toute une catégorie d’instructions `raise` que vous n’écrivez pas : ne revalidez pas vos propres annotations de type.
|
||||
|
||||
!!! info
|
||||
Tout ce que décrit cette page est ce qu’un **client** voit, et le `Client` en mémoire avec lequel
|
||||
vous écrirez vos tests voit exactement la même chose. Même `raise_exceptions=True` ne retransforme
|
||||
pas une erreur d’outil en traceback : au moment où ce drapeau pourrait agir, votre exception est déjà
|
||||
devenue le résultat `is_error=True`. Faites vos assertions sur le résultat. **[Tests](../get-started/testing.md)** présente ce schéma.
|
||||
Tout ce qu’un **client** voit sur cette page, le `Client` en mémoire avec lequel vous écrirez vos
|
||||
tests le voit aussi. Même `raise_exceptions=True` ne rend pas à l’appelant l’exception d’un outil
|
||||
en échec : au moment où ce drapeau pourrait agir, votre exception est déjà devenue le résultat
|
||||
`is_error=True`. Faites vos assertions sur le résultat. Si vous avez besoin du traceback d’un plantage,
|
||||
il est dans le journal du serveur, et le `caplog` de pytest le capture. **[Tests](../get-started/testing.md)** présente ce schéma.
|
||||
|
||||
## Récapitulatif {#recap}
|
||||
|
||||
* Levez **n’importe quelle exception** dans un outil -> l’appel renvoie `is_error=True` avec votre message dans `content`. Le modèle le lit et peut réessayer. C’est le comportement par défaut.
|
||||
* Levez **`ToolError`** dans un outil -> l’appel renvoie `is_error=True` avec votre message dans `content`. Le modèle le lit et peut réessayer.
|
||||
* Levez **`MCPError`** -> l’appel lui-même échoue avec une erreur JSON-RPC. Le modèle ne voit rien ; c’est l’hôte qui s’en occupe. `code`, `message` et `data` arrivent intacts.
|
||||
* La question qui tranche : *un modèle plus malin aurait-il pu éviter cela ?* Oui -> exception. Non -> `MCPError`.
|
||||
* La question qui tranche : *un modèle plus malin aurait-il pu éviter cela ?* Oui -> `ToolError`. Non -> `MCPError`.
|
||||
* Toute **autre exception** est un plantage -> `is_error=True` avec seulement `Error executing tool <name>` pour le modèle, et un enregistrement `ERROR` avec le traceback pour vous.
|
||||
* `ResourceNotFoundError` depuis un gestionnaire (handler) de ressource -> le `-32602` du protocole, avec l’URI dans `data`.
|
||||
* Les mauvais arguments sont rejetés d’après le schéma avant que votre fonction ne s’exécute ; vous n’avez pas de `raise` à écrire pour eux.
|
||||
* `from mcp import MCPError` ; les constantes de codes d’erreur viennent de `mcp.types`.
|
||||
* Imports : `from mcp import MCPError`, `from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError`, et les constantes de codes d’erreur depuis `mcp.types`.
|
||||
|
||||
Les erreurs sont gérées. C’est tout ce qu’un serveur *expose*. Ce que chaque gestionnaire peut lire, et faire en retour auprès du client pendant qu’il s’exécute, fait l’objet de la section suivante : **[Dans votre gestionnaire](../handlers/index.md)**.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue