Skip to content

Add Websockets example #2291

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions samples/apps/websockets/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Using websockets with FastAPI and AutoGen

## Running the example

1. Navigate to the directory containing the example:
```
cd samples/apps/websockets
```

2. Install the necessary dependencies:
```
./setup.py
```

3. Run the application:
```
uvicorn application:app --reload
```

You should now be able to access the application in your web browser at `http://localhost:8000`.
182 changes: 182 additions & 0 deletions samples/apps/websockets/application.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
#!/usr/bin/env python

import asyncio
import logging
import os
from contextlib import asynccontextmanager # noqa: E402
from datetime import datetime
from typing import AsyncIterator, Dict, Iterator, List

import uvicorn # noqa: E402
from fastapi import FastAPI # noqa: E402
from fastapi.responses import HTMLResponse # noqa: E402
from websockets.sync.client import connect as ws_connect

import autogen
from autogen.io.websockets import IOWebsockets

PORT = 8000

# logger = getLogger(__name__)
logger = logging.getLogger("uvicorn")


def _get_config_list() -> List[Dict[str, str]]:
"""Get a list of config dictionaries with API keys for OpenAI and Azure OpenAI.

Returns:
List[Dict[str, str]]: A list of config dictionaries with API keys.

Example:
>>> _get_config_list()
[
{
'model': 'gpt-35-turbo-16k',
'api_key': '0123456789abcdef0123456789abcdef',
'base_url': 'https://my-deployment.openai.azure.com/',
'api_type': 'azure',
'api_version': '2024-02-15-preview',
},
{
'model': 'gpt-4',
'api_key': '0123456789abcdef0123456789abcdef',
},
]
"""
# can use both OpenAI and Azure OpenAI API keys
config_list = [
{
"model": "gpt-35-turbo-16k",
"api_key": os.environ.get("AZURE_OPENAI_API_KEY"),
"base_url": os.environ.get("AZURE_OPENAI_BASE_URL"),
"api_type": "azure",
"api_version": os.environ.get("AZURE_OPENAI_API_VERSION"),
},
{
"model": "gpt-4",
"api_key": os.environ.get("OPENAI_API_KEY"),
},
]
# filter out configs with no API key
config_list = [llm_config for llm_config in config_list if llm_config["api_key"] is not None]

if not config_list:
raise ValueError(
"No API keys found. Please set either AZURE_OPENAI_API_KEY or OPENAI_API_KEY environment variable."
)

return config_list


def on_connect(iostream: IOWebsockets) -> None:
logger.info(f"on_connect(): Connected to client using IOWebsockets {iostream}")

logger.info("on_connect(): Receiving message from client.")

# get the initial message from the client
initial_msg = iostream.input()

# instantiate an agent named "chatbot"
agent = autogen.ConversableAgent(
name="chatbot",
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.",
llm_config={
"config_list": _get_config_list(),
"stream": True,
},
)

# create a UserProxyAgent instance named "user_proxy"
user_proxy = autogen.UserProxyAgent(
name="user_proxy",
system_message="A proxy for the user.",
is_termination_msg=lambda x: x.get("content", "") and x.get("content", "").rstrip().endswith("TERMINATE"),
human_input_mode="NEVER",
max_consecutive_auto_reply=10,
code_execution_config=False,
)

# register the weather_forecast function
def weather_forecast(city: str) -> str:
return f"The weather forecast for {city} at {datetime.now()} is sunny."

autogen.register_function(
weather_forecast, caller=agent, executor=user_proxy, description="Weather forecast for a city"
)

# instantiate a chat
logger.info(
f"on_connect(): Initiating chat with the agent ({agent.name}) and the user proxy ({user_proxy.name}) using the message '{initial_msg}'",
)
user_proxy.initiate_chat( # noqa: F704
agent,
message=initial_msg,
)

logger.info("on_connect(): Finished the task successfully.")


html = """
<!DOCTYPE html>
<html>
<head>
<title>Autogen websocket test</title>
</head>
<body>
<h1>WebSocket Chat</h1>
<form action="" onsubmit="sendMessage(event)">
<input type="text" id="messageText" autocomplete="off" value="Write a poem about the current wearther in Paris or London, you choose."/>
<button>Send</button>
</form>
<ul id='messages'>
</ul>
<script>
var ws = new WebSocket("ws://localhost:8080/ws");
ws.onmessage = function(event) {
var messages = document.getElementById('messages')
var message = document.createElement('li')
var content = document.createTextNode(event.data)
message.appendChild(content)
messages.appendChild(message)
};
function sendMessage(event) {
var input = document.getElementById("messageText")
ws.send(input.value)
input.value = ''
event.preventDefault()
}
</script>
</body>
</html>
"""


@asynccontextmanager
async def run_websocket_server(app: FastAPI) -> AsyncIterator[None]:
with IOWebsockets.run_server_in_thread(on_connect=on_connect, port=8080) as uri:
logger.info(f"Websocket server started at {uri}.")

yield


app = FastAPI(lifespan=run_websocket_server)


@app.get("/")
async def get() -> HTMLResponse:
return HTMLResponse(html)


async def start_uvicorn() -> None:
config = uvicorn.Config(app)
server = uvicorn.Server(config)
try:
await server.serve() # noqa: F704
except KeyboardInterrupt:
logger.info("Shutting down server")


if __name__ == "__main__":
# set the log level to INFO
logger.setLevel("INFO")
asyncio.run(start_uvicorn())
15 changes: 15 additions & 0 deletions samples/apps/websockets/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env python

# Equivalent to running the basj script below, but with an additional check if the files was moved:
# cd ../../..
# pip install -e .[websockets] fastapi uvicorn

import subprocess
from pathlib import Path

repo_root = Path(__file__).parents[3]
if not (repo_root / "setup.py").exists():
raise RuntimeError("This script has been moved, please run it from its original location.")

print("Installing the package in editable mode, with the websockets extra, and fastapi and uvicorn...", flush=True)
subprocess.run(["pip", "install", "-e", ".[websockets]", "fastapi", "uvicorn"], cwd=repo_root, check=True)
Loading