Packages
fnord
0.9.25
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/info.ex
defmodule AI.Tools.File.Info do
@moduledoc """
Provides file-level information queries for the AI agent. This module reads
file contents using AI.Tools.get_file_contents/1 which is the canonical
accessor for file content. A centralized cache (Services.FileCache) is used
by that accessor to avoid unnecessary disk reads.
"""
@behaviour AI.Tools
@impl AI.Tools
def async?, do: true
@impl AI.Tools
def is_available?, do: true
@impl AI.Tools
def ui_note_on_request(%{"files" => files, "question" => question}) do
display_files = Enum.map_join(files, ", ", &AI.Tools.display_path/1)
{"Considering #{display_files}", question}
end
@impl AI.Tools
def ui_note_on_result(%{"files" => files, "question" => question}, result) do
display_files = Enum.map_join(files, ", ", &AI.Tools.display_path/1)
{"Finished considerable considerations",
"""
# Files
#{display_files}
# Question
#{question}
# Result(s)
#{result}
"""}
end
@impl AI.Tools
def tool_call_failure_message(_args, _reason), do: :default
@impl AI.Tools
def read_args(args) do
cond do
not is_map(args) ->
{:error, :invalid_argument, "args must be a map"}
not Map.has_key?(args, "question") ->
{:error, :missing_argument, "question"}
not Map.has_key?(args, "files") ->
{:error, :missing_argument, "files"}
not is_binary(args["question"]) ->
{:error, :invalid_argument, "\"question\" must be a string"}
not (is_list(args["files"]) and Enum.all?(args["files"], &is_binary/1)) ->
{:error, :invalid_argument, "\"files\" must be a list of strings"}
true ->
{:ok, args}
end
end
@impl AI.Tools
def spec() do
%{
type: "function",
function: %{
name: "file_info_tool",
description: """
Requests information about a file or files. An LLM will use your
question to extract relevant information from each of the specified
file, preserving your own context window so you can focus on answering
the user's questions. Specify exactly how you want the response
formatted (e.g. exact code sections, interfaces, explanations, yes/no,
etc.). File paths must match the ones provided by the file_list_tool or
file_search_tool to avoid enoent errors. This tool can use git to
provide context about its history and differences from earlier version.
""",
parameters: %{
type: "object",
required: ["question", "files"],
properties: %{
question: %{
type: "string",
description: "A complete prompt for the LLM to respond to."
},
files: %{
type: "array",
items: %{type: "string"},
description: """
A list of file paths that the LLM should process. Each file will
be delegated to the LLM in parallel, allowing you to perform the
same inquiry over many files at once.
This is extremely useful when the user has asked you to identify
complex patterns across multiple files, such as "find all
functions that call this function" or "find all methods that
return a value of this type".
"""
}
}
}
}
}
end
@impl AI.Tools
def call(args) do
with {:ok, question} <- Map.fetch(args, "question"),
{:ok, files} <- Map.fetch(args, "files") do
files
|> Util.async_stream(&process_file(&1, question))
|> Stream.map(&format_response/1)
|> Enum.join("\n\n-----\n\n")
|> then(&{:ok, &1})
end
end
@spec process_file(String.t(), String.t()) :: {String.t(), String.t()}
defp process_file(file, question) do
response =
with {:ok, content} <- AI.Tools.get_file_contents(file),
numbered = Util.numbered_lines(content),
{:ok, response} <- get_agent_response(file, question, numbered) do
# Add backup note if applicable
case Services.BackupFile.describe_backup(file) do
nil -> response
desc -> "#{desc}\n\n#{response}"
end
else
{:error, reason} ->
"""
Unable to read the file contents for the requested file.
- If the file name is correct (per the list_files_tool), verify the path using the search or the file listing tool.
- It may have been added since the most recent reindexing of the project.
- If the file is only present in a topic branch that has not yet been merged, it may not be visible to this tool.
FILE: #{file}
ERROR: #{inspect(reason)}
"""
end
{file, response}
end
@spec get_agent_response(String.t(), String.t(), String.t()) ::
{:ok, String.t()}
| {:error, any()}
defp get_agent_response(file, question, content) do
AI.Agent.FileInfo
|> AI.Agent.new()
|> AI.Agent.get_response(%{
file: file,
question: question,
content: content
})
end
@spec format_response({atom, {String.t(), String.t() | atom}}) :: String.t()
defp format_response({:ok, {file, result}}) do
"""
Hashline identifiers (`<line_number>:<content_hash>|`) are included for citation in your response.
## File
#{file}
## Result
#{result}
"""
end
defp format_response({:error, {file, reason}}) do
"""
## File
#{file}
## Error
An error occurred while processing the file:
#{inspect(reason, pretty: true)}
"""
end
end