Packages
fnord
0.8.72
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/agent/compactor.ex.0.1.bak
defmodule AI.Agent.Compactor do
@behaviour AI.Agent
@model AI.Model.large_context(:balanced)
@max_attempts 3
@min_length 512
@target_ratio 0.8
# Minimum acceptable tokens for a compacted summary; prevent trivial context wipes
# Note: set to >0 in production if you want to reject trivial summaries; tests expect tiny outputs.
@min_summary_tokens 0
@system_prompt """
You are an AI Agent in a larger system.
You will be presented with a transcript of a conversation between a user and an AI assistant, along with any research the assistant has done.
Your task: Reformat into compact meeting minutes while preserving all content needed for context.
Do not use smart quotes, smart apostrophes, emojis, or other special characters.
Read through the messages and reason through a new, more compact narrative that preserves the key points and context.
Consider the conversation from the user's perspective: what expectations would they have for the assistant's memory and understanding of the conversation?
Preserve decision points, assumptions, trade-offs, mistakes and corrections, and include rationales leading to any state changes.
Use the following output template:
# Original User Prompt
[full text of the original user prompt]
# Research and Responses
[outline of facts, findings, and conclusions from the research portions of the conversation]
# Conversation Timeline
[
Present a timeline of the conversation, listing each message with its role and a brief summary of its content.
Messages at the end of the conversation should include WAY more detail than those at the beginning, reflecting the decision cascade and evolving context
Don't waste space with formatting or JSON; just use plain text.
]
# Continuation Context
[
What was the assistant doing before the conversation grew too long?
Your response will form the complete prompt for the LLM's next response.
This section MUST guarantee that the LLM continues exactly where it left off.
]
"""
@impl AI.Agent
def get_response(%{messages: [%{role: "developer", content: @system_prompt} | _]}) do
raise "Refusing to compact a compaction prompt"
end
def get_response(%{messages: messages} = opts) do
attempts = Map.get(opts, :attempts, 0)
tx_list = transcript(messages, [])
latest_user_prompt =
messages
|> Enum.reverse()
|> Enum.find_value(fn
%{role: "user", content: content} when is_binary(content) -> content
_ -> nil
end)
prompt_to_use =
case latest_user_prompt do
nil -> @system_prompt <> "\nLATEST_USER_PROMPT:\n"
content -> @system_prompt <> "\nLATEST_USER_PROMPT:\n" <> content
end
# Early guard: empty transcript -> skip model call and retries
if tx_list == [] do
{:error, :empty_after_filtering}
else
transcript_json = Jason.encode!(tx_list, pretty: true)
original_length = byte_size(transcript_json)
UI.info(
"Compaction starting",
"Transcript JSON size: #{original_length} bytes. Recent messages preserved; proceeding with compaction."
)
UI.info(
"Summarizing conversation transcript (expected)",
"We are summarizing assistant/tool transcript for compactness. All user messages are preserved verbatim and excluded from this transcript. The latest user prompt is provided separately."
)
AI.Accumulator.get_response(
model: @model,
prompt: prompt_to_use,
input: transcript_json
)
|> case do
{:ok, %{response: response}} ->
summary =
"""
Summary of conversation and research thus far:
#{response}
"""
# Guard against trivial, near-empty summaries that would wipe context
new_tokens = AI.PretendTokenizer.guesstimate_tokens(summary)
if new_tokens < @min_summary_tokens do
UI.error(
"Compaction failed",
"Summary too small (#{new_tokens} < #{@min_summary_tokens} tokens)"
)
{:error, :summary_too_small}
else
new_length = byte_size(summary)
difference = original_length - new_length
percent = difference / original_length * 100.0
UI.info("""
Compaction results:
Original: #{Util.format_number(original_length)} bytes
Compacted: #{Util.format_number(new_length)} bytes
Savings: #{percent}% (#{Util.format_number(difference)} bytes)
""")
cond do
original_length < @min_length ->
UI.debug("Compaction retries skipped", "Original is too small to justify retries")
if new_length < original_length do
summary |> AI.Util.system_msg() |> then(&{:ok, [&1]})
else
UI.warn("Compaction failed", "Summary is larger than original; aborting")
{:error, :compaction_failed}
end
new_length > original_length * @target_ratio and attempts < @max_attempts ->
UI.warn(
"Compaction insufficient",
"Attempting another pass (#{attempts + 1}/#{@max_attempts})"
)
get_response(%{messages: messages, attempts: attempts + 1})
true ->
UI.debug("Compacted conversation", summary)
if new_length < original_length do
summary |> AI.Util.system_msg() |> then(&{:ok, [&1]})
else
UI.warn("Compaction failed", "Summary is larger than original; aborting")
{:error, :compaction_failed}
end
end
end
{:error, reason} ->
{:error, reason}
end
end
end
defp transcript([], acc), do: Enum.reverse(acc)
defp transcript([%{role: "system"} | rest], acc), do: transcript(rest, acc)
defp transcript([%{role: "developer"} | rest], acc), do: transcript(rest, acc)
defp transcript([%{role: "user"} | rest], acc), do: transcript(rest, acc)
defp transcript([%{role: "tool", name: "notify_tool"} | rest], acc), do: transcript(rest, acc)
defp transcript([%{role: "tool", name: name, content: content} | rest], acc) do
transcript(rest, [%{role: "tool", name: name, content: content} | acc])
end
defp transcript([%{role: "assistant", content: nil} | rest], acc), do: transcript(rest, acc)
defp transcript([%{role: "assistant", content: content} = msg | rest], acc)
when is_binary(content) do
if String.starts_with?(content, "<think>") do
# skip internal reasoning
transcript(rest, acc)
else
# include assistant message
transcript(rest, [msg | acc])
end
end
defp transcript([msg | rest], acc), do: transcript(rest, [msg | acc])
end