Overview #
- Async Python code can be non-blocking for things like network requests
- AsyncIO only uses one thread with an event loop
- This document only outlines modern Python async code
- Modern async python code is a lot like JavaScript
- FastAPI is built on
AnyIOwhich supports theasynciostandard library
Coroutines #
- These behave like generators and can be defined with
async def- You can
awaitother coroutines inside of a coroutine (async function)
- You can
- Using the
yieldkeyword in a coroutine will make this an async generator - A coroutine is returned by an
asyncfunction- You can await this coroutine to get the return value of the function
- These are comparable to goroutines
asyncio.queueallows two coroutines to share data- This is intended for coroutines to use that are running in the same event loop (thread)
AsyncIO #
asyncio.runstarts the event loop and will schedule the coroutine provided- This is the main entrypoint for async programs
asyncio.waitcan be used to wait for multiple coroutines to finish- This can return pending and completed coroutines, and you can configure when to return (e.g. all complete, first coroutine completed, etc.)
asyncio.Eventcan be used as a signal between coroutines that some event happened- This is similar to a
threading.Eventand is essentially a boolean flag
- This is similar to a
Tasks #
- A task is a single unit of work that can be scheduled in the event loop
- Tasks can be created with
asyncio.create_task- Tasks are wrappers around coroutines and you can use these to schedule coroutines to run concurrently
- This scheduled the coroutine to run in the next iteration of the event loop – even before awaiting
- This is why tasks are useful
- You can await tasks
ayncio.ensure_futurewas an older way of creating tasks- Now, you should use
asyncio.create_taskif you have a coroutine and only useasyncio.ensure_futureif you have an arbitrary future
- Now, you should use
Generators #
- Allow you to define functions that behave as iterators which can be entered or exited at any time
- Async generators are just a coroutine that uses
yieldinstead ofreturn- To loop over an async generator you must use
async for - Each iteration of the loop returns an
awaitableobject that must be awaited
- To loop over an async generator you must use
FastAPI #
-
To stream server-side events, you need an async iterable
-
The challenge – Return an async iterable to FastAPI top-level
-
- Make the
get_chat_responsefunction an async generator that yields streamed tokens for each LLM call (probably best)
- Make the
-
- Figure out a way with async callbacks
- I don’t like this approach because now we are coupling an async event to the langchain callbacks
- This won’t scale well when we want to fire events for tools/function calls, etc.
- Probably the way to do this is to call astream and manually build up the messages with +=
- This seems to be the only way to go non-blocking all the way up the stack
- The get singular chat response can’t block and return a final value or the
- This seems to be the only way to go non-blocking all the way up the stack
-