The ProblemYou’re making the jump to asynchronous Python to speed up your OpenAI API calls. But instead of faster results, you get a wall of red text. This usually happens the moment you switch from the standard OpenAI() client to AsyncOpenAI() and try to execute your code using asyncio.run().
Your notebook will likely trigger this specific error:
RuntimeError: This event loop is already running. Passing coroutines is forbidden when an event loop is already running.
Why this happens in JupyterThis happens because Jupyter Notebooks run on an IPython kernel. This kernel is already busy running an asyncio event loop to keep the interface responsive. Since Python’s standard library doesn't allow loops to be nested, calling asyncio.run() inside a notebook triggers a direct conflict. It tries to start a new loop where one is already active.
The OpenAI AsyncOpenAI client depends on these loops for non-blocking requests. When it detects two loops fighting for control, it shuts down to prevent data corruption.
The Quick Fix: nest-asyncioThe nest-asyncio package is a tiny utility that reconfigures the existing loop. It allows you to run asyncio.run() even when a loop is already active. This is the best choice if you want to keep your code compatible with standard Python scripts.
Step 1: Install the packageRun this command in a notebook cell to install the 1MB library:
%pip install nest-asyncio
Step 2: Apply the patchAdd these two lines at the top of your notebook. This only needs to be done once per session.
import nest_asyncio
nest_asyncio.apply()
Step 3: Run your AsyncOpenAI codeNow your async functions will run without crashing. Here is a complete example:
import asyncio
from openai import AsyncOpenAI
import nest_asyncio
nest_asyncio.apply()
client = AsyncOpenAI(api_key="your_api_key_here")
async def fetch_completion():
response = await client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": "Hello!"}]
)
print(response.choices[0].message.content)
asyncio.run(fetch_completion())
The Modern Fix: Top-Level AwaitIf you prefer a cleaner codebase, skip the extra library. Modern Jupyter environments (IPython 7.0 and later) support "top-level await" right out of the box. This means you can await functions directly in a cell without using asyncio.run() at all.
This approach reduces boilerplate and is generally considered best practice for interactive environments. Here is the streamlined version:
from openai import AsyncOpenAI
client = AsyncOpenAI(api_key="your_api_key_here")
# No asyncio.run() needed here
response = await client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": "Tell me a joke."}]
)
print(response.choices[0].message.content)
To verify the fixTest the fix by running three requests at once. This proves your environment handles concurrency correctly. If the code below prints three responses simultaneously, your setup is perfect:
async def test_concurrent():
tasks = [
client.chat.completions.create(model="gpt-3.5-turbo", messages=[{"role": "user", "content": f"Count to {i}"}])
for i in range(1, 4)
]
responses = await asyncio.gather(*tasks)
for res in responses:
print(res.choices[0].message.content)
await test_concurrent()

