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,25 +1,25 @@
|
|||
---
|
||||
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`를 발생시키면 **프로토콜**이 보게 됩니다. 그 밖의 것을 발생시키면 크래시로 취급됩니다. 모델은 호출이 실패했다는 사실만 알게 되고, 트레이스백은 로그에 남습니다.
|
||||
|
||||
이 페이지는 둘 중 무엇을 선택할지에 관한 내용입니다.
|
||||
이 페이지는 무엇을 선택할지에 관한 내용입니다.
|
||||
|
||||
## 모델이 고칠 수 있는 오류 {#an-error-the-model-can-fix}
|
||||
|
||||
무언가를 조회하는 도구를 하나 두고, 조회가 실패하게 해 봅시다.
|
||||
|
||||
```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`는 여느 Python 함수가 그러듯 평범한 `ValueError`를 발생시킵니다.
|
||||
`mcp.server.mcpserver.exceptions`에 있는 `ToolError`는 도구가 무언가 잘못되었다고 모델에게 알리는 수단입니다.
|
||||
|
||||
카탈로그에 없는 제목으로 호출하고 결과를 살펴보세요.
|
||||
|
||||
|
|
@ -30,13 +30,15 @@ result.structured_content # None
|
|||
```
|
||||
|
||||
* 요청은 **성공했습니다**. 결과가 있고, 호출한 쪽에서는 아무 예외도 발생하지 않았습니다.
|
||||
* `is_error`가 `True`이고, 예외 메시지(앞에 도구 이름이 붙음)가 `content`에, 즉 모델이 읽는 바로 그 자리에 들어 있습니다.
|
||||
* `is_error`가 `True`이고, 메시지(앞에 도구 이름이 붙음)가 `content`에, 즉 모델이 읽는 바로 그 자리에 들어 있습니다.
|
||||
* `structured_content`는 `None`입니다. 실패한 호출에는 구조화할 반환 값이 없습니다.
|
||||
|
||||
이것이 **도구 오류**이며, 도구가 발생시키는 **모든** 예외의 기본 동작입니다. 그리고 거의 언제나 원하는 동작이기도 합니다.
|
||||
이것이 **도구 오류**이며, 거의 언제나 원하는 동작입니다.
|
||||
|
||||
도구를 호출하는 쪽은 모델입니다. 인자를 고른 것도 모델입니다. 그래서 도구 오류는 대화의 한 차례가 됩니다. 모델은 *"No book titled 'Nothing' in the catalog."*를 읽고, 제목을 잘못 추측했다는 것을 깨닫고, 더 나은 제목으로 다시 호출합니다. `raise` 한 줄을 썼을 뿐인데 스스로 교정하는 에이전트를 얻은 셈입니다.
|
||||
|
||||
서버에서 `ToolError`는 로그의 `INFO` 한 줄이며, 트레이스백은 없습니다. 예상한 실패이므로 조사할 것이 없습니다.
|
||||
|
||||
!!! tip
|
||||
도구에서 오류 메시지를 `return`으로 돌려주지 마세요. 반환된 문자열은 `is_error=False`이므로,
|
||||
모델에게(그리고 모든 클라이언트 UI에게) 도구가 제대로 작동했고 그 문자열이 답인 것처럼 보입니다.
|
||||
|
|
@ -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`를 제외한 그 밖의 모든 예외는 크래시입니다. 클라이언트는 URI만 밝히는 `-32603`을 받고, 트레이스백은 `ERROR` 수준으로 로그에 남습니다. 템플릿을 비롯해 리소스에 관한 나머지 모든 내용은 **[리소스](resources.md)**에서 다룹니다.
|
||||
|
||||
## 직접 발생시킬 일이 없는 오류 {#errors-you-never-raise}
|
||||
|
||||
|
|
@ -120,19 +141,21 @@ result.structured_content # None
|
|||
이는 작성하지 않아도 되는 `raise` 문이 한 부류 통째로 있다는 뜻입니다. 타입 힌트를 직접 다시 검증하지 마세요.
|
||||
|
||||
!!! info
|
||||
이 페이지의 모든 내용은 **클라이언트**가 보는 것이며, 테스트를 작성할 때 쓸 인메모리 `Client`도
|
||||
정확히 같은 것을 봅니다. `raise_exceptions=True`조차 도구 오류를 트레이스백으로 되돌리지 않습니다.
|
||||
이 페이지에서 **클라이언트**가 보는 모든 것은, 테스트를 작성할 때 쓸 인메모리 `Client`도
|
||||
똑같이 봅니다. `raise_exceptions=True`조차 실패한 도구의 예외를 호출한 쪽으로 되돌려 주지 않습니다.
|
||||
그 플래그가 동작할 수 있는 시점에는 예외가 이미 `is_error=True` 결과가 되어 있기 때문입니다.
|
||||
결과에 대해 단언하세요. 이 패턴은 **[테스트](../get-started/testing.md)**에서 다룹니다.
|
||||
검증은 결과를 대상으로 하세요. 크래시의 트레이스백이 필요하다면 서버 로그에 있으며, pytest의
|
||||
`caplog`로 캡처할 수 있습니다. 이 패턴은 **[테스트](../get-started/testing.md)**에서 다룹니다.
|
||||
|
||||
## 요약 {#recap}
|
||||
|
||||
* 도구에서 **아무 예외**나 발생시키면 -> 호출은 `is_error=True`와 함께 메시지를 `content`에 담아 반환합니다. 모델이 읽고 재시도할 수 있습니다. 이것이 기본 동작입니다.
|
||||
* 도구에서 **`ToolError`**를 발생시키면 -> 호출은 `is_error=True`와 함께 메시지를 `content`에 담아 반환합니다. 모델이 읽고 재시도할 수 있습니다.
|
||||
* **`MCPError`**를 발생시키면 -> 호출 자체가 JSON-RPC 오류로 실패합니다. 모델은 아무것도 보지 못하고, 호스트가 처리합니다. `code`, `message`, `data`는 그대로 유지됩니다.
|
||||
* 판단 기준이 되는 질문은 **더 똑똑한 모델이었다면 이 상황을 피할 수 있었을까?**입니다. 예 -> 예외. 아니요 -> `MCPError`.
|
||||
* 판단 기준이 되는 질문은 **더 똑똑한 모델이었다면 이 상황을 피할 수 있었을까?**입니다. 예 -> `ToolError`. 아니요 -> `MCPError`.
|
||||
* 그 밖의 **모든 예외**는 크래시입니다 -> 모델에게는 `Error executing tool <name>`만 담긴 `is_error=True`가 가고, 로그에는 트레이스백이 담긴 `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