LlamaIndex SubQuestionQueryEngine — decomposition errors and question overflow (2026) — Fix Guide (2026) | AI Error Hub
Home Providers LlamaIndex SubQuestion errors
LlamaIndex Retrieval · SubQuestion Severity: Medium HTTP n/a

LlamaIndex SubQuestionQueryEngine — over-decomposition and vague sub-queries

<code>SubQuestionQueryEngine</code> is the LlamaIndex pattern for complex questions that need answers from multiple sources. It decomposes the user's query into per-tool sub-questions, runs each, and synthesizes the aggregate.

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

Quick fix (TL;DR)

Resolution: SubQuestionQueryEngine uses an LLM to break the query into sub-questions, each targeted at a specific QueryEngineTool. Failure modes: (a) too many sub-questions (cost blowup); (b) sub-questions that are vague or off-topic; (c) tools do not have the info the sub-question expects. Fix by (a) capping question_gen output, (b) writing rich tool descriptions, and (c) enabling use_async for parallel sub-question execution.

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.

Too many sub-questions
# Query: "Compare our pricing to competitors"
# Generated 14 sub-questions across 3 tools
# Cost: $0.87 for one query
Vague sub-questions
# Sub-question 1: "General information about pricing"
# Sub-question 2: "Company information"
# ← too vague to retrieve well; poor final answer
Sub-question routed to wrong tool
# Product tool asked: "What is your refund policy?"
# Product tool returns nothing useful; final answer incomplete

Reference

When to use SubQuestionQueryEngine

Query typeRecommendation
Simple lookupPlain query engine
Domain-specific with one topicRouterQueryEngine
Multi-part question spanning domainsSubQuestionQueryEngine
Iterative / conversationalChat engine or agent
Complex reasoning + toolsAgent

Root causes, ranked by frequency

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

  • 26%
    Question generator produces too many sub-questions. Cost multiplies; retrieval quality drops.
  • 18%
    Sub-questions too vague. Generic like "info about X" instead of specific.
  • 14%
    Wrong tool for a sub-question. Question generator misroutes based on sparse tool descriptions.
  • 10%
    use_async=False on many sub-questions. Sequential execution takes N × single_query time.
  • 8%
    Question generator LLM different from synthesis LLM. Config confusion; wrong LLM used for one step.
  • 7%
    Sub-question overlap. Two sub-questions ask the same thing to different tools; redundant work.
  • 10%
    Final synthesis loses per-sub-question source attribution. User cannot cite which source answered what.
  • 7%
    Query gen prompt too permissive. Default prompt generates 5-10 sub-questions on many queries.

Fixes — copy-paste solutions

Fix #1

Configure SubQuestionQueryEngine with async and a bounded question generator

Parallel execution + capped decomposition = usable production behavior.

Enable use_async=True so sub-questions run in parallel. Configure the question generator with a custom prompt that limits sub-question count.

bounded_subquestion.py
from llama_index.core.query_engine import SubQuestionQueryEngine
from llama_index.core.question_gen import LLMQuestionGenerator
from llama_index.core.tools import QueryEngineTool, ToolMetadata
from llama_index.llms.openai import OpenAI

llm = OpenAI(model="gpt-4o-mini")

# --- Tools with rich descriptions ---
policy_tool = QueryEngineTool(
    query_engine=policy_index.as_query_engine(similarity_top_k=6),
    metadata=ToolMetadata(
        name="policies",
        description="Company policies: refund, return, warranty, shipping, ToS.",
    ),
)

product_tool = QueryEngineTool(
    query_engine=product_index.as_query_engine(similarity_top_k=6),
    metadata=ToolMetadata(
        name="products",
        description="Product catalog: features, specs, pricing.",
    ),
)

# --- Custom question generator prompt (capped) ---
CUSTOM_QUESTION_GEN_PROMPT = """
Given a user question and a list of tools, generate the MINIMUM number of
sub-questions (at most 4) needed to answer the user's question. Each sub-question
should be targeted at exactly one tool and be specific enough to retrieve
relevant information.

Do NOT generate sub-questions that are vague or redundant.
Do NOT generate more than 4 sub-questions total.

Tools:
{tools_str}

User question: {query_str}

Sub-questions (JSON array):
"""

# --- SubQuestionQueryEngine with async and bounded generator ---
question_gen = LLMQuestionGenerator.from_defaults(
    llm=llm,
    prompt_template_str=CUSTOM_QUESTION_GEN_PROMPT,
)

subq_engine = SubQuestionQueryEngine.from_defaults(
    query_engine_tools=[policy_tool, product_tool],
    question_gen=question_gen,
    llm=llm,
    use_async=True,           # parallel sub-question execution
    verbose=True,             # prints sub-questions
)

response = subq_engine.query(
    "Compare the refund policy with product warranty terms for the ACME-5000."
)
print(response)
print(f"\nSource nodes: {len(response.source_nodes)}")
The 4-question cap in the custom prompt keeps cost predictable. If some genuine queries need more, raise it — but 4 is a good starting point for most apps.
Fix #2

Log per-sub-question source attribution

Users need to know which source answered which part.

SubQuestionQueryEngine tracks intermediate results. Expose the mapping from sub-question to answer to source so downstream can cite properly.

subquestion_attribution.py
from llama_index.core.query_engine import SubQuestionQueryEngine
from llama_index.core.callbacks import CallbackManager, LlamaDebugHandler, CBEventType

