Error Handling

Copy Markdown

Sycophant uses Splode for structured error handling. All errors implement Splode.Error and are organized into three classes.

Error Classes

Invalid (caller errors)

Errors you can fix before sending the request:

ErrorWhen
MissingModelNo model argument provided
MissingCredentialsNo credentials found for the provider
InvalidParamsParameters fail Zoi schema validation
InvalidSchemaResponse schema is malformed (Zoi or JSON Schema)
InvalidResponseResponse fails JSON Schema validation
InvalidSerializationDeserialization encounters unknown type
InvalidEmbeddingInputEmbedding input is malformed
InvalidRegistrationModule registration error

Provider (remote API errors)

Errors from the LLM provider's API:

ErrorWhen
RateLimitedAPI rate limit exceeded (HTTP 429)
ServerErrorProvider returned 5xx
BadRequestProvider rejected the request (HTTP 400)
AuthenticationFailedInvalid credentials (HTTP 401/403)
ModelNotFoundModel does not exist at the provider
ContentFilteredContent was blocked by safety filters
ResponseInvalidProvider returned unparseable response

Unknown

Catch-all for errors that don't fit the above categories.

Pattern Matching

Match on specific error modules for targeted handling:

case Sycophant.generate_text("openai:gpt-4o-mini", messages) do
  {:ok, response} ->
    response.text

  {:error, %Sycophant.Error.Provider.RateLimited{}} ->
    Process.sleep(1000)
    retry()

  {:error, %Sycophant.Error.Provider.ContentFiltered{}} ->
    "Content was blocked by safety filters"

  {:error, %Sycophant.Error.Invalid.MissingCredentials{}} ->
    "Please configure your API key"

  {:error, error} ->
    Logger.error("LLM request failed: #{Splode.Error.message(error)}")
    "Something went wrong"
end

Matching by Class

Match on the error class to handle categories:

case Sycophant.generate_text("openai:gpt-4o-mini", messages) do
  {:ok, response} ->
    {:ok, response}

  {:error, %{class: :invalid}} ->
    # Caller mistake -- fix the request and retry
    {:error, :bad_request}

  {:error, %{class: :provider}} ->
    # Remote failure -- log and maybe retry
    {:error, :provider_error}

  {:error, _} ->
    {:error, :unknown}
end

Error Messages

Use Splode.Error.message/1 to get a human-readable description:

{:error, error} = Sycophant.generate_text("bad:model", messages)
Splode.Error.message(error)
#=> "Model not found: bad:model"

Telemetry Integration

Failed requests emit [:sycophant, :request, :error] telemetry events with the error and its class in the metadata. See the Telemetry guide for details.