Tencent/WeKnora · error

Zhipu API returned status %d (%s): %s

Error message

Zhipu API returned status %d (%s): %s

What it means

When the Zhipu API returns a non-200 status, zhipuHTTPError first tries to decode a structured error object from the body; if present it reports status, error code, and message in one formatted error. This is the structured variant of the non-200 handling path.

Source

Thrown at internal/infrastructure/web_search/zhipu.go:231

	}
	return time.Time{}, false
}

func readZhipuResponseBody(reader io.Reader) ([]byte, error) {
	body, err := io.ReadAll(io.LimitReader(reader, maxZhipuResponseBytes+1))
	if err != nil {
		return nil, fmt.Errorf("failed to read Zhipu response: %w", err)
	}
	if len(body) > maxZhipuResponseBytes {
		return nil, fmt.Errorf("Zhipu response exceeds %d bytes", maxZhipuResponseBytes)
	}
	return body, nil
}

func zhipuHTTPError(statusCode int, body []byte) error {
	var response zhipuSearchResponse
	if err := json.Unmarshal(body, &response); err == nil && (response.Error.Code != "" || response.Error.Message != "") {
		return fmt.Errorf("Zhipu API returned status %d (%s): %s", statusCode, response.Error.Code, response.Error.Message)
	}
	detail := strings.TrimSpace(string(body))
	if len(detail) > 4096 {
		detail = detail[:4096]
	}
	if detail == "" {
		return fmt.Errorf("Zhipu API returned status %d", statusCode)
	}
	return fmt.Errorf("Zhipu API returned status %d: %s", statusCode, detail)
}

type zhipuSearchRequest struct {
	SearchQuery  string `json:"search_query"`
	SearchEngine string `json:"search_engine"`
	SearchIntent bool   `json:"search_intent"`
	Count        int    `json:"count"`
	ContentSize  string `json:"content_size"`
}

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Read the embedded error code: 401/403 → fix API key; 429 → back off and retry; 4xx → fix request params
  2. Confirm the Authorization header carries a valid Bearer token (p.apiKey is set)
  3. Check Zhipu service status if 5xx persists
  4. Implement retry-with-backoff for 429/5xx only

Example fix

// before
if err != nil { log.Println(err); os.Exit(1) } // opaque
// after
if err != nil {
    var apiErr *ZhipuStatusError
    if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusTooManyRequests {
        time.Sleep(backoff); return retry(ctx)
    }
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

if !strings.HasPrefix(apiURL, "https://") { return errors.New("zhipu endpoint must be https") }
if apiKey == "" { return errors.New("zhipu api key required") }

Type guard

func statusCodeOf(err error) (int, bool) {
    m := regexp.MustCompile(`status (\d{3})`).FindStringSubmatch(err.Error())
    if m == nil { return 0, false }
    c, _ := strconv.Atoi(m[1]); return c, true
}

Try / catch

results, err := provider.Search(ctx, q)
if err != nil {
    if sc, ok := statusCodeOf(err); ok && (sc == 429 || sc >= 500) { return retryWithBackoff(ctx, q) }
    return err
}

Prevention

When it happens

Trigger: Calling Search() when Zhipu returns 4xx/5xx AND the body contains a parseable {error:{code,message}} object.

Common situations: 401/403 from an invalid or expired API key; 429 rate limiting; 400 from invalid search_engine or query parameters.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/b79979dc6f00b417. Report an issue: GitHub.