# `Mojentic.LLM.ChatSession`
[🔗](https://github.com/svetzal/mojentic-ex/blob/v1.5.0/lib/mojentic/llm/chat_session.ex#L1)

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?")

# `sized_message`

```elixir
@type sized_message() :: %{
  message: Mojentic.LLM.Message.t(),
  token_length: non_neg_integer()
}
```

# `stream_handle`

```elixir
@type stream_handle() :: {t(), pid()}
```

# `t`

```elixir
@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
}
```

# `finalize_stream`

```elixir
@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 by `send_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)

# `messages`

```elixir
@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)

# `new`

```elixir
@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])

# `send`

```elixir
@spec send(t(), String.t()) :: {:ok, String.t(), t()} | {:error, term()}
```

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 instance
- `query` - 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?")

# `send_stream`

```elixir
@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:
1. `send_stream/2` returns a stream and a handle
2. After consuming the stream, call `finalize_stream/1` with 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 instance
- `query` - The user's query text

## Returns

- `{:ok, stream, handle}` where stream is an `Enumerable.t()` of string chunks
  and handle is passed to `finalize_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)

# `token_count`

```elixir
@spec token_count(t()) :: non_neg_integer()
```

Returns the total token count of all messages.

## Examples

    token_count = ChatSession.token_count(session)
    # => 1234

---

*Consult [api-reference.md](api-reference.md) for complete listing*
