Manages stateful conversation sessions with an LLM.
ChatSession maintains conversation history with automatic context window management based on token counts. When the context exceeds the maximum token limit, the oldest messages are removed (preserving the system prompt).
Features
- Automatic message history tracking
- Token-based context window management
- Tool support through broker integration
- Configurable system prompt and temperature
- Tokenizer integration for accurate token counting
Examples
alias Mojentic.LLM.{Broker, ChatSession}
alias Mojentic.LLM.Gateways.Ollama
broker = Broker.new("qwen3:32b", Ollama)
session = ChatSession.new(broker)
{:ok, response} = ChatSession.send(session, "Hello!")
# => {:ok, "Hello! How can I help you?", updated_session}
# Continue conversation
{:ok, response, session} = ChatSession.send(session, "Tell me a joke")With Tools
alias Mojentic.LLM.Tools.DateResolver
session = ChatSession.new(broker, tools: [DateResolver])
{:ok, response, session} = ChatSession.send(session, "What day is tomorrow?")
Summary
Functions
Finalizes a streaming send by recording the accumulated response in the session.
Returns the current message history.
Creates a new ChatSession.
Sends a query to the LLM and returns the response.
Sends a query to the LLM and returns a stream of response chunks.
Returns the total token count of all messages.
Types
@type sized_message() :: %{ message: Mojentic.LLM.Message.t(), token_length: non_neg_integer() }
@type t() :: %Mojentic.LLM.ChatSession{ broker: Mojentic.LLM.Broker.t(), max_context: non_neg_integer(), messages: [sized_message()], system_prompt: String.t(), temperature: float(), tokenizer: Mojentic.LLM.Gateways.TokenizerGateway.t(), tools: [module()] | nil }
Functions
@spec finalize_stream(stream_handle()) :: t()
Finalizes a streaming send by recording the accumulated response in the session.
Must be called after the stream returned by send_stream/2 has been fully consumed.
Parameters
handle- The handle returned bysend_stream/2
Returns
The updated session with the assistant's response recorded in history.
Examples
{:ok, stream, handle} = ChatSession.send_stream(session, "Tell me a story")
stream |> Stream.each(&IO.write/1) |> Stream.run()
session = ChatSession.finalize_stream(handle)
@spec messages(t()) :: [sized_message()]
Returns the current message history.
Messages are returned with their token lengths for debugging and monitoring context usage.
Examples
messages = ChatSession.messages(session)
total_tokens = Enum.reduce(messages, 0, fn m, acc -> acc + m.token_length end)
@spec new( Mojentic.LLM.Broker.t(), keyword() ) :: t()
Creates a new ChatSession.
Options
:system_prompt- System prompt for the conversation (default: "You are a helpful assistant."):tools- List of tool modules to make available to the LLM (default: nil):max_context- Maximum token count for context window (default: 32,768):tokenizer- TokenizerGateway instance (default: auto-created with gpt2):temperature- Temperature for response generation (default: 1.0)
Examples
broker = Broker.new("qwen3:32b", Ollama)
session = ChatSession.new(broker)
# With custom options
session = ChatSession.new(broker,
system_prompt: "You are a coding assistant.",
max_context: 16_384,
temperature: 0.7
)
# With tools
session = ChatSession.new(broker, tools: [MyTool])
Sends a query to the LLM and returns the response.
The query is added as a user message, the LLM generates a response, and the response is added as an assistant message. Both are tracked in the conversation history.
Parameters
session- The ChatSession instancequery- The user's query text
Returns
{:ok, response, updated_session}- Success with response text and updated session{:error, reason}- Error from broker
Examples
{:ok, response, session} = ChatSession.send(session, "What is 2+2?")
# => {:ok, "2+2 equals 4.", updated_session}
# Continue conversation
{:ok, response, session} = ChatSession.send(session, "And what about 3+3?")
@spec send_stream(t(), String.t()) :: {:ok, Enumerable.t(), stream_handle()}
Sends a query to the LLM and returns a stream of response chunks.
Because Elixir data is immutable, streaming requires a two-phase approach:
send_stream/2returns a stream and a handle- After consuming the stream, call
finalize_stream/1with the handle to get the updated session
The user message is added to the session before streaming begins. An Agent process accumulates chunks as they flow through the stream.
Parameters
session- The ChatSession instancequery- The user's query text
Returns
{:ok, stream, handle}where stream is anEnumerable.t()of string chunks and handle is passed tofinalize_stream/1
Examples
{:ok, stream, handle} = ChatSession.send_stream(session, "Tell me a story")
stream |> Stream.each(&IO.write/1) |> Stream.run()
session = ChatSession.finalize_stream(handle)
@spec token_count(t()) :: non_neg_integer()
Returns the total token count of all messages.
Examples
token_count = ChatSession.token_count(session)
# => 1234