Packages
fnord
0.4.13
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/ask.ex
defmodule Ask do
defstruct [
:opts,
:ai,
:question,
:matched_files,
:tool_call,
:buffer,
:last_chunk,
:messages
]
@tool_call_acc %{
id: "",
func: "",
args: "",
output: ""
}
def new(opts) do
%Ask{
opts: opts,
ai: AI.new(),
question: opts.question,
matched_files: [],
tool_call: @tool_call_acc,
buffer: "",
last_chunk: "",
messages: [
AI.system_message(),
AI.user_message(opts.question)
]
}
end
def run(ask) do
ask
|> start_output()
|> send_request()
|> end_output()
end
def reset_buffers(ask) do
%Ask{ask | buffer: "", last_chunk: "", tool_call: @tool_call_acc}
end
defp start_output(%Ask{opts: %{quiet: true}} = ask) do
ask
end
defp start_output(ask) do
Owl.LiveScreen.add_block(:answer, state: "Assistant is thinking...")
ask
end
defp output(%Ask{opts: %{quiet: true}, last_chunk: data} = ask) do
IO.write(data)
ask
end
defp output(ask) do
Owl.LiveScreen.update(:answer, ask.buffer)
Owl.LiveScreen.await_render()
ask
end
defp end_output(%Ask{opts: %{quiet: true}} = ask) do
IO.puts("")
ask
end
defp end_output(ask) do
Owl.IO.puts("")
ask
end
# -----------------------------------------------------------------------------
# Stream processing
# -----------------------------------------------------------------------------
def send_request(%{buffer: "", tool_call: @tool_call_acc} = ask) do
ask.ai
|> AI.stream(ask.messages)
|> process_stream(ask)
end
def send_request(ask) do
ask.ai
|> AI.stream(ask.messages)
|> process_stream(ask |> reset_buffers())
end
defp process_stream(stream, ask) do
stream
|> Stream.flat_map(& &1)
|> Enum.reduce(ask, fn event, ask ->
handle_response(ask, event)
end)
end
# -----------------------------------------------------------------------------
# General event handling
# -----------------------------------------------------------------------------
# Strip out the :data and "choices" wrappers
defp handle_response(ask, %{data: %{"choices" => event}}) do
handle_response(ask, event)
end
# -----------------------------------------------------------------------------
# Message events
# -----------------------------------------------------------------------------
# The message is complete
defp handle_response(ask, [%{"finish_reason" => "stop"}]) do
%Ask{ask | messages: ask.messages ++ [AI.assistant_message(ask.buffer)]}
|> reset_buffers()
end
# Extract the message content
defp handle_response(ask, [%{"delta" => %{"content" => content}}])
when not is_nil(content) do
%Ask{ask | last_chunk: content, buffer: ask.buffer <> content}
|> output()
end
# -----------------------------------------------------------------------------
# Tool call events
# -----------------------------------------------------------------------------
defp handle_response(ask, [
%{
"delta" => %{
"tool_calls" => [
%{"id" => id, "function" => %{"name" => name}}
]
}
}
]) do
tool_call =
ask.tool_call
|> Map.put(:id, id)
|> Map.put(:func, name)
%Ask{ask | tool_call: tool_call}
end
# Collect tool call fragments (both "name" and "arguments")
defp handle_response(ask, [%{"delta" => %{"tool_calls" => [%{"function" => frag}]}}]) do
# Extract fragments of "id", "name", and "arguments" if they exist
name_frag = Map.get(frag, "name", "")
args_frag = Map.get(frag, "arguments", "")
# Accumulate the fragments into the tool call
tool_call =
ask.tool_call
|> Map.update!(:func, &(&1 <> name_frag))
|> Map.update!(:args, &(&1 <> args_frag))
%Ask{ask | tool_call: tool_call}
end
# Handle the completion of tool calls
defp handle_response(ask, [%{"finish_reason" => "tool_calls"}]) do
with {:ok, output} <- handle_tool_call(ask) do
tool_request =
AI.assistant_tool_message(
ask.tool_call.id,
ask.tool_call.func,
ask.tool_call.args
)
tool_response = AI.tool_message(ask.tool_call.id, ask.tool_call.func, output)
messages = ask.messages ++ [tool_request, tool_response]
%Ask{ask | messages: messages, tool_call: %{ask.tool_call | output: output}}
|> send_request()
end
end
# Catch-all for unhandled events
defp handle_response(ask, event) do
IO.inspect(event, label: "UNHANDLED")
ask
end
# -----------------------------------------------------------------------------
# Tool call outputs
# -----------------------------------------------------------------------------
# Function to execute the tool call
defp handle_tool_call(%{tool_call: %{func: "search_tool", args: args_json}} = ask) do
with {:ok, args} <- Jason.decode(args_json),
{:ok, query} <- Map.fetch(args, "query") do
ask.opts
|> Map.put(:concurrency, ask.opts.concurrency)
|> Map.put(:detail, true)
|> Map.put(:limit, 10)
|> Map.put(:query, query)
|> Search.new()
|> Search.get_results()
|> Enum.map(fn {file, score, data} ->
"""
-----
# File: #{file} | Score: #{score}
## Summary
#{data["summary"]}
## Contents
```
#{data["contents"]}
```
"""
end)
|> Enum.join("\n")
|> then(&{:ok, &1})
else
{:error, reason} -> {:error, reason}
end
end
defp handle_tool_call(_ask) do
{:error, :unhandled_tool_call}
end
end