Skip to content

Commit 6750e67

Browse files
authored
Add Websockets example (microsoft#2291)
* add websockets example * polishing * README.me renamed to README.md * polishing
1 parent beacf4d commit 6750e67

File tree

3 files changed

+217
-0
lines changed

3 files changed

+217
-0
lines changed

samples/apps/websockets/README.md

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Using websockets with FastAPI and AutoGen
2+
3+
## Running the example
4+
5+
1. Navigate to the directory containing the example:
6+
```
7+
cd samples/apps/websockets
8+
```
9+
10+
2. Install the necessary dependencies:
11+
```
12+
./setup.py
13+
```
14+
15+
3. Run the application:
16+
```
17+
uvicorn application:app --reload
18+
```
19+
20+
You should now be able to access the application in your web browser at `http://localhost:8000`.
+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
#!/usr/bin/env python
2+
3+
import asyncio
4+
import logging
5+
import os
6+
from contextlib import asynccontextmanager # noqa: E402
7+
from datetime import datetime
8+
from typing import AsyncIterator, Dict, Iterator, List
9+
10+
import uvicorn # noqa: E402
11+
from fastapi import FastAPI # noqa: E402
12+
from fastapi.responses import HTMLResponse # noqa: E402
13+
from websockets.sync.client import connect as ws_connect
14+
15+
import autogen
16+
from autogen.io.websockets import IOWebsockets
17+
18+
PORT = 8000
19+
20+
# logger = getLogger(__name__)
21+
logger = logging.getLogger("uvicorn")
22+
23+
24+
def _get_config_list() -> List[Dict[str, str]]:
25+
"""Get a list of config dictionaries with API keys for OpenAI and Azure OpenAI.
26+
27+
Returns:
28+
List[Dict[str, str]]: A list of config dictionaries with API keys.
29+
30+
Example:
31+
>>> _get_config_list()
32+
[
33+
{
34+
'model': 'gpt-35-turbo-16k',
35+
'api_key': '0123456789abcdef0123456789abcdef',
36+
'base_url': 'https://my-deployment.openai.azure.com/',
37+
'api_type': 'azure',
38+
'api_version': '2024-02-15-preview',
39+
},
40+
{
41+
'model': 'gpt-4',
42+
'api_key': '0123456789abcdef0123456789abcdef',
43+
},
44+
]
45+
"""
46+
# can use both OpenAI and Azure OpenAI API keys
47+
config_list = [
48+
{
49+
"model": "gpt-35-turbo-16k",
50+
"api_key": os.environ.get("AZURE_OPENAI_API_KEY"),
51+
"base_url": os.environ.get("AZURE_OPENAI_BASE_URL"),
52+
"api_type": "azure",
53+
"api_version": os.environ.get("AZURE_OPENAI_API_VERSION"),
54+
},
55+
{
56+
"model": "gpt-4",
57+
"api_key": os.environ.get("OPENAI_API_KEY"),
58+
},
59+
]
60+
# filter out configs with no API key
61+
config_list = [llm_config for llm_config in config_list if llm_config["api_key"] is not None]
62+
63+
if not config_list:
64+
raise ValueError(
65+
"No API keys found. Please set either AZURE_OPENAI_API_KEY or OPENAI_API_KEY environment variable."
66+
)
67+
68+
return config_list
69+
70+
71+
def on_connect(iostream: IOWebsockets) -> None:
72+
logger.info(f"on_connect(): Connected to client using IOWebsockets {iostream}")
73+
74+
logger.info("on_connect(): Receiving message from client.")
75+
76+
# get the initial message from the client
77+
initial_msg = iostream.input()
78+
79+
# instantiate an agent named "chatbot"
80+
agent = autogen.ConversableAgent(
81+
name="chatbot",
82+
system_message="Complete a task given to you and reply TERMINATE when the task is done. If asked about the weather, use tool weather_forecast(city) to get the weather forecast for a city.",
83+
llm_config={
84+
"config_list": _get_config_list(),
85+
"stream": True,
86+
},
87+
)
88+
89+
# create a UserProxyAgent instance named "user_proxy"
90+
user_proxy = autogen.UserProxyAgent(
91+
name="user_proxy",
92+
system_message="A proxy for the user.",
93+
is_termination_msg=lambda x: x.get("content", "") and x.get("content", "").rstrip().endswith("TERMINATE"),
94+
human_input_mode="NEVER",
95+
max_consecutive_auto_reply=10,
96+
code_execution_config=False,
97+
)
98+
99+
# register the weather_forecast function
100+
def weather_forecast(city: str) -> str:
101+
return f"The weather forecast for {city} at {datetime.now()} is sunny."
102+
103+
autogen.register_function(
104+
weather_forecast, caller=agent, executor=user_proxy, description="Weather forecast for a city"
105+
)
106+
107+
# instantiate a chat
108+
logger.info(
109+
f"on_connect(): Initiating chat with the agent ({agent.name}) and the user proxy ({user_proxy.name}) using the message '{initial_msg}'",
110+
)
111+
user_proxy.initiate_chat( # noqa: F704
112+
agent,
113+
message=initial_msg,
114+
)
115+
116+
logger.info("on_connect(): Finished the task successfully.")
117+
118+
119+
html = """
120+
<!DOCTYPE html>
121+
<html>
122+
<head>
123+
<title>Autogen websocket test</title>
124+
</head>
125+
<body>
126+
<h1>WebSocket Chat</h1>
127+
<form action="" onsubmit="sendMessage(event)">
128+
<input type="text" id="messageText" autocomplete="off" value="Write a poem about the current wearther in Paris or London, you choose."/>
129+
<button>Send</button>
130+
</form>
131+
<ul id='messages'>
132+
</ul>
133+
<script>
134+
var ws = new WebSocket("ws://localhost:8080/ws");
135+
ws.onmessage = function(event) {
136+
var messages = document.getElementById('messages')
137+
var message = document.createElement('li')
138+
var content = document.createTextNode(event.data)
139+
message.appendChild(content)
140+
messages.appendChild(message)
141+
};
142+
function sendMessage(event) {
143+
var input = document.getElementById("messageText")
144+
ws.send(input.value)
145+
input.value = ''
146+
event.preventDefault()
147+
}
148+
</script>
149+
</body>
150+
</html>
151+
"""
152+
153+
154+
@asynccontextmanager
155+
async def run_websocket_server(app: FastAPI) -> AsyncIterator[None]:
156+
with IOWebsockets.run_server_in_thread(on_connect=on_connect, port=8080) as uri:
157+
logger.info(f"Websocket server started at {uri}.")
158+
159+
yield
160+
161+
162+
app = FastAPI(lifespan=run_websocket_server)
163+
164+
165+
@app.get("/")
166+
async def get() -> HTMLResponse:
167+
return HTMLResponse(html)
168+
169+
170+
async def start_uvicorn() -> None:
171+
config = uvicorn.Config(app)
172+
server = uvicorn.Server(config)
173+
try:
174+
await server.serve() # noqa: F704
175+
except KeyboardInterrupt:
176+
logger.info("Shutting down server")
177+
178+
179+
if __name__ == "__main__":
180+
# set the log level to INFO
181+
logger.setLevel("INFO")
182+
asyncio.run(start_uvicorn())

samples/apps/websockets/setup.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env python
2+
3+
# Equivalent to running the basj script below, but with an additional check if the files was moved:
4+
# cd ../../..
5+
# pip install -e .[websockets] fastapi uvicorn
6+
7+
import subprocess
8+
from pathlib import Path
9+
10+
repo_root = Path(__file__).parents[3]
11+
if not (repo_root / "setup.py").exists():
12+
raise RuntimeError("This script has been moved, please run it from its original location.")
13+
14+
print("Installing the package in editable mode, with the websockets extra, and fastapi and uvicorn...", flush=True)
15+
subprocess.run(["pip", "install", "-e", ".[websockets]", "fastapi", "uvicorn"], cwd=repo_root, check=True)

0 commit comments

Comments
 (0)