From be50962884a17705bf8ee0f726f48796a523b4fc Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Wed, 8 Jan 2025 01:17:07 -0800 Subject: [PATCH] feat: ai bot example --- README.md | 6 +-- examples/ai.py | 129 +++++++++++++++++++++++++++++++++++++++++++++ examples/ollama.py | 61 --------------------- 3 files changed, 132 insertions(+), 64 deletions(-) create mode 100644 examples/ai.py delete mode 100644 examples/ollama.py diff --git a/README.md b/README.md index beb8ebf..4d726ce 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,15 @@ This repository provides an experimental boilerplate for building bots compatibl ## 🛠️ Getting Started with Examples This repository includes an `/examples` folder containing runnable example bots that demonstrate basic functionality. -To run an example, execute the corresponding module using the `-m` flag in Python. For example, to run the `ollama` example: +To run an example, execute the corresponding module using the `-m` flag in Python. For example, to run the `ai` example: ```bash -python -m examples.ollama +python -m examples.ai ``` > **Note**: Ensure that your current working directory (PWD) is the root of this repository when running examples, as this is required for proper execution. -Replace `ollama` in the command above with the specific example you’d like to execute from the `/examples` folder. +Replace `ai` in the command above with the specific example you’d like to execute from the `/examples` folder. ## 🚧 Disclaimer This project is an early-stage proof of concept. **APIs will break** and existing functionality may change as Open WebUI evolves to include native bot support. This repository is not production-ready and primarily serves experimental and exploratory purposes. diff --git a/examples/ai.py b/examples/ai.py new file mode 100644 index 0000000..323d783 --- /dev/null +++ b/examples/ai.py @@ -0,0 +1,129 @@ +import asyncio +import socketio +from env import WEBUI_URL, TOKEN +from utils import send_message, send_typing + + +MODEL_ID = "llama3.2:latest" + +# Create an asynchronous Socket.IO client instance +sio = socketio.AsyncClient(logger=False, engineio_logger=False) + + +# Event handlers +@sio.event +async def connect(): + print("Connected!") + + +@sio.event +async def disconnect(): + print("Disconnected from the server!") + + +import aiohttp +import asyncio + + +async def openai_chat_completion(messages): + payload = { + "model": MODEL_ID, + "messages": messages, + "stream": False, + } + + async with aiohttp.ClientSession() as session: + async with session.post( + f"{WEBUI_URL}/api/chat/completions", + headers={"Authorization": f"Bearer {TOKEN}"}, + json=payload, + ) as response: + if response.status == 200: + return await response.json() + else: + # Optional: Handle errors or return raw response text + return {"error": await response.text(), "status": response.status} + + +# Define a function to handle channel events +def events(user_id): + @sio.on("channel-events") + async def channel_events(data): + if data["user"]["id"] == user_id: + # Ignore events from the bot itself + return + + if data["data"]["type"] == "message": + print(f'{data["user"]["name"]}: {data["data"]["data"]["content"]}') + await send_typing(sio, data["channel_id"]) + + async def send_typing_until_complete(channel_id, coro): + """ + Sends typing indicators every second until the provided coroutine completes. + """ + task = asyncio.create_task(coro) # Begin the provided coroutine task + try: + # While the task is running, send typing indicators every second + while not task.done(): + await send_typing(sio, channel_id) + await asyncio.sleep(1) + # Await the actual result of the coroutine + return await task + except Exception as e: + task.cancel() + raise e # Propagate any exceptions that occurred in the coroutine + + # OpenAI API coroutine + openai_task = openai_chat_completion( + [ + {"role": "system", "content": "You are a friendly AI."}, + {"role": "user", "content": data["data"]["data"]["content"]}, + ] + ) + + try: + # Run OpenAI coroutine while showing typing indicators + response = await send_typing_until_complete( + data["channel_id"], openai_task + ) + + if response.get("choices"): + completion = response["choices"][0]["message"]["content"] + await send_message(data["channel_id"], completion) + else: + await send_message( + data["channel_id"], "I'm sorry, I don't understand." + ) + except Exception: + await send_message( + data["channel_id"], + "Something went wrong while processing your request.", + ) + + +# Define an async function for the main workflow +async def main(): + try: + print(f"Connecting to {WEBUI_URL}...") + await sio.connect( + WEBUI_URL, socketio_path="/ws/socket.io", transports=["websocket"] + ) + print("Connection established!") + except Exception as e: + print(f"Failed to connect: {e}") + return + + # Callback function for user-join + async def join_callback(data): + events(data["id"]) # Attach the event handlers dynamically + + # Authenticate with the server + await sio.emit("user-join", {"auth": {"token": TOKEN}}, callback=join_callback) + + # Wait indefinitely to keep the connection open + await sio.wait() + + +# Actually run the async `main` function using `asyncio` +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/ollama.py b/examples/ollama.py deleted file mode 100644 index ed1d5e3..0000000 --- a/examples/ollama.py +++ /dev/null @@ -1,61 +0,0 @@ -import asyncio -import socketio -from env import WEBUI_URL, TOKEN -from utils import send_message, send_typing - -# Create an asynchronous Socket.IO client instance -sio = socketio.AsyncClient(logger=False, engineio_logger=False) - - -# Event handlers -@sio.event -async def connect(): - print("Connected!") - - -@sio.event -async def disconnect(): - print("Disconnected from the server!") - - -# Define a function to handle channel events -def events(user_id): - @sio.on("channel-events") - async def channel_events(data): - if data["user"]["id"] == user_id: - # Ignore events from the bot itself - return - - if data["data"]["type"] == "message": - print(f'{data["user"]["name"]}: {data["data"]["data"]["content"]}') - await send_typing(sio, data["channel_id"]) - await asyncio.sleep(1) # Simulate a delay - await send_message(data["channel_id"], "Pong!") - - -# Define an async function for the main workflow -async def main(): - try: - print(f"Connecting to {WEBUI_URL}...") - await sio.connect( - WEBUI_URL, socketio_path="/ws/socket.io", transports=["websocket"] - ) - print("Connection established!") - except Exception as e: - print(f"Failed to connect: {e}") - return - - # Callback function for user-join - async def join_callback(data): - events(data["id"]) # Attach the event handlers dynamically - - # Authenticate with the server - await sio.emit("user-join", {"auth": {"token": TOKEN}}, callback=join_callback) - - # Wait indefinitely to keep the connection open - await sio.wait() - - -# Actually run the async `main` function using `asyncio` -if __name__ == "__main__": - asyncio.run(main())