Skip to content
Closed
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
10 changes: 10 additions & 0 deletions test_samples/e2e_test/.envLocal
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTID=
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__CLIENTSECRET=
CONNECTIONS__SERVICE_CONNECTION__SETTINGS__TENANTID=

AZURE_OPENAI_ENDPOINT=
AZURE_OPENAI_API_KEY=
AZURE_OPENAI_API_VERSION=
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME=
AZURE_OPENAI_TEXT_DEPLOYMENT_NAME=
AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME=
246 changes: 246 additions & 0 deletions test_samples/e2e_test/agent_bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
from __future__ import annotations
import re
from typing import Optional, Union
from os import environ
import json

from microsoft.agents.hosting.core import (
AgentApplication,
TurnState,
TurnContext,
MessageFactory,
)
from microsoft.agents.activity import (
ActivityTypes,
InvokeResponse,
Activity,
ConversationUpdateTypes,
ChannelAccount,
Attachment,
)

from agents import (
Agent as OpenAIAgent,
Model,
ModelProvider,
OpenAIChatCompletionsModel,
RunConfig,
Runner,
ModelSettings,
)

from pydantic import BaseModel, Field
from semantic_kernel import Kernel
from semantic_kernel.contents import ChatHistory
from weather.agents.weather_forecast_agent import WeatherForecastAgent

from openai import AsyncAzureOpenAI


class AgentBot:
def __init__(self, client: AsyncAzureOpenAI):
self.client = client
self.multiple_message_pattern = re.compile(r"(\w+)\s+(\d+)")
self.weather_message_pattern = re.compile(r"^w: .*")

def register_handlers(self, agent_app: AgentApplication[TurnState]):
"""Register all handlers with the agent application"""
agent_app.conversation_update(ConversationUpdateTypes.MEMBERS_ADDED)(
self.on_members_added
)
agent_app.message(self.weather_message_pattern)(self.on_weather_message)
agent_app.message(self.multiple_message_pattern)(self.on_multiple_message)
agent_app.message(re.compile(r"^poem$"))(self.on_poem_message)
agent_app.activity(ActivityTypes.message)(self.on_message)
agent_app.activity(ActivityTypes.invoke)(self.on_invoke)
agent_app.activity(ActivityTypes.message_update)(self.on_message_edit)
agent_app.activity(ActivityTypes.event)(self.on_meeting_events)

async def on_members_added(self, context: TurnContext, _state: TurnState):
await context.send_activity(MessageFactory.text("Hello and Welcome!"))

async def on_weather_message(self, context: TurnContext, state: TurnState):

context.streaming_response.queue_informative_update(
"Working on a response for you"
)

chat_history = state.get_value(
"ConversationState.chatHistory",
lambda: ChatHistory(),
target_cls=ChatHistory,
)

weather_agent = WeatherForecastAgent()

forecast_response = await weather_agent.invoke_agent_async(
context.activity.text, chat_history
)
if forecast_response == None:
context.streaming_response.queue_text_chunk(
"Sorry, I couldn't get the weather forecast at the moment."
)
await context.streaming_response.end_stream()
return

if forecast_response.contentType == "AdaptiveCard":
context.streaming_response.set_attachments(
[
Attachment(
content_type="application/vnd.microsoft.card.adaptive",
content=forecast_response.content,
)
]
)
else:
context.streaming_response.queue_text_chunk(forecast_response.content)

await context.streaming_response.end_stream()

async def on_multiple_message(self, context: TurnContext, state: TurnState):
counter = state.get_value(
"ConversationState.counter",
default_value_factory=(lambda: 0),
target_cls=int,
)

match = self.multiple_message_pattern.match(context.activity.text)
if not match:
return
word = match.group(1)
count = int(match.group(2))
for _ in range(count):
await context.send_activity(f"[{counter}] You said: {word}")
counter += 1

state.set_value("ConversationState.counter", counter)

async def on_poem_message(self, context: TurnContext, state: TurnState):
try:
context.streaming_response.queue_informative_update(
"Hold on for an awesome poem about Apollo..."
)

