Packages
fnord
0.7.15
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: %AI.OpenAI{}
}
@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
client = AI.OpenAI.new(recv_timeout: @api_timeout)
%AI{client: client}
end
# -----------------------------------------------------------------------------
# Completions
# -----------------------------------------------------------------------------
def get_completion(ai, model, msgs, tools) do
request = [ai.client, model, msgs, tools]
do_get_completion(ai, request, @default_max_attempts, 1)
end
defp do_get_completion(_ai, _request, max, attempt) when attempt > max do
{:error, "Request timed out after #{attempt} attempts."}
end
defp do_get_completion(ai, request, max, attempt) do
if attempt > 1, do: Process.sleep(@retry_interval)
AI.OpenAI
|> apply(:get_completion, request)
|> case do
{:error, :timeout} -> do_get_completion(ai, request, max, attempt + 1)
etc -> etc
end
end
# -----------------------------------------------------------------------------
# Embeddings
# -----------------------------------------------------------------------------
@embeddings_model AI.Model.embeddings()
@doc """
Identical to `get_embeddings/2`, but raises an error if the request fails.
"""
def get_embeddings!(ai, text) do
with {:ok, embeddings} <- get_embeddings(ai, text) do
embeddings
else
{:error, reason} -> raise reason
end
end
@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.
This function will retry the request up to `@default_max_attempts` times.
Each time it makes a new attempt, it dials back the number of tokens
processed by 10% to avoid hitting the model's input limit.
"""
def get_embeddings(ai, text, attempt \\ 1)
def get_embeddings(_ai, _text, attempt) when attempt > @default_max_attempts do
{:error, :max_attempts_reached}
end
def get_embeddings(ai, text, attempt) do
if AI.PretendTokenizer.over_max_for_openai_embeddings?(text) do
{:error, :input_too_large}
else
# Since we only guesstimate token counts, we dial back the context window
# by an increasingly larger factor with each attempt.
reduction_factor =
case attempt do
1 -> 0.75
2 -> 0.50
_ -> 0.25
end
chunks = AI.PretendTokenizer.chunk(text, @embeddings_model, reduction_factor)
AI.OpenAI.get_embedding(ai.client, @embeddings_model, chunks)
|> case do
{:ok, embeddings} ->
# For each dimension, find the maximum value across all embeddings.
# This isn't necessarily the _most_ accurate, but it selects the
# highest rating for each dimension found in the file, which should be
# reasonable for semantic searching.
embeddings
|> Enum.reduce_while([], fn
embedding, [] -> {:cont, embedding}
embedding, acc -> {:cont, Enum.zip_with(acc, embedding, fn a, b -> max(a, b) end)}
end)
|> then(&{:ok, &1})
{:error, reason} ->
if attempt < @default_max_attempts do
Process.sleep(@retry_interval)
get_embeddings(ai, text, attempt + 1)
else
{:error, reason}
end
end
end
end
end