Description
What happened?
Describe the bug
I was working through the agentchat_fastapi examples—specifically the WebSocket example (app_team.py). While the example works out of the box, I tried connecting using a custom WebSocket client. Submitting and receiving messages works just fine.
However, when I close the socket on the client side, the server prints an error message and continues to listen.
raise RuntimeError(f"Failed to get user input: {str(e)}") from e
RuntimeError: Failed to get user input: Failed to get user input: (1000, '')
Unexpected error: Unexpected ASGI message 'websocket.send', after sending 'websocket.close' or response already completed
Tracking down the issue was fairly straightforward. The server tries one last time to send a message because it's still waiting for user input (the app_team.py wraps websocket.receive_json inside a UserProxy agent, so it's effectively waiting for input).
What happens next is also simple:
- The WebSocket raises a WebSocketDisconnect exception, as it should.
- This exception is caught by a broad Exception, and then a RuntimeError is raised instead:
raise RuntimeError("Failed to get user input: )
Trying to address this by subclassing the UserProxy agent revealed that this pattern—i.e., catching broad exceptions—is used quite frequently. This obscures the root cause of errors.
In this particular example (WebSocketDisconnect), it prevents the chat-endpoint in the agent_fastapi examples from returning gracefully after the client disconnects. (Note: there's a workaround using nested try/except blocks in the examples. However, the inner loop also uses a broad except that fails when trying to send on a closed socket.)
I've found this pattern of obscuring exceptions throughout the codebase, not just here.
To Reproduce
Run app_team.py
from the agentchat_fastapi example and run the following script.
You can also kill the script instead of closing the socket. Ensure that the Server waits for input after at least one message was sent.
import asyncio
import json
import websockets
async def wsclient(uri="ws://localhost:8002/ws/chat"):
"""
1. send message to server
2. receive messages from server
3. server is waiting for input (UserProxyAgent)
3. close websocket on client side
"""
async with websockets.connect(uri) as websocket:
await websocket.send(json.dumps({"source": "user", "content": "Hello World"}))
async for response in websocket:
response = json.loads(response)
print(f"{response['source']}", response["type"])
if response["type"] == "UserInputRequestedEvent":
# causes incorrect errorhandling on the server side if
# close the socket while the server is waiting for input
await websocket.close()
if __name__ == "__main__":
asyncio.run(wsclient())
Expected behavior
Avoid catching of broad/blind Exceptions see:
- https://pylint.readthedocs.io/en/latest/user_guide/messages/warning/broad-exception-caught.html
- https://docs.astral.sh/ruff/rules/blind-except/
The WebSocketDisconnect exception should be propagated so that the chat_endpoint can exit gracefully. No reraising other Exception classes, just raise
or raise the same Exception Class e.g. raise WebSockentDisconnect('cusom message' from e
Which packages was the bug in?
Python AgentChat (autogen-agentchat>=0.4.0)
AutoGen library version.
Python 0.5.1