2
2
# Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
3
3
4
4
from typing import AsyncGenerator , Dict , Optional , Tuple
5
- from quart import Blueprint , jsonify , request , Response , render_template , current_app
6
5
7
6
import asyncio
8
7
import json , os
9
8
10
- import os
11
- from azure .ai .projects .aio import AIProjectClient
12
- from azure .identity import DefaultAzureCredential
13
9
14
10
from azure .ai .projects .models import (
15
11
MessageDeltaChunk ,
16
12
ThreadMessage ,
17
- FileSearchTool ,
18
- AsyncToolSet ,
19
- FilePurpose ,
20
13
ThreadMessage ,
21
- StreamEventData ,
22
14
AsyncAgentEventHandler ,
23
- Agent ,
24
- VectorStore
25
15
)
26
16
27
- class ChatBlueprint (Blueprint ):
28
- ai_client : AIProjectClient
29
- agent : Agent
30
- files : Dict [str , str ]
31
- vector_store : VectorStore
32
17
33
- bp = ChatBlueprint ("chat" , __name__ , template_folder = "templates" , static_folder = "static" )
18
+ import json
19
+
20
+ import fastapi
21
+ from fastapi import Request
22
+ from fastapi .responses import HTMLResponse
23
+ from fastapi .templating import Jinja2Templates
24
+
25
+ from .shared import bp
26
+
27
+
28
+ router = fastapi .APIRouter ()
29
+ templates = Jinja2Templates (directory = "api/templates" )
30
+
31
+
34
32
35
33
class MyEventHandler (AsyncAgentEventHandler [str ]):
36
34
@@ -62,66 +60,10 @@ async def on_done(
62
60
63
61
64
62
65
- @bp .before_app_serving
66
- async def start_server ():
67
-
68
- ai_client = AIProjectClient .from_connection_string (
69
- credential = DefaultAzureCredential (exclude_shared_token_cache_credential = True ),
70
- conn_str = os .environ ["PROJECT_CONNECTION_STRING" ],
71
- )
72
-
73
- # TODO: add more files are not supported for citation at the moment
74
- file_names = ["product_info_1.md" , "product_info_2.md" ]
75
- files : Dict [str , str ] = {}
76
- for file_name in file_names :
77
- file_path = os .path .abspath (os .path .join (os .path .dirname (__file__ ), '..' , 'files' , file_name ))
78
- print (f"Uploading file { file_path } " )
79
- file = await ai_client .agents .upload_file_and_poll (file_path = file_path , purpose = FilePurpose .AGENTS )
80
- files .update ({file .id : file_path })
81
-
82
- vector_store = await ai_client .agents .create_vector_store_and_poll (file_ids = list (files .keys ()), name = "sample_store" )
83
63
84
- file_search_tool = FileSearchTool (vector_store_ids = [vector_store .id ])
85
-
86
- tool_set = AsyncToolSet ()
87
- tool_set .add (file_search_tool )
88
-
89
- print (f"ToolResource: { tool_set .resources } " )
90
-
91
- agent = await ai_client .agents .create_agent (
92
- model = "gpt-4o-mini" , name = "my-assistant" , instructions = "You are helpful assistant" , tools = tool_set .definitions , tool_resources = tool_set .resources
93
- )
94
-
95
- print (f"Created agent, agent ID: { agent .id } " )
96
-
97
- bp .ai_client = ai_client
98
- bp .agent = agent
99
- bp .vector_store = vector_store
100
- bp .files = files
101
-
102
-
103
- @bp .after_app_serving
104
- async def stop_server ():
105
- for file_id in bp .files .keys ():
106
- await bp .ai_client .agents .delete_file (file_id )
107
- print (f"Deleted file { file_id } " )
108
-
109
- await bp .ai_client .agents .delete_vector_store (bp .vector_store .id )
110
- print (f"Deleted vector store { bp .vector_store .id } " )
111
-
112
- await bp .ai_client .agents .delete_agent (bp .agent .id )
113
-
114
- print (f"Deleted agent { bp .agent .id } " )
115
-
116
- await bp .ai_client .close ()
117
- print ("Closed AIProjectClient" )
118
-
119
-
120
-
121
-
122
- @bp .get ("/" )
123
- async def index ():
124
- return await render_template ("index.html" )
64
+ @router .get ("/" , response_class = HTMLResponse )
65
+ async def index (request : Request ):
66
+ return templates .TemplateResponse ("index.html" , {"request" : request })
125
67
126
68
127
69
@@ -135,8 +77,8 @@ async def get_result(thread_id: str, agent_id: str) -> AsyncGenerator[str, None]
135
77
if event_func_return_val :
136
78
yield event_func_return_val
137
79
138
- @bp . route ( ' /chat' , methods = [ 'POST' ] )
139
- async def chat ():
80
+ @router . post ( " /chat" )
81
+ async def chat (request : Request ):
140
82
thread_id = request .cookies .get ('thread_id' )
141
83
agent_id = request .cookies .get ('agent_id' )
142
84
thread = None
@@ -146,16 +88,18 @@ async def chat():
146
88
try :
147
89
thread = await bp .ai_client .agents .get_thread (thread_id )
148
90
except Exception as e :
149
- current_app . logger . error ( f"Failed to retrieve thread with ID { thread_id } : { e } " )
91
+ return fastapi . responses . JSONResponse ( content = { "error" : f"Failed to retrieve thread with ID { thread_id } : { e } " }, status_code = 400 )
150
92
if thread is None :
151
93
thread = await bp .ai_client .agents .create_thread ()
152
94
153
95
thread_id = thread .id
154
96
agent_id = bp .agent .id
155
- user_message = await request .get_json ()
97
+ user_message = await request .json ()
98
+
99
+ print (f"user_message: { user_message } " )
156
100
157
101
if not hasattr (bp , 'ai_client' ):
158
- return jsonify ( {"error" : "Agent is not initialized" }), 500
102
+ return fastapi . responses . JSONResponse ( content = {"error" : "Agent is not initialized" }, status_code = 500 )
159
103
160
104
message = await bp .ai_client .agents .create_message (
161
105
thread_id = thread .id , role = "user" , content = user_message ['message' ]
@@ -170,25 +114,24 @@ async def chat():
170
114
'Content-Type' : 'text/event-stream'
171
115
}
172
116
173
- response = Response (get_result (thread_id , agent_id ), headers = headers )
117
+ response = fastapi . responses . StreamingResponse (get_result (thread_id , agent_id ), headers = headers )
174
118
response .set_cookie ('thread_id' , thread_id )
175
119
response .set_cookie ('agent_id' , agent_id )
176
120
return response
177
121
178
- @bp .route ('/fetch-document' , methods = ['GET' ])
179
- async def fetch_document ():
180
- file_id = request .args .get ('file_id' )
181
- current_app .logger .info (f"Fetching document: { file_id } " )
122
+ @router .get ("/fetch-document" )
123
+ async def fetch_document (request : Request ):
124
+ file_id = request .query_params .get ('file_id' )
182
125
if not file_id :
183
- return jsonify ({ "error" : "file_id is required" }), 400
126
+ raise fastapi . HTTPException ( status_code = 400 , detail = "file_id is required" )
184
127
185
128
try :
186
129
# Read the file content asynchronously using asyncio.to_thread
187
130
data = await asyncio .to_thread (read_file , bp .files [file_id ])
188
- return Response (data , content_type = 'text/plain' )
131
+ return fastapi . responses . PlainTextResponse (data )
189
132
190
133
except Exception as e :
191
- return jsonify ( {"error" : str (e )}), 500
134
+ return fastapi . responses . JSONResponse ( content = {"error" : str (e )}, status_code = 500 )
192
135
193
136
def read_file (path ):
194
137
with open (path , 'r' ) as file :
0 commit comments