Packages

fnord

0.5.6

AI code archaeology

Current section

Files

Jump to
fnord lib ai tools answers.ex
Raw

lib/ai/tools/answers.ex

defmodule AI.Tools.Answers do
@agents_file "data/answers.yaml"
@external_resource @agents_file
@agent_defs YamlElixir.read_from_file!(@agents_file)
@agents @agent_defs |> Enum.map(& &1["name"])
@agent @agent_defs |> Enum.map(&{&1["name"], &1}) |> Map.new()
@model "gpt-4o"
@max_tokens 128_000
def agent_names(), do: @agents
def agent_description(%{"name" => name, "description" => description}) do
"- #{name}: #{description}"
end
def agent_description_list() do
@agent_defs
|> Enum.map(&agent_description/1)
|> Enum.join("\n")
end
@non_git_tools [
AI.Tools.tool_spec!("file_contents_tool"),
AI.Tools.tool_spec!("file_info_tool"),
AI.Tools.tool_spec!("file_list_tool"),
AI.Tools.tool_spec!("file_search_tool"),
AI.Tools.tool_spec!("file_spelunker_tool"),
AI.Tools.tool_spec!("notes_search_tool")
]
@git_tools [
AI.Tools.tool_spec!("git_diff_branch_tool"),
AI.Tools.tool_spec!("git_list_branches_tool"),
AI.Tools.tool_spec!("git_log_tool"),
AI.Tools.tool_spec!("git_pickaxe_tool"),
AI.Tools.tool_spec!("git_show_tool")
]
@tools @non_git_tools ++ @git_tools
# -----------------------------------------------------------------------------
# Behaviour implementation
# -----------------------------------------------------------------------------
@behaviour AI.Tools
@impl AI.Tools
def ui_note_on_request(%{"agent" => agent}) do
{"Generating a response", "#{agent}"}
end
@impl AI.Tools
def ui_note_on_result(_completion, _args), do: nil
@impl AI.Tools
def read_args(args) do
with {:ok, agent} <- read_agent(args) do
{:ok, %{"agent" => agent}}
end
end
defp read_agent(%{"agent" => agent}) when agent in @agents, do: {:ok, agent}
defp read_agent(%{"agent" => _}), do: {:error, :invalid_argument, "agent"}
defp read_agent(_), do: {:error, :missing_argument, "agent"}
@impl AI.Tools
def spec() do
%{
type: "function",
function: %{
name: "answers_tool",
description: """
Directs one of the following AI Agents to build a response for the user's query.
The Agent will receive a full transcript of all of the research performed thus far.
Each Agent is customized to provide the optimal response format for a class of user query.
""",
strict: true,
parameters: %{
additionalProperties: false,
type: "object",
required: ["agent"],
properties: %{
agent: %{
type: "string",
description: """
The name of the Agent to use to build the response. MUST be one of the following:
#{agent_description_list()}
"""
}
}
}
}
}
end
@impl AI.Tools
def call(%{messages: messages}, %{"agent" => agent}) do
with {:ok, %{response: response}} <- get_response(agent, messages) do
{:ok, response}
end
end
# ----------------------------------------------------------------------------
# Private functions
# ----------------------------------------------------------------------------
defp get_response(agent, messages) do
with {:ok, agent} <- Map.fetch(@agent, agent),
{:ok, prompt} <- Map.fetch(agent, "prompt"),
{:ok, {question, non_system_msgs}} <- get_transcript(messages),
{:ok, transcript} <- Jason.encode(non_system_msgs) do
AI.Completion.get(AI.new(),
max_tokens: @max_tokens,
model: @model,
use_planner: false,
log_msgs: false,
log_tool_calls: false,
log_tool_call_results: false,
tools: available_tools(),
messages: [
AI.Util.system_msg(prompt),
AI.Util.user_msg("""
Never refer to the "transcript".
Call it the "research" instead.
The following is a transcript of the research performed:
```json
#{transcript}
```
The user's question is:
> #{question}
""")
]
)
end
end
defp get_transcript(messages) do
with {:ok, question} <- get_user_query(messages),
non_system_msgs <- Enum.reject(messages, &is_system_msg?/1) do
{:ok, {question, non_system_msgs}}
end
end
defp is_system_msg?(%{role: "system"}), do: true
defp is_system_msg?(_), do: false
defp get_user_query(messages) do
messages
|> Enum.filter(&(&1.role == "user"))
|> List.first()
|> then(fn msg -> {:ok, msg.content} end)
end
defp available_tools() do
if Git.is_git_repo?() do
@tools
else
@non_git_tools
end
end
end