Current section

Files

Jump to
oapi_open_ai lib open_ai client.ex
Raw

lib/open_ai/client.ex

defmodule OpenAi.Client do
@moduledoc """
Module responsible for making requests to the OpenAI API.
And transforming the response to the expected response type.
"""
alias OpenAi.Config
@doc """
Makes a request to the OpenAI API
based on the provided parameters.
Returns the response body transformed
to the expected response type.
Primarily used by the operation modules.
"""
def request(
%{
method: method,
url: url,
response: expected_response
} = params
) do
%HTTPoison.Request{
method: method,
url: Path.join(Config.base_url(), url),
headers:
[
{"Authorization", "Bearer #{Config.api_key()}"}
]
|> Kernel.++(params |> add_content_type_header())
|> Kernel.++(Config.http_headers!()),
params: params |> Map.get(:query),
options: Config.http_options() ++ Map.get(params, :opts, [])
}
|> attach_body(params)
|> Config.http_client().request()
|> transform_to_expected_response(expected_response)
end
defp add_content_type_header(%{request: request} = _params) do
request
|> Enum.find(fn
{"multipart/form-data", _} -> true
_ -> false
end)
|> case do
{"multipart/form-data", {_struct, _}} ->
[{"Content-Type", "multipart/form-data"}]
_ ->
[{"Content-Type", "application/json"}]
end
end
defp add_content_type_header(_) do
[{"Content-Type", "application/json"}]
end
defp attach_body(%{method: method} = request, %{
request: [{"multipart/form-data", _}],
body: %{file: file_path} = body
})
when method in [:post, :put, :patch, :delete, :options] do
body =
body
|> Map.delete(:file)
|> remove_nil_values()
|> Map.to_list()
|> Enum.map(fn {k, v} -> {"#{k}", v} end)
multi =
{:file, "#{file_path}",
{"form-data", [name: "file", filename: file_path |> Path.basename()]}, []}
request
|> Map.put(:body, {:multipart, [multi | body]})
end
defp attach_body(%{method: method} = request, %{body: body})
when method in [:post, :put, :patch, :delete, :options] and
(body |> is_map or body |> is_list) do
body =
body
|> remove_nil_values()
|> Poison.encode!()
request |> Map.put(:body, body)
end
defp attach_body(request, _params) do
request
end
defp remove_nil_values(struct) when struct |> is_struct() do
struct
|> Map.from_struct()
|> remove_nil_values()
end
defp remove_nil_values(map) when map |> is_map do
map
|> Enum.filter(fn {_, v} -> v != nil end)
|> Enum.reduce(%{}, fn {k, v}, acc ->
Map.put(acc, k, v |> remove_nil_values())
end)
end
defp remove_nil_values(list) when list |> is_list do
list
|> Enum.filter(fn v -> v != nil end)
|> Enum.map(&remove_nil_values/1)
end
defp remove_nil_values(v), do: v
defp transform_to_expected_response(
{:ok,
%HTTPoison.Response{
status_code: response_status_code,
body: body
}},
[{expected_status_code, {:string, :generic}}]
)
when response_status_code == expected_status_code do
{:ok, body}
end
defp transform_to_expected_response(
{:ok,
%HTTPoison.Response{
status_code: response_status_code,
body: body
}},
[{expected_status_code, expected_type}]
)
when response_status_code == expected_status_code do
body
|> Poison.decode()
|> case do
{:ok, decoded_body} ->
decoded_body |> __MODULE__.Value.transform(expected_type)
{:error, _} ->
{:error, "Failed to decode response body for #{inspect(expected_type)}"}
end
end
defp transform_to_expected_response(
{:ok,
%HTTPoison.Response{
status_code: response_status_code,
body: body
}},
_
) do
body =
body
|> Poison.decode()
|> case do
{:ok, res} -> res
{:error, _} -> body
end
{:error,
%{
status_code: response_status_code,
body: body
}}
end
defp transform_to_expected_response(
{:ok, %HTTPoison.AsyncResponse{} = resp},
_expected_response
) do
resp |> __MODULE__.Stream.transform()
end
defp transform_to_expected_response(
{:error, %HTTPoison.Error{reason: reason}},
_expected_response
) do
{:error, reason}
end
defp transform_to_expected_response({:error, error}, _expected_response) do
{:error, error}
end
end