Skip to content

Commit 8249754

Browse files
authored
Merge pull request #212 from Azure-Samples/gk/agents
add function calling example and logic apps workflow
2 parents c952c1d + fb8ca90 commit 8249754

File tree

20 files changed

+1101
-487
lines changed

20 files changed

+1101
-487
lines changed

.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ tfplan
373373
*.crt
374374
*.pub
375375
id_rsa
376-
.vscode/
376+
377377
python/expense-classification-guidance/.env
378378

379379
python/expense-classification-guidance/.idea/
@@ -389,3 +389,5 @@ sandbox/usecases/rag/dotnet/.env
389389
sandbox/notebooks/prompt-engineering/dotnet/.env
390390

391391
sandbox/prompt-engineering/notebooks/prompt-engineering/dotnet/.env
392+
393+
.vscode/settings.json

.vscode/extensions.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"recommendations": [
3+
"ms-azuretools.vscode-azurefunctions",
4+
"ms-python.python"
5+
]
6+
}

.vscode/launch.json

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"name": ".NET Core Launch (web)",
6+
"type": "coreclr",
7+
"request": "launch",
8+
"preLaunchTask": "build",
9+
"program": "${workspaceFolder}/dotnet/recommendation-service/bin/Debug/net7.0/GBB.Miyagi.RecommendationService.dll",
10+
"args": [
11+
"--verbose"
12+
],
13+
"cwd": "${workspaceFolder}/dotnet/recommendation-service",
14+
"stopAtEntry": false,
15+
"serverReadyAction": {
16+
"action": "openExternally",
17+
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
18+
},
19+
"env": {
20+
"ASPNETCORE_ENVIRONMENT": "Development"
21+
},
22+
"sourceFileMap": {
23+
"/Views": "${workspaceFolder}/Views"
24+
}
25+
},
26+
{
27+
"name": ".NET Core Attach",
28+
"type": "coreclr",
29+
"request": "attach"
30+
},
31+
{
32+
"name": "Attach to Python Functions",
33+
"type": "python",
34+
"request": "attach",
35+
"port": 9091,
36+
"preLaunchTask": "func: host start"
37+
}
38+
]
39+
}

