Nous.Transcript (nous v0.13.3)
View SourceLightweight conversation history compaction.
Provides utility functions for managing conversation message lists
without requiring an LLM call. For LLM-powered summarization,
see Nous.Plugins.Summarization.
Usage
messages = [msg1, msg2, msg3, ..., msg20]
# Keep last 10 messages, summarize the rest
compacted = Nous.Transcript.compact(messages, 10)
# Auto-compact: every 20 messages, keep last 10
compacted = Nous.Transcript.maybe_compact(messages, every: 20, keep_last: 10)
# Auto-compact: at 80% of token budget
compacted = Nous.Transcript.maybe_compact(messages,
token_budget: 128_000,
keep_last: 10
)
# Both triggers (whichever fires first)
compacted = Nous.Transcript.maybe_compact(messages,
every: 30,
token_budget: 128_000,
threshold: 0.8,
keep_last: 10
)
# Run compaction in the background (returns a Task)
task = Nous.Transcript.compact_async(messages, 10)
compacted = Task.await(task)
# Fire-and-forget with callback
Nous.Transcript.compact_async(messages, 10, fn compacted ->
send(self(), {:compacted, compacted})
end)
# Estimate token count
tokens = Nous.Transcript.estimate_tokens("Hello world, how are you?")
#=> 5
Summary
Functions
Compacts a message list by keeping the last keep_last messages.
Compacts messages asynchronously under Nous.TaskSupervisor.
Compacts messages in the background with a callback.
Estimates total tokens across a list of messages.
Estimates the token count of a string using word count as a proxy.
Automatically compacts messages when a trigger condition is met.
Like maybe_compact/2 but runs asynchronously with a callback.
Checks if a message list should be compacted based on a threshold.
Functions
@spec compact([Nous.Message.t()], pos_integer()) :: [Nous.Message.t()]
Compacts a message list by keeping the last keep_last messages.
If messages exceed the threshold, older messages are replaced with a summary system message. System messages at the start are always preserved.
Returns the original list if it's already within the limit.
Examples
iex> messages = for i <- 1..20, do: Nous.Message.user("Message #{i}")
iex> compacted = Nous.Transcript.compact(messages, 10)
iex> length(compacted)
11
@spec compact_async([Nous.Message.t()], pos_integer()) :: Task.t()
Compacts messages asynchronously under Nous.TaskSupervisor.
Returns a Task that resolves to the compacted message list.
Useful when compaction runs inside a GenServer and you don't
want to block the current process.
Examples
task = Nous.Transcript.compact_async(messages, 10)
# ... do other work ...
compacted = Task.await(task)With a callback (fire-and-forget)
Nous.Transcript.compact_async(messages, 10, fn compacted ->
send(self(), {:compacted, compacted})
end)
@spec compact_async([Nous.Message.t()], pos_integer(), ([Nous.Message.t()] -> any())) :: {:ok, pid()}
Compacts messages in the background with a callback.
Starts a fire-and-forget task under Nous.TaskSupervisor.
The callback receives the compacted message list when done.
Returns {:ok, pid}.
Examples
{:ok, _pid} = Nous.Transcript.compact_async(messages, 10, fn compacted ->
GenServer.cast(self, {:update_messages, compacted})
end)
@spec estimate_messages_tokens([Nous.Message.t()]) :: non_neg_integer()
Estimates total tokens across a list of messages.
Examples
iex> messages = [Nous.Message.user("Hello"), Nous.Message.assistant("Hi there")]
iex> Nous.Transcript.estimate_messages_tokens(messages)
3
@spec estimate_tokens(String.t() | nil) :: non_neg_integer()
Estimates the token count of a string using word count as a proxy.
This is a rough estimate (~1.3 tokens per word for English text). For precise counting, use a proper tokenizer.
Examples
iex> Nous.Transcript.estimate_tokens("Hello world")
2
iex> Nous.Transcript.estimate_tokens("")
0
@spec maybe_compact( [Nous.Message.t()], keyword() ) :: [Nous.Message.t()]
Automatically compacts messages when a trigger condition is met.
Returns the original messages unchanged if no trigger fires. Supports message count, token budget, or both (OR logic).
Options
:every— compact when message count exceeds this number:token_budget— total token budget for the conversation:threshold— fraction of token budget that triggers compaction (default0.8):keep_last— how many recent messages to keep (required)
Examples
# Compact every 20 messages
messages = Nous.Transcript.maybe_compact(messages, every: 20, keep_last: 10)
# Compact at 80% of 128k token budget
messages = Nous.Transcript.maybe_compact(messages,
token_budget: 128_000,
keep_last: 10
)
# Both triggers — whichever fires first
messages = Nous.Transcript.maybe_compact(messages,
every: 30,
token_budget: 128_000,
threshold: 0.75,
keep_last: 10
)
@spec maybe_compact_async([Nous.Message.t()], keyword(), (term() -> any())) :: {:ok, pid()}
Like maybe_compact/2 but runs asynchronously with a callback.
The callback receives {:compacted, messages} if compaction happened,
or {:unchanged, messages} if no trigger fired.
Examples
Nous.Transcript.maybe_compact_async(messages,
[every: 20, keep_last: 10],
fn
{:compacted, msgs} -> GenServer.cast(self, {:update, msgs})
{:unchanged, _msgs} -> :ok
end
)
@spec should_compact?([Nous.Message.t()], pos_integer()) :: boolean()
Checks if a message list should be compacted based on a threshold.
Examples
iex> messages = for i <- 1..25, do: Nous.Message.user("msg #{i}")
iex> Nous.Transcript.should_compact?(messages, 20)
true