Current section
Files
Jump to
Current section
Files
lib/puck/context.ex
defmodule Puck.Context do
@moduledoc """
Conversation context with message history and metadata.
## Example
context = Puck.Context.new()
context = Puck.Context.add_message(context, :user, "Hello!")
"""
alias Puck.Message
@type t :: %__MODULE__{
messages: [Message.t()],
metadata: map()
}
defstruct messages: [], metadata: %{}
@doc """
Creates a new empty context.
## Examples
iex> Puck.Context.new()
%Puck.Context{messages: [], metadata: %{}}
iex> Puck.Context.new(metadata: %{session_id: "abc123"})
%Puck.Context{messages: [], metadata: %{session_id: "abc123"}}
"""
def new(opts \\ []) do
%__MODULE__{
messages: Keyword.get(opts, :messages, []),
metadata: Keyword.get(opts, :metadata, %{})
}
end
@doc """
Adds a message to the context.
Content can be a string (wrapped automatically), a single Part, or a list of Parts.
## Examples
iex> context = Puck.Context.new()
iex> context = Puck.Context.add_message(context, :user, "Hello!")
iex> length(context.messages)
1
"""
def add_message(%__MODULE__{} = context, role, content, metadata \\ %{}) do
message = Message.new(role, content, metadata)
%{context | messages: context.messages ++ [message]}
end
@doc """
Returns the messages in the context.
## Examples
iex> context = Puck.Context.new()
iex> context = Puck.Context.add_message(context, :user, "Hello!")
iex> length(Puck.Context.messages(context))
1
"""
def messages(%__MODULE__{messages: messages}), do: messages
@doc """
Returns the last message in the context, or nil if empty.
## Examples
iex> context = Puck.Context.new()
iex> Puck.Context.last_message(context)
nil
"""
def last_message(%__MODULE__{messages: []}), do: nil
def last_message(%__MODULE__{messages: messages}), do: List.last(messages)
@doc """
Returns the number of messages in the context.
## Examples
iex> context = Puck.Context.new()
iex> Puck.Context.message_count(context)
0
"""
def message_count(%__MODULE__{messages: messages}), do: length(messages)
@doc """
Updates the context metadata.
## Examples
iex> context = Puck.Context.new()
iex> context = Puck.Context.put_metadata(context, :session_id, "abc123")
iex> context.metadata
%{session_id: "abc123"}
"""
def put_metadata(%__MODULE__{} = context, key, value) do
%{context | metadata: Map.put(context.metadata, key, value)}
end
@doc """
Gets a value from the context metadata.
## Examples
iex> context = Puck.Context.new(metadata: %{session_id: "abc123"})
iex> Puck.Context.get_metadata(context, :session_id)
"abc123"
iex> Puck.Context.get_metadata(context, :missing)
nil
"""
def get_metadata(%__MODULE__{metadata: metadata}, key, default \\ nil) do
Map.get(metadata, key, default)
end
@doc """
Clears all messages from the context, preserving metadata.
Useful for starting a fresh conversation while keeping session information.
## Examples
iex> context = Puck.Context.new(metadata: %{session_id: "abc123"})
iex> context = Puck.Context.add_message(context, :user, "Hello!")
iex> context = Puck.Context.clear(context)
iex> {context.messages, context.metadata}
{[], %{session_id: "abc123"}}
"""
def clear(%__MODULE__{} = context) do
%{context | messages: []}
end
@doc """
Compacts the context using the specified strategy.
Delegates to `Puck.Compaction.compact/2`.
## Examples
{:ok, compacted} = Puck.Context.compact(context, {Puck.Compaction.Summarize, %{
client: client,
keep_last: 3
}})
"""
def compact(%__MODULE__{} = context, strategy_tuple) do
Puck.Compaction.compact(context, strategy_tuple)
end
@doc """
Returns the total token count from context metadata.
This value is tracked by the runtime after each LLM call.
Returns 0 if no token information has been recorded.
## Examples
iex> context = Puck.Context.new()
iex> Puck.Context.total_tokens(context)
0
"""
def total_tokens(%__MODULE__{} = context) do
get_metadata(context, :total_tokens, 0)
end
end