Skip to content

Commit bf639e6

Browse files
authored
Merge pull request Azure#72 from Azure/alemor-assistants
Added Sales Copilot.
2 parents cf498e6 + 3c44459 commit bf639e6

13 files changed

Lines changed: 676 additions & 1 deletion

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ appsettings.json
88
.azure
99

1010
.env
11-
*_db
11+
*_db
12+
13+
__pycache__
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from openai import AzureOpenAI
2+
from AgentRegistration import AgentRegistration
3+
from AgentSettings import AgentSettings
4+
from ArgumentException import ArgumentExceptionError
5+
6+
7+
class AgentProxy:
8+
def __init__(self, settings=None, client=None, registered_agents: list[AgentRegistration] = None):
9+
self.settings = settings
10+
self.client = client
11+
self.registered_agents = registered_agents
12+
13+
if registered_agents is None:
14+
raise ArgumentExceptionError("Missing registered_agents")
15+
16+
if settings is None:
17+
self.settings = AgentSettings()
18+
19+
if client is None:
20+
client = AzureOpenAI(
21+
api_key=self.settings.api_key,
22+
api_version=self.settings.api_version,
23+
azure_endpoint=self.settings.api_endpoint)
24+
25+
def __semantic_intent(self, prompt: str) -> str:
26+
prompt_template = """system:
27+
You are an agent that can determine intent from the following list of intents and return the intent that best matches the user's question or statement.
28+
29+
List of intents:
30+
<INTENTS>
31+
OtherAgent: any other question
32+
33+
user:
34+
<QUESTION>
35+
36+
Output in ONE word."""
37+
38+
intents = ""
39+
for reg_agent in self.registered_agents:
40+
intents += f"{reg_agent.intent}: {reg_agent.intent_desc}\n"
41+
42+
full_prompt = prompt_template.replace(
43+
"<INTENTS>", intents).replace("<QUESTION>", prompt)
44+
completion = self.client.chat.completions.create(
45+
model=self.settings.model_deployment,
46+
messages=[
47+
{
48+
"role": "user",
49+
"content": full_prompt,
50+
},
51+
],
52+
max_tokens=2,
53+
temperature=0.1
54+
)
55+
try:
56+
intent = completion.choices[0].message.content
57+
return intent
58+
except:
59+
return "Unknown"
60+
61+
def process_for_intent(self, user_name, user_id, prompt: str) -> str:
62+
intent = self.__semantic_intent(prompt)
63+
print(f'Intent: {intent}')
64+
if intent is None or intent == "OtherAgent" or intent == "Unknown":
65+
completion = self.client.chat.completions.create(
66+
model=self.settings.model_deployment,
67+
messages=[
68+
{
69+
"role": "user",
70+
"content": prompt,
71+
}
72+
]
73+
)
74+
print(completion.choices[0].message.content)
75+
else:
76+
for registered_agent in self.registered_agents:
77+
if registered_agent.intent == intent:
78+
return registered_agent.agent.process_prompt(user_name, user_id, prompt)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from AgentSettings import AgentSettings
2+
from openai import AzureOpenAI
3+
from ArgumentException import ArgumentExceptionError
4+
from AssistantAgent import AssistantAgent
5+
6+
7+
class AgentRegistration:
8+
"""This function is to hold the agent registration information"""
9+
10+
def __init__(self, settings=None, client=None, intent: str = None, intent_desc: str = None, agent: AssistantAgent = None):
11+
self.settings = settings
12+
self.client = client
13+
self.agent = agent
14+
self.intent = intent
15+
self.intent_desc = intent_desc
16+
17+
if intent is None:
18+
raise ArgumentExceptionError("intent parameter is missing")
19+
if intent_desc is None:
20+
raise ArgumentExceptionError("intent_desc parameter is missing")
21+
22+
if settings is None:
23+
self.settings = AgentSettings()
24+
25+
if client is None:
26+
client = AzureOpenAI(
27+
api_key=self.settings.api_key,
28+
api_version=self.settings.api_version,
29+
azure_endpoint=self.settings.api_endpoint)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from dotenv import load_dotenv
2+
import os
3+
4+
5+
class AgentSettings:
6+
def __init__(self):
7+
load_dotenv()
8+
self.api_endpoint = os.getenv("OPENAI_URI")
9+
self.api_key = os.getenv("OPENAI_KEY")
10+
self.api_version = os.getenv("OPENAI_VERSION")
11+
self.model_deployment = os.getenv("OPENAI_GPT_DEPLOYMENT")
12+
self.email_URI = os.getenv("EMAIL_URI")
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class ArgumentExceptionError(Exception):
2+
def __init__(self, msg):
3+
self.msg = msg
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
from typing import Iterable
2+
import os
3+
import io
4+
import time
5+
from datetime import datetime
6+
from pathlib import Path
7+
8+
9+
from AgentSettings import AgentSettings
10+
11+
from openai.types.beta.threads.message_content_image_file import MessageContentImageFile
12+
from openai.types.beta.threads.message_content_text import MessageContentText
13+
from openai.types.beta.threads.messages import MessageFile
14+
from openai.types import FileObject
15+
from PIL import Image
16+
from ArgumentException import ArgumentExceptionError
17+
18+
19+
class AssistantAgent:
20+
def __init__(self, settings, client, name, instructions, data_folder, tools_list, keep_state: bool = False, fn_calling_delegate=None):
21+
if name is None:
22+
raise ArgumentExceptionError("name parameter missing")
23+
if instructions is None:
24+
raise ArgumentExceptionError("instructions parameter missing")
25+
if tools_list is None:
26+
raise ArgumentExceptionError("tools_list parameter missing")
27+
28+
self.assistant = None
29+
self.settings = settings
30+
self.client = client
31+
self.name = name
32+
self.instructions = instructions
33+
self.data_folder = data_folder
34+
self.tools_list = tools_list
35+
self.fn_calling_delegate = fn_calling_delegate
36+
self.keep_state = keep_state
37+
self.ai_threads = []
38+
self.ai_files = []
39+
self.file_ids = []
40+
self.get_agent()
41+
42+
def upload_file(self, path: str) -> FileObject:
43+
print(path)
44+
with Path(path).open("rb") as f:
45+
return self.client.files.create(file=f, purpose="assistants")
46+
47+
def upload_all_files(self):
48+
files_in_folder = os.listdir(self.data_folder)
49+
local_file_list = []
50+
for file in files_in_folder:
51+
filePath = self.data_folder + file
52+
assistant_file = self.upload_file(filePath)
53+
self.ai_files.append(assistant_file)
54+
local_file_list.append(assistant_file)
55+
self.file_ids = [file.id for file in local_file_list]
56+
57+
def get_agent(self):
58+
if self.data_folder is not None:
59+
self.upload_all_files()
60+
self.assistant = self.client.beta.assistants.create(
61+
name=self.name, # "Sales Assistant",
62+
# "You are a sales assistant. You can answer questions related to customer orders.",
63+
instructions=self.instructions,
64+
tools=self.tools_list,
65+
model=self.settings.model_deployment,
66+
file_ids=self.file_ids
67+
)
68+
else:
69+
self.assistant = self.client.beta.assistants.create(
70+
name=self.name, # "Sales Assistant",
71+
# "You are a sales assistant. You can answer questions related to customer orders.",
72+
instructions=self.instructions,
73+
tools=self.tools_list,
74+
model=self.settings.model_deployment
75+
)
76+
77+
def process_prompt(self, user_name: str, user_id: str, prompt: str) -> None:
78+
79+
# if keep_state:
80+
# thread_id = check_if_thread_exists(user_id)
81+
82+
# # If a thread doesn't exist, create one and store it
83+
# if thread_id is None:
84+
# print(f"Creating new thread for {name} with user_id {user_id}")
85+
# thread = self.client.beta.threads.create()
86+
# store_thread(user_id, thread)
87+
# thread_id = thread.id
88+
# # Otherwise, retrieve the existing thread
89+
# else:
90+
# print(
91+
# f"Retrieving existing thread for {name} with user_id {user_id}")
92+
# thread = self.client.beta.threads.retrieve(thread_id)
93+
# add_thread(thread)
94+
# else:
95+
thread = self.client.beta.threads.create()
96+
97+
self.client.beta.threads.messages.create(
98+
thread_id=thread.id, role="user", content=prompt)
99+
100+
run = self.client.beta.threads.runs.create(
101+
thread_id=thread.id,
102+
assistant_id=self.assistant.id,
103+
instructions="Please address the user as Jane Doe. The user has a premium account. Be assertive, accurate, and polite. Ask if the user has further questions. Do not provide explanations for the answers."
104+
+ "The current date and time is: "
105+
+ datetime.now().strftime("%x %X")
106+
+ ". ",
107+
)
108+
109+
print("processing ...")
110+
while True:
111+
run = self.client.beta.threads.runs.retrieve(
112+
thread_id=thread.id, run_id=run.id)
113+
if run.status == "completed":
114+
# Handle completed
115+
messages = self.client.beta.threads.messages.list(
116+
thread_id=thread.id)
117+
self.print_messages(user_name, messages)
118+
break
119+
if run.status == "failed":
120+
messages = self.client.beta.threads.messages.list(
121+
thread_id=thread.id)
122+
self.print_messages(user_name, messages)
123+
# Handle failed
124+
break
125+
if run.status == "expired":
126+
# Handle expired
127+
break
128+
if run.status == "cancelled":
129+
# Handle cancelled
130+
break
131+
if run.status == "requires_action":
132+
if self.fn_calling_delegate:
133+
self.fn_calling_delegate(self.client, thread, run)
134+
else:
135+
time.sleep(5)
136+
if not self.keep_state:
137+
self.client.beta.threads.delete(thread.id)
138+
print("Deleted thread: ", thread.id)
139+
140+
def read_assistant_file(self, file_id: str):
141+
response_content = self.client.files.content(file_id)
142+
return response_content.read()
143+
144+
def print_messages(self, name: str, messages: Iterable[MessageFile]) -> None:
145+
message_list = []
146+
147+
# Get all the messages till the last user message
148+
for message in messages:
149+
message_list.append(message)
150+
if message.role == "user":
151+
break
152+
153+
# Reverse the messages to show the last user message first
154+
message_list.reverse()
155+
156+
# Print the user or Assistant messages or images
157+
for message in message_list:
158+
for item in message.content:
159+
# Determine the content type
160+
if isinstance(item, MessageContentText):
161+
if message.role == "user":
162+
print(f"user: {name}:\n{item.text.value}\n")
163+
else:
164+
print(f"{message.role}:\n{item.text.value}\n")
165+
file_annotations = item.text.annotations
166+
if file_annotations:
167+
for annotation in file_annotations:
168+
file_id = annotation.file_path.file_id
169+
content = self.read_assistant_file(file_id)
170+
print(f"Annotation Content:\n{str(content)}\n")
171+
elif isinstance(item, MessageContentImageFile):
172+
# Retrieve image from file id
173+
data_in_bytes = self.read_assistant_file(
174+
item.image_file.file_id)
175+
# Convert bytes to image
176+
readable_buffer = io.BytesIO(data_in_bytes)
177+
image = Image.open(readable_buffer)
178+
# Resize image to fit in terminal
179+
width, height = image.size
180+
image = image.resize(
181+
(width // 2, height // 2), Image.LANCZOS)
182+
# Display image
183+
image.show()
184+
185+
def cleanup(self):
186+
print(self.client.beta.assistants.delete(self.assistant.id))
187+
print("Deleting: ", len(self.ai_threads), " threads.")
188+
for thread in self.ai_threads:
189+
print(self.client.beta.threads.delete(thread.id))
190+
print("Deleting: ", len(self.ai_files), " files.")
191+
for file in self.ai_files:
192+
print(self.client.files.delete(file.id))
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CustomerID,Name,State
2+
1000,John Doe,FL
3+
1001,Jane Smith,NY
4+
1002,Michael Johnson,TX
5+
1003,Sarah Williams,CA
6+
1004,Robert Brown,IL
7+
1005,Emily Davis,OH
8+
1006,David Wilson,FL
9+
1007,Jennifer Taylor,GA
10+
1008,Christopher Anderson,MI
11+
1009,Lisa Martinez,NC
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ItemID,Description
2+
1000,Intel i7 Latop
3+
1001,AMD Ryzen 7 Desktop
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
OrderID,CustomerID,SellerID,ItemID,Qty,Price
2+
1000,1000,1000,1000,10,150.25
3+
1001,1001,1001,1001,20,250.50
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SellerID,Name,State
2+
1000,Carlos Lopez,CA
3+
1001,Deepak Usman,CA

0 commit comments

Comments
 (0)