Spaces:
Sleeping
Sleeping
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from typing import List, Dict, Optional, Set, Callable, Any
|
3 |
+
|
4 |
+
# Azure AI Agents SDK (API key auth)
|
5 |
+
from azure.core.credentials import AzureKeyCredential
|
6 |
+
from azure.ai.agents import AgentsClient
|
7 |
+
from azure.ai.agents.models import (
|
8 |
+
FunctionTool,
|
9 |
+
ToolSet,
|
10 |
+
ListSortOrder,
|
11 |
+
MessageRole,
|
12 |
+
)
|
13 |
+
|
14 |
+
from user_functions import user_functions # your callable tool(s)
|
15 |
+
|
16 |
+
|
17 |
+
# ---------- Core Agent Helpers ----------
|
18 |
+
|
19 |
+
def init_agent(
|
20 |
+
endpoint: str,
|
21 |
+
api_key: str,
|
22 |
+
model_deployment: str,
|
23 |
+
) -> dict:
|
24 |
+
"""
|
25 |
+
Initialize an Azure AI Agent with a custom FunctionTool.
|
26 |
+
Returns a session dict containing client, agent_id, thread_id, etc.
|
27 |
+
"""
|
28 |
+
if not endpoint or not api_key or not model_deployment:
|
29 |
+
raise ValueError("Please provide endpoint, key, and model deployment name.")
|
30 |
+
|
31 |
+
client = AgentsClient(
|
32 |
+
endpoint=endpoint.strip(),
|
33 |
+
credential=AzureKeyCredential(api_key.strip()),
|
34 |
+
)
|
35 |
+
|
36 |
+
# Build toolset with your functions and enable auto function calls
|
37 |
+
functions_tool = FunctionTool(user_functions)
|
38 |
+
toolset = ToolSet()
|
39 |
+
toolset.add(functions_tool)
|
40 |
+
|
41 |
+
client.enable_auto_function_calls(toolset)
|
42 |
+
|
43 |
+
# Create the agent
|
44 |
+
agent = client.create_agent(
|
45 |
+
model=model_deployment.strip(),
|
46 |
+
name="support-agent",
|
47 |
+
instructions=(
|
48 |
+
"You are a technical support agent. "
|
49 |
+
"When a user has a technical issue, ask for their email address and a description "
|
50 |
+
"of the issue. Then submit a support ticket using the available function. "
|
51 |
+
"If a file is saved, tell the user the file name."
|
52 |
+
),
|
53 |
+
toolset=toolset,
|
54 |
+
)
|
55 |
+
|
56 |
+
# Create a thread for the conversation
|
57 |
+
thread = client.threads.create()
|
58 |
+
|
59 |
+
# Session we keep in Gradio state
|
60 |
+
return {
|
61 |
+
"endpoint": endpoint.strip(),
|
62 |
+
"api_key": api_key.strip(),
|
63 |
+
"model": model_deployment.strip(),
|
64 |
+
"client": client,
|
65 |
+
"agent_id": agent.id,
|
66 |
+
"thread_id": thread.id,
|
67 |
+
}
|
68 |
+
|
69 |
+
|
70 |
+
def send_to_agent(user_msg: str, session: dict):
|
71 |
+
"""
|
72 |
+
Send message to the agent thread and return:
|
73 |
+
- agent_reply (str)
|
74 |
+
- history_str (str) chronological log
|
75 |
+
"""
|
76 |
+
if not session or "client" not in session:
|
77 |
+
raise ValueError("Agent is not initialized. Click 'Connect & Prepare' first.")
|
78 |
+
|
79 |
+
client: AgentsClient = session["client"]
|
80 |
+
agent_id = session["agent_id"]
|
81 |
+
thread_id = session["thread_id"]
|
82 |
+
|
83 |
+
# Add user message
|
84 |
+
client.messages.create(
|
85 |
+
thread_id=thread_id,
|
86 |
+
role="user",
|
87 |
+
content=user_msg,
|
88 |
+
)
|
89 |
+
|
90 |
+
# Run and wait (auto function-calls enabled)
|
91 |
+
run = client.runs.create_and_process(thread_id=thread_id, agent_id=agent_id)
|
92 |
+
if getattr(run, "status", None) == "failed":
|
93 |
+
last_error = getattr(run, "last_error", "Unknown error")
|
94 |
+
return f"Run failed: {last_error}", ""
|
95 |
+
|
96 |
+
# Last agent message
|
97 |
+
last_msg = client.messages.get_last_message_text_by_role(
|
98 |
+
thread_id=thread_id,
|
99 |
+
role=MessageRole.AGENT,
|
100 |
+
)
|
101 |
+
agent_reply = last_msg.text.value if last_msg else "(No reply text found.)"
|
102 |
+
|
103 |
+
# Build readable history (chronological)
|
104 |
+
history_lines = []
|
105 |
+
messages = client.messages.list(thread_id=thread_id, order=ListSortOrder.ASCENDING)
|
106 |
+
for m in messages:
|
107 |
+
if m.text_messages:
|
108 |
+
last_text = m.text_messages[-1].text.value
|
109 |
+
history_lines.append(f"{m.role}: {last_text}")
|
110 |
+
history_str = "\n\n".join(history_lines)
|
111 |
+
|
112 |
+
return agent_reply, history_str
|
113 |
+
|
114 |
+
|
115 |
+
def teardown(session: dict) -> str:
|
116 |
+
"""
|
117 |
+
Delete the agent to reduce costs. (Threads are retained by the service.)
|
118 |
+
"""
|
119 |
+
if not session:
|
120 |
+
return "Nothing to clean up."
|
121 |
+
|
122 |
+
notes = []
|
123 |
+
try:
|
124 |
+
client: AgentsClient = session.get("client")
|
125 |
+
agent_id = session.get("agent_id")
|
126 |
+
if client and agent_id:
|
127 |
+
client.delete_agent(agent_id)
|
128 |
+
notes.append("Deleted agent.")
|
129 |
+
except Exception as e:
|
130 |
+
notes.append(f"Cleanup warning: {e}")
|
131 |
+
|
132 |
+
return " ".join(notes) if notes else "Cleanup complete."
|
133 |
+
|
134 |
+
|
135 |
+
# ---------- Gradio UI ----------
|
136 |
+
|
137 |
+
with gr.Blocks(title="Azure AI Support Agent (Functions) — Gradio") as demo:
|
138 |
+
gr.Markdown(
|
139 |
+
"## Azure AI Support Agent (Custom Function Tool)\n"
|
140 |
+
"Enter your **Project Endpoint** and **Key**, set your **Model Deployment** (e.g., `gpt-4o`), then chat.\n"
|
141 |
+
"The agent can call a custom function to **submit a support ticket** and save it as a file."
|
142 |
+
)
|
143 |
+
|
144 |
+
with gr.Row():
|
145 |
+
endpoint = gr.Textbox(label="Project Endpoint", placeholder="https://<your-project-endpoint>")
|
146 |
+
api_key = gr.Textbox(label="Project Key", placeholder="paste your key", type="password")
|
147 |
+
|
148 |
+
with gr.Row():
|
149 |
+
model = gr.Textbox(label="Model Deployment Name", value="gpt-4o")
|
150 |
+
|
151 |
+
session_state = gr.State(value=None)
|
152 |
+
|
153 |
+
connect_btn = gr.Button("🔌 Connect & Prepare Agent", variant="primary")
|
154 |
+
connect_status = gr.Markdown("")
|
155 |
+
|
156 |
+
with gr.Row():
|
157 |
+
chatbot = gr.Chatbot(
|
158 |
+
label="Conversation",
|
159 |
+
height=420,
|
160 |
+
type="messages", # openai-style dicts: {"role": "...", "content": "..."}
|
161 |
+
)
|
162 |
+
|
163 |
+
user_input = gr.Textbox(label="Your message", placeholder="e.g., I have a technical problem")
|
164 |
+
with gr.Row():
|
165 |
+
send_btn = gr.Button("Send ▶")
|
166 |
+
cleanup_btn = gr.Button("Delete Agent & Cleanup 🧹")
|
167 |
+
|
168 |
+
history = gr.Textbox(label="Conversation Log (chronological)", lines=12)
|
169 |
+
|
170 |
+
# ----- Callbacks -----
|
171 |
+
|
172 |
+
def on_connect(ep, key, mdl):
|
173 |
+
try:
|
174 |
+
sess = init_agent(ep, key, mdl)
|
175 |
+
return sess, "✅ Connected. Support agent and thread are ready."
|
176 |
+
except Exception as e:
|
177 |
+
return None, f"❌ Connection error: {e}"
|
178 |
+
|
179 |
+
connect_btn.click(
|
180 |
+
fn=on_connect,
|
181 |
+
inputs=[endpoint, api_key, model],
|
182 |
+
outputs=[session_state, connect_status],
|
183 |
+
)
|
184 |
+
|
185 |
+
def on_send(msg: str, session: dict, chat_msgs: List[Dict[str, str]]):
|
186 |
+
if not msg:
|
187 |
+
return gr.update(), gr.update(), gr.update(value="Please enter a message.")
|
188 |
+
try:
|
189 |
+
agent_reply, log = send_to_agent(msg, session)
|
190 |
+
chat_msgs = (chat_msgs or []) + [
|
191 |
+
{"role": "user", "content": msg},
|
192 |
+
{"role": "assistant", "content": agent_reply},
|
193 |
+
]
|
194 |
+
return chat_msgs, "", gr.update(value=log) # clear input, update log
|
195 |
+
except Exception as e:
|
196 |
+
return chat_msgs, msg, gr.update(value=f"❌ Error: {e}")
|
197 |
+
|
198 |
+
send_btn.click(
|
199 |
+
fn=on_send,
|
200 |
+
inputs=[user_input, session_state, chatbot],
|
201 |
+
outputs=[chatbot, user_input, history],
|
202 |
+
)
|
203 |
+
|
204 |
+
def on_cleanup(session):
|
205 |
+
try:
|
206 |
+
msg = teardown(session)
|
207 |
+
return None, f"🧹 {msg}"
|
208 |
+
except Exception as e:
|
209 |
+
return session, f"⚠️ Cleanup error: {e}"
|
210 |
+
|
211 |
+
cleanup_btn.click(
|
212 |
+
fn=on_cleanup,
|
213 |
+
inputs=[session_state],
|
214 |
+
outputs=[session_state, connect_status],
|
215 |
+
)
|
216 |
+
|
217 |
+
if __name__ == "__main__":
|
218 |
+
demo.launch()
|