stream = await self.client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "system",
"content": """
You are a creative assistant who has deeply studied Greek and Roman Gods, You also know all of the Percy Jackson Series
You write poems about the Greek Gods as they are depicted in the Percy Jackson books.
You format the poems in a way that is easy to read and understand
You break your poems into stanzas
You format your poems in Markdown using double lines to separate stanzas
""",
},
{
"role": "user",
"content": "Write a poem about the Greek God Apollo as depicted in the Percy Jackson books",
},
],
stream=True,
max_tokens=1000,
)

async for update in stream:
if len(update.choices) > 0:
delta = update.choices[0].delta
if delta.content:
context.streaming_response.queue_text_chunk(delta.content)
finally:
await context.streaming_response.end_stream()

async def on_message(self, context: TurnContext, state: TurnState):
counter = state.get_value(
"ConversationState.counter",
default_value_factory=(lambda: 0),
target_cls=int,
)
await context.send_activity(f"[{counter}] You said: {context.activity.text}")
counter += 1
state.set_value("ConversationState.counter", counter)

async def on_invoke(self, context: TurnContext, state: TurnState):

# Simulate Teams extensions until implemented
if context.activity.name == "composeExtension/query":
invoke_response = InvokeResponse(
status=200,
body={
"ComposeExtension": {
"type": "result",
"AttachmentLayout": "list",
"Attachments": [
{"content_type": "test", "content_url": "example.com"}
],
}
},
)

await context.send_activity(
Activity(type=ActivityTypes.invoke_response, value=invoke_response)
)
elif context.activity.name == "composeExtension/queryLink":
invoke_response = InvokeResponse(
status=200,
body={
"ChannelId": "msteams",
"ComposeExtension": {
"type": "result",
"text": "On Query Link",
},
},
)
await context.send_activity(
Activity(type=ActivityTypes.invoke_response, value=invoke_response)
)
elif context.activity.name == "composeExtension/selectItem":
value = context.activity.value
invoke_response = InvokeResponse(
status=200,
body={
"ChannelId": "msteams",
"ComposeExtension": {
"type": "result",
"AttachmentLayout": "list",
"Attachments": [
{
"contenttype": "application/vnd.microsoft.card.thumbnail",
"content": {
"title": f"{value["id"]}, {value["version"]}"
},
}
],
},
},
)
await context.send_activity(
Activity(type=ActivityTypes.invoke_response, value=invoke_response)
)
else:
invoke_response = InvokeResponse(
status=200,
body={"message": "Invoke received.", "data": context.activity.value},
)

await context.send_activity(
Activity(type=ActivityTypes.invoke_response, value=invoke_response)
)

async def on_message_edit(self, context: TurnContext, state: TurnState):
await context.send_activity(f"Message Edited: {context.activity.id}")

async def on_meeting_events(self, context: TurnContext, state: TurnState):
if context.activity.name == "application/vnd.microsoft.meetingStart":
await context.send_activity(
f"Meeting started with ID: {context.activity.value["id"]}"
)
elif context.activity.name == "application/vnd.microsoft.meetingEnd":
await context.send_activity(
f"Meeting ended with ID: {context.activity.value["id"]}"
)
elif (
context.activity.name == "application/vnd.microsoft.meetingParticipantJoin"
):
await context.send_activity("Welcome to the meeting!")
90 changes: 90 additions & 0 deletions test_samples/e2e_test/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
from __future__ import annotations
import logging
from aiohttp.web import Application, Request, Response, run_app
from dotenv import load_dotenv
from os import environ, path

from semantic_kernel import Kernel
from semantic_kernel.utils.logging import setup_logging
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from openai import AsyncAzureOpenAI

from microsoft.agents.hosting.aiohttp import (
CloudAdapter,
jwt_authorization_middleware,
start_agent_process,
)
from microsoft.agents.hosting.core import (
Authorization,
AgentApplication,
TurnState,
MemoryStorage,
)
from microsoft.agents.authentication.msal import MsalConnectionManager
from microsoft.agents.activity import load_configuration_from_env, ConversationUpdateTypes, ActivityTypes
import re

