Skip to content
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

* `chat_github()` now uses `chat_openai_compatible()` for improved compatibility, and `models_github()` now supports custom `base_url` configuration (@D-M4rk, #877).
* `chat_ollama()` now contains a slot for `top_k` within the `params` argument (@frankiethull).
* `chat_github()` and other OpenAI-compatible providers now robustly handle rate limit errors (HTTP 429) where the API returns a text body despite sending a JSON content-type header. This prevents crashes and allows the built-in retry logic to function correctly (@D-M4rk, #901).

# ellmer 0.4.0

Expand Down
28 changes: 18 additions & 10 deletions R/provider-openai-compatible.R
Original file line number Diff line number Diff line change
Expand Up @@ -153,18 +153,26 @@ method(base_request_error, ProviderOpenAICompatible) <- function(
req
) {
req_error(req, body = function(resp) {
if (resp_content_type(resp) == "application/json") {
error <- resp_body_json(resp)$error
if (is_string(error)) {
error
} else if (is.list(error)) {
error$message
} else {
prettify(resp_body_string(resp))
is_json <- identical(resp_content_type(resp), "application/json")

if (is_json) {
# Try parsing JSON, but fall back to text if it fails (e.g. 429 text body)
body <- tryCatch(resp_body_json(resp), error = function(e) NULL)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest making a is_json() function like:

is_json <- function(text) {
  tryCatch({
     jsonlite::read_json(text)
     TRUE
  }), function(err) FALSE
}

Then alway use resp_string_string()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated identical, for is_json() helper approach—would you like me to implement that as well, or is the current tryCatch around resp_body_json() sufficient for now?


if (!is.null(body)) {
error <- body$error
if (is_string(error)) {
return(error)
} else if (is.list(error)) {
return(error$message)
} else {
return(prettify(resp_body_string(resp)))
}
}
} else if (resp_content_type(resp) == "text/plain") {
resp_body_string(resp)
}

# Fallback for text/plain, other types, or failed JSON parsing
resp_body_string(resp)
})
}

Expand Down