Packages
fnord
0.7.10
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/cmd/ask.ex
defmodule Cmd.Ask do
@project_not_found_error "Project not found; verify that the project has been indexed."
@template_not_found_error "Template file not found; verify that the output template file exists."
@default_rounds 1
@behaviour Cmd
@impl Cmd
def spec() do
[
ask: [
name: "ask",
about: "Ask the AI a question about the project",
options: [
project: [
value_name: "PROJECT",
long: "--project",
short: "-p",
help: "Project name",
required: true
],
directory: [
value_name: "DIRECTORY",
long: "--directory",
short: "-d",
help:
"If the project has not yet been created in fnord, the project root directory is required.",
required: false
],
question: [
value_name: "QUESTION",
long: "--question",
short: "-q",
help: "The prompt to ask the AI",
required: true
],
rounds: [
value_name: "ROUNDS",
long: "--rounds",
short: "-R",
help:
"The number of research rounds to perform. Additional rounds generally result in more thorough research.",
parser: :integer,
default: @default_rounds,
required: false
],
workers: [
value_name: "WORKERS",
long: "--workers",
short: "-w",
help: "Limits the number of concurrent OpenAI requests",
parser: :integer,
default: Cmd.default_workers()
],
follow: [
long: "--follow",
short: "-f",
help: "Follow up the conversation with another question/prompt"
],
template: [
value_name: "TEMPLATE",
long: "--template",
short: "-t",
help: """
The path to a file containing an output template for the AI to
follow when generating a response. Include '$$MOTD$$' in the
template to include the message of the day in the response.
"""
]
],
flags: [
replay: [
long: "--replay",
short: "-r",
help: "Replay a conversation (with --follow is set)"
]
]
]
]
end
@impl Cmd
def run(opts, _subcommands, _unknown) do
ai = AI.new()
start_time = System.monotonic_time(:second)
with {:ok, opts} <- validate(opts),
{:ok, template} <- read_template(opts),
{:ok, msgs, conversation} <- restore_conversation(opts),
opts <- opts |> Map.put(:template, template) |> Map.put(:msgs, msgs),
%{msgs: msgs, usage: usage, context: context} <- AI.Agent.Reason.get_response(ai, opts),
{:ok, conversation_id} <- save_conversation(conversation, msgs) do
end_time = System.monotonic_time(:second)
time_taken = end_time - start_time
pct_context_used = Float.round(usage / context * 100, 2)
UI.flush()
usage_str = Util.format_number(usage)
context_str = Util.format_number(context)
IO.puts("""
-----
- Response generated in #{time_taken} seconds
- Tokens used: #{usage_str} | #{pct_context_used}% of context window (#{context_str})
- Conversation saved with ID #{conversation_id}
""")
else
{:error, :project_not_found} ->
UI.error(@project_not_found_error)
{:error, :directory_not_found} ->
UI.error("""
The selected project has not been created in fnord and you did not provide a --directory option.
You can either:
- Create the project in fnord by running `fnord index --project <project> --directory <directory>`
- Provide a valid root --directory for the project.
""")
{:error, :template_not_found} ->
UI.error(@template_not_found_error)
{:error, :invalid_rounds} ->
UI.error("--rounds expects a positive integer")
{:error, :conversation_not_found} ->
UI.error("Conversation ID #{opts[:follow]} not found")
{:ok, :testing} ->
:ok
end
end
defp validate(opts) do
with {:ok, opts} <- validate_project(opts),
:ok <- validate_conversation(opts),
:ok <- validate_template(opts),
:ok <- validate_rounds(opts) do
{:ok, opts}
end
end
defp validate_rounds(%{rounds: rounds}) when rounds > 0, do: :ok
defp validate_rounds(_opts), do: {:error, :invalid_rounds}
defp validate_conversation(%{follow: conversation_id}) when is_binary(conversation_id) do
conversation_id
|> Store.Project.Conversation.new()
|> Store.Project.Conversation.exists?()
|> case do
true -> :ok
false -> {:error, :conversation_not_found}
end
end
defp validate_conversation(_opts), do: :ok
defp validate_project(opts) do
project = Store.get_project(opts[:project])
exists? = Store.Project.exists_in_store?(project)
indexed? = exists? and Store.Project.has_index?(project)
directory = opts[:directory]
cond do
# Selected project is indexed - all good.
indexed? ->
{:ok, opts}
# Selected project exists, but is not indexed; we can use the directory
# from the project config.
exists? ->
{:ok, %{opts | directory: project.source_root}}
# Selected project has not been created, but a directory arg was provided
# that does exist. We can use that.
!is_nil(directory) and File.exists?(directory) ->
opts[:project]
|> Store.get_project()
|> Store.Project.save_settings(directory)
|> Store.Project.create()
|> Store.Project.make_default_for_session()
{:ok, %{opts | directory: directory}}
# Selected project has not been created, and no directory arg was provided.
# We can't do anything.
true ->
{:error, :directory_not_found}
end
end
defp validate_template(%{template: nil}), do: :ok
defp validate_template(%{template: template}) do
if File.exists?(template) do
:ok
else
{:error, :template_not_found}
end
end
defp read_template(%{template: nil}), do: {:ok, nil}
defp read_template(%{template: template}), do: File.read(template)
defp get_conversation(%{follow: conversation_id}) when is_binary(conversation_id) do
Store.Project.Conversation.new(conversation_id)
end
defp get_conversation(_opts) do
Store.Project.Conversation.new()
end
defp restore_conversation(opts) do
conversation = get_conversation(opts)
messages =
if Store.Project.Conversation.exists?(conversation) do
{:ok, _ts, messages} = Store.Project.Conversation.read(conversation)
messages
else
[]
end
{:ok, messages, conversation}
end
defp save_conversation(conversation, messages) do
Store.Project.Conversation.write(conversation, messages)
UI.debug("Conversation saved to file", conversation.store_path)
UI.report_step("Conversation saved", conversation.id)
{:ok, conversation.id}
end
end