Packages
fnord
0.7.9
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/util.ex
defmodule AI.Util do
@role_system "developer"
@role_user "user"
@role_assistant "assistant"
@role_tool "tool"
def note_format_prompt do
"""
Your audience is another AI LLM agent.
Optimize token usage and efficiency using the following guidelines:
- Avoid human-specific language conventions like articles, connecting phrases, or redundant words.
- Use a structured, non-linear format with concise key-value pairs, hierarchical lists, or markup-like tags.
- Prioritize key information first, followed by secondary details as needed.
- Use shorthand or domain-specific terms wherever possible.
- Ensure the output is unambiguous but not necessarily human-readable.
Respond STRICTLY in the `topic` format below. **Do not deviate.**
**Required format:**
- Use this structure: `{topic <topic> {fact <fact>} {fact <fact>} ...}`
- `<topic>` and `<fact>` are either:
- Bare string: a short string that does NOT contain `{` or `}`
- Quoted string: a string bounded by `"`s which may contain escaped `"`
- Place exactly ONE topic per line.
- Failure to adhere to the exact format will result in an invalid output.
Example output:
{topic dog {fact is mammal} {fact 4 legs} {fact strong sense smell}}
{topic cat {fact is mammal} {fact 4 legs} {fact assholes}}
{topic bird {fact is avian} {fact 2 wings} {fact some fly}}
{topic "sea creature" {fact is aquatic} {fact "can be delicious"} {fact "not always a \"fish\""}}
"""
end
@spec validate_notes_string(String.t()) ::
{:ok, [String.t()]}
| {:error, :invalid_format}
def validate_notes_string(notes_string) do
notes_string
|> parse_topic_list()
|> Enum.reduce_while([], fn text, acc ->
if Store.Project.Note.is_valid_format?(text) do
{:cont, [text | acc]}
else
{:halt, :invalid_format}
end
end)
|> case do
:invalid_format -> {:error, :invalid_format}
notes -> {:ok, notes}
end
end
@spec parse_topic_list(String.t()) :: [String.t()]
def parse_topic_list(input_str) do
input_str
|> String.trim("```")
|> String.trim("'''")
|> String.trim("\"\"\"")
|> String.trim()
|> String.split("\n")
|> Enum.map(&String.trim/1)
end
# Computes the cosine similarity between two vectors
def cosine_similarity(vec1, vec2) do
if length(vec1) != length(vec2) do
raise ArgumentError, """
Vectors must have the same length to compute cosine similarity.
- Left: #{length(vec1)}
- Right: #{length(vec2)}
"""
end
dot_product = Enum.zip(vec1, vec2) |> Enum.reduce(0.0, fn {a, b}, acc -> acc + a * b end)
magnitude1 = :math.sqrt(Enum.reduce(vec1, 0.0, fn x, acc -> acc + x * x end))
magnitude2 = :math.sqrt(Enum.reduce(vec2, 0.0, fn x, acc -> acc + x * x end))
if magnitude1 == 0.0 or magnitude2 == 0.0 do
0.0
else
dot_product / (magnitude1 * magnitude2)
end
end
# -----------------------------------------------------------------------------
# Building transcripts
# -----------------------------------------------------------------------------
@doc """
Builds a "transcript" of the research process by converting the messages into
text. This is most commonly used to generate a transcript of the research
performed in a conversation for various agents and tool calls.
"""
def research_transcript(msgs) do
# Make a lookup for tool call args by id
tool_call_args = build_tool_call_args(msgs)
msgs
# Remove the first message, which is the orchestrating agent's system prompt
|> Enum.drop(1)
# Convert messages into text
|> Enum.reduce([], fn
%{role: @role_user, content: content}, acc ->
["User Query: #{content}" | acc]
%{role: @role_assistant, content: content}, acc when is_binary(content) ->
[content | acc]
# Not supported in reasoning models, but still may be present in older
# conversations.
%{role: "system", content: _}, acc ->
acc
%{role: @role_system, content: _content}, acc ->
acc
%{role: @role_tool, tool_call_id: id, name: name, content: content}, acc ->
args = tool_call_args[id] |> Jason.encode!()
text = """
Performed research using the tool, `#{name}`, with the following arguments:
`#{args}`
Result:
#{content}
"""
[text | acc]
_msg, acc ->
acc
end)
|> Enum.reverse()
|> Enum.join("\n-----\n")
end
defp build_tool_call_args(msgs) do
msgs
|> Enum.reduce(%{}, fn msg, acc ->
case msg do
%{role: @role_assistant, content: nil, tool_calls: tool_calls} ->
tool_calls
|> Enum.map(fn %{id: id, function: %{arguments: args}} -> {id, args} end)
|> Enum.into(acc)
_ ->
acc
end
end)
end
@doc """
Extracts the user's *most recent* query from the conversation messages.
"""
def user_query(messages) do
messages
|> Enum.filter(&(&1.role == @role_user))
|> List.first()
|> then(& &1.content)
end
# -----------------------------------------------------------------------------
# Messages
# -----------------------------------------------------------------------------
@doc """
Creates a system message object, used to define the assistant's behavior for
the conversation.
"""
def system_msg(msg) do
%{
role: @role_system,
content: msg
}
end
@doc """
Creates a user message object, representing the user's input prompt.
"""
def user_msg(msg) do
%{
role: @role_user,
content: msg
}
end
@doc """
Creates an assistant message object, representing the assistant's response.
"""
def assistant_msg(msg) do
%{
role: @role_assistant,
content: msg
}
end
@doc """
This is the tool outputs message, which must come immediately after the
`assistant_tool_msg/3` message with the same `tool_call_id` (`id`).
"""
def tool_msg(id, func, output) do
%{
role: @role_tool,
name: func,
tool_call_id: id,
content: output
}
end
@doc """
This is the tool call message, which must come immediately before the
`tool_msg/3` message with the same `tool_call_id` (`id`).
"""
def assistant_tool_msg(id, func, args) do
%{
role: @role_assistant,
content: nil,
tool_calls: [
%{
id: id,
type: "function",
function: %{
name: func,
arguments: args
}
}
]
}
end
end