zylon-ai/private-gpt · error · ValueError

Maximum number of iterations for condensing exceeded.

Error message

Maximum number of iterations for condensing exceeded.

What it means

ValueError from ConversationCondenser._condense_from_left. Condensing from the left is recursive with an iteration counter; if it cannot reduce the history below max_length within MAX_CONDENSE_ITERATIONS (=2) passes, it raises instead of looping forever. The guard is `iteration > MAX_CONDENSE_ITERATIONS`, so roughly the third recursive pass fails.

Source

Thrown at private_gpt/components/chat/processors/chat_history/memory/strategies/condenser.py:476

            None,
        )
        if last_idx is None:
            return chat_history
        return self._drop_thinking(chat_history[:last_idx]) + chat_history[last_idx:]

    async def _condense_from_left(
        self,
        llm: LLM,
        tokenizer_fn: TokenizerFn | None,
        chat_history: list[ChatMessage],
        max_length: int,
        left_tokens: int | None = None,
        last_user_tokens: int | None = None,
        right_tokens: int | None = None,
        iteration: int = 0,
    ) -> list[ChatMessage]:
        if iteration > MAX_CONDENSE_ITERATIONS:
            raise ValueError("Maximum number of iterations for condensing exceeded.")

        (
            left_messages,
            last_user_message,
            right_messages,
        ) = await asyncio.to_thread(self._split_conversation, chat_history)

        # Compute per-component token counts if not provided by caller
        if left_tokens is None or last_user_tokens is None or right_tokens is None:
            left_tokens, last_user_tokens, right_tokens = await asyncio.gather(
                self._get_messages_tokens(left_messages, tokenizer_fn=tokenizer_fn),
                self._get_messages_tokens(last_user_message, tokenizer_fn=tokenizer_fn),
                self._get_messages_tokens(right_messages, tokenizer_fn=tokenizer_fn),
            )
        assert left_tokens is not None
        assert last_user_tokens is not None
        assert right_tokens is not None

View on GitHub (pinned to 4a030776a3)

Solutions

  1. Increase the condense max_length (context window budget) so convergence is achievable within 2 iterations.
  2. Trim or drop very old messages before condensing (pre-truncate history length in messages).
  3. Verify the summarizer LLM actually compresses — check its prompt/model; a weak model may paraphrase at full length.
  4. Catch ValueError at the condense call site and fall back to naive truncation of history.

Example fix

// before
condense:
  max_length: 512   # tiny budget -> iterations exhausted

// after
condense:
  max_length: 4096
Defensive patterns

Strategy: fallback

Validate before calling

estimated = await estimate_token_count(chat_history, tokenizer)
if estimated > 4 * max_length:  # far beyond what 2 iterations can compress
    chat_history = chat_history[-20:]  # pre-trim before condensing

Try / catch

try:
    condensed = await condenser.condense(history)
except ValueError as e:
    if "iterations" in str(e):
        return history[-keep_last_n:]  # fallback: naive tail truncation keeping a user turn
    raise

Prevention

When it happens

Trigger: A chat history so long relative to max_length that summarizing the left side twice still exceeds the limit; a very small max_length configuration; token counts not shrinking because summarization returns nearly the same length; LLM summarizer failing to compress.

Common situations: max_tokens/max_length set close to the size of a single message; huge pasted documents in history; tokenizer mismatch making estimates not decrease; aggressive context windows set too small for the conversation.

Related errors


AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15). Data as JSON: /api/errors/39a3be1867ec30f7. Report an issue: GitHub.