How to Fix 'async event loop error' in LlamaIndex (Python)
What this error means
The async event loop error in LlamaIndex usually means you called an async API from the wrong execution context. In practice, it shows up when you mix asyncio.run(), Jupyter notebooks, FastAPI, or background tasks with LlamaIndex methods that already manage the event loop.
The exact failure often looks like one of these:
- •
RuntimeError: This event loop is already running - •
RuntimeError: asyncio.run() cannot be called from a running event loop - •
RuntimeError: There is no current event loop in thread 'ThreadPoolExecutor-0_0'
The Most Common Cause
The #1 cause is calling asyncio.run() inside an environment that already has an active event loop, especially Jupyter, FastAPI, or another async app.
LlamaIndex has both sync and async APIs. If you call an async method from a notebook cell or inside an async route and wrap it again with asyncio.run(), Python throws the error before LlamaIndex can finish.
Wrong pattern vs right pattern
| Broken code | Fixed code |
|---|---|
| ```python | |
| import asyncio | |
| from llama_index.core import VectorStoreIndex, SimpleDirectoryReader |
docs = SimpleDirectoryReader("data").load_data() index = VectorStoreIndex.from_documents(docs)
Jupyter / FastAPI / any running loop
asyncio.run(index.as_query_engine().aquery("What is in the docs?"))
|python
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
docs = SimpleDirectoryReader("data").load_data() index = VectorStoreIndex.from_documents(docs)
Sync path: use sync API
query_engine = index.as_query_engine() response = query_engine.query("What is in the docs?") print(response)
If you are already inside async code, keep it async all the way:
| Broken code | Fixed code |
|---|---|
|```python
import asyncio
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
async def main():
docs = SimpleDirectoryReader("data").load_data()
index = VectorStoreIndex.from_documents(docs)
result = asyncio.run(index.as_query_engine().aquery("Summarize this"))
print(result)
asyncio.run(main())
```|```python
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
async def main():
docs = SimpleDirectoryReader("data").load_data()
index = VectorStoreIndex.from_documents(docs)
result = await index.as_query_engine().aquery("Summarize this")
print(result)
# only call asyncio.run() at the top level of a script
import asyncio
asyncio.run(main())
```|
The rule is simple:
- Use `.query()` / `.retrieve()` / sync helpers in sync code
- Use `await ...aquery()` / `await ...aretrieve()` in async code
- Call `asyncio.run()` only once, at the top level of a plain Python script
## Other Possible Causes
### 1. Running async code inside Jupyter without `await`
Jupyter already runs an event loop. If you try to start another one, you get:
`RuntimeError: This event loop is already running`
Use direct `await` in notebooks:
```python
# broken in notebook
import asyncio
response = asyncio.run(index.as_query_engine().aquery("Hello"))
# fixed in notebook
response = await index.as_query_engine().aquery("Hello")
2. Mixing sync and async LlamaIndex APIs
If you create an async query engine but call it like a sync object, or vice versa, you can trigger loop-related failures.
# broken
query_engine = index.as_query_engine()
response = await query_engine.query("Find invoices") # query() is sync
# fixed
response = await query_engine.aquery("Find invoices")
3. Calling LlamaIndex from a thread without an event loop
This happens in worker threads, Celery tasks, or custom executors.
RuntimeError: There is no current event loop in thread ...
# broken pattern inside a worker thread
def worker():
engine = index.as_query_engine()
return asyncio.get_event_loop().run_until_complete(engine.aquery("status"))
# fixed pattern: create and own the loop explicitly if needed
def worker():
import asyncio
async def run():
engine = index.as_query_engine()
return await engine.aquery("status")
return asyncio.run(run())
If your framework already owns threading and async boundaries, keep the LlamaIndex call on the main async path instead of forcing loops into workers.
4. Using FastAPI route handlers incorrectly
FastAPI supports async routes natively. Wrapping LlamaIndex calls with asyncio.run() inside a request handler is a common mistake.
# broken
from fastapi import FastAPI
import asyncio
app = FastAPI()
@app.get("/search")
async def search():
return asyncio.run(index.as_query_engine().aquery("policy details"))
# fixed
@app.get("/search")
async def search():
return await index.as_query_engine().aquery("policy details")
How to Debug It
- •
Read the exact exception text
- •If you see
asyncio.run() cannot be called from a running event loop, you are double-starting the loop. - •If you see
This event loop is already running, you're probably in Jupyter or an async web server. - •If you see
There is no current event loop, you're likely in a worker thread.
- •If you see
- •
Check whether your caller is already async
- •Jupyter cells are effectively async.
- •FastAPI route handlers declared with
async defare already inside a running loop. - •Any function using
awaitupstream means you should not callasyncio.run()downstream.
- •
Inspect which LlamaIndex method you are calling
- •Sync methods:
.query(),.retrieve() - •Async methods:
.aquery(),.aretrieve() - •Do not wrap sync methods in async plumbing unless you have a specific reason.
- •Sync methods:
- •
Reduce to a plain Python script
- •Move the same code into
script.py. - •Run it with
python script.py. - •If it works there but fails elsewhere, the issue is your runtime context, not LlamaIndex itself.
- •Move the same code into
Prevention
- •
Keep one boundary for async ownership:
- •scripts own their own loop with one top-level
asyncio.run() - •web frameworks own the loop; use
awaitonly
- •scripts own their own loop with one top-level
- •
Pick one API style per call path:
- •sync path:
.query() - •async path:
.aquery()
- •sync path:
- •
Add a small integration test for your runtime target:
- •notebook usage if your team prototypes there
- •FastAPI if that’s your production entrypoint
- •worker-thread execution if jobs run outside request handlers
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
By Cyprian Aarons, AI Consultant at Topiax.
Want the complete 8-step roadmap?
Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.
Get the Starter Kit