# Enable the debug handler to see all events
debug = LlamaDebugHandler(print_trace_on_end=False)
cb_manager = CallbackManager([debug])

subq_engine = SubQuestionQueryEngine.from_defaults(
    query_engine_tools=[policy_tool, product_tool],
    llm=llm,
    use_async=True,
    verbose=True,
    callback_manager=cb_manager,
)

response = subq_engine.query("...")
print(f"Final: {response.response}")

# --- Extract per-sub-question intermediate steps ---
sub_events = debug.get_events(CBEventType.SUB_QUESTION)

for event in sub_events:
    payload = event.payload or {}
    sub_q_ans = payload.get("sub_question")   # type: SubQuestionAnswerPair
    if sub_q_ans:
        print(f"\nSub-question: {sub_q_ans.sub_q.sub_question}")
        print(f"Tool:         {sub_q_ans.sub_q.tool_name}")
        print(f"Answer:       {sub_q_ans.answer[:200]}")
        print(f"Sources:      {[n.metadata.get('source') for n in sub_q_ans.sources or []]}")

# --- For UI: attach citations per sub-answer ---
def render_response_with_citations(response, debug):
    output = [response.response, "\n\n**Sources:**"]
    for event in debug.get_events(CBEventType.SUB_QUESTION):
        sq = (event.payload or {}).get("sub_question")
        if sq:
            output.append(
                f"- **{sq.sub_q.sub_question}** ({sq.sub_q.tool_name}): "
                f"{[n.metadata.get('source', '?') for n in sq.sources or []]}"
            )
    return "\n".join(output)

print(render_response_with_citations(response, debug))
For compliance-sensitive use cases, always render per-sub-question sources. Aggregated responses lose the "which source answered what" that users depend on.
Fix #3

Reserve SubQuestionQueryEngine for actually-decomposable queries

Not every complex-looking query needs decomposition.

For queries that can be answered from one tool, RouterQueryEngine or plain query engine is cheaper and simpler. Use SubQuestion when the query truly spans multiple domains.

when_to_use_subquestion.py
# --- Bad match for SubQuestion — single-domain query ---
# query = "What is the refund policy?"
# Just use policy_engine directly

# --- Good match for SubQuestion — multi-domain query ---
# query = "Compare the refund policy against the ACME-5000 warranty"
# Needs policies AND products; sub-questions naturally decompose

# --- Bad match for SubQuestion — iterative reasoning ---
# query = "Investigate why sales dropped last quarter and recommend a fix"
# Needs iterative reasoning + planning; use an agent

# --- Simple heuristic router ---
from llama_index.core.query_engine import RouterQueryEngine, SubQuestionQueryEngine

# Set up both engines
router_engine = RouterQueryEngine(
    selector=PydanticSingleSelector.from_defaults(llm=llm),
    query_engine_tools=[policy_tool, product_tool],
)
subq_engine = SubQuestionQueryEngine.from_defaults(
    query_engine_tools=[policy_tool, product_tool],
    llm=llm,
    use_async=True,
)

# --- Meta-router that picks between them ---
def smart_query(query: str) -> str:
    # Heuristic: multi-domain markers
    multi_domain_markers = [
        "compare", "and", "also", "both", "in addition",
        "how does X relate to Y", "along with",
    ]
    if any(marker in query.lower() for marker in multi_domain_markers):
        return subq_engine.query(query).response
    return router_engine.query(query).response

# --- Or use an LLM upfront to route ---
def llm_smart_query(query: str) -> str:
    """Use a cheap LLM call to decide."""
    decision = llm.complete(
        f"Should this query be answered by decomposing into sub-questions "
        f"across multiple domains, or by a single specialized tool?\n\n"
        f"Query: {query}\n\n"
        f"Answer 'single' or 'decompose':"
    ).text.strip().lower()

    if "decompose" in decision:
        return subq_engine.query(query).response
    return router_engine.query(query).response
For most apps, RouterQueryEngine handles 80% of queries at a fraction of the cost. Reserve SubQuestion for the 20% that genuinely need decomposition.

Prevention checklist

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

  • Only use SubQuestionQueryEngine when queries span multiple domains — otherwise use a router or single engine.
  • Cap sub-question count via a custom question_gen prompt.
  • Always set use_async=True; sequential decomposition is impractically slow.
  • Log per-sub-question sources for citation and evaluation.
  • Write rich tool descriptions — the question generator uses them to target sub-questions.
  • Monitor cost per query in production; a single decomposition can cost 5-10x a plain query.
  • For heterogeneous query mix, combine with a router that decides "decompose or not".

Frequently asked questions

SubQuestion decomposes deterministically in ONE step: LLM plans all sub-questions upfront, then executes them. An agent iterates: reason, act, observe, reason. SubQuestion is cheaper and simpler; agents handle open-ended interactions.
Yes — pass prompt_template_str to LLMQuestionGenerator, or write a custom generator subclassing BaseQuestionGenerator. Useful for domain-specific decomposition patterns.
SubQuestion is not naturally streaming — it must complete all sub-questions before final synthesis. For streaming to end users, either stream just the final synthesis step, or fall back to a plain query engine.
Yes — nothing prevents duplicate tool targets. If your questions consistently duplicate, tune the question generator prompt to avoid redundancy.
Cheap-and-capable is the target. GPT-4o-mini, Claude Haiku, Gemini Flash all work well. Don't use the largest / most expensive model just for planning — it wastes cost with little quality gain.

Get the weekly AI-error digest

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