Обработка ошибок
Каждый метод сервиса возвращает обычный error. Сбои на транспортном уровне
(DNS, отказ соединения, таймаут) возвращаются обёрнутыми в fmt.Errorf; всё,
что отклонил сам API Z.AI, возвращается как *client.APIError со
структурированными полями, по которым можно ветвить логику, вместо разбора
строк сообщения.
resp, err := c.Chat().Create(ctx, req)
if err != nil {
var apiErr *client.APIError
if errors.As(err, &apiErr) {
fmt.Printf("[%d] %s\n", apiErr.Code, apiErr.UserMessage)
switch apiErr.Category {
case client.ErrorCategoryAuth:
// bad/expired key — don't retry, tell the user
case client.ErrorCategoryQuota:
// out of balance/quota — don't retry, surface to the user
case client.ErrorCategoryRateLimit:
if apiErr.IsRetriable {
// the client already retries this internally up to
// Config.MaxRetries — you'll only see it here if retries
// were exhausted or disabled (MaxRetries: -1)
}
}
return
}
// transport-level failure — network, DNS, timeout
}
Поля APIError:
| Поле | Назначение |
|---|---|
HTTPStatus |
HTTP-код состояния |
Code |
Бизнес-код ошибки Z.AI (int) |
Message |
Исходное сообщение от API |
Category |
Одна из категорий ниже |
UserMessage |
Более понятное, заранее подготовленное описание |
IsRetriable |
Считает ли собственная логика повторов клиента эту ошибку временной |
RequestID |
Для поддержки/отладки, когда API его вернул |
Вспомогательные предикаты: IsAuthError(), IsRateLimitError(),
IsQuotaError(), IsParameterError(), IsServerError() — эквивалентны прямой
проверке .Category, предоставлены для читаемости в местах вызова.
Поведение повторов из коробки
Client.doRequest (используется каждым сервисом) уже повторяет ошибки
429/5xx/сети с экспоненциальной задержкой, джиттером и поддержкой
Retry-After, до Config.MaxRetries (по умолчанию 3; установите -1 для
отключения). Обычно вам не нужен собственный цикл повторов —
APIError.IsRetriable подскажет, исчерпала ли ошибка, дошедшая до вашего
кода, эти повторные попытки.
Справочник кодов ошибок
| Code | Constant | Category | Повторяемый |
|---|---|---|---|
| 1000 | ErrCodeAuthFailed |
Auth | Нет |
| 1001 | ErrCodeAuthNotFound |
Auth | Нет |
| 1003 | ErrCodeAuthTokenExpired |
Auth | Нет |
| 1005 | ErrCodeAuthNeed2FA |
Auth | Нет |
| 1113 | ErrCodeInsufficientBalance |
Quota | Нет |
| 1302 | ErrCodeRateLimitReached |
RateLimit | Да |
| 1305 | ErrCodeServiceOverloaded |
Server | Да |
| 1308 | ErrCodeUsageLimitReached |
Quota | Нет |
| 1309 | ErrCodeCodingPlanExpired |
Quota | Нет |
| 1310 | ErrCodeWeeklyMonthlyExhausted |
Quota | Нет |
| 1311 | ErrCodeModelNotIncluded |
Quota | Нет |
| 1313 | ErrCodeFairUsageViolation |
Quota | Нет |
| 1314 | ErrCodeEnterpriseExpired |
Quota | Нет |
| 1315 | ErrCodeEnterpriseKeyOnly |
Quota | Нет |
| 1316–1321 | usage-limit variants | Quota | Нет |
| 1210 | ErrCodeInvalidParameter |
Parameter | Нет |
| 1211 | ErrCodeUnknownModel |
Parameter | Нет |
| 1212 | ErrCodeMethodNotSupported |
Parameter | Нет |
| 1213 | ErrCodeParameterMissing |
Parameter | Нет |
| 1214 | ErrCodeParameterInvalid |
Parameter | Нет |
| 1215 | ErrCodeParametersConflict |
Parameter | Нет |
| 1221 | ErrCodeAPITakenOffline |
Parameter | Нет |
| 1222 | ErrCodeAPINotExist |
Parameter | Нет |
| 1261 | ErrCodePromptTooLong |
Parameter | Нет |
| 1301 | ErrCodeUnsafeContent |
Content | Нет |
| 1220 | ErrCodeNoPermission |
Permission | Нет |
| -1 | ErrCodeInternalError |
Server | Да |
| 1200 | ErrCodeAPICallError |
Server | Да |
| 1230 | ErrCodeProcessError |
Server | Да |
| 1234 | ErrCodeNetworkError |
Server | Да |
Код ошибки, неизвестный этому клиенту, по умолчанию получает
ErrorCategoryServer с IsRetriable: true — разумное значение по умолчанию
(неизвестные коды трактуются как временные проблемы сервера), но именно для
перечисленных выше констант задано специально подобранное UserMessage и
решение о повторе.
Источник истины: pkg/client/errors.go.
Особенность: 200 со встроенным сбоем
Несколько эндпоинтов (Invoke/AsyncResult у агентов) возвращают HTTP 200
даже в случае неудачи на бизнес-уровне — сбой встроен в тело ответа, а не
сигнализируется через HTTP-статус. Ненулевой error из этих методов означает,
что не сработал транспортный уровень; для обнаружения сбоя на бизнес-уровне
внутри успешного ответа проверяйте resp.Failed() (или напрямую resp.Error/
resp.Status). Оба типа ответов документируют это в методе Failed() —
легко пропустить, если проверять только err != nil.