-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
6d01bba
commit b83069b
Showing
8 changed files
with
254 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import sys | ||
import traceback | ||
from datetime import datetime | ||
|
||
from aiohttp import web | ||
from aiohttp.web import Request, Response | ||
from botbuilder.core import ( | ||
TurnContext, | ||
) | ||
from botbuilder.core.integration import aiohttp_error_middleware | ||
from botbuilder.integration.aiohttp import CloudAdapter, ConfigurationBotFrameworkAuthentication | ||
from botbuilder.schema import Activity, ActivityTypes | ||
|
||
from bots.assistant import AssistantBot | ||
from core.config import settings as CONFIG | ||
|
||
|
||
# Create adapter. | ||
# See https://aka.ms/about-bot-adapter to learn more about how bots work. | ||
ADAPTER = CloudAdapter(ConfigurationBotFrameworkAuthentication(CONFIG)) | ||
|
||
|
||
# Catch-all for errors. | ||
async def on_error(context: TurnContext, error: Exception): | ||
# This check writes out errors to console log .vs. app insights. | ||
# NOTE: In production environment, you should consider logging this to Azure | ||
# application insights. | ||
print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr) | ||
traceback.print_exc() | ||
|
||
# Send a message to the user | ||
await context.send_activity("The bot encountered an error or bug.") | ||
await context.send_activity( | ||
"To continue to run this bot, please fix the bot source code." | ||
) | ||
# Send a trace activity if we're talking to the Bot Framework Emulator | ||
if context.activity.channel_id == "emulator": | ||
# Create a trace activity that contains the error object | ||
trace_activity = Activity( | ||
label="TurnError", | ||
name="on_turn_error Trace", | ||
timestamp=datetime.now(datetime.UTC), | ||
type=ActivityTypes.trace, | ||
value=f"{error}", | ||
value_type="https://www.botframework.com/schemas/error", | ||
) | ||
# Send a trace activity, which will be displayed in Bot Framework Emulator | ||
await context.send_activity(trace_activity) | ||
|
||
|
||
ADAPTER.on_turn_error = on_error | ||
|
||
# Create the Bot | ||
BOT = AssistantBot() | ||
|
||
|
||
# Listen for incoming requests on /api/messages | ||
async def messages(req: Request) -> Response: | ||
return await ADAPTER.process(req, BOT) | ||
|
||
|
||
APP = web.Application(middlewares=[aiohttp_error_middleware]) | ||
APP.router.add_post("/api/messages", messages) | ||
|
||
if __name__ == "__main__": | ||
try: | ||
web.run_app(APP, host="localhost", port=CONFIG.PORT) | ||
except Exception as error: | ||
raise error |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
from typing import List | ||
from botbuilder.core import ActivityHandler, MessageFactory, TurnContext | ||
from botbuilder.schema import ChannelAccount | ||
from llm.assisstant import assistant_handler | ||
|
||
|
||
class AssistantBot(ActivityHandler): | ||
thread_id = None | ||
|
||
async def on_members_added_activity( | ||
self, members_added: List[ChannelAccount], turn_context: TurnContext | ||
): | ||
"""Onboards new members to the assistant by creating a new thread and adding a initial welcome message. | ||
members_added (List[ChannelAccount]): The list of channel accounts. | ||
turn_context (TurnContext): The turn context. | ||
RETURNS (str): The welcome message actvity is being returned. | ||
""" | ||
for member in members_added: | ||
if member.id != turn_context.activity.recipient.id: | ||
# Initialize thread in assistant | ||
self.thread_id = assistant_handler.create_thread() | ||
# Respond with welcome message | ||
await turn_context.send_activity("Hello and welcome! I am your personal joke assistant. How can I help you today?") | ||
|
||
async def on_message_activity(self, turn_context: TurnContext): | ||
"""Acts upon new messages added to a channel. | ||
turn_context (TurnContext): The turn context. | ||
RETURNS (str): The assistant message actvity is being returned. | ||
""" | ||
# Interact with assistant | ||
message = assistant_handler.send_message( | ||
message=turn_context.activity.text, | ||
thread_id=self.thread_id, | ||
) | ||
if message: | ||
return await turn_context.send_activity( | ||
MessageFactory.text(message) | ||
) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import logging | ||
|
||
from pydantic import Field | ||
from pydantic_settings import BaseSettings | ||
|
||
|
||
class Settings(BaseSettings): | ||
# General project settings | ||
PROJECT_NAME: str = "BotAssistantSample" | ||
SERVER_NAME: str = "BotAssistantSample" | ||
APP_VERSION: str = "v0.0.1" | ||
PORT: int = 3978 | ||
|
||
# Logging settings | ||
LOGGING_LEVEL: int = logging.INFO | ||
DEBUG: bool = False | ||
APPLICATIONINSIGHTS_CONNECTION_STRING: str = Field( | ||
default="", alias="APPLICATIONINSIGHTS_CONNECTION_STRING" | ||
) | ||
|
||
# Authentication settings | ||
APP_ID: str = Field(default="", alias="MICROSOFT_APP_ID") | ||
APP_PASSWORD : str = Field(default="", alias="MICROSOFT_APP_PASSWORD") | ||
APP_TENANTID : str = Field(default="", alias="MICROSOFT_APP_TENANTID") | ||
APP_TYPE : str = Field(default="MultiTenant", alias="MICROSOFT_APP_TYPE") | ||
|
||
# Azure Open AI settings | ||
AZURE_OPEN_AI_ENDPOINT: str | ||
AZURE_OPEN_AI_API_VERSION: str | ||
AZURE_OPENAI_API_KEY: str | ||
AZURE_OPENAI_ASSISTANT_ID: str | ||
|
||
|
||
settings = Settings() |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import time | ||
import logging | ||
import json | ||
|
||
from core.config import settings | ||
|
||
from openai import AzureOpenAI | ||
from openai.types.beta.threads import Run | ||
|
||
class AssistantHandler: | ||
def __init__(self, *args,) -> None: | ||
self.client = AzureOpenAI( | ||
api_key=settings.AZURE_OPENAI_API_KEY, | ||
api_version=settings.AZURE_OPEN_AI_API_VERSION, | ||
azure_endpoint=settings.AZURE_OPEN_AI_ENDPOINT, | ||
) | ||
self.assistant_id = settings.AZURE_OPENAI_ASSISTANT_ID | ||
|
||
def create_thread(self) -> str: | ||
"""Create a thread in the assistant. | ||
RETURNS (str): Thread id of the newly created thread. | ||
""" | ||
thread = self.client.beta.threads.create() | ||
logging.debug(f"Created thread with thread id: '{thread.id}'") | ||
return thread.id | ||
|
||
def send_message(self, message: str, thread_id: str) -> str | None: | ||
"""Send a message to the thread and return the response from the assistant. | ||
message (str): The message to be sent to the assistant. | ||
thread_id (str): The thread id to which the message should be sent to the assistant. | ||
RETURNS (str): The response from the assistant is being returned. | ||
""" | ||
logging.debug(f"Adding message to thread with thread id: '{thread_id}' - Mesage: '{message}'") | ||
if thread_id is None: | ||
return None | ||
|
||
message = self.client.beta.threads.messages.create( | ||
thread_id=thread_id, | ||
content=message, | ||
role="user", | ||
) | ||
|
||
run = self.client.beta.threads.runs.create( | ||
thread_id=thread_id, | ||
assistant_id=self.assistant_id, | ||
) | ||
run = self.__wait_for_run(run=run, thread_id=thread_id) | ||
run = self.__check_for_tools(run=run, thread_id=thread_id) | ||
message = self.__get_assisstant_response(thread_id=thread_id) | ||
|
||
return message | ||
|
||
def __wait_for_run(self, run: Run, thread_id: str) -> Run: | ||
"""Wait for the run to complete and return the run once completed. | ||
run (Run): The run object of the assistant. | ||
thread_id (str): The thread id to which the message should be sent to the assistant. | ||
RETURNS (Run): The run that completed is being returned. | ||
""" | ||
while run.status not in ["completed", "cancelled", "expired", "failed"]: | ||
time.sleep(0.5) | ||
run = self.client.beta.threads.runs.retrieve( | ||
thread_id=thread_id, | ||
run_id=run.id | ||
) | ||
status = run.status | ||
logging.debug(f"Status of run '{run.id}' in thread '{thread_id}': {status}") | ||
return run | ||
|
||
def __check_for_tools(self, run: Run, thread_id: str) -> Run: | ||
"""Acts upon tools configured for the assistant. Runs the thread afterwards and returns the completed run. | ||
run (Run): The run object of the assistant. | ||
thread_id (str): The thread id to which the message should be sent to the assistant. | ||
RETURNS (Run): The run that completed is being returned. | ||
""" | ||
if run.required_action: | ||
pass | ||
|
||
# Do Action for Function and restart run after submitting action | ||
# run = self.wait_for_run(run=run, thread_id=thread_id) | ||
return run | ||
|
||
def __get_assisstant_response(self, thread_id: str) -> str: | ||
"""Gets the latest response from the assistant thread. | ||
thread_id (str): The thread id from which the latest message should be fetched. | ||
RETURNS (str): The latest message from the assistant in the thread. | ||
""" | ||
# Get message list and load as json object | ||
messages = self.client.beta.threads.messages.list( | ||
thread_id=thread_id, | ||
) | ||
messages_json = json.loads(messages.model_dump_json()) | ||
|
||
# Extract message from json object | ||
message_data_0 = messages_json.get("data", [{"content": [{"text": {"value": ""}}]}]).pop(0) | ||
message_data_0_content_0 = message_data_0.get("content", [{"text": {"value": ""}}]).pop(0) | ||
first_message_text = message_data_0_content_0.get("text", {"value": ""}).get("value") | ||
logging.debug(f"Response from Assistant in thread '{thread_id}': {first_message_text}") | ||
|
||
return first_message_text | ||
|
||
assistant_handler = AssistantHandler() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
botbuilder-core~=4.16.1 | ||
botbuilder-integration-aiohttp~=4.16.1 | ||
pydantic-settings~=2.2.1 | ||
pydantic~=2.7.0 | ||
openai~=1.42.0 |