.vscode/tasks.json

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
{
2+
"version": "2.0.0",
3+
"tasks": [
4+
{
5+
"label": "build",
6+
"command": "dotnet",
7+
"type": "process",
8+
"args": [
9+
"build",
10+
"${workspaceFolder}/dotnet/recommendation-service/GBB.Miyagi.RecommendationService.csproj",
11+
"/property:GenerateFullPaths=true",
12+
"/consoleloggerparameters:NoSummary"
13+
],
14+
"problemMatcher": "$msCompile"
15+
},
16+
{
17+
"label": "publish",
18+
"command": "dotnet",
19+
"type": "process",
20+
"args": [
21+
"publish",
22+
"${workspaceFolder}/dotnet/recommendation-service/GBB.Miyagi.RecommendationService.csproj",
23+
"/property:GenerateFullPaths=true",
24+
"/consoleloggerparameters:NoSummary"
25+
],
26+
"problemMatcher": "$msCompile"
27+
},
28+
{
29+
"label": "watch",
30+
"command": "dotnet",
31+
"type": "process",
32+
"args": [
33+
"watch",
34+
"run",
35+
"--project",
36+
"${workspaceFolder}/dotnet/recommendation-service/GBB.Miyagi.RecommendationService.csproj"
37+
],
38+
"problemMatcher": "$msCompile"
39+
},
40+
{
41+
"type": "func",
42+
"label": "func: host start",
43+
"command": "host start",
44+
"problemMatcher": "$func-python-watch",
45+
"isBackground": true,
46+
"dependsOn": "pip install (functions)",
47+
"options": {
48+
"cwd": "${workspaceFolder}/sandbox\\agents\\plugins"
49+
}
50+
},
51+
{
52+
"label": "pip install (functions)",
53+
"type": "shell",
54+
"osx": {
55+
"command": "${config:azureFunctions.pythonVenv}/bin/python -m pip install -r requirements.txt"
56+
},
57+
"windows": {
58+
"command": "${config:azureFunctions.pythonVenv}\\Scripts\\python -m pip install -r requirements.txt"
59+
},
60+
"linux": {
61+
"command": "${config:azureFunctions.pythonVenv}/bin/python -m pip install -r requirements.txt"
62+
},
63+
"problemMatcher": [],
64+
"options": {
65+
"cwd": "${workspaceFolder}/sandbox\\agents\\plugins"
66+
}
67+
}
68+
]
69+
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
OPENAI_API_KEY=<your-api-key>
22
OPENAI_MODEL_NAME=<your-model-name>
3-
OPENAI_ENDPOINT=<your-endpoint>
3+
OPENAI_ENDPOINT=<your-endpoint>
4+
DATA_COLLECTION_LOGIC_APPS_URI=<your-logic-app-workflow-uri>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# azure_ai_utils.py
2+
import io
3+
import matplotlib.pyplot as plt
4+
from typing import Iterable
5+
from pathlib import Path
6+
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
7+
8+
# Import AzureOpenAI and assistant-related Message classes
9+
from openai import AzureOpenAI
10+
from openai.types.beta.threads.message_content_image_file import MessageContentImageFile
11+
from openai.types.beta.threads.message_content_text import MessageContentText
12+
from openai.types.beta.threads.messages import MessageFile
13+
14+
15+
class NotCompletedException(Exception):
16+
"""Custom exception for handling incomplete run statuses."""
17+
pass
18+
19+
20+
class AzureAIUtils:
21+
"""
22+
A utility class for various Azure AI operations including
23+
message formatting, file uploading, and lifecycle status checking.
24+
"""
25+
26+
def __init__(self, client: AzureOpenAI):
27+
"""
28+
Initialize the utility class with an AzureOpenAI client.
29+
30+
Parameters:
31+
client (AzureOpenAI): An instance of the AzureOpenAI client.
32+
"""
33+
self.client = client
34+
35+
def format_response(self, messages: Iterable[MessageFile]) -> None:
36+
"""
37+
Formats and prints the content of messages from AzureOpenAI.
38+
39+
Parameters:
40+
messages (Iterable[MessageFile]): An iterable of MessageFile objects.
41+
"""
42+
message_list = []
43+
44+
for message in messages:
45+
message_list.append(message)
46+
if message.role == "user":
47+
break
48+
49+
message_list.reverse()
50+
51+
for message in message_list:
52+
for item in message.content:
53+
if isinstance(item, MessageContentText):
54+
print(f"{message.role}:\n{item.text.value}\n")
55+
elif isinstance(item, MessageContentImageFile):
56+
try:
57+
response_content = self.client.files.content(
58+
item.image_file.file_id
59+
)
60+
data_in_bytes = response_content.read()
61+
readable_buffer = io.BytesIO(data_in_bytes)
62+
image = plt.imread(readable_buffer, format="jpeg")
63+
plt.imshow(image)
64+
plt.axis("off")
65+
plt.show()
66+
except Exception as e:
67+
print(f"Exception: {e}")
68+
69+
def upload_file(self, path: Path):
70+
"""
71+
Uploads a file to AzureOpenAI.
72+
73+
Parameters:
74+
path (Path): The path to the file to be uploaded.
75+
76+
Returns:
77+
FileObject: The file object created in AzureOpenAI.
78+
"""
79+
with path.open("rb") as f:
80+
return self.client.files.create(file=f, purpose="assistants")
81+
82+
@retry(
83+
stop=stop_after_attempt(15),
84+
wait=wait_exponential(multiplier=1.5, min=4, max=20),
85+
retry=retry_if_exception_type(NotCompletedException),
86+
)
87+
def get_run_lifecycle_status(self, thread_id, run_id):
88+
"""
89+
Retrieves and prints the lifecycle status of a run and
90+
retries based on specific conditions.
91+
92+
Parameters:
93+
thread_id: The ID of the thread.
94+
run_id: The ID of the run.
95+
96+
Returns:
97+
The run object if its status is 'completed', 'failed', 'expired', or 'cancelled'.
98+
99+
Raises:
100+
NotCompletedException: If the run is not yet completed.
101+
"""
102+
run = self.client.beta.threads.runs.retrieve(thread_id=thread_id, run_id=run_id)
103+
print(f"Run status: {run.status}")
104+
if run.status in ["completed", "failed", "expired", "cancelled"]:
105+
print(f"Run info: {run}")
106+
return run
107+
elif run.status == "requires_action":
108+
pass # Handle cases that require action differently
109+
else:
110+
raise NotCompletedException("Run not completed yet")

0 commit comments

Comments
 (0)