Packages
fnord
0.4.30
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.ex
defmodule AI do
@moduledoc """
AI is a behavior module that defines the interface for interacting with
OpenAI's API. It provides a common interface for the various OpenAI-powered
operations used by the application.
"""
defstruct [
:client,
:api_key
]
@type t :: %__MODULE__{
client: %OpenaiEx{}
}
@api_timeout 5 * 60 * 1000
@default_max_attempts 3
@retry_interval 250
@doc """
Create a new AI instance. Instances share the same client connection.
"""
def new() do
api_key = System.get_env("OPENAI_API_KEY")
if is_nil(api_key) do
raise "Missing OpenAI API key. Please set the OPENAI_API_KEY environment variable."
end
openai =
api_key
|> OpenaiEx.new()
|> OpenaiEx.with_receive_timeout(@api_timeout)
%AI{client: openai}
end
# -----------------------------------------------------------------------------
# Completions
# -----------------------------------------------------------------------------
def get_completion(ai, options \\ []) do
with {:ok, model} <- Keyword.fetch(options, :model),
{:ok, messages} <- get_messages(options),
{:ok, tools} <- get_tools(options) do
max = Keyword.get(options, :attempts, @default_max_attempts)
args = [model: model, messages: messages] ++ tools
request = OpenaiEx.Chat.Completions.new(args)
get_completion(ai, request, max, 1)
end
end
defp get_completion(_ai, _request, max, attempt) when attempt > max do
{:error, "Request timed out after #{attempt} attempts."}
end
defp get_completion(ai, request, max, attempt) do
ai.client
|> OpenaiEx.Chat.Completions.create(request)
|> case do
{:ok, %{"choices" => [event]}} ->
{:ok, event}
{:error, %OpenaiEx.Error{message: "Request timed out."}} ->
Process.sleep(@retry_interval)
get_completion(ai, request, max, attempt + 1)
{:error, %OpenaiEx.Error{message: msg}} ->
{:error, msg}
end
end
defp get_tools(opts) do
Keyword.get(opts, :tools, nil)
|> case do
nil -> {:ok, []}
tools -> {:ok, [tools: tools]}
end
end
defp get_messages(opts) do
Keyword.get(opts, :messages, nil)
|> case do
nil ->
with {:ok, system_prompt} <- Keyword.fetch(opts, :system),
{:ok, user_prompt} <- Keyword.fetch(opts, :user) do
{:ok, [AI.Util.system_msg(system_prompt), AI.Util.user_msg(user_prompt)]}
end
messages ->
{:ok, messages}
end
end
# -----------------------------------------------------------------------------
# Embeddings
# -----------------------------------------------------------------------------
@token_limit 8192
@model "text-embedding-3-large"
@doc """
Get embeddings for the given text. The text is split into chunks of 8192
tokens to avoid exceeding the model's input limit. Returns a list of
embeddings for each chunk.
"""
def get_embeddings(ai, text, options \\ []) do
max = Keyword.get(options, :attempts, @default_max_attempts)
text
|> AI.Util.split_text(@token_limit)
|> Enum.map(&OpenaiEx.Embeddings.new(model: @model, input: &1))
|> Enum.reduce_while([], fn request, embeddings ->
ai
|> get_embedding(request, max, 1)
|> case do
{:ok, embedding} -> {:cont, [embedding | embeddings]}
{:error, reason} -> {:halt, {:error, reason}}
end
end)
|> case do
{:error, reason} -> {:error, reason}
embeddings -> {:ok, Enum.reverse(embeddings)}
end
end
defp get_embedding(_ai, _request, max, attempt) when attempt > max do
{:error, "Request timed out after #{attempt} attempts."}
end
defp get_embedding(ai, request, max, attempt) do
ai.client
|> OpenaiEx.Embeddings.create(request)
|> case do
{:ok, %{"data" => [%{"embedding" => embedding}]}} ->
{:ok, embedding}
{:error, %OpenaiEx.Error{message: "Request timed out."}} ->
Process.sleep(@retry_interval)
get_embedding(ai, request, max, attempt + 1)
{:error, %OpenaiEx.Error{message: msg}} ->
{:error, msg}
end
end
end