diff --git a/llama-index-core/llama_index/core/command_line/mappings.json b/llama-index-core/llama_index/core/command_line/mappings.json
index 15d6126eff904df07afab3b326a518c46a13f010..378f8498e5d6c5f2dd9a59dd6fb3c97d2e756342 100644
--- a/llama-index-core/llama_index/core/command_line/mappings.json
+++ b/llama-index-core/llama_index/core/command_line/mappings.json
@@ -906,5 +906,6 @@
   "run_jobs": "llama_index.core.async_utils",
   "DecomposeQueryTransform": "llama_index.core.query.query_transform.base",
   "get_eval_results": "llama_index.core.evaluation.eval_utils",
-  "REPLICATE_MULTI_MODAL_LLM_MODELS": "llama_index.multi_modal_llms.replicate.base"
+  "REPLICATE_MULTI_MODAL_LLM_MODELS": "llama_index.multi_modal_llms.replicate.base",
+  "QueryUnderstandingPack": "llama_index.packs.query_understanding_agent"
 }
diff --git a/llama-index-packs/llama-index-packs-query-understanding-agent/.gitignore b/llama-index-packs/llama-index-packs-query-understanding-agent/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..990c18de229088f55c6c514fd0f2d49981d1b0e7
--- /dev/null
+++ b/llama-index-packs/llama-index-packs-query-understanding-agent/.gitignore
@@ -0,0 +1,153 @@
+llama_index/_static
+.DS_Store
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+bin/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+etc/
+include/
+lib/
+lib64/
+parts/
+sdist/
+share/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+.ruff_cache
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+notebooks/
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+pyvenv.cfg
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# Jetbrains
+.idea
+modules/
+*.swp
+
+# VsCode
+.vscode
+
+# pipenv
+Pipfile
+Pipfile.lock
+
+# pyright
+pyrightconfig.json
diff --git a/llama-index-packs/llama-index-packs-query-understanding-agent/BUILD b/llama-index-packs/llama-index-packs-query-understanding-agent/BUILD
new file mode 100644
index 0000000000000000000000000000000000000000..2d3d88d1eab9ce4898e7dfc34d7224ae0467316c
--- /dev/null
+++ b/llama-index-packs/llama-index-packs-query-understanding-agent/BUILD
@@ -0,0 +1,7 @@
+poetry_requirements(
+    name="poetry",
+)
+
+python_requirements(
+    name="reqs",
+)
diff --git a/llama-index-packs/llama-index-packs-query-understanding-agent/Makefile b/llama-index-packs/llama-index-packs-query-understanding-agent/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..b9eab05aa370629a4a3de75df3ff64cd53887b68
--- /dev/null
+++ b/llama-index-packs/llama-index-packs-query-understanding-agent/Makefile
@@ -0,0 +1,17 @@
+GIT_ROOT ?= $(shell git rev-parse --show-toplevel)
+
+help:	## Show all Makefile targets.
+	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[33m%-30s\033[0m %s\n", $$1, $$2}'
+
+format:	## Run code autoformatters (black).
+	pre-commit install
+	git ls-files | xargs pre-commit run black --files
+
+lint:	## Run linters: pre-commit (black, ruff, codespell) and mypy
+	pre-commit install && git ls-files | xargs pre-commit run --show-diff-on-failure --files
+
+test:	## Run tests via pytest.
+	pytest tests
+
+watch-docs:	## Build and watch documentation.
+	sphinx-autobuild docs/ docs/_build/html --open-browser --watch $(GIT_ROOT)/llama_index/
diff --git a/llama-index-packs/llama-index-packs-query-understanding-agent/README.md b/llama-index-packs/llama-index-packs-query-understanding-agent/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..2fcadd1c28969ac005c66ff98cd70393cce40aee
--- /dev/null
+++ b/llama-index-packs/llama-index-packs-query-understanding-agent/README.md
@@ -0,0 +1,39 @@
+# LlamaIndex Packs Integration: Query Understanding Agent
+
+This LlamaPack implements Query Understanding Agent
+
+Taking inspiration from Humans - when asked a query, humans would clarify what the query means before proceeding if the human sensed the query is unclear. This LlamaPack implements this.
+
+### Installation
+
+```bash
+pip install llama-index
+```
+
+## CLI Usage
+
+You can download llamapacks directly using `llamaindex-cli`, which comes installed with the `llama-index` python package:
+
+```bash
+llamaindex-cli download-llamapack QueryUnderstandingAgent --download-dir ./query_understanding_agent
+```
+
+You can then inspect the files at `./query_understanding_agent` and use them as a template for your own project.
+
+## Code Usage
+
+You can download the pack to a the `./query_understanding_agent` directory:
+
+```python
+from llama_index.core.llama_pack import download_llama_pack
+
+# download and install dependencies
+QueryUnderstandingAgentPack = download_llama_pack(
+    "QueryUnderstandingAgent", "./query_understanding_agent"
+)
+
+# You can use any llama-hub loader to get documents!
+```
+
+From here, you can use the pack, or inspect and modify the pack in `./query_understanding_agent`.
+See example notebook for usage.
diff --git a/llama-index-packs/llama-index-packs-query-understanding-agent/examples/query_understanding_agent.ipynb b/llama-index-packs/llama-index-packs-query-understanding-agent/examples/query_understanding_agent.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..8dea220651eb5c4e006df14b07ff5ac434fcc0e3
--- /dev/null
+++ b/llama-index-packs/llama-index-packs-query-understanding-agent/examples/query_understanding_agent.ipynb
@@ -0,0 +1,288 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "True"
+      ]
+     },
+     "execution_count": null,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "from dotenv import load_dotenv\n",
+    "\n",
+    "load_dotenv()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Grab Data"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "--2024-03-07 21:54:37--  https://raw.githubusercontent.com/run-llama/llama_index/main/docs/examples/data/paul_graham/paul_graham_essay.txt\n",
+      "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 2606:50c0:8000::154, 2606:50c0:8001::154, 2606:50c0:8002::154, ...\n",
+      "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|2606:50c0:8000::154|:443... connected.\n",
+      "HTTP request sent, awaiting response... 200 OK\n",
+      "Length: 75042 (73K) [text/plain]\n",
+      "Saving to: ‘data/paul_graham/paul_graham_essay.txt’\n",
+      "\n",
+      "data/paul_graham/pa 100%[===================>]  73.28K  --.-KB/s    in 0.01s   \n",
+      "\n",
+      "2024-03-07 21:54:37 (5.91 MB/s) - ‘data/paul_graham/paul_graham_essay.txt’ saved [75042/75042]\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "!mkdir -p 'data/paul_graham/'\n",
+    "!wget 'https://raw.githubusercontent.com/run-llama/llama_index/main/docs/examples/data/paul_graham/paul_graham_essay.txt' -O 'data/paul_graham/paul_graham_essay.txt'"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "steve_jobs_text = \"\"\"1995 was the year when Steve Jobs was approaching the end of his exile from Apple after being kicked out of the company a decade earlier. Him as his new company, NeXT, were brought back in to save Apple from near bankruptcy.Jul 25, 2018\"\"\""
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Load Data"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from llama_index.core import SimpleDirectoryReader, VectorStoreIndex\n",
+    "from llama_index.core.response.pprint_utils import pprint_response\n",
+    "from llama_index.llms.openai import OpenAI\n",
+    "from llama_index.core.schema import Document\n",
+    "\n",
+    "# Tool 1\n",
+    "llm = OpenAI()\n",
+    "data = SimpleDirectoryReader(input_dir=\"./data/paul_graham/\").load_data()\n",
+    "index = VectorStoreIndex.from_documents(data)\n",
+    "\n",
+    "# Tool 2\n",
+    "steve_index = VectorStoreIndex.from_documents([Document(text=steve_jobs_text)])"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Agents"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from llama_index.core.agent import AgentRunner\n",
+    "from llama_index.packs.query_understanding_agent.step import (\n",
+    "    QueryUnderstandingAgentWorker,\n",
+    "    HumanInputRequiredException,\n",
+    ")\n",
+    "from llama_index.core.tools import QueryEngineTool\n",
+    "\n",
+    "llm = OpenAI(model=\"gpt-4\")\n",
+    "\n",
+    "tools = [\n",
+    "    QueryEngineTool.from_defaults(\n",
+    "        query_engine=index.as_query_engine(),\n",
+    "        description=\"A tool that is useful for retrieving specific snippets from the Paul Graham's life\",\n",
+    "    ),\n",
+    "    QueryEngineTool.from_defaults(\n",
+    "        query_engine=steve_index.as_query_engine(),\n",
+    "        description=\"A tool that is useful for retrieving specific snippets from the Steve Jobs's life\",\n",
+    "    ),\n",
+    "]"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Baseline"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "This does not programmatically pause to detect ambiguity to allow user to provide input. Not only that, sometimes this hallucinate to a random subject."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "\u001b[1;3;38;5;200mThought: The user is asking about the author but hasn't specified which author. I have tools to query information about Paul Graham and Steve Jobs. I need to ask the user to specify the author.\n",
+      "\n",
+      "Action: None\n",
+      "Answer: Could you please specify which author you are referring to? I have information about Paul Graham and Steve Jobs.\n",
+      "\u001b[0m"
+     ]
+    }
+   ],
+   "source": [
+    "from llama_index.core.agent import ReActAgentWorker\n",
+    "\n",
+    "callback_manager = llm.callback_manager\n",
+    "agent_worker = ReActAgentWorker.from_tools(\n",
+    "    tools,\n",
+    "    llm=llm,\n",
+    "    verbose=True,\n",
+    "    callback_manager=callback_manager,\n",
+    ")\n",
+    "agent = AgentRunner(agent_worker, callback_manager=callback_manager)\n",
+    "orig_question = \"What did the author do in the summer of 1995?\"\n",
+    "response = agent.chat(orig_question)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Query Understanding Agent Worker"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "This allows the agent to ask user for clarification if the user query is unclear"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# from llama_index.core.agent import ReActAgentWorker\n",
+    "callback_manager = llm.callback_manager\n",
+    "agent_worker = QueryUnderstandingAgentWorker.from_tools(\n",
+    "    tools,\n",
+    "    llm=llm,\n",
+    "    callback_manager=callback_manager,\n",
+    ")\n",
+    "agent = AgentRunner(agent_worker, callback_manager=callback_manager)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "response: In the summer of 1995, Steve Jobs was involved in the process of returning to Apple after his departure from the company a decade earlier.\n"
+     ]
+    }
+   ],
+   "source": [
+    "# from llama_index.llms.openai import OpenAI\n",
+    "\n",
+    "orig_question = \"what did he do in the summer of 1995?\"\n",
+    "llm = OpenAI(model=\"gpt-4\")\n",
+    "clarifying_questions = []\n",
+    "\n",
+    "try:\n",
+    "    response = agent.chat(orig_question)\n",
+    "except HumanInputRequiredException as e:\n",
+    "    response = input(e.message)\n",
+    "    clarifying_questions.append((e.message, response))\n",
+    "    should_end = False\n",
+    "    while not should_end:\n",
+    "        clarifying_texts = \"\\n\".join(\n",
+    "            [\n",
+    "                f\"\"\"\n",
+    "   Q: {question}\n",
+    "   A: {answer}\n",
+    "        \"\"\"\n",
+    "                for question, answer in clarifying_questions\n",
+    "            ]\n",
+    "        )\n",
+    "        query_text = f\"\"\"\n",
+    "Given a query and a set of clarifying questions, please rewrite the query to be more clear.\n",
+    "Example:\n",
+    "Q: What trajectory is the monthly earning from the three months: April, May and June?\n",
+    "Clarifying Questions:\n",
+    "   Q: What year are you referring to?\n",
+    "   A: In 2022\n",
+    "   Q: What company are you referring to?\n",
+    "   A: Uber\n",
+    "Rewrite: What was the trajectory of Uber's monthly earnings for the months of April, May, and June in 2022?\n",
+    "\n",
+    "Q:{orig_question}\n",
+    "Clarifying Questions: {clarifying_texts}\n",
+    "Rewrite: \"\"\"\n",
+    "        rewrite_response = llm.complete(query_text)\n",
+    "        orig_question = rewrite_response\n",
+    "        try:\n",
+    "            output = agent.chat(rewrite_response.text)\n",
+    "            should_end = True\n",
+    "            print(f\"response: {output.response}\")\n",
+    "        except HumanInputRequiredException as er:\n",
+    "            response = input(er.message)\n",
+    "            clarifying_questions.append((er.message, response))"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/llama-index-packs/llama-index-packs-query-understanding-agent/llama_index/packs/query_understanding_agent/BUILD b/llama-index-packs/llama-index-packs-query-understanding-agent/llama_index/packs/query_understanding_agent/BUILD
new file mode 100644
index 0000000000000000000000000000000000000000..db46e8d6c978c67e301dd6c47bee08c1b3fd141c
--- /dev/null
+++ b/llama-index-packs/llama-index-packs-query-understanding-agent/llama_index/packs/query_understanding_agent/BUILD
@@ -0,0 +1 @@
+python_sources()
diff --git a/llama-index-packs/llama-index-packs-query-understanding-agent/llama_index/packs/query_understanding_agent/__init__.py b/llama-index-packs/llama-index-packs-query-understanding-agent/llama_index/packs/query_understanding_agent/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..ef02cda6e15c5823e1bbb8b80f1225da3ae45f26
--- /dev/null
+++ b/llama-index-packs/llama-index-packs-query-understanding-agent/llama_index/packs/query_understanding_agent/__init__.py
@@ -0,0 +1,4 @@
+from llama_index.packs.query_understanding_agent.base import QueryUnderstandingAgentPack
+
+
+__all__ = ["QueryUnderstandingAgentPack"]
diff --git a/llama-index-packs/llama-index-packs-query-understanding-agent/llama_index/packs/query_understanding_agent/base.py b/llama-index-packs/llama-index-packs-query-understanding-agent/llama_index/packs/query_understanding_agent/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..698c1b77519bb2bba1ac31e0bc2fd7891fb1061a
--- /dev/null
+++ b/llama-index-packs/llama-index-packs-query-understanding-agent/llama_index/packs/query_understanding_agent/base.py
@@ -0,0 +1,58 @@
+"""Query Understanding agent pack."""
+
+from typing import Any, Dict, List, Optional
+
+from llama_index.core.agent import AgentRunner
+from llama_index.core.callbacks import CallbackManager
+from llama_index.core.llama_pack.base import BaseLlamaPack
+from llama_index.core.llms.llm import LLM
+from llama_index.core.tools.types import BaseTool
+from llama_index.llms.openai import OpenAI
+
+from .step import QueryUnderstandingAgentWorker
+
+
+class QueryUnderstandingAgentPack(BaseLlamaPack):
+    """LLMCompilerAgent pack.
+
+    Args:
+        tools (List[BaseTool]): List of tools to use.
+        llm (Optional[LLM]): LLM to use.
+    """
+
+    def __init__(
+        self,
+        tools: List[BaseTool],
+        llm: Optional[LLM] = None,
+        callback_manager: Optional[CallbackManager] = None,
+        agent_worker_kwargs: Optional[Dict[str, Any]] = None,
+        agent_runner_kwargs: Optional[Dict[str, Any]] = None,
+    ) -> None:
+        """Init params."""
+        self.llm = llm or OpenAI(model="gpt-4")
+        self.callback_manager = callback_manager or self.llm.callback_manager
+        self.agent_worker = QueryUnderstandingAgentWorker.from_tools(
+            tools,
+            llm=llm,
+            verbose=True,
+            callback_manager=self.callback_manager,
+            **(agent_worker_kwargs or {})
+        )
+        self.agent = AgentRunner(
+            self.agent_worker,
+            callback_manager=self.callback_manager,
+            **(agent_runner_kwargs or {})
+        )
+
+    def get_modules(self) -> Dict[str, Any]:
+        """Get modules."""
+        return {
+            "llm": self.llm,
+            "callback_manager": self.callback_manager,
+            "agent_worker": self.agent_worker,
+            "agent": self.agent,
+        }
+
+    def run(self, *args: Any, **kwargs: Any) -> Any:
+        """Run the pipeline."""
+        return self.agent.chat(*args, **kwargs)
diff --git a/llama-index-packs/llama-index-packs-query-understanding-agent/llama_index/packs/query_understanding_agent/step.py b/llama-index-packs/llama-index-packs-query-understanding-agent/llama_index/packs/query_understanding_agent/step.py
new file mode 100644
index 0000000000000000000000000000000000000000..38ef228566c7181d69ad39b232a1f7386fecc049
--- /dev/null
+++ b/llama-index-packs/llama-index-packs-query-understanding-agent/llama_index/packs/query_understanding_agent/step.py
@@ -0,0 +1,277 @@
+from llama_index.core.bridge.pydantic import Field, BaseModel, PrivateAttr
+from llama_index.core import PromptTemplate
+from typing import Any, Dict, List, Optional
+from dataclasses import dataclass, field
+
+import uuid
+
+from llama_index.core.agent.types import (
+    Task,
+    TaskStep,
+    TaskStepOutput,
+)
+from llama_index.core.agent.custom.simple import CustomSimpleAgentWorker
+from llama_index.core.callbacks import (
+    trace_method,
+)
+from llama_index.core.chat_engine.types import AgentChatResponse
+
+
+from llama_index.core.agent import CustomSimpleAgentWorker, Task
+from typing import Dict, Any, List, Tuple, Optional
+from llama_index.core.tools import BaseTool, QueryEngineTool
+from llama_index.core.query_engine import RouterQueryEngine
+from llama_index.core.prompts import ChatPromptTemplate
+
+from llama_index.core.llms import ChatMessage, MessageRole
+
+DEFAULT_PROMPT_STR = """
+Given the question, the tools provided and response, please determine if the question is clear enough to provide the response (with the help of the tools).
+If the question is not clear enough, provide a clarifying_question for user to clarify. Provide a clarifying question that only need plan text answers. DO NOT ASK the user a similar question to the one the user asks you.
+If the response is sufficient, then return has_error: false, and requires_human_input: false. No need to make the response perfect, as long as it is sufficient.
+
+Given the questions and tools, here are several ways to clarify questions:
+- If there are multiple tools for each timeframe, then if the timeframe is not specified in the question, we need to ask to be clear on the timeframe.
+- Be clear on which subjects the user is referring to, ask which subject the user is referring to if there are a tool for each subject
+
+Example 1:
+Tools:
+A useful tool for financial documents for uber in 2022
+A useful tool for financial documents for lyft in 2022
+
+Question: What is the company's financial stats in 2022?
+Response: Uber's financial stats is 20k in the year 2022
+
+Answer:
+{{"requires_human_input": true, "has_error": true, "clarifying_question": "Which company are you referring to?", "explanation": "Given the tools and the question, it is not clear which company the user is referring to"}}
+
+Example 2:
+Tools:
+A useful tool for financial documents for uber in 2022
+A useful tool for financial documents for lyft in 2022
+
+Question: What is the Uber's financial stats in 2022?
+Response: Uber's financial stats is 20k in the year 2022
+
+Answer:
+{{"requires_human_input": false, "has_error": false, "clarifying_question": "", "explanation": "It is quite clear that the user is referring to uber and the year 2022"}}
+
+Tools:
+{tools}
+
+Question: {query_str}
+Response: {response_str}
+
+Please return the evaluation of the response in the following JSON format.
+Answer:
+"""
+
+
+@dataclass
+class AgentChatComplexResponse(AgentChatResponse):
+    """Agent chat response with metadata."""
+
+    output: Dict[str, Any] = field(default_factory=dict)
+
+
+def get_chat_prompt_template(
+    system_prompt: str, current_reasoning: Tuple[str, str]
+) -> ChatPromptTemplate:
+    system_msg = ChatMessage(role=MessageRole.SYSTEM, content=system_prompt)
+    messages = [system_msg]
+    for raw_msg in current_reasoning:
+        if raw_msg[0] == "user":
+            messages.append(ChatMessage(role=MessageRole.USER, content=raw_msg[1]))
+        else:
+            messages.append(ChatMessage(role=MessageRole.ASSISTANT, content=raw_msg[1]))
+    return ChatPromptTemplate(message_templates=messages)
+
+
+class ResponseEval(BaseModel):
+    """Evaluation of whether the response has an error."""
+
+    clarifying_question: str = Field(
+        ..., description="The clarifying question, if human input is required"
+    )
+    explanation: str = Field(
+        ...,
+        description=(
+            "The explanation for the error OR for the clarifying question."
+            "Can include the direct stack trace as well."
+        ),
+    )
+    has_error: bool = Field(..., description="Whether the response has an error")
+    requires_human_input: bool = Field(
+        ...,
+        description="Whether the response needs human input. If true, the clarifying question should be provided.",
+    )
+
+
+class HumanInputRequiredException(Exception):
+    """Exception raised when human input is required."""
+
+    def __init__(
+        self,
+        message="Human input is required",
+        task_id: Optional[str] = None,
+        step: TaskStep = None,
+    ):
+        self.message = message
+        self.task_id = task_id
+        self.step = step
+        super().__init__(self.message)
+
+
+class QueryUnderstandingAgentWorker(CustomSimpleAgentWorker):
+    """Agent worker that adds a retry layer on top of a router.
+
+    Continues iterating until there's no errors / task is done.
+
+    """
+
+    prompt_str: str = Field(default=DEFAULT_PROMPT_STR)
+    max_iterations: int = Field(default=10)
+
+    _router_query_engine: RouterQueryEngine = PrivateAttr()
+
+    def __init__(self, tools: List[BaseTool], **kwargs: Any) -> None:
+        """Init params."""
+        # validate that all tools are query engine tools
+        for tool in tools:
+            if not isinstance(tool, QueryEngineTool):
+                raise ValueError(
+                    f"Tool {tool.metadata.name} is not a query engine tool."
+                )
+        self._router_query_engine = RouterQueryEngine.from_defaults(
+            llm=kwargs.get("llm"),
+            select_multi=False,
+            query_engine_tools=tools,
+            verbose=kwargs.get("verbose", False),
+        )
+        super().__init__(
+            tools=tools,
+            **kwargs,
+        )
+
+    def _initialize_state(self, task: Task, **kwargs: Any) -> Dict[str, Any]:
+        """Initialize state."""
+        return {"count": 0, "current_reasoning": []}
+
+    def _run_llm_program(self, query_str, response_str, tools):
+        for _ in range(3):
+            try:
+                return self.llm.structured_predict(
+                    ResponseEval,
+                    PromptTemplate(self.prompt_str),
+                    query_str=query_str,
+                    response_str=str(response_str),
+                    tools=tools,
+                )
+            except Exception as e:
+                print(f"Attempt failed with error: {e}")
+                continue
+        raise Exception("Failed to run LLM program after 3 attempts")
+
+    def _run_step(
+        self, state: Dict[str, Any], task: Task, input: Optional[str] = None
+    ) -> Tuple[AgentChatComplexResponse, bool]:
+        """Run step.
+
+        Returns:
+            Tuple of (agent_response, is_done)
+
+        """
+        if input is not None:
+            # if input is specified, override input
+            new_input = input
+        elif "new_input" not in state:
+            new_input = task.input
+        else:
+            new_input = state["new_input"]["text"]
+
+        if self.verbose:
+            print(f"> Current Input: {new_input}")
+
+        # first run router query engine
+        response = self._router_query_engine.query(new_input)
+
+        # append to current reasoning
+        state["current_reasoning"].extend(
+            [("user", new_input), ("assistant", str(response))]
+        )
+
+        # Then, check for errors
+        # dynamically create pydantic program for structured output extraction based on template
+        tools = "\n".join([a.description for a in self._router_query_engine._metadatas])
+        response_eval = self._run_llm_program(
+            query_str=new_input, response_str=str(response), tools=tools
+        )
+
+        if self.verbose:
+            print(f"> Question: {new_input}")
+            print(f"> Response: {response}")
+            print(f"> Response eval: {response_eval.dict()}")
+
+        # return response
+        if response_eval.requires_human_input:
+            return (
+                AgentChatComplexResponse(
+                    response=response,
+                    output={
+                        "type": "requires_human_input",
+                        "clarifying_question": str(response_eval.clarifying_question),
+                        "has_error": response_eval.has_error,
+                        "explanation": response_eval.explanation,
+                    },
+                ),
+                True,
+            )
+
+        return AgentChatComplexResponse(response=response), not response_eval.has_error
+
+    @trace_method("run_step")
+    def run_step(self, step: TaskStep, task: Task, **kwargs: Any) -> TaskStepOutput:
+        """Run step."""
+        output, is_done = self._run_step(step.step_state, task, input=step.input)
+        if output.output and output.output["type"] == "requires_human_input":
+            raise HumanInputRequiredException(
+                message=output.output["clarifying_question"],
+                task_id=task.task_id,
+                step=step,
+            )
+
+        response = self._get_task_step_response(output, step, is_done)
+        # sync step state with task state
+        task.extra_state.update(step.step_state)
+        return response
+
+    def _get_task_step_response(
+        self,
+        output: Dict,
+        step: TaskStep,
+        is_done: bool,
+    ) -> TaskStepOutput:
+        """Get task step response."""
+        if is_done:
+            new_steps = []
+        else:
+            new_steps = [
+                step.get_next_step(
+                    step_id=str(uuid.uuid4()),
+                    # NOTE: input is unused
+                    input=None,
+                )
+            ]
+
+        return TaskStepOutput(
+            output=output,
+            task_step=step,
+            is_last=is_done,
+            next_steps=new_steps,
+        )
+
+    def _finalize_task(self, state: Dict[str, Any], **kwargs) -> None:
+        """Finalize task."""
+        # nothing to finalize here
+        # this is usually if you want to modify any sort of
+        # internal state beyond what is set in `_initialize_state`
diff --git a/llama-index-packs/llama-index-packs-query-understanding-agent/pyproject.toml b/llama-index-packs/llama-index-packs-query-understanding-agent/pyproject.toml
new file mode 100644
index 0000000000000000000000000000000000000000..55dd33421244e1c7db4f5664ea3a24bd24483903
--- /dev/null
+++ b/llama-index-packs/llama-index-packs-query-understanding-agent/pyproject.toml
@@ -0,0 +1,58 @@
+[build-system]
+build-backend = "poetry.core.masonry.api"
+requires = ["poetry-core"]
+
+[tool.codespell]
+check-filenames = true
+check-hidden = true
+# Feel free to un-skip examples, and experimental, you will just need to
+# work through many typos (--write-changes and --interactive will help)
+skip = "*.csv,*.html,*.json,*.jsonl,*.pdf,*.txt,*.ipynb"
+
+[tool.llamahub]
+contains_example = false
+import_path = "llama_index.packs.query_understanding_agent"
+
+[tool.llamahub.class_authors]
+QueryUnderstandingPack = "llama-index"
+
+[tool.mypy]
+disallow_untyped_defs = true
+# Remove venv skip when integrated with pre-commit
+exclude = ["_static", "build", "examples", "notebooks", "venv"]
+ignore_missing_imports = true
+python_version = "3.8"
+
+[tool.poetry]
+authors = ["Sasha Sheng <hackgoofer@gmail.com>"]
+description = "llama-index packs query understanding agent integration"
+exclude = ["**/BUILD"]
+license = "MIT"
+name = "llama-index-packs-query-understanding-agent"
+packages = [{include = "llama_index/"}]
+readme = "README.md"
+version = "0.1.0"
+
+[tool.poetry.dependencies]
+python = ">=3.8.1,<4.0"
+llama-index-core = "^0.10.0"
+llama-index-llms-openai = "^0.1.7"
+
+[tool.poetry.group.dev.dependencies]
+black = {extras = ["jupyter"], version = "<=23.9.1,>=23.7.0"}
+codespell = {extras = ["toml"], version = ">=v2.2.6"}
+ipython = "8.10.0"
+jupyter = "^1.0.0"
+mypy = "0.991"
+pre-commit = "3.2.0"
+pylint = "2.15.10"
+pytest = "7.2.1"
+pytest-mock = "3.11.1"
+ruff = "0.0.292"
+tree-sitter-languages = "^1.8.0"
+types-Deprecated = ">=0.1.0"
+types-PyYAML = "^6.0.12.12"
+types-protobuf = "^4.24.0.4"
+types-redis = "4.5.5.0"
+types-requests = "2.28.11.8"  # TODO: unpin when mypy>0.991
+types-setuptools = "67.1.0.0"
diff --git a/llama-index-packs/llama-index-packs-query-understanding-agent/requirements.txt b/llama-index-packs/llama-index-packs-query-understanding-agent/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/llama-index-packs/llama-index-packs-query-understanding-agent/tests/BUILD b/llama-index-packs/llama-index-packs-query-understanding-agent/tests/BUILD
new file mode 100644
index 0000000000000000000000000000000000000000..dabf212d7e7162849c24a733909ac4f645d75a31
--- /dev/null
+++ b/llama-index-packs/llama-index-packs-query-understanding-agent/tests/BUILD
@@ -0,0 +1 @@
+python_tests()
diff --git a/llama-index-packs/llama-index-packs-query-understanding-agent/tests/__init__.py b/llama-index-packs/llama-index-packs-query-understanding-agent/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/llama-index-packs/llama-index-packs-query-understanding-agent/tests/test_packs_query_understanding_agent.py b/llama-index-packs/llama-index-packs-query-understanding-agent/tests/test_packs_query_understanding_agent.py
new file mode 100644
index 0000000000000000000000000000000000000000..9329d52faf49350d6acd251f05b4a0d791be8838
--- /dev/null
+++ b/llama-index-packs/llama-index-packs-query-understanding-agent/tests/test_packs_query_understanding_agent.py
@@ -0,0 +1,7 @@
+from llama_index.core.llama_pack import BaseLlamaPack
+from llama_index.packs.query_understanding_agent import QueryUnderstandingAgentPack
+
+
+def test_class():
+    names_of_base_classes = [b.__name__ for b in QueryUnderstandingAgentPack.__mro__]
+    assert BaseLlamaPack.__name__ in names_of_base_classes