from agent_bot import AgentBot

# Load environment variables
load_dotenv(path.join(path.dirname(__file__), ".env"))

# Load configuration
agents_sdk_config = load_configuration_from_env(environ)

# Initialize storage and connection manager
STORAGE = MemoryStorage()
CONNECTION_MANAGER = MsalConnectionManager(**agents_sdk_config)
ADAPTER = CloudAdapter(connection_manager=CONNECTION_MANAGER)
AUTHORIZATION = Authorization(STORAGE, CONNECTION_MANAGER, **agents_sdk_config)

# Initialize Semantic Kernel
kernel = Kernel()

chat_completion = AzureChatCompletion(
deployment_name=environ.get("AZURE_OPENAI_DEPLOYMENT_NAME", "gpt-4o"),
base_url=environ.get("AZURE_OPENAI_ENDPOINT"),
api_key=environ.get("AZURE_OPENAI_API_KEY"),
service_id="adaptive_card_service"
)

kernel.add_service(chat_completion)

# Initialize Azure OpenAI client
client = AsyncAzureOpenAI(
api_version=environ.get("AZURE_OPENAI_API_VERSION"),
azure_endpoint=environ.get("AZURE_OPENAI_ENDPOINT"),
api_key=environ.get("AZURE_OPENAI_API_KEY"),
)

# Initialize Agent Application
AGENT_APP_INSTANCE = AgentApplication[TurnState](
storage=STORAGE, adapter=ADAPTER, authorization=AUTHORIZATION, **agents_sdk_config
)

# Create and configure the AgentBot
AGENT = AgentBot(client)
AGENT.register_handlers(AGENT_APP_INSTANCE)

# Listen for incoming requests on /api/messages
async def messages(req: Request) -> Response:
agent: AgentApplication = req.app["agent_app"]
adapter: CloudAdapter = req.app["adapter"]
return await start_agent_process(
req,
agent,
adapter,
)

# Create the application
APP = Application(middlewares=[jwt_authorization_middleware])
APP.router.add_post("/api/messages", messages)
APP["agent_configuration"] = CONNECTION_MANAGER.get_default_connection_configuration()
APP["agent_app"] = AGENT_APP_INSTANCE
APP["adapter"] = ADAPTER

if __name__ == "__main__":
try:
run_app(APP, host="localhost", port=3978)
except Exception as error:
raise error
18 changes: 18 additions & 0 deletions test_samples/e2e_test/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from os import environ
from microsoft.agents.hosting.core import AuthTypes, AgentAuthConfiguration


class DefaultConfig(AgentAuthConfiguration):
"""Agent Configuration"""

def __init__(self) -> None:
self.AUTH_TYPE = AuthTypes.client_secret
self.TENANT_ID = "" or environ.get("TENANT_ID")
self.CLIENT_ID = "" or environ.get("CLIENT_ID")
self.CLIENT_SECRET = "" or environ.get("CLIENT_SECRET")
self.AZURE_OPENAI_API_KEY = "" or environ.get("AZURE_OPENAI_API_KEY")
self.AZURE_OPENAI_ENDPOINT = "" or environ.get("AZURE_OPENAI_ENDPOINT")
self.AZURE_OPENAI_API_VERSION = "" or environ.get(
"AZURE_OPENAI_API_VERSION", "2024-06-01"
)
self.PORT = 3978
11 changes: 11 additions & 0 deletions test_samples/e2e_test/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
openai
openai-agents
semantic-kernel

-e ../../libraries/microsoft-agents-hosting-core/ editable_mode=compat
-e ../../libraries/microsoft-agents-hosting-aiohttp/ editable_mode=compat
-e ../../libraries/microsoft-agents-authentication-msal/ editable_mode=compat
-e ../../libraries/microsoft-agents-activity/ editable_mode=compat
-e ../../libraries/microsoft-agents-hosting-teams/ editable_mode=compat
-e ../../libraries/microsoft-agents-copilotstudio-client/ editable_mode=compat
-e ../../libraries/microsoft-agents-storage-blob/ editable_mode=compat
Loading