LlamaIndex chat engine — CondenseQuestion vs Context vs SimpleChat mode selection (2026) — Fix Guide (2026) | AI Error Hub
Home Providers LlamaIndex Chat engine modes
LlamaIndex Chat · Chat Engine Severity: Medium HTTP n/a

LlamaIndex as_chat_engine() — mode selection matters more than tutorials suggest

A chat engine is a query engine plus conversation memory. LlamaIndex offers several chat modes, each with different failure characteristics on follow-up questions and multi-turn conversations.

By Sana K. · Cloud AI Reliability Engineer Published 2026-07-26 Verified 2026-07-26

Quick fix (TL;DR)

Resolution: The three main chat modes: condense_question rewrites the follow-up as a standalone question then retrieves; context retrieves on the raw follow-up and stuffs context into the prompt; condense_plus_context does both. Failure modes: condense loses conversation context on follow-ups; context stuffs stale retrieved chunks. Fix by (a) choosing the mode that matches your conversation style, (b) using condense_plus_context for anything real, and (c) capping token_limit on memory to prevent context bloat.

Real error messages you'll see

These are the exact strings returned by the LlamaIndex framework and its integrations when this error occurs. Copy-paste-searching any of them should land on this page.

Follow-up ignored — condense mode
# User: "What is the refund policy?"
# Bot:  "Refunds within 30 days, minus shipping."
# User: "What about for damaged items?"
# Bot:  "I don't have information about damaged items."
# ← condense rewrote to "damaged items policy" losing "refund" context
Context mode drifts into old context
# User asks about API v2 endpoints
# Bot answers correctly using v2 docs
# User asks about pagination — bot mixes v1 and v2 pagination
# ← old retrieved chunks from v2 turn are still in context
SimpleChat has no retrieval
# SimpleChatEngine ignores the index; pure LLM chat
# useful for debug but easy to ship by accident

Reference

Chat mode comparison

ModeHow it retrievesBest for
condense_questionRewrites follow-up as standalone, then retrievesSearch-like chat where each turn is a query
contextRetrieves on raw user message; stuffs contextConversational RAG with follow-up context
condense_plus_contextBest of both — rewrites AND retrieves fresh each turnRecommended for most production apps
simpleNo retrieval; pure LLM chatDebug / fallback / no-RAG baseline
reactReAct agent with query engine as toolWhen retrieval is one of many actions
openaiOpenAI function-calling agent with query engine as toolOpenAI-specific tool orchestration
bestAuto: openai on OpenAI models, react otherwiseWhen you want the framework to pick

Root causes, ranked by frequency

Based on developer reports across LlamaIndex forums, GitHub issues, and Discord community during 2025–2026.

  • 25%
    condense_question loses context. Condensation drops the previous topic; retrieval misses relevant chunks.
  • 18%
    context mode keeps stale nodes in prompt. Prior-turn context bleeds into follow-up answers.
  • 14%
    Memory not bounded. Conversation grows without limit; every turn pays for all prior context.
  • 10%
    Wrong mode for use case. condense used for casual chat; context used for search-like queries.
  • 8%
    System prompt lost in condense. condense rewrites lose the system-prompt persona.
  • 7%
    ReAct chat mode + no tools. Wraps query engine unnecessarily; adds latency.
  • 7%
    Streaming broken in condense mode. Rewrite step is not streaming; UI shows nothing.
  • 11%
    Chat history not persisted. Every session starts fresh even though user expects continuity.

Fixes — copy-paste solutions

Fix #1

Use condense_plus_context for most production apps

The best of both retrieval strategies.

condense_plus_context rewrites the question to be standalone (good for retrieval), keeps the full conversation for the LLM (good for answer quality). It is the safest default.

chat_engine_default.py
from llama_index.core.memory import ChatMemoryBuffer
from llama_index.core import VectorStoreIndex

# ... index built already ...

# Bounded memory — prevents unbounded context growth
memory = ChatMemoryBuffer.from_defaults(token_limit=4000)

chat_engine = index.as_chat_engine(
    chat_mode="condense_plus_context",
    memory=memory,
    similarity_top_k=6,
    system_prompt=(
        "You are a helpful assistant with access to a knowledge base. "
        "When you can, cite the source document."
    ),
    verbose=True,                     # dev-time: see the rewrite + retrieval
)

# --- Multi-turn conversation ---
response = chat_engine.chat("What is the refund policy?")
print(response)
# Refunds within 30 days, minus shipping costs. [source: refund_policy.pdf]

response = chat_engine.chat("What about damaged items?")
print(response)
# For damaged items, refunds are processed within 5 business days
# and cover full shipping cost. [source: refund_policy.pdf, section 3.2]

# Access chat history
for msg in chat_engine.chat_history:
    print(f"{msg.role}: {msg.content[:80]}")

# Reset when switching topics or on user request
chat_engine.reset()
The verbose=True flag prints the rewritten standalone question at each turn. Watch this in dev — bad rewrites are the biggest source of "why did the follow-up work poorly?" bugs.
Fix #2

Bound memory and manage session state

Chat engines without memory bounds spiral into unbounded cost.

Always set a token_limit on ChatMemoryBuffer. For production, persist chat history to disk / DB across sessions.

memory_management.py
from llama_index.core.memory import ChatMemoryBuffer, ChatSummaryMemoryBuffer
from llama_index.llms.openai import OpenAI
from llama_index.core.llms import ChatMessage, MessageRole

# --- Simple buffer with token limit ---
memory = ChatMemoryBuffer.from_defaults(token_limit=3000)

