defmodule OpenaiEx do
@moduledoc """
`OpenaiEx` is an Elixir library that provides a community-maintained client for
the OpenAI API.
The library closely follows the structure of the [official OpenAI API client libraries](https://platform.openai.com/docs/api-reference)
for [Python](https://github.com/openai/openai-python) making it easy to understand
and reuse existing documentation and code.
"""
defstruct token: nil,
organization: nil,
project: nil,
beta: nil,
base_url: "https://api.openai.com/v1",
receive_timeout: 15_000,
stream_timeout: :infinity,
finch_name: OpenaiEx.Finch,
_ep_path_mapping: &OpenaiEx._identity/1,
_http_headers: nil
@doc """
Creates a new OpenaiEx struct with the specified token and organization.
See https://platform.openai.com/docs/api-reference/authentication for details.
"""
def new(token, organization \\ nil, project \\ nil) do
headers =
[{"Authorization", "Bearer #{token}"}] ++
if(is_nil(organization),
do: [],
else: [{"OpenAI-Organization", organization}]
) ++
if(is_nil(project),
do: [],
else: [{"OpenAI-Project", project}]
)
%OpenaiEx{
token: token,
organization: organization,
project: project,
_http_headers: headers
}
end
@doc """
Create file parameter struct for use in multipart requests.
OpenAI API has endpoints which need a file parameter, such as Files and Audio.
This function creates a file parameter given a name (optional) and content or a local file path.
"""
def new_file(name: name, content: content) do
{name, content}
end
def new_file(path: path) do
{path}
end
# Globals for internal library use, **not** for public use.
@assistants_beta_string "assistants=v2"
@doc false
def with_assistants_beta(openai = %OpenaiEx{}) do
openai
|> Map.put(:beta, @assistants_beta_string)
|> Map.get_and_update(:_http_headers, fn headers ->
{headers, headers ++ [{"OpenAI-Beta", @assistants_beta_string}]}
end)
|> elem(1)
end
# Globals to allow slight changes to API
# Not public, and with no guarantee that they will continue to be supported.
@doc false
def _identity(x), do: x
@doc false
def _with_ep_path_mapping(openai = %OpenaiEx{}, ep_path_mapping) do
openai |> Map.put(:_ep_path_mapping, ep_path_mapping)
end
# https://learn.microsoft.com/en-us/azure/ai-services/openai/reference
@doc false
def _azure_ep_path_mapping(api_version) do
fn ep ->
case ep do
"/chat/completions" -> "/chat/completions?api-version=#{api_version}"
"/completions" -> "/completions?api-version=#{api_version}"
"/embeddings" -> "/embeddings?api-version=#{api_version}"
_ -> ep
end
end
end
# Azure OpenAI. Not public and with no guarantee of continued support.
def _for_azure(openai = %OpenaiEx{}, resource_name, deployment_id, api_version) do
openai
|> with_base_url(
"https://#{resource_name}.openai.azure.com/openai/deployments/#{deployment_id}"
)
|> _with_ep_path_mapping(_azure_ep_path_mapping(api_version))
end
def _for_azure(azure_api_key, resource_name, deployment_id, api_version) do
%OpenaiEx{
_http_headers: [{"api-key", "#{azure_api_key}"}]
}
|> _for_azure(resource_name, deployment_id, api_version)
end
# Globals for public use.
def with_base_url(openai = %OpenaiEx{}, base_url) do
openai |> Map.put(:base_url, base_url)
end
def with_additional_headers(openai = %OpenaiEx{}, additional_headers) do
Map.update(openai, :_http_headers, [], fn existing_headers ->
existing_headers ++ Enum.to_list(additional_headers)
end)
end
def with_receive_timeout(openai = %OpenaiEx{}, timeout)
when is_integer(timeout) and timeout > 0 do
openai |> Map.put(:receive_timeout, timeout)
end
def with_stream_timeout(openai = %OpenaiEx{}, timeout)
when is_integer(timeout) and timeout > 0 do
openai |> Map.put(:stream_timeout, timeout)
end
def with_finch_name(openai = %OpenaiEx{}, finch_name) do
openai |> Map.put(:finch_name, finch_name)
end
@doc false
def list_query_fields() do
[
:after,
:before,
:limit,
:order
]
end
end