Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@ def attachment_activity(
type=ActivityTypes.message,
attachment_layout=attachment_layout,
attachments=attachments,
input_hint=input_hint,
)
if text:
message.text = text
if speak:
message.speak = speak
if input_hint:
message.input_hint = input_hint
return message


Expand Down
71 changes: 71 additions & 0 deletions test_samples/weather-agent-open-ai/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import os
from agents import set_tracing_export_api_key
from dotenv import load_dotenv
from aiohttp.web import Application, Request, Response, run_app

from microsoft.agents.builder import RestChannelServiceClientFactory
from microsoft.agents.hosting.aiohttp import CloudAdapter, jwt_authorization_middleware
from microsoft.agents.authorization import (
Connections,
AccessTokenProviderBase,
ClaimsIdentity,
)
from microsoft.agents.authentication.msal import MsalAuth
from openai import AsyncAzureOpenAI

from weather_agent import WeatherAgent
from config import DefaultConfig

load_dotenv()

CONFIG = DefaultConfig()
AUTH_PROVIDER = MsalAuth(DefaultConfig())


class DefaultConnection(Connections):
def get_default_connection(self) -> AccessTokenProviderBase:
pass

def get_token_provider(
self, claims_identity: ClaimsIdentity, service_url: str
) -> AccessTokenProviderBase:
return AUTH_PROVIDER

def get_connection(self, connection_name: str) -> AccessTokenProviderBase:
pass


CHANNEL_CLIENT_FACTORY = RestChannelServiceClientFactory(CONFIG, DefaultConnection())

# Create adapter.
ADAPTER = CloudAdapter(CHANNEL_CLIENT_FACTORY)

# gets the API Key from environment variable AZURE_OPENAI_API_KEY
CLIENT = AsyncAzureOpenAI(
api_version=CONFIG.AZURE_OPENAI_API_VERSION,
azure_endpoint=CONFIG.AZURE_OPENAI_ENDPOINT,
)

# Create the Agent
AGENT = WeatherAgent(client=CLIENT)


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


APP = Application(middlewares=[jwt_authorization_middleware])
APP.router.add_post("/api/messages", messages)
APP["agent_configuration"] = CONFIG
APP["adapter"] = ADAPTER

if __name__ == "__main__":
try:
run_app(APP, host="localhost", port=CONFIG.PORT)
except Exception as error:
raise error
18 changes: 18 additions & 0 deletions test_samples/weather-agent-open-ai/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from os import environ
from microsoft.agents.authentication.msal import AuthTypes, MsalAuthConfiguration


class DefaultConfig(MsalAuthConfiguration):
"""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
2 changes: 2 additions & 0 deletions test_samples/weather-agent-open-ai/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
openai
openai-agents
10 changes: 10 additions & 0 deletions test_samples/weather-agent-open-ai/tools/date_time_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from agents import function_tool
from datetime import datetime


@function_tool
def get_date() -> str:
"""
A function tool that returns the current date and time.
"""
return datetime.now().isoformat()
23 changes: 23 additions & 0 deletions test_samples/weather-agent-open-ai/tools/get_weather_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import random
from pydantic import BaseModel

from agents import function_tool


class Weather(BaseModel):
city: str
temperature: str
conditions: str
date: str


@function_tool
def get_weather(city: str, date: str) -> Weather:
print("[debug] get_weather called")
temperature = random.randint(8, 21)
return Weather(
city=city,
temperature=f"{temperature}C",
conditions="Sunny with wind.",
date=date,
)
82 changes: 82 additions & 0 deletions test_samples/weather-agent-open-ai/weather_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from __future__ import annotations

from typing import Union
from microsoft.agents.builder import ActivityHandler, MessageFactory, TurnContext
from microsoft.agents.core.models import ChannelAccount, Attachment

from agents import (
Agent as OpenAIAgent,
Model,
ModelProvider,
OpenAIChatCompletionsModel,
RunConfig,
Runner,
)
from openai import AsyncAzureOpenAI
from pydantic import BaseModel, Field

from tools.get_weather_tool import get_weather
from tools.date_time_tool import get_date


class WeatherForecastAgentResponse(BaseModel):
contentType: str = Field(pattern=r"^(Text|AdaptiveCard)$")
content: Union[dict, str]


class WeatherAgent(ActivityHandler):
def __init__(self, client: AsyncAzureOpenAI):
self.agent = OpenAIAgent(
name="WeatherAgent",
instructions=""""
You are a friendly assistant that helps people find a weather forecast for a given time and place.
Do not reply with MD format nor plain text. You can ONLY respond in JSON format with the following JSON schema
{
"contentType": "'Text' if you don't have a forecast or 'AdaptiveCard' if you do",
"content": "{The content of the response, may be plain text, or JSON based adaptive card}"
}
You may ask follow up questions until you have enough information to answer the customers question,
but once you have a forecast forecast, make sure to format it nicely using an adaptive card.
""",
tools=[get_weather, get_date],
)

class CustomModelProvider(ModelProvider):
def get_model(self, model_name: str | None) -> Model:
return OpenAIChatCompletionsModel(
model=model_name or "gpt-4o", openai_client=client
)

self.custom_model_provider = CustomModelProvider()

async def on_members_added_activity(
self, members_added: list[ChannelAccount], turn_context: TurnContext
):
for member in members_added:
if member.id != turn_context.activity.recipient.id:
await turn_context.send_activity("Hello and welcome!")

async def on_message_activity(self, turn_context: TurnContext):
response = await Runner.run(
self.agent,
turn_context.activity.text,
run_config=RunConfig(
model_provider=self.custom_model_provider,
tracing_disabled=True,
),
)

llm_response = WeatherForecastAgentResponse.model_validate_json(
response.final_output
)
if llm_response.contentType == "AdaptiveCard":
activity = MessageFactory.attachment(
Attachment(
content_type="application/vnd.microsoft.card.adaptive",
content=llm_response.content,
)
)
elif llm_response.contentType == "Text":
activity = MessageFactory.text(llm_response.content)

return await turn_context.send_activity(activity)