Packages
fnord
0.7.21
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/default/rollups.ex
defmodule AI.Agent.Default.Rollups do
@behaviour AI.Agent
@model AI.Model.fast()
@prompt """
You are given a sequence of JSONL-encoded messages representing a conversation.
Each line is one JSON object.
There are four types of lines:
- User messages ({"role": "user", ...})
- Assistant messages ({"role": "assistant", ...})
- Tool messages ({"role": "tool", ...})
- Timestamp markers ({"timestamp": "<ISO8601 string>"})
Lines are in chronological order.
Some lines may be timestamp markers.
Timestamp markers are inserted into the conversation at irregular intervals, when it is determined that a new interaction has begun.
Use the timestamp markers to to determine the boundaries of each month.
Your task is to generate a summary for each calendar month covered in the input.
For each month in which there is any activity (at least one message), output a summary of that month's conversation activity, focusing on the most important topics, actions, questions, and decisions.
If there are recurring themes or changes over the month, note them.
If a month only contains trivial or system/tool messages, note this in the summary.
Be particularly careful to preserve solutions to problems, useful troubleshooting steps, significant decisions, user preferences, and important facts about the user or their projects.
Do not generate summaries for months with no activity.
Do not include messages, timestamps, or any other content in your output.
Do not include summaries for months not present in the input.
Do not attempt to deduplicate or merge months.
Be concise, but preserve important context for future reference.
Your output must be valid JSONL, one object per line, each in this format:
```
{"type": "summary", "month": "YYYY-MM", "content": "<your summary>"}
```
It is an error to include any explanation, preamble, or reasoning in your output.
Respond ONLY with the JSONL summary lines, without any additional text or formatting.
"""
@impl AI.Agent
def get_response(_opts) do
with {:ok, conversation_jsonl} <- get_conversation_history_jsonl(),
{:ok, summaries} <- get_summaries(conversation_jsonl) do
{:ok, summaries}
else
{:error, reason} -> {:error, reason}
end
end
defp get_summaries(conversation_jsonl, attempt \\ 1)
defp get_summaries(_, attempt) when attempt > 3 do
{:error, :max_attempts_reached}
end
defp get_summaries(conversation_jsonl, attempt) do
AI.Completion.get(
model: @model,
prompt: @prompt,
messages: [
AI.Util.system_msg(@prompt),
AI.Util.user_msg(conversation_jsonl)
]
)
|> case do
{:ok, %{response: response}} ->
msgs =
response
|> String.split("\n")
|> Enum.reject(&(&1 == ""))
|> Enum.map(fn line ->
with {:ok, json} <- Jason.decode(line) do
json
else
_ -> nil
end
end)
# Verify whether the response contains valid JSONL summaries.
# If the response is not valid JSONL, retry
if Enum.any?(msgs, &is_nil/1) do
get_summaries(conversation_jsonl, attempt + 1)
else
{:ok, msgs}
end
{:error, reason} ->
UI.warn("AI model failed to generate conversation rollups: #{reason}")
# If the AI model fails, retry up to 3 times
get_summaries(conversation_jsonl, attempt + 1)
end
end
defp get_conversation_history_jsonl do
Store.DefaultProject.Conversation.read_file()
# Reverse the stream to process in reverse chronological order
|> Enum.reverse()
# Drop messages until we get to the most recent timestamp marker
|> Enum.drop_while(fn
%{"timestamp" => _} -> false
_ -> true
end)
# The list now contains everything up to (and including) the most recent
# timestamp marker.
|> Enum.reverse()
|> Enum.map(&Jason.encode!/1)
|> Enum.join("\n")
|> case do
"" -> {:error, :no_conversation_history}
jsonl -> {:ok, jsonl}
end
end
end