# --- Summary memory for very long conversations ---
llm = OpenAI(model="gpt-4o-mini")
summary_memory = ChatSummaryMemoryBuffer.from_defaults(
    token_limit=3000,
    summarize_prompt="Summarize the conversation so far, preserving all facts.",
    llm=llm,
)

# --- Load prior conversation from your DB ---
def load_chat_history(user_id: str, conversation_id: str) -> list[ChatMessage]:
    # Fetch from Postgres/Redis/etc.
    rows = fetch_history_rows(user_id, conversation_id)
    return [ChatMessage(role=r["role"], content=r["content"]) for r in rows]

# --- Reconstruct the chat engine with loaded history ---
prior_msgs = load_chat_history("u_alice", "c_20260803")

memory = ChatMemoryBuffer.from_defaults(
    token_limit=3000,
    chat_history=prior_msgs,
)

chat_engine = index.as_chat_engine(
    chat_mode="condense_plus_context",
    memory=memory,
    similarity_top_k=6,
)

# Continue where the user left off
response = chat_engine.chat("Can you elaborate on your last answer?")
print(response)

# --- Save updated history back after the turn ---
def save_chat_history(user_id, conversation_id, messages):
    for msg in messages:
        upsert_row(user_id, conversation_id, msg.role, msg.content)

save_chat_history("u_alice", "c_20260803", chat_engine.chat_history)
For very long-running conversations, use ChatSummaryMemoryBuffer which summarizes older turns automatically. Trades some fidelity for bounded cost — useful for support-agent style workloads.
Fix #3

Debug bad follow-up handling by inspecting the rewrite

When the follow-up fails, the condensed question is usually the culprit.

Enable verbose mode or call the internals directly to see what standalone question was generated. Adjust the condense prompt if needed.

debug_condense.py
from llama_index.core.chat_engine import CondensePlusContextChatEngine
from llama_index.core.memory import ChatMemoryBuffer
from llama_index.llms.openai import OpenAI

llm = OpenAI(model="gpt-4o-mini")
memory = ChatMemoryBuffer.from_defaults(token_limit=3000)

# --- Custom condense prompt for better follow-up handling ---
CUSTOM_CONDENSE_PROMPT = """
Given the following conversation and a follow-up question, rewrite the follow-up
question to be a standalone question that captures all relevant context.

Rules:
1. If the follow-up refers to something in prior turns (e.g. "what about X?"),
   INCLUDE the original topic in the rewrite.
2. If the follow-up is a fresh topic, use it as-is.
3. Preserve every named entity, product, and number mentioned in prior turns
   that's relevant to the follow-up.

Chat history:
{chat_history}

Follow-up: {question}

Standalone question:
"""

from llama_index.core.prompts import PromptTemplate

chat_engine = CondensePlusContextChatEngine.from_defaults(
    retriever=index.as_retriever(similarity_top_k=6),
    llm=llm,
    memory=memory,
    condense_prompt=PromptTemplate(CUSTOM_CONDENSE_PROMPT),
    verbose=True,     # prints the condensed question every turn
)

# --- Follow-up handling test ---
chat_engine.chat("What is the refund policy?")
# Bot: Refunds within 30 days...

response = chat_engine.chat("What about for damaged items?")
# You will see the printed rewrite:
# > "What is the refund policy for damaged items?"
# instead of
# > "What is the damaged items policy?"

# --- Manually inspect the condensed question ---
# For deeper debugging, subclass and log every step
class DebugChatEngine(CondensePlusContextChatEngine):
    def _condense_question(self, chat_history, question):
        standalone = super()._condense_question(chat_history, question)
        print(f"[condense] {question!r} -> {standalone!r}")
        return standalone
The default condense prompt sometimes strips essential context. A custom prompt that explicitly requires preserving prior-turn entities fixes most follow-up quality issues.

Prevention checklist

Ship these seven safeguards once and this error stops appearing in your logs.

  • Use condense_plus_context as the default chat mode for production RAG chat.
  • Always set token_limit on the memory buffer.
  • Persist chat history to a DB; do not rely on in-process memory.
  • For very long conversations, use ChatSummaryMemoryBuffer.
  • Enable verbose=True in dev to observe the rewrite step.
  • Test with representative follow-up patterns before shipping — pronouns, "what about X", "and Y?".
  • Provide a "reset conversation" affordance to the user; call chat_engine.reset().

Frequently asked questions

condense_question rewrites the follow-up into a standalone question, then retrieves. context retrieves on the raw follow-up and includes prior turns in the LLM prompt as history. condense_plus_context does both — usually the right default.
Chat engine for pure RAG conversations. Agent (react/openai chat modes) when the user might need actions beyond retrieval — booking, updating records, calling external APIs. Chat engines are simpler and faster when retrieval is enough.
Use chat_engine.stream_chat("...") — returns a StreamingAgentChatResponse. Iterate response.response_gen for chunks. Streaming works best with context mode; condense modes have a non-streaming rewrite step.
Yes — response.source_nodes contains the retrieved nodes. Use for citation UI. Some modes (react agent) expose intermediate steps via response.sources.
Similar concepts — bound conversation state to a chat model. LlamaIndex's ChatMemoryBuffer is in-process; you persist it yourself. LangChain's RunnableWithMessageHistory pairs with pluggable backends (SQL, Redis) natively.

Get the weekly AI-error digest

New fixes, provider status recaps, and one deep tutorial — every Tuesday. 8,400+ engineers.