Packages
fnord
0.9.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/tools/file/search.ex
defmodule AI.Tools.File.Search do
@max_search_results 25
@behaviour AI.Tools
@impl AI.Tools
def async?, do: true
@doc """
This tool requires that the project has been indexed to use. If the project
has not been indexed, the tool should not be made available.
"""
@impl AI.Tools
def is_available?(), do: AI.Tools.has_indexed_project()
@impl AI.Tools
def ui_note_on_request(args), do: {"Semantic search", args["query"]}
@impl AI.Tools
def ui_note_on_result(_args, _result), do: nil
@impl AI.Tools
def tool_call_failure_message(_args, _reason), do: :default
@impl AI.Tools
def read_args(%{"query" => query}), do: {:ok, %{"query" => query}}
@impl AI.Tools
def spec() do
%{
type: "function",
function: %{
name: "file_search_tool",
description: """
The search tool implements a robust semantic search to find project
files matching your query input. Each file in the search index was
primed for semantic search using an LLM to generate contextual content
related to the file contents, allowing for improved fuzzy searches.
This tool is ideal when you need to find files related to a topic but
do not have an exact string to match. For example, you can search for
"user authentication" to find files related to user login, regardless
of whether they contain that exact phrase.
If you are looking for an exact pattern, your best bet is to use grep
or a similar command via the `shell_tool`. This tool is optimized for
identifying content when you are not sure what you are looking for. For
example, you can search for "which endpoints require user
authentication?" or "where are feature flags defined?" to find files
related to those topics, even if the exact phrases do not appear in the
file names or contents.
Note that this tool does not have access to historical data or commit
messages. It only searches the most recently indexed version of the
project.
""",
strict: true,
parameters: %{
additionalProperties: false,
type: "object",
required: ["query"],
properties: %{
query: %{
type: "string",
description: "The search query string."
}
}
}
}
}
end
@impl AI.Tools
def call(args) do
with {:ok, query} <- Map.fetch(args, "query"),
{:ok, matches} <- search(query),
{:ok, index_state_msg} <- index_state() do
{:ok, build_response(matches, index_state_msg)}
end
end
# Build the complete tool response by formatting matches and the index state banner.
defp build_response(matches, index_state_msg) do
header = "[search_tool]"
body = matches |> Enum.map(&format_entry/1) |> Enum.join("\n-----\n")
[header, body, "-----", index_state_msg]
|> Enum.join("\n")
end
# Format a single search result entry as markdown with file, similarity, and summary.
defp format_entry({file, score, data}) do
"""
# `#{file}` (cosine similarity: #{score})
#{data["summary"]}
"""
end
# -----------------------------------------------------------------------------
# Returns the current state of the project index, including new, stale, and
# deleted files.
# -----------------------------------------------------------------------------
defp index_state do
with {:ok, project} <- Store.get_project() do
%{new: new, stale: stale, deleted: deleted} = Store.Project.index_status(project)
msg =
"""
The results of this search may be affected by the state of the project index.
The current index state is:
- New files (not yet indexed): #{length(new)}
- Stale files (outdated index): #{length(stale)}
- Deleted files (indexed but deleted in project): #{length(deleted)}
If you are seeing unexpected search results, try reindexing with the `file_reindex_tool` tool.
"""
{:ok, msg}
end
end
# -----------------------------------------------------------------------------
# Searches the database for matches to the search query. Returns a list of
# `{file, score, data}` tuples.
# -----------------------------------------------------------------------------
defp search(query) do
%{
detail: true,
limit: @max_search_results,
query: query
}
|> Search.Files.new()
|> Search.Files.get_results()
|> case do
{:ok, results} ->
results
|> Enum.map(fn {entry, score, data} ->
{entry.rel_path, score, data}
end)
|> then(&{:ok, &1})
{:error, reason} ->
{:error, reason}
end
end
end