Packages
fnord
0.8.82
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/completion/compaction.ex
defmodule AI.Completion.Compaction do
@moduledoc """
Compacts conversation history for AI completion requests. Compacts by
summarizing tool calls and assistant messages following each user message,
while preserving user messages and special system messages.
"""
@spec compact(map(), map()) :: map()
@spec compact(map(), map()) :: map()
def compact(state, opts) do
with {:ok, {name_msg, msgs}} <- extract_name_and_msgs(state),
{:ok, segments} <- build_segments(msgs),
{:ok, first_pass} <- run_first_pass(state, name_msg, segments, opts),
{:ok, final_state} <- maybe_run_second_pass(state, name_msg, first_pass, opts) do
final_state
else
{:error, _reason} ->
# On any internal error, log and preserve the original state
UI.warn(
"Compaction skipped",
"Internal error during compaction; original history preserved"
)
state
end
end
defp extract_name_and_msgs(state) do
messages = Map.get(state, :messages, [])
name_msg =
Enum.find(messages, fn
%{role: "system", content: content} when is_binary(content) ->
content =~ ~r/Your name is .+\./
_ ->
false
end)
msgs =
case name_msg do
nil -> messages
msg -> List.delete(messages, msg)
end
{:ok, {name_msg, msgs}}
end
defp build_segments(msgs) do
{segments, last_user, last_non_user} =
Enum.reduce(msgs, {[], nil, []}, fn msg, {segs, curr_user, curr_non_user} ->
if Map.get(msg, :role) == "user" do
new_segs =
if curr_user do
segs ++ [%{user: curr_user, non_user: curr_non_user}]
else
segs
end
{new_segs, msg, []}
else
{segs, curr_user, curr_non_user ++ [msg]}
end
end)
segments =
if last_user do
segments ++ [%{user: last_user, non_user: last_non_user}]
else
segments
end
{:ok, segments}
end
defp run_first_pass(_state, name_msg, segments, _opts) do
{head_segments, last_segment_list} =
case segments do
[] -> {[], []}
_ -> Enum.split(segments, -1)
end
{compacted_head, ctx_after_head} =
Enum.reduce(head_segments, {[], []}, fn %{user: user, non_user: non_user}, {acc, ctx} ->
to_summarize = ctx ++ [user] ++ non_user
summary_result =
AI.Agent.Compactor
|> AI.Agent.new(named?: false)
|> AI.Agent.get_response(%{messages: to_summarize})
case summary_result do
{:ok, [summary_msg]} ->
msgs = [user, summary_msg]
{acc ++ msgs, ctx ++ msgs}
_ ->
msgs = [user] ++ non_user
{acc ++ msgs, ctx ++ msgs}
end
end)
raw_last_msgs =
case last_segment_list do
[%{user: user, non_user: non_user}] -> [user] ++ non_user
_ -> []
end
first_pass_messages =
if(name_msg, do: [name_msg], else: []) ++
compacted_head ++
raw_last_msgs
new_usage_1 =
first_pass_messages
|> Enum.map(&Map.get(&1, :content))
|> Enum.filter(&is_binary/1)
|> Enum.map(&AI.PretendTokenizer.guesstimate_tokens/1)
|> Enum.sum()
{:ok,
%{
messages: first_pass_messages,
usage: new_usage_1,
compacted_head: compacted_head,
ctx_after_head: ctx_after_head,
last_segment_list: last_segment_list
}}
end
defp maybe_run_second_pass(state, name_msg, first_pass, opts) do
%{
messages: first_msgs,
usage: usage_1,
compacted_head: compacted_head,
ctx_after_head: ctx_after_head,
last_segment_list: last_segment_list
} = first_pass
target_pct = Map.get(opts, :target_pct, 0.8)
if last_segment_list == [] or
meets_target?(state.model.context, usage_1, target_pct) do
UI.info("Compaction run", "Simplified partial compaction completed")
{:ok, %{state | messages: first_msgs, usage: usage_1}}
else
{compacted_last, _} =
Enum.reduce(last_segment_list, {[], ctx_after_head}, fn %{user: user, non_user: non_user},
{acc, ctx} ->
to_summarize = ctx ++ [user] ++ non_user
summary_result =
AI.Agent.Compactor
|> AI.Agent.new(named?: false)
|> AI.Agent.get_response(%{messages: to_summarize})
case summary_result do
{:ok, [summary_msg]} ->
msgs = [user, summary_msg]
{acc ++ msgs, ctx ++ msgs}
_ ->
msgs = [user] ++ non_user
{acc ++ msgs, ctx ++ msgs}
end
end)
second_pass_messages =
if(name_msg, do: [name_msg], else: []) ++
compacted_head ++
compacted_last
new_usage_2 =
second_pass_messages
|> Enum.map(&Map.get(&1, :content))
|> Enum.filter(&is_binary/1)
|> Enum.map(&AI.PretendTokenizer.guesstimate_tokens/1)
|> Enum.sum()
UI.info("Compaction run", "Simplified partial compaction completed")
{:ok, %{state | messages: second_pass_messages, usage: new_usage_2}}
end
end
defp meets_target?(context, usage, target_pct) when is_number(context) and context > 0 do
usage <= target_pct * context
end
defp meets_target?(_context, _usage, _target_pct), do: true
end