Packages
fnord
0.7.6
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/openai.ex
defmodule AI.OpenAI do
@moduledoc """
This module provides functions to interact with the OpenAI API, including
getting embeddings and completions.
"""
@api_key_error "Missing OpenAI API key. Please set the OPENAI_API_KEY environment variable."
@embedding_endpoint "https://api.openai.com/v1/embeddings"
@completion_endpoint "https://api.openai.com/v1/chat/completions"
defstruct [
:api_key,
:http_options
]
def new(http_options) do
workers = Application.get_env(:fnord, :workers, Cmd.default_workers())
%__MODULE__{
api_key: get_api_key(),
http_options:
[hackney_options: [pool: :openai, max_connections: workers]]
|> Keyword.merge(http_options)
}
end
def get_embedding(openai, model, input) do
headers = [get_auth_header(openai)]
payload =
%{encoding_format: "float", input: input}
|> Map.merge(get_model_params(model))
Http.post_json(@embedding_endpoint, headers, payload, openai.http_options)
|> case do
{:ok, %{"data" => [%{"embedding" => embedding}]}} -> {:ok, embedding}
{:error, error} -> {:error, error}
end
end
def get_completion(openai, model, msgs, tools \\ nil) do
headers = [get_auth_header(openai)]
payload =
%{messages: msgs}
|> Map.merge(get_model_params(model))
|> Map.merge(
case tools do
nil -> %{}
tools -> %{tools: tools}
end
)
Http.post_json(@completion_endpoint, headers, payload, openai.http_options)
|> case do
{:ok, response} -> get_completion_response(response)
{:error, error} -> get_error_response(error)
end
end
# -----------------------------------------------------------------------------
# Private functions
# -----------------------------------------------------------------------------
defp get_api_key() do
case System.get_env("OPENAI_API_KEY", nil) do
nil -> raise @api_key_error
api_key -> api_key
end
end
defp get_auth_header(openai) do
{"Authorization", "Bearer #{openai.api_key}"}
end
defp get_model_params(model) when is_binary(model) do
%{model: model}
end
defp get_model_params(%AI.Model{reasoning: reasoning} = model) when is_nil(reasoning) do
%{model: model.model}
end
defp get_model_params(%AI.Model{reasoning: reasoning} = model) do
%{model: model.model, reasoning_effort: reasoning}
end
defp get_completion_response(%{"choices" => [%{"message" => response}]}) do
get_completion_response(response)
end
defp get_completion_response(%{"tool_calls" => tool_calls}) do
{:ok, :tool, Enum.map(tool_calls, &get_tool_call/1)}
end
defp get_completion_response(%{"content" => response}) do
{:ok, :msg, response}
end
defp get_tool_call(%{"id" => id, "function" => %{"name" => name, "arguments" => args}}) do
%{id: id, function: %{name: name, arguments: args}}
end
defp get_error_response(:closed), do: {:error, "Connection closed"}
defp get_error_response(:timeout), do: {:error, "Connection timed out"}
defp get_error_response({http_status, json_error_string}) do
json_error_string
|> Jason.decode()
|> case do
{:ok, %{"error" => %{"code" => code, "message" => msg}}} ->
{:error, %{http_status: http_status, code: code, message: msg}}
{:ok, error} ->
{:error, %{http_status: http_status, error: inspect(error, pretty: true)}}
{:error, _} ->
{:error, %{http_status: http_status, error: json_error_string}}
end
end
end