Packages
fnord
0.9.38
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/self_help/docs.ex
defmodule AI.Tools.SelfHelp.Docs do
@moduledoc """
Searches fnord's published documentation to answer questions about features,
configuration, and usage patterns. Uses a web-search-capable model scoped to
fnord's canonical documentation sites.
"""
@behaviour AI.Tools
# Build canonical URL lists from the user-facing doc lanes at compile time
# so adding a new guide or use-case doc automatically expands the search
# scope without editing this module. Two lanes are in scope:
#
# - docs/user/ - feature/reference guides.
# - docs/use-cases/ - end-to-end workflow runbooks.
#
# Both are published to hexdocs (see mix.exs, which globs the same two
# directories), so a hexdocs URL resolves for every file here. Dev docs
# under docs/dev/ are intentionally out of scope: they're architecture
# notes for contributors/LLMs working on fnord itself, not answers to
# end-user questions.
#
# Each lane's README.md is its hand-curated index and is NOT in hexdocs
# extras (excluded in mix.exs to avoid a `readme.html` filename collision
# with the top-level README.md), so a URL for either would 404. The root
# README is already represented as the hardcoded `readme.html` entry
# below; drop both README duplicates from the glob before building URLs.
@doc_paths (Path.wildcard("docs/user/*.md") ++ Path.wildcard("docs/use-cases/*.md"))
|> Enum.reject(&(&1 in ["docs/user/README.md", "docs/use-cases/README.md"]))
|> Enum.sort()
for path <- @doc_paths, do: @external_resource(path)
@hexdocs_urls [
"- https://hexdocs.pm/fnord/readme.html"
| Enum.map(@doc_paths, fn path ->
"- https://hexdocs.pm/fnord/#{Path.basename(path, ".md")}.html"
end)
]
|> Enum.join("\n")
@github_urls [
"- https://github.com/sysread/fnord/blob/main/README.md"
| Enum.map(@doc_paths, fn path ->
"- https://github.com/sysread/fnord/blob/main/#{path}"
end)
]
|> Enum.join("\n")
@system_prompt """
You are a documentation lookup tool for fnord, an AI-powered CLI for codebase research and editing.
Start with these canonical sources.
Hexdocs (preferred):
#{@hexdocs_urls}
GitHub (fallback):
#{@github_urls}
Follow links from these pages when the answer requires it.
GitHub's web UI URLs sometimes fail to load. When fetching content from GitHub,
convert to raw URLs. For example:
https://github.com/sysread/fnord/blob/main/docs/user/README.md
becomes:
https://raw.githubusercontent.com/sysread/fnord/refs/heads/main/docs/user/README.md
Provide a direct, concise answer based on the documentation content.
Include inline citations (page titles or URLs) when referencing specific information.
If the documentation does not cover the question, say so explicitly.
"""
@impl AI.Tools
def async?(), do: true
@impl AI.Tools
def is_available?(), do: true
@impl AI.Tools
def ui_note_on_request(%{"question" => q}), do: {"Researching fnord docs", q}
def ui_note_on_request(_), do: "Researching fnord docs"
@impl AI.Tools
def ui_note_on_result(_, _), do: nil
@impl AI.Tools
def tool_call_failure_message(_, _), do: :default
@impl AI.Tools
def spec do
%{
type: "function",
name: "fnord_help_docs_tool",
description: """
Search fnord's published documentation to answer questions about features, configuration, and usage.
Use this tool when the user asks about how fnord works, what a feature does, or how to configure something.
For CLI structure questions (flags, subcommands, command tree), prefer fnord_help_cli_tool instead.
""",
parameters: %{
type: "object",
additionalProperties: false,
required: ["question"],
properties: %{
"question" => %{
type: "string",
description: "The question to research in fnord's documentation."
}
}
}
}
end
@impl AI.Tools
def read_args(args), do: {:ok, args}
@impl AI.Tools
def call(args) do
with {:ok, question} <- AI.Tools.get_arg(args, "question") do
search_docs(question)
end
end
defp search_docs(question) do
AI.Completion.get(
model: AI.Model.web_search(),
web_search?: true,
messages: [
AI.Util.system_msg(@system_prompt),
AI.Util.user_msg(question)
]
)
|> case do
{:ok, %{response: response}} ->
{:ok, response}
# Completion exposes the usage count alongside :context_length_exceeded;
# collapsing it to an :error with a descriptive reason keeps the tool
# caller contract single-shaped without dropping signal for logs.
{:error, :context_length_exceeded, _usage} ->
{:error, "documentation research exceeded the context window"}
{:error, reason} ->
{:error, reason}
end
end
end