Harden OpenAI api layer

- Support rate limiting
- Add proper error handling (don't rely on exceptions)

Closes #147
This commit is contained in:
Claudio Ortolina
2026-04-05 16:01:29 +01:00
parent a5df68c7ad
commit 7b5b7e68dd
4 changed files with 37 additions and 24 deletions
+3 -1
View File
@@ -72,7 +72,9 @@ config :music_library, BraveSearch,
user_agent: user_agent,
api_cooldown: 1000
config :music_library, OpenAI, api_key: "change me"
config :music_library, OpenAI,
api_key: "change me",
api_cooldown: 250
# Configure esbuild (the version is required)
config :esbuild,
+2 -1
View File
@@ -96,7 +96,8 @@ config :music_library, OpenAI,
req_options: [
plug: {Req.Test, OpenAI.API},
max_retries: 0
]
],
api_cooldown: 0
config :phoenix_test, :endpoint, MusicLibraryWeb.Endpoint
+11 -8
View File
@@ -3,8 +3,7 @@ defmodule OpenAI.API do
@spec gpt(OpenAI.Completion.t(), OpenAI.Config.t()) :: {:ok, map()} | {:error, term()}
def gpt(completion, config) do
resp =
config
case config
|> new_request()
|> Req.merge(
url: "/v1/chat/completions",
@@ -17,13 +16,16 @@ defmodule OpenAI.API do
temperature: completion.temperature
}
)
|> Req.post!()
|> Req.post() do
{:ok, %{status: status, body: body}} when status in 200..299 ->
content = get_in(body, ["choices", Access.at(0), "message", "content"])
JSON.decode(content)
if resp.status in 200..299 do
content = get_in(resp.body, ["choices", Access.at(0), "message", "content"])
{:ok, JSON.decode!(content)}
else
{:error, resp.body}
{:ok, %{body: body}} ->
{:error, "OpenAI API error: #{inspect(body)}"}
{:error, exception} ->
{:error, "Connection error: #{Exception.message(exception)}"}
end
end
@@ -92,6 +94,7 @@ defmodule OpenAI.API do
auth: {:bearer, config.api_key}
)
|> Req.Request.merge_options(config.req_options)
|> Req.RateLimiter.attach(name: :open_ai, cooldown: config.api_cooldown)
|> Req.Request.append_request_steps(log_attempt: &log_attempt/1)
|> Req.Request.append_response_steps(log_error: &log_error/1)
end
+9 -2
View File
@@ -1,12 +1,14 @@
defmodule OpenAI.Config do
@type t :: %__MODULE__{
api_key: String.t(),
req_options: Keyword.t()
req_options: Keyword.t(),
api_cooldown: non_neg_integer()
}
@enforce_keys [:api_key]
defstruct api_key: "",
req_options: []
req_options: [],
api_cooldown: 500
@schema NimbleOptions.new!(
api_key: [
@@ -17,6 +19,11 @@ defmodule OpenAI.Config do
type: :keyword_list,
required: false,
default: []
],
api_cooldown: [
type: :integer,
required: false,
default: 500
]
)