Packages
fnord
0.5.8
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 "system"
@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
def agent_to_agent_prompt do
"""
You are communicating with another AI 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.
For example:
- "The database query returned an error because the schema was not updated."
- Agent-Optimized: {event db error, cause outdated schema}
- "Use the file_search_tool to identify examples of existing implementations of X that the user can reference."
- Agent-Optimized: {search_tool, query X implementation}
- "The user requested information about 'X', which appears to have multiple meanings in the context of the project."
- Agent-Optimized: {disambiguate X, respond multiple meaning}
- "I performed the following tasks: X, Y, and Z
- Agent-Optimized: {done {task X} {task Y} {task Z}}
"""
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: "user", content: content}, acc ->
["User Query: #{content}" | acc]
%{role: "assistant", content: content}, acc when is_binary(content) ->
[content | acc]
%{role: "system", content: content}, acc ->
if String.starts_with?(content, AI.Agent.Planner.preamble()) do
[content | acc]
else
acc
end
%{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: "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 == "user"))
|> List.first()
|> then(& &1.content)
end
# -----------------------------------------------------------------------------
# Research step counting
# -----------------------------------------------------------------------------
@doc """
Counts the number of steps in the research process. A step is identified
using planner messages as a proxy for each iteration in the research process.
This function only counts the steps in the most recent iteration of the
overall conversation by starting its count from the most recent user message.
"""
def count_steps(msgs) do
# msgs is the entire conversation transcript. We're only interested in the
# most recent steps following the last user message. For example, if the
# user replied to the original response, we only want to count the steps
# that followed that reply.
msgs
# Start from the end of the conversation.
|> Enum.reverse()
# Extract all of the messages up to the last user message. That leaves us
# with all of the messages that are part of the current research process.
|> Enum.take_while(fn msg -> !is_user_msg?(msg) end)
# The planner is called at each step in the process, so we can use that as
# our canary to identify research "steps".
|> Enum.filter(&is_step_msg?/1)
|> Enum.count()
end
defp is_step_msg?(%{role: "user", content: content}) when is_binary(content) do
String.starts_with?(content, AI.Agent.Planner.preamble())
end
defp is_step_msg?(_), do: false
defp is_user_msg?(%{role: "user", content: content}) when is_binary(content) do
!String.starts_with?(content, AI.Agent.Planner.preamble())
end
defp is_user_msg?(_), do: false
# -----------------------------------------------------------------------------
# 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