Packages
fnord
0.4.3
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 System.get_env("OPENAI_API_KEY")
@api_timeout 45_000
@embedding_model "text-embedding-3-large"
@summary_model "gpt-4o-mini"
@summary_prompt """
You are a command line program that summarizes the content of a file, whether
it is code or documentation, like an intelligent `ctags`.
Based on the type of file you receive, produce the following data:
### For Code Files:
- **Synopsis**
- **Languages present in the file**
- **Business logic and behaviors**
- **List of symbols**
- **Map of calls to other modules**
### For Documentation Files (e.g., README, Wiki Pages, General Documentation):
- **Synopsis**: A brief overview of what the document covers.
- **Topics and Sections**: A list of main topics or sections in the document.
- **Definitions and Key Terms**: Any specialized terms or jargon defined in the document.
- **Links and References**: Important links or references included in the document.
- **Key Points and Highlights**: Main points or takeaways from the document.
Restrict your analysis to only what appears in the file. This is used to
generate a search index, so we want to avoid false positives from external
sources.
Respond ONLY with your markdown-formatted summary.
"""
@callback new() :: struct()
@callback get_embeddings(struct(), String.t()) :: {:ok, [String.t()]} | {:error, term()}
@callback get_summary(struct(), String.t(), String.t()) :: {:ok, String.t()} | {:error, term()}
@behaviour AI
@impl AI
@doc """
Create a new AI instance. Instances share the same client connection.
"""
def new() do
openai = OpenaiEx.new(@api_key) |> OpenaiEx.with_receive_timeout(@api_timeout)
%AI{client: openai}
end
@impl AI
@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) do
embeddings =
split_text(text, 8192)
|> Enum.map(fn chunk ->
OpenaiEx.Embeddings.create(
ai.client,
OpenaiEx.Embeddings.new(
model: @embedding_model,
input: chunk
)
)
|> case do
{:ok, %{"data" => [%{"embedding" => embedding}]}} -> embedding
_ -> nil
end
end)
|> Enum.filter(fn x -> not is_nil(x) end)
{:ok, embeddings}
end
@impl AI
@doc """
Get a summary of the given text. The text is truncated to 128k tokens to
avoid exceeding the model's input limit. Returns a summary of the text.
"""
def get_summary(ai, file, text) do
input = "# File name: #{file}\n```\n#{text}\n```"
# The model is limited to 128k tokens input, so, for now, we'll just
# truncate the input if it's too long.
input = truncate_text(input, 128_000)
OpenaiEx.Chat.Completions.create(
ai.client,
OpenaiEx.Chat.Completions.new(
model: @summary_model,
messages: [
OpenaiEx.ChatMessage.system(@summary_prompt),
OpenaiEx.ChatMessage.user(input)
]
)
)
|> case do
{:ok, %{"choices" => [%{"message" => %{"content" => summary}}]}} -> {:ok, summary}
{:error, reason} -> {:error, reason}
response -> {:error, "unexpected response: #{inspect(response)}"}
end
end
defp truncate_text(text, max_tokens) do
if String.length(text) > max_tokens do
String.slice(text, 0, max_tokens)
else
text
end
end
defp split_text(input, max_tokens) do
Gpt3Tokenizer.encode(input)
|> Enum.chunk_every(max_tokens)
|> Enum.map(&Gpt3Tokenizer.decode(&1))
end
end