lib/api.ex

defmodule ExOpenAi.Api do
  @moduledoc """
  The main module for the ExOpenAi API client.

  Provides a wrapper around Httpoison for making requests to the OpenAI API.
  """

  use HTTPoison.Base

  alias ExOpenAi.Config
  alias ExOpenAi.Parser
  alias ExOpenAi.URLGenerator, as: URL
  alias __MODULE__

  # Image generation can take some time, so we need to increase the timeout
  @default_timeout 25_000

  @type data :: map | list

  @doc """
  Create a new resource in the OpenAI API with a POST request.
  ## Examples
      ExOpenAi.Api.create(ExOpenAi.Completion, [prompt: "tell me a joke", max_tokens: 500])
      {:ok, %Completion{ ... }}
      ExOpenAi.Api.create(ExOpenAi.Completion, [])
      {:error, %{"model" => "model is required"}, 400}
  """
  @spec create(atom, data :: list | map, list) :: Parser.success() | Parser.error()
  def create(module, data, options \\ []) do
    data = format_data(data)

    module
    |> URL.infer_url()
    |> Api.post!(data, options, recv_timeout: @default_timeout)
    |> Parser.parse(module, options[:simple])
  end

  @doc """
  Builds the authorization header for the request.
  """
  @spec auth_header(options :: list) :: list
  def auth_header(options \\ []) do
    auth_header([], {options[:api_key], options[:organization]})
  end

  @doc """
  Builds the actual authorization header for the request.

  ## Examples

    iex> ExOpenAi.Api.auth_header([], {"api123", "org345"})
    [{"Authorization", "Bearer api123"}, {"OpenAI-Organization", "org345"}]

    iex> ExOpenAi.Api.auth_header([], {"api123", nil})
    [{"Authorization", "Bearer api123"}]
  """
  @spec auth_header(header :: list, tuple()) :: list
  def auth_header(headers, {api_key, organzation}) do
    headers
    |> put_header("Authorization", "Bearer #{api_key}")
    |> put_header("OpenAI-Organization", organzation)
  end

  defp put_header(headers, _key, value) when is_nil(value), do: headers
  defp put_header(headers, key, value), do: headers ++ [{key, value}]

  @spec format_data(list | map) :: binary
  def format_data(data)

  def format_data(data) when is_list(data) do
    data
    |> Map.new()
    |> Jason.encode!()
  end

  def format_data(data) when is_map(data), do: Jason.encode!(data)

  ###
  # HTTPoison API
  ###
  def process_request_headers(headers \\ []) do
    headers
    |> Keyword.put(:"Content-Type", "application/json")
    |> auth_header({Config.api_key(), Config.organization()})
  end
end