Packages
fnord
0.9.30
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/ai/endpoint/openai.ex
defmodule AI.Endpoint.OpenAI do
@moduledoc """
OpenAI-specific endpoint implementation.
Provides the endpoint path and error classification used by AI.Endpoint's
centralized retry/backoff logic.
"""
@behaviour AI.Endpoint
@base_url "https://api.openai.com"
@impl AI.Endpoint
@spec endpoint_path() :: String.t()
def endpoint_path, do: "#{@base_url}/v1/chat/completions"
@doc """
Provider-specific error classifier for OpenAI/Cloudflare style responses.
See `AI.Endpoint.endpoint_error_classify/4` for contract details.
"""
@impl AI.Endpoint
@spec endpoint_error_classify(integer | nil, binary | nil, list | nil, term | nil) ::
:ok | {:retry, atom, non_neg_integer | nil} | {:fail, atom, binary}
def endpoint_error_classify(status, body, _headers, transport_reason) do
case {status, body, transport_reason} do
{nil, nil, :timeout} ->
{:retry, :network_glitch, nil}
{nil, nil, :closed} ->
{:retry, :network_glitch, nil}
{nil, nil, {:tls_alert, _}} ->
{:retry, :network_glitch, nil}
{nil, nil, {:ssl, _}} ->
{:retry, :network_glitch, nil}
{429, b, _} when is_binary(b) ->
cond do
throttled_json?(b) -> {:retry, :throttled, parse_try_again_ms(b)}
cloudflare_plaintext?(b) -> {:retry, :intermediary, parse_try_again_ms(b) || 300}
true -> {:retry, :throttled, nil}
end
{s, _b, _} when is_integer(s) and s >= 500 and s < 600 ->
{:retry, :server_error, nil}
{401, _b, _} ->
{:fail, :unauthorized, "Unauthorized"}
{403, _b, _} ->
{:fail, :forbidden, "Forbidden"}
_ ->
:ok
end
end
defp throttled_json?(body) do
case SafeJson.decode(body) do
{:ok, %{"error" => %{"code" => code}}} when code in ["rate_limit_exceeded", "rate_limit"] ->
true
_ ->
false
end
end
defp cloudflare_plaintext?(body) when is_binary(body) do
Regex.match?(~r/cloudflare|please\s+try\s+again/i, body)
end
defp parse_try_again_ms(body) when is_binary(body) do
case Regex.run(~r/try\s+again\s+in\s+(\d+)ms/i, body) do
[_, ms] -> max(1, String.to_integer(ms))
_ -> nil
end
end
end