sigoden/aichat · error
{message}
Error message
{message} What it means
Raised by `catch_error` (src/client/common.rs:530) when a non-2xx API response body has a top-level `message` string field but none of the earlier structured shapes (`error` object, `errors[0]`, `data[0].error`, `detail`+`status`) match. The provider's `message` text is surfaced directly to the caller. This is the last structured fallback before the generic 'Invalid response data' error.
Solutions
- Act on the provider's message text contained in the error (invalid key, model not found, rate limited, etc.).
- Confirm the API key and environment variable (<CLIENT>_API_KEY) are set correctly.
- Check the requested model name exists on the provider (run fetch_models or check /v1/models).
- If it's a rate-limit message, wait and retry with backoff.
Example fix
// before
let client = create_openai_compatible_client("http://localhost:8080")?;
// after: ensure server is running and model loaded
// curl http://localhost:8080/v1/models first; then retry with a valid model name Defensive patterns
Strategy: try-catch
Validate before calling
// preflight: confirm model exists on provider let names = fetch_models(api_base, Some(&api_key)).await?; assert!(names.contains(&model_name));
Type guard
fn is_message_error(err: &anyhow::Error) -> bool {
// catch_error surfaces provider `message` verbatim
!err.to_string().is_empty()
} Try / catch
match client.embeddings(&req).await {
Err(e) if e.to_string().to_lowercase().contains("rate limit") => tokio::time::sleep(d).await, retry(),
Err(e) => return Err(e),
Ok(r) => Ok(r),
} Prevention
- Set <CLIENT>_API_KEY correctly and test with curl first.
- Confirm requested model names exist via the provider's models endpoint.
- Handle 429 messages with exponential backoff.
- Keep provider clients matched to provider endpoints so structured error parsing works.
When it happens
Trigger: chat_completions, embeddings, claude/openai variants, or streaming calls hitting an API that reports errors as `{"message": "..."}` (common for Anthropic-style or custom REST providers) with a non-2xx status, without accompanying `status`/`detail` fields.
Common situations: Anthropic or Claude-compatible gateways returning `{"type":"error","message":...}` variants; custom OpenAI-compatible servers (Ollama, LM Studio, vLLM) reporting quota/auth/model errors via `message`; rate-limit responses from proxied endpoints.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
AI-assisted analysis of sigoden/aichat@82976d349a (2026-09-09).
Data as JSON: /api/errors/0b7468fdc91025c3.
Report an issue: GitHub.
Appendix: source
Thrown at src/client/common.rs:530
error.get("code").and_then(|v| v.as_u64()),
json_str_from_map(error, "message"),
) {
bail!("{message} (status: {code})")
}
} else if let Some(error) = data[0]["error"].as_object() {
if let (Some(status), Some(message)) = (
json_str_from_map(error, "status"),
json_str_from_map(error, "message"),
) {
bail!("{message} (status: {status})")
}
} else if let (Some(detail), Some(status)) = (data["detail"].as_str(), data["status"].as_i64())
{
bail!("{detail} (status: {status})");
} else if let Some(error) = data["error"].as_str() {
bail!("{error}");
} else if let Some(message) = data["message"].as_str() {
bail!("{message}");
}
bail!("Invalid response data: {data} (status: {status})");
}
pub fn json_str_from_map<'a>(
map: &'a serde_json::Map<String, Value>,
field_name: &str,
) -> Option<&'a str> {
map.get(field_name).and_then(|v| v.as_str())
}
async fn set_client_models_config(client_config: &mut Value, client: &str) -> Result<String> {
if let Some(provider) = ALL_PROVIDER_MODELS.iter().find(|v| v.provider == client) {
let models: Vec<String> = provider
.models
.iter()
.filter(|v| v.model_type == "chat")
.map(|v| v.name.clone())View on GitHub (pinned to 82976d349a)