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
|
||||
---
|
||||
# Обработка ошибок {#handling-errors}
|
||||
|
||||
Инструмент может завершиться неудачей двумя способами, и SDK обрабатывает их совершенно по-разному.
|
||||
Инструмент может завершиться неудачей тремя способами, и SDK обрабатывает каждый из них по-своему.
|
||||
|
||||
Выбросьте обычное исключение — и его увидит **модель**. Выбросьте `MCPError` — и его увидит **протокол**.
|
||||
Выбросьте `ToolError` — и ваше сообщение увидит **модель**. Выбросьте `MCPError` — и его увидит **протокол**. Выбросьте что-то другое — и это уже сбой: модель узнает только, что вызов не удался, а трассировка попадёт в ваш лог.
|
||||
|
||||
Эта страница о том, как выбрать.
|
||||
|
||||
|
|
@ -15,11 +15,11 @@ translation:
|
|||
|
||||
Возьмём инструмент, который что-то ищет, и пусть поиск ничего не найдёт:
|
||||
|
||||
```python title="server.py" hl_lines="11-12"
|
||||
```python title="server.py" hl_lines="2 12-13"
|
||||
--8<-- "docs_src/handling_errors/tutorial001.py"
|
||||
```
|
||||
|
||||
В этих двух строках нет ничего специфичного для MCP. `get_author` выбрасывает обычный `ValueError`, как любая функция на Python.
|
||||
`ToolError` из `mcp.server.mcpserver.exceptions` — это способ, которым инструмент сообщает модели, что что-то пошло не так.
|
||||
|
||||
Вызовите его с названием, которого нет в каталоге, и посмотрите на результат:
|
||||
|
||||
|
|
@ -30,12 +30,14 @@ result.structured_content # None
|
|||
```
|
||||
|
||||
* Запрос **выполнен успешно**. Результат есть; на вызывающей стороне ничего не выброшено.
|
||||
* `is_error` равен `True`, а сообщение вашего исключения (с префиксом в виде имени инструмента) лежит в `content` — ровно там, где читает модель.
|
||||
* `is_error` равен `True`, а ваше сообщение (с префиксом в виде имени инструмента) лежит в `content` — ровно там, где читает модель.
|
||||
* `structured_content` равен `None`. У неудачного вызова нет возвращаемого значения, которое можно было бы структурировать.
|
||||
|
||||
Это **ошибка инструмента**, и так по умолчанию обрабатывается *любое* исключение, выброшенное инструментом. И почти всегда это именно то, что нужно.
|
||||
Это **ошибка инструмента**, и почти всегда это именно то, что нужно.
|
||||
|
||||
Ваш инструмент вызывает модель. Она же выбрала аргументы. Поэтому ошибка инструмента — это реплика в диалоге: модель читает *«No book titled 'Nothing' in the catalog.»*, понимает, что ошиблась с названием, и вызывает инструмент снова с более подходящим. Вы написали один `raise` и получили агента, который исправляет себя сам.
|
||||
Вызывает ваш инструмент именно модель. Она же выбрала аргументы. Поэтому ошибка инструмента — это реплика в диалоге: модель читает *«No book titled 'Nothing' in the catalog.»*, понимает, что ошиблась с названием, и вызывает инструмент снова с более подходящим. Вы написали один `raise` и получили агента, который исправляет себя сам.
|
||||
|
||||
На сервере `ToolError` — это одна строка уровня `INFO` в логе, без трассировки. Вы её предвидели, так что расследовать нечего.
|
||||
|
||||
!!! tip
|
||||
Никогда не возвращайте сообщение об ошибке из инструмента через `return`. У возвращённой строки
|
||||
|
|
@ -44,7 +46,7 @@ result.structured_content # None
|
|||
|
||||
## Ошибка, которую модель исправить не может {#an-error-the-model-cannot-fix}
|
||||
|
||||
Теперь замените `ValueError` на `MCPError`.
|
||||
Теперь замените `ToolError` на `MCPError`.
|
||||
|
||||
```python title="server.py" hl_lines="1 3 14"
|
||||
--8<-- "docs_src/handling_errors/tutorial002.py"
|
||||
|
|
@ -77,10 +79,10 @@ result.structured_content # None
|
|||
|
||||
Два пути отвечают на два разных вопроса.
|
||||
|
||||
* **Выбрасывайте любое исключение** при сбое *выполнения*: то, что инструмент пытался сделать, не получилось. Вызов выбрала модель, значит, модель и должна увидеть последствия и получить шанс исправиться. Опечатка в названии, тайм-аут внешнего API, несуществующая строка в таблице — всё это ошибки инструмента.
|
||||
* **Выбрасывайте `ToolError`** при сбое *выполнения*: то, что инструмент пытался сделать, не получилось. Вызов выбрала модель, значит, модель и должна увидеть последствия и получить шанс исправиться. Опечатка в названии, тайм-аут внешнего API, несуществующая строка в таблице — всё это ошибки инструмента.
|
||||
* **Выбрасывайте `MCPError`**, когда отклонить нужно *сам запрос*: у клиента нет возможности, от которой зависит инструмент, сервер не в состоянии обслуживать кого бы то ни было, вызывающая сторона пропустила обязательный шаг. Никакая повторная попытка модели ничего из этого не исправит, так что передавать ей сообщение бессмысленно.
|
||||
|
||||
Решает один вопрос: **могла бы более умная модель этого избежать?** Да -> обычное исключение. Нет -> `MCPError`.
|
||||
Решает один вопрос: **могла бы более умная модель этого избежать?** Да -> `ToolError`. Нет -> `MCPError`.
|
||||
|
||||
По этому критерию вторая версия `get_author` выбрала неверно: правильное название всё исправляет, значит, модель заслуживала увидеть сообщение. Она здесь, чтобы показать механизм, а не чтобы его рекомендовать.
|
||||
|
||||
|
|
@ -89,6 +91,25 @@ result.structured_content # None
|
|||
полезную нагрузку `data`. Что бы вы в них ни положили, именно это и получит клиент: SDK передаёт
|
||||
выброшенный `MCPError` дословно, не очищая его.
|
||||
|
||||
## Любое другое исключение {#any-other-exception}
|
||||
|
||||
Теперь уберите проверку и дайте поиску по словарю упасть самому:
|
||||
|
||||
```python title="server.py" hl_lines="11"
|
||||
--8<-- "docs_src/handling_errors/tutorial004.py"
|
||||
```
|
||||
|
||||
`CATALOG[title]` выбрасывает `KeyError`. Вы этого не предусмотрели, поэтому SDK считает это сбоем:
|
||||
|
||||
```python
|
||||
result.is_error # True
|
||||
result.content # [TextContent(text="Error executing tool get_author")]
|
||||
```
|
||||
|
||||
Вызов по-прежнему возвращает `is_error=True`, так что модель знает, что он не удался, и может двигаться дальше. Чего она не получает, так это текста исключения: `KeyError` из вашего кода или гора SQL из драйвера тремя библиотеками ниже могут описывать внутреннее устройство сервера, поэтому за его пределы этот текст не выходит.
|
||||
|
||||
Вместо модели его получаете вы. Сервер пишет сбой в лог на уровне `ERROR` с полной трассировкой — как `Tool 'get_author' raised an unexpected exception`. Поэтому в эксплуатации лог на уровне `WARNING` молчит при каждом `ToolError` и подаёт голос в тот момент, когда что-то действительно сломалось.
|
||||
|
||||
## Ресурс, которого не существует {#a-resource-that-doesnt-exist}
|
||||
|
||||
Ресурсы проводят ту же границу и для частого случая поставляются с одним именованным исключением.
|
||||
|
|
@ -109,7 +130,7 @@ result.structured_content # None
|
|||
}
|
||||
```
|
||||
|
||||
Обратите внимание: здесь нет полурезультата с `is_error=True`. Чтение ресурса либо возвращает содержимое, либо завершается ошибкой — у ресурсов есть только протокольный путь. Шаблоны и всё остальное о ресурсах — на странице **[Ресурсы](resources.md)**.
|
||||
Обратите внимание: здесь нет полурезультата с `is_error=True`. Чтение ресурса либо возвращает содержимое, либо завершается ошибкой — у ресурсов есть только протокольный путь. `ResourceError` — то же самое для сбоя, который не сводится к «не найдено» (`-32603`, ваше сообщение), и оба оставляют в вашем логе одну строку уровня `INFO`. Любое другое исключение, кроме `MCPError`, — это сбой: клиент получает `-32603` с указанием одного лишь URI, а трассировка уходит в ваш лог на уровне `ERROR`. Шаблоны и всё остальное о ресурсах — на странице **[Ресурсы](resources.md)**.
|
||||
|
||||
## Ошибки, которые вы никогда не выбрасываете {#errors-you-never-raise}
|
||||
|
||||
|
|
@ -120,19 +141,21 @@ result.structured_content # None
|
|||
Это целый класс операторов `raise`, которые писать не нужно: не проверяйте повторно собственные аннотации типов.
|
||||
|
||||
!!! info
|
||||
Всё на этой странице — это то, что видит **клиент**, и `Client` в памяти, с которым вы будете
|
||||
писать тесты, видит ровно то же самое. Даже `raise_exceptions=True` не превращает ошибку инструмента
|
||||
обратно в трассировку: к моменту, когда этот флаг мог бы сработать, ваше исключение уже стало
|
||||
результатом с `is_error=True`. Проверяйте результат. Этот приём описан на странице **[Тестирование](../get-started/testing.md)**.
|
||||
Всё, что на этой странице видит **клиент**, видит и `Client` в памяти, с которым вы будете
|
||||
писать тесты. Даже `raise_exceptions=True` не возвращает исключение упавшего
|
||||
инструмента вызывающей стороне: к моменту, когда этот флаг мог бы сработать, ваше исключение уже стало
|
||||
результатом с `is_error=True`. Проверяйте результат. Если нужна трассировка сбоя, она в логе
|
||||
сервера, и `caplog` из pytest её перехватывает. Этот приём описан на странице **[Тестирование](../get-started/testing.md)**.
|
||||
|
||||
## Итоги {#recap}
|
||||
|
||||
* Выбрасываете **любое исключение** в инструменте -> вызов возвращает `is_error=True` с вашим сообщением в `content`. Модель читает его и может повторить попытку. Это поведение по умолчанию.
|
||||
* Выбрасываете **`ToolError`** в инструменте -> вызов возвращает `is_error=True` с вашим сообщением в `content`. Модель читает его и может повторить попытку.
|
||||
* Выбрасываете **`MCPError`** -> сам вызов завершается ошибкой JSON-RPC. Модель ничего не видит; разбирается хост. `code`, `message` и `data` доходят без изменений.
|
||||
* Решающий вопрос: *могла бы более умная модель этого избежать?* Да -> исключение. Нет -> `MCPError`.
|
||||
* Решающий вопрос: *могла бы более умная модель этого избежать?* Да -> `ToolError`. Нет -> `MCPError`.
|
||||
* Любое **другое исключение** — это сбой -> `is_error=True`, где для модели только `Error executing tool <name>`, а для вас — запись уровня `ERROR` с трассировкой.
|
||||
* `ResourceNotFoundError` из обработчика ресурса -> протокольный `-32602` с URI в `data`.
|
||||
* Некорректные аргументы отклоняются по схеме до запуска вашей функции; `raise` для них не нужен.
|
||||
* `from mcp import MCPError`; константы кодов ошибок — из `mcp.types`.
|
||||
* Импорты: `from mcp import MCPError`, `from mcp.server.mcpserver.exceptions import ToolError, ResourceError, ResourceNotFoundError`, а константы кодов ошибок — из `mcp.types`.
|
||||
|
||||
С ошибками разобрались. Это всё, что сервер *предоставляет*. Что каждый обработчик может прочитать и что сделать в сторону клиента во время выполнения — в следующем разделе: **[Внутри обработчика](../handlers/index.md)**.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue