Skip to content

Commit 9597ffb

Browse files
committed
logic apps
1 parent a31fb98 commit 9597ffb

3 files changed

Lines changed: 463 additions & 0 deletions

File tree

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# pylint: disable=line-too-long,useless-suppression
2+
# ------------------------------------
3+
# Copyright (c) Microsoft Corporation.
4+
# Licensed under the MIT License.
5+
# ------------------------------------
6+
7+
"""
8+
DESCRIPTION:
9+
This sample demonstrates how to use agents with Logic Apps to execute the task of sending an email.
10+
11+
PREREQUISITES:
12+
1) Create a Logic App within the same resource group as your Azure AI Project in Azure Portal
13+
2) To configure your Logic App to send emails, you must include an HTTP request trigger that is
14+
configured to accept JSON with 'to', 'subject', and 'body'. The guide to creating a Logic App Workflow
15+
can be found here:
16+
https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/assistants-logic-apps#create-logic-apps-workflows-for-function-calling
17+
18+
USAGE:
19+
python logic_apps.py
20+
21+
Before running the sample:
22+
23+
pip install azure-ai-projects azure-identity
24+
25+
Set this environment variables with your own values:
26+
1) PROJECT_ENDPOINT - The project endpoint, as found in the overview page of your
27+
Azure AI Foundry project.
28+
2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in
29+
the "Models + endpoints" tab in your Azure AI Foundry project.
30+
31+
Replace the following values in the sample with your own values:
32+
1) <LOGIC_APP_NAME> - The name of the Logic App you created.
33+
2) <TRIGGER_NAME> - The name of the trigger in the Logic App you created (the default name for HTTP
34+
triggers in the Azure Portal is "When_a_HTTP_request_is_received").
35+
3) <RECIPIENT_EMAIL> - The email address of the recipient.
36+
"""
37+
38+
# <imports>
39+
import os
40+
import requests
41+
from typing import Set
42+
43+
from azure.ai.projects import AIProjectClient
44+
from azure.ai.agents.models import ToolSet, FunctionTool
45+
from azure.identity import DefaultAzureCredential
46+
47+
# Example user function
48+
from user_functions import fetch_current_datetime
49+
50+
# Import AzureLogicAppTool and the function factory from user_logic_apps
51+
from user_logic_apps import AzureLogicAppTool, create_send_email_function
52+
# </imports>
53+
54+
# <client_initialization>
55+
# Create the project client
56+
project_client = AIProjectClient.from_connection_string(
57+
credential=DefaultAzureCredential(),
58+
conn_str=os.environ["PROJECT_ENDPOINT"],
59+
)
60+
# </client_initialization>
61+
62+
# <logic_app_tool_setup>
63+
# Extract subscription and resource group from the project scope
64+
subscription_id = project_client.scope["subscription_id"]
65+
resource_group = project_client.scope["resource_group_name"]
66+
67+
# Logic App details
68+
logic_app_name = "<LOGIC_APP_NAME>"
69+
trigger_name = "<TRIGGER_NAME>"
70+
71+
# Create and initialize AzureLogicAppTool utility
72+
logic_app_tool = AzureLogicAppTool(subscription_id, resource_group)
73+
logic_app_tool.register_logic_app(logic_app_name, trigger_name)
74+
print(f"Registered logic app '{logic_app_name}' with trigger '{trigger_name}'.")
75+
# </logic_app_tool_setup>
76+
77+
# <function_creation>
78+
# Create the specialized "send_email_via_logic_app" function for your agent tools
79+
send_email_func = create_send_email_function(logic_app_tool, logic_app_name)
80+
81+
# Prepare the function tools for the agent
82+
functions_to_use: Set = {
83+
fetch_current_datetime,
84+
send_email_func, # This references the AzureLogicAppTool instance via closure
85+
}
86+
# </function_creation>
87+
88+
with project_client:
89+
# <agent_creation>
90+
# Create an agent
91+
functions = FunctionTool(functions=functions_to_use)
92+
toolset = ToolSet()
93+
toolset.add(functions)
94+
95+
agent = project_client.agents.create_agent(
96+
model=os.environ["MODEL_DEPLOYMENT_NAME"],
97+
name="SendEmailAgent",
98+
instructions="You are a specialized agent for sending emails.",
99+
toolset=toolset,
100+
)
101+
print(f"Created agent, ID: {agent.id}")
102+
# </agent_creation>
103+
104+
# <thread_management>
105+
# Create a thread for communication
106+
thread = project_client.agents.create_thread()
107+
print(f"Created thread, ID: {thread.id}")
108+
109+
# Create a message in the thread
110+
message = project_client.agents.create_message(
111+
thread_id=thread.id,
112+
role="user",
113+
content="Hello, please send an email to <RECIPIENT_EMAIL> with the date and time in '%Y-%m-%d %H:%M:%S' format.",
114+
)
115+
print(f"Created message, ID: {message.id}")
116+
# </thread_management>
117+
118+
# <message_processing>
119+
# Create and process an agent run in the thread
120+
run = project_client.agents.create_and_process_run(thread_id=thread.id, agent_id=agent.id)
121+
print(f"Run finished with status: {run.status}")
122+
123+
if run.status == "failed":
124+
print(f"Run failed: {run.last_error}")
125+
# </message_processing>
126+
127+
# <cleanup>
128+
# Delete the agent when done
129+
project_client.agents.delete_agent(agent.id)
130+
print("Deleted agent")
131+
132+
# Fetch and log all messages
133+
messages = project_client.agents.list_messages(thread_id=thread.id)
134+
print(f"Messages: {messages}")
135+
# </cleanup>
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
# pylint: disable=line-too-long,useless-suppression
2+
# ------------------------------------
3+
# Copyright (c) Microsoft Corporation.
4+
# Licensed under the MIT License.
5+
# ------------------------------------
6+
7+
import json
8+
import datetime
9+
from typing import Any, Callable, Set, Dict, List, Optional
10+
11+
# These are the user-defined functions that can be called by the agent.
12+
13+
14+
def fetch_current_datetime(format: Optional[str] = None) -> str:
15+
"""
16+
Get the current time as a JSON string, optionally formatted.
17+
18+
:param format (Optional[str]): The format in which to return the current time. Defaults to None, which uses a standard format.
19+
:return: The current time in JSON format.
20+
:rtype: str
21+
"""
22+
current_time = datetime.datetime.now()
23+
24+
# Use the provided format if available, else use a default format
25+
if format:
26+
time_format = format
27+
else:
28+
time_format = "%Y-%m-%d %H:%M:%S"
29+
30+
time_json = json.dumps({"current_time": current_time.strftime(time_format)})
31+
return time_json
32+
33+
34+
def fetch_weather(location: str) -> str:
35+
"""
36+
Fetches the weather information for the specified location.
37+
38+
:param location (str): The location to fetch weather for.
39+
:return: Weather information as a JSON string.
40+
:rtype: str
41+
"""
42+
# In a real-world scenario, you'd integrate with a weather API.
43+
# Here, we'll mock the response.
44+
mock_weather_data = {"New York": "Sunny, 25°C", "London": "Cloudy, 18°C", "Tokyo": "Rainy, 22°C"}
45+
weather = mock_weather_data.get(location, "Weather data not available for this location.")
46+
weather_json = json.dumps({"weather": weather})
47+
return weather_json
48+
49+
50+
def send_email(recipient: str, subject: str, body: str) -> str:
51+
"""
52+
Sends an email with the specified subject and body to the recipient.
53+
54+
:param recipient (str): Email address of the recipient.
55+
:param subject (str): Subject of the email.
56+
:param body (str): Body content of the email.
57+
:return: Confirmation message.
58+
:rtype: str
59+
"""
60+
# In a real-world scenario, you'd use an SMTP server or an email service API.
61+
# Here, we'll mock the email sending.
62+
print(f"Sending email to {recipient}...")
63+
print(f"Subject: {subject}")
64+
print(f"Body:\n{body}")
65+
66+
message_json = json.dumps({"message": f"Email successfully sent to {recipient}."})
67+
return message_json
68+
69+
70+
def send_email_using_recipient_name(recipient: str, subject: str, body: str) -> str:
71+
"""
72+
Sends an email with the specified subject and body to the recipient.
73+
74+
:param recipient (str): Name of the recipient.
75+
:param subject (str): Subject of the email.
76+
:param body (str): Body content of the email.
77+
:return: Confirmation message.
78+
:rtype: str
79+
"""
80+
# In a real-world scenario, you'd use an SMTP server or an email service API.
81+
# Here, we'll mock the email sending.
82+
print(f"Sending email to {recipient}...")
83+
print(f"Subject: {subject}")
84+
print(f"Body:\n{body}")
85+
86+
message_json = json.dumps({"message": f"Email successfully sent to {recipient}."})
87+
return message_json
88+
89+
90+
def calculate_sum(a: int, b: int) -> str:
91+
"""Calculates the sum of two integers.
92+
93+
:param a (int): First integer.
94+
:rtype: int
95+
:param b (int): Second integer.
96+
:rtype: int
97+
98+
:return: The sum of the two integers.
99+
:rtype: str
100+
"""
101+
result = a + b
102+
return json.dumps({"result": result})
103+
104+
105+
def convert_temperature(celsius: float) -> str:
106+
"""Converts temperature from Celsius to Fahrenheit.
107+
108+
:param celsius (float): Temperature in Celsius.
109+
:rtype: float
110+
111+
:return: Temperature in Fahrenheit.
112+
:rtype: str
113+
"""
114+
fahrenheit = (celsius * 9 / 5) + 32
115+
return json.dumps({"fahrenheit": fahrenheit})
116+
117+
118+
def toggle_flag(flag: bool) -> str:
119+
"""Toggles a boolean flag.
120+
121+
:param flag (bool): The flag to toggle.
122+
:rtype: bool
123+
124+
:return: The toggled flag.
125+
:rtype: str
126+
"""
127+
toggled = not flag
128+
return json.dumps({"toggled_flag": toggled})
129+
130+
131+
def merge_dicts(dict1: Dict[str, Any], dict2: Dict[str, Any]) -> str:
132+
"""Merges two dictionaries.
133+
134+
:param dict1 (Dict[str, Any]): First dictionary.
135+
:rtype: dict
136+
:param dict2 (Dict[str, Any]): Second dictionary.
137+
:rtype: dict
138+
139+
:return: The merged dictionary.
140+
:rtype: str
141+
"""
142+
merged = dict1.copy()
143+
merged.update(dict2)
144+
return json.dumps({"merged_dict": merged})
145+
146+
147+
def get_user_info(user_id: int) -> str:
148+
"""Retrieves user information based on user ID.
149+
150+
:param user_id (int): ID of the user.
151+
:rtype: int
152+
153+
:return: User information as a JSON string.
154+
:rtype: str
155+
"""
156+
mock_users = {
157+
1: {"name": "Alice", "email": "alice@example.com"},
158+
2: {"name": "Bob", "email": "bob@example.com"},
159+
3: {"name": "Charlie", "email": "charlie@example.com"},
160+
}
161+
user_info = mock_users.get(user_id, {"error": "User not found."})
162+
return json.dumps({"user_info": user_info})
163+
164+
165+
def longest_word_in_sentences(sentences: List[str]) -> str:
166+
"""Finds the longest word in each sentence.
167+
168+
:param sentences (List[str]): A list of sentences.
169+
:return: A JSON string mapping each sentence to its longest word.
170+
:rtype: str
171+
"""
172+
if not sentences:
173+
return json.dumps({"error": "The list of sentences is empty"})
174+
175+
longest_words = {}
176+
for sentence in sentences:
177+
# Split sentence into words
178+
words = sentence.split()
179+
if words:
180+
# Find the longest word
181+
longest_word = max(words, key=len)
182+
longest_words[sentence] = longest_word
183+
else:
184+
longest_words[sentence] = ""
185+
186+
return json.dumps({"longest_words": longest_words})
187+
188+
189+
def process_records(records: List[Dict[str, int]]) -> str:
190+
"""
191+
Process a list of records, where each record is a dictionary with string keys and integer values.
192+
193+
:param records: A list containing dictionaries that map strings to integers.
194+
:return: A list of sums of the integer values in each record.
195+
"""
196+
sums = []
197+
for record in records:
198+
# Sum up all the values in each dictionary and append the result to the sums list
199+
total = sum(record.values())
200+
sums.append(total)
201+
return json.dumps({"sums": sums})
202+
203+
204+
# Example User Input for Each Function
205+
# 1. Fetch Current DateTime
206+
# User Input: "What is the current date and time?"
207+
# User Input: "What is the current date and time in '%Y-%m-%d %H:%M:%S' format?"
208+
209+
# 2. Fetch Weather
210+
# User Input: "Can you provide the weather information for New York?"
211+
212+
# 3. Send Email
213+
# User Input: "Send an email to john.doe@example.com with the subject 'Meeting Reminder' and body 'Don't forget our meeting at 3 PM.'"
214+
215+
# 4. Calculate Sum
216+
# User Input: "What is the sum of 45 and 55?"
217+
218+
# 5. Convert Temperature
219+
# User Input: "Convert 25 degrees Celsius to Fahrenheit."
220+
221+
# 6. Toggle Flag
222+
# User Input: "Toggle the flag True."
223+
224+
# 7. Merge Dictionaries
225+
# User Input: "Merge these two dictionaries: {'name': 'Alice'} and {'age': 30}."
226+
227+
# 8. Get User Info
228+
# User Input: "Retrieve user information for user ID 1."
229+
230+
# 9. Longest Word in Sentences
231+
# User Input: "Find the longest word in each of these sentences: ['The quick brown fox jumps over the lazy dog', 'Python is an amazing programming language', 'Azure AI capabilities are impressive']."
232+
233+
# 10. Process Records
234+
# User Input: "Process the following records: [{'a': 10, 'b': 20}, {'x': 5, 'y': 15, 'z': 25}, {'m': 30}]."
235+
236+
# Statically defined user functions for fast reference
237+
user_functions: Set[Callable[..., Any]] = {
238+
fetch_current_datetime,
239+
fetch_weather,
240+
send_email,
241+
calculate_sum,
242+
convert_temperature,
243+
toggle_flag,
244+
merge_dicts,
245+
get_user_info,
246+
longest_word_in_sentences,
247+
process_records,
248+
}

0 commit comments

Comments
 (0)