I am trying to write an asynchronous chat client in Streamlit. Synchronous chat clients (where prompts and responses occur sequentially on the same thread) are of course very easy to write. However, I have not been able to write an asynchronous one.
I am writing a multi-user chat application where clients can run in separate processes. I am using the NATS publish/subscribe framework for the backend. NATS uses asyncio. A client is notified when when a subject that it has subscribed to has new messages and displays them. The client can also publish messages entered in the GUI to the system.
I have not been able to write a Streamlit app that handles both receiving and sending. The best I’ve been able to do is write the receiving part.
import asyncio
import streamlit as st
from nats.aio.client import Client
async def main():
st.set_page_config(page_title="NATS Listener")
st.title("NATS Listener")
chat_container = st.container(height=300)
while True:
for message in await subject_messages():
with chat_container.chat_message("user"):
st.write(message.data.decode())
async def subject_messages():
client = Client()
await client.connect()
subscription = await client.subscribe("chat", max_msgs=1)
messages = []
async for msg in subscription.messages:
messages.append(msg)
return messages
if __name__ == "__main__":
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
asyncio.run(main())
except KeyboardInterrupt:
pass
finally:
print("Closing Loop")
loop.close()
If you launch this and then in a separate process use a NATS command line client to publish messages to the “chat” subject, you will see the following.
So far so good. Now I want to add a chat_input
element for sending messages. The problem is that for this element to always be active it has to go inside the while True
loop in main
, which causes multiple instances to be created. I can’t figure out how to get around this.
I think the problem comes down to the fact that the client has to be listening for asynchronous notifications throughout its entire lifetime. To make this happen I have to do tricky things with asyncio loops that conflict with Streamlit’s run-the-script-once model. I feel like I’m forcing Streamlit to do something it doesn’t want to do.
From searching online it appears that I’m not the only one confused about how to make asyncio and Streamlit work together. Also the Native asyncio support issue makes me wonder if what I want to do is currently possible.
Is there a way to implement an asynchronous chat application like I have described? Or am I hitting a limitation of Streamlit?