Skip to main content

History summarization

Pre-retrieval starts with the conversation you already have — not with the database. Large Language Models have context windows measured in tens or hundreds of thousands of tokens. That sounds generous until you remember what a RAG prompt actually contains: a system instruction, retrieved document chunks, the current question, and the full prior conversation. Every follow-up turn makes that prompt larger. Even when the hard limit is not hit yet, long histories dilute attention — the model has more room to lose the thread of what matters.

History summarization is the optional first gate in the pre-retrieval phase. Instead of always shipping the full transcript to later LLM calls (fetch check, answer generation), you compress older turns once the conversation exceeds a token budget.

The idea

Summarization in a chat setting is not the same task as summarizing a news article. You need a short rewrite that:

  • Preserves facts, decisions, and constraints the user already established.
  • Keeps the language of the conversation (users notice when a Dutch chat suddenly gets an English summary injected).
  • Remains usable as history — i.e. something you can put back into the message list and continue from.

RAG Me Up does this only when there is already history and when summarization is enabled in the .env file.

How RAG Me Up decides to summarize

Inside handle_user_interaction (and the streaming twin), the pipeline first checks whether summarization is on. If it is, the history is flattened into a single string and tokenized with tiktoken using the encoder named in summarization_encoder:

if os.getenv("use_summarization") == "True":
history_string = "\n\n".join(
[f"{message['role']}: {message['content']}" for message in history]
)
history_size = len(self.tiktoken_encoder.encode(history_string))
if history_size > int(os.getenv("summarization_threshold")):
(response, _) = self.llm.generate_response(
None,
os.getenv("summarization_query").format(history=history_string),
[]
)
history = history[:1] + [{"role": "assistant", "content": response}]

A few design choices are worth calling out:

  • The threshold is measured in tokens, not characters. Character counts are a poor proxy across languages and tokenizers.
  • When a summary is produced, RAG Me Up keeps the first history message and replaces the rest with a single assistant message containing the summary. That keeps a lightweight anchor of the conversation start while collapsing everything else.
  • Summarization runs before the document-fetch check, so later steps see the compressed history.

Configuration

VariableRole
use_summarizationTrue / False toggle
summarization_thresholdToken count that triggers summarization
summarization_queryPrompt template; must include {history}
summarization_encodertiktoken model name used only for counting

Leave a buffer between summarization_threshold and your LLM's true context window. The summarization prompt itself, the RAG instruction, and retrieved documents still need room after compression.

When to turn it on

For short, task-oriented chats (a handful of turns), summarization is usually unnecessary overhead — an extra LLM call on every long turn. It becomes valuable when:

  • Users hold long investigative conversations over the same corpus.
  • Your RAG instruction plus top-k chunks already consume a large fraction of the window.
  • You care more about staying within limits than about perfect fidelity of early turns.

If you need perfect auditability of every prior user message, summarization is the wrong tool; persist the full transcript in your UI/database and only summarize the prompt view sent to the model — which is effectively what RAG Me Up's client/server split already enables.