Packages
fnord
0.4.17
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/agent/search.ex
defmodule AI.Agent.Search do
@moduledoc """
When `AI.Agent.Answers` receives a tool call request from the assistant, it
will use this module to perform the search using `Search`. The contents of
the files matched are then sent to the `AI.Agent.RelevantFileSections`
module, which will identify the aspects of the file that are relevant to the
user's query as well as the `AI.Agent.Answers`' search criteria.
This is not a true AI agent, but rather a module that performs the search and
coordinates the use of the `AI.Agent.RelevantFileSections` agent to identify
the relevant sections of each of the file results.
"""
defstruct [
:ai,
:opts,
:user_query,
:search_query
]
@max_search_results 5
@max_retries 3
def new(ai, user_query, search_query, opts) do
%AI.Agent.Search{
ai: ai,
opts: opts,
user_query: user_query,
search_query: search_query
}
end
@doc """
Performs a concurrent search on behalf of the `AI.Agent.Answers` agent. It
uses the `Search` module to search the database for matches to the search
query. Then, it concurrently feeds the contents of each matched file to the
`AI.Agent.RelevantFileSections` agent, which will identify the relevant
sections of the file.
"""
def search(agent) do
with {:ok, matches} <- get_matches(agent),
{:ok, queue} <- get_queue(agent),
{:ok, results} <- process_matches(queue, matches) do
Queue.shutdown(queue)
Queue.join(queue)
{:ok, results}
end
end
# -----------------------------------------------------------------------------
# Starts and returns a process pool using the `Queue` module. It is
# configured to retrieve the relevant sections of the file using the
# `AI.Agent.RelevantFileSections` agent which match the user's query and the
# `AI.Agent.Answers` agent's search criteria.
# -----------------------------------------------------------------------------
defp get_queue(agent) do
Queue.start_link(agent.opts.concurrency, fn {file, score, data} ->
get_entry_agent_response(agent, {file, score, data})
end)
end
# -----------------------------------------------------------------------------
# Processes the matches returned by the `Search` module. It then uses the
# `Queue` module to concurrently request that the
# `AI.Agent.RelevantFileSections` agent identify the relevant sections of the
# file that match the user's query and the `AI.Agent.Answers` agent's search
# criteria.
# -----------------------------------------------------------------------------
defp process_matches(queue, matches) do
with_retries(@max_retries, fn ->
matches
|> Queue.map(queue)
|> Enum.reduce([], fn
{:ok, result}, acc ->
[result | acc]
{:error, reason}, acc ->
IO.inspect(:stderr, reason, label: "search agent warning")
acc
end)
|> then(&{:ok, &1})
end)
end
defp with_retries(max_attempts, fun) do
with_retries(max_attempts, 1, fun)
end
defp with_retries(max_attempts, current_attempt, fun) do
if current_attempt > max_attempts do
{:error, :max_attempts_reached}
else
try do
fun.()
rescue
error ->
IO.inspect(:stderr, error,
label: "search agent error (attempt #{current_attempt}/#{max_attempts})"
)
Process.sleep(200)
with_retries(max_attempts, current_attempt + 1, fun)
end
end
end
# -----------------------------------------------------------------------------
# Reads the file contents, get the relevant sections using the
# `AI.Agent.RelevantFileSections` agent, and returns a formatted string
# response that includes the file and match information, the summary of the
# file (that was generated when the file was indexed by the `AI.Summarizer`
# agent), and the relevant sections of the file that were identified by the
# `AI.Agent.RelevantFileSections` agent.
# -----------------------------------------------------------------------------
defp get_entry_agent_response(agent, {file, score, data}) do
with {:ok, file_content} <- File.read(file),
{:ok, sections} <- get_relevant_sections(agent, file_content) do
result = """
-----
# File: #{file} | Score: #{score}
## Summary
#{data["summary"]}
## Relevant Sections
#{sections}
"""
{:ok, result}
end
end
# -----------------------------------------------------------------------------
# Uses the `AI.RelevantFileSections` agent to identify the sections of the
# file that are relevant to the user's and `AI.Agent.Answers` agent's queries.
# -----------------------------------------------------------------------------
defp get_relevant_sections(agent, file_content) do
AI.Agent.RelevantFileSections.new(
agent.ai,
agent.user_query,
agent.search_query,
file_content
)
|> AI.Agent.RelevantFileSections.get_summary()
end
# -----------------------------------------------------------------------------
# Searches the database for matches to the search query. Returns a list of
# `{file, score, data}` tuples.
# -----------------------------------------------------------------------------
defp get_matches(agent) do
agent.opts
|> Map.put(:concurrency, agent.opts.concurrency)
|> Map.put(:detail, true)
|> Map.put(:limit, @max_search_results)
|> Map.put(:query, agent.search_query)
|> Search.new()
|> Search.get_results()
|> then(&{:ok, &1})
end
end