Skip to content

Commit 8187ed9

Browse files
committed
migrated to fast api but need to work on bicep
1 parent 362e89f commit 8187ed9

File tree

13 files changed

+170
-283
lines changed

13 files changed

+170
-283
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ To achieve this, when users submit a message to the web server, the web server w
5858
5. Start the services with this command:
5959

6060
```shell
61-
python -m quart --app src.quartapp run --port 50505 --reload
61+
cd src
62+
python -m uvicorn "api.main:create_app" --port 50505 --reload
6263
```
6364

6465
6. Click 'http://localhost:50505' in the browser to run the application.

src/api/__init__.py

Whitespace-only changes.
File renamed without changes.

src/api/main.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import contextlib
2+
import logging
3+
import os
4+
5+
import fastapi
6+
from azure.ai.projects.aio import AIProjectClient
7+
from dotenv import load_dotenv
8+
from fastapi.staticfiles import StaticFiles
9+
10+
11+
from typing import AsyncGenerator, Dict, Optional, Tuple
12+
13+
14+
import os
15+
from azure.ai.projects.aio import AIProjectClient
16+
from azure.identity import DefaultAzureCredential
17+
18+
from azure.ai.projects.models import (
19+
MessageDeltaChunk,
20+
ThreadMessage,
21+
FileSearchTool,
22+
AsyncToolSet,
23+
FilePurpose,
24+
ThreadMessage,
25+
StreamEventData,
26+
AsyncAgentEventHandler,
27+
Agent,
28+
VectorStore
29+
)
30+
31+
from .shared import bp
32+
33+
34+
35+
logger = logging.getLogger("azureaiapp")
36+
logger.setLevel(logging.INFO)
37+
38+
39+
@contextlib.asynccontextmanager
40+
async def lifespan(app: fastapi.FastAPI):
41+
42+
43+
44+
ai_client = AIProjectClient.from_connection_string(
45+
credential=DefaultAzureCredential(exclude_shared_token_cache_credential=True),
46+
conn_str=os.environ["PROJECT_CONNECTION_STRING"],
47+
)
48+
49+
# TODO: add more files are not supported for citation at the moment
50+
file_names = ["product_info_1.md", "product_info_2.md"]
51+
files: Dict[str, str] = {}
52+
for file_name in file_names:
53+
file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'files', file_name))
54+
print(f"Uploading file {file_path}")
55+
file = await ai_client.agents.upload_file_and_poll(file_path=file_path, purpose=FilePurpose.AGENTS)
56+
files.update({file.id: file_path})
57+
58+
vector_store = await ai_client.agents.create_vector_store_and_poll(file_ids=list(files.keys()), name="sample_store")
59+
60+
file_search_tool = FileSearchTool(vector_store_ids=[vector_store.id])
61+
62+
tool_set = AsyncToolSet()
63+
tool_set.add(file_search_tool)
64+
65+
print(f"ToolResource: {tool_set.resources}")
66+
67+
agent = await ai_client.agents.create_agent(
68+
model="gpt-4o-mini", name="my-assistant", instructions="You are helpful assistant", tools = tool_set.definitions, tool_resources=tool_set.resources
69+
)
70+
71+
print(f"Created agent, agent ID: {agent.id}")
72+
73+
bp.ai_client = ai_client
74+
bp.agent = agent
75+
bp.vector_store = vector_store
76+
bp.files = files
77+
78+
yield
79+
80+
await stop_server()
81+
82+
83+
async def stop_server():
84+
for file_id in bp.files.keys():
85+
await bp.ai_client.agents.delete_file(file_id)
86+
print(f"Deleted file {file_id}")
87+
88+
await bp.ai_client.agents.delete_vector_store(bp.vector_store.id)
89+
print(f"Deleted vector store {bp.vector_store.id}")
90+
91+
await bp.ai_client.agents.delete_agent(bp.agent.id)
92+
93+
print(f"Deleted agent {bp.agent.id}")
94+
95+
await bp.ai_client.close()
96+
print("Closed AIProjectClient")
97+
98+
99+
100+
def create_app():
101+
if not os.getenv("RUNNING_IN_PRODUCTION"):
102+
logger.info("Loading .env file")
103+
load_dotenv(override=True)
104+
105+
app = fastapi.FastAPI(lifespan=lifespan)
106+
app.mount("/static", StaticFiles(directory="api/static"), name="static")
107+
108+
from . import routes # noqa
109+
110+
app.include_router(routes.router)
111+
112+
return app

src/quartapp/chat.py renamed to src/api/routes.py

Lines changed: 31 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,33 @@
22
# Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
33

44
from typing import AsyncGenerator, Dict, Optional, Tuple
5-
from quart import Blueprint, jsonify, request, Response, render_template, current_app
65

76
import asyncio
87
import json, os
98

10-
import os
11-
from azure.ai.projects.aio import AIProjectClient
12-
from azure.identity import DefaultAzureCredential
139

1410
from azure.ai.projects.models import (
1511
MessageDeltaChunk,
1612
ThreadMessage,
17-
FileSearchTool,
18-
AsyncToolSet,
19-
FilePurpose,
2013
ThreadMessage,
21-
StreamEventData,
2214
AsyncAgentEventHandler,
23-
Agent,
24-
VectorStore
2515
)
2616

27-
class ChatBlueprint(Blueprint):
28-
ai_client: AIProjectClient
29-
agent: Agent
30-
files: Dict[str, str]
31-
vector_store: VectorStore
3217

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+
3432

3533
class MyEventHandler(AsyncAgentEventHandler[str]):
3634

@@ -62,66 +60,10 @@ async def on_done(
6260

6361

6462

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")
8363

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})
12567

12668

12769

@@ -135,8 +77,8 @@ async def get_result(thread_id: str, agent_id: str) -> AsyncGenerator[str, None]
13577
if event_func_return_val:
13678
yield event_func_return_val
13779

138-
@bp.route('/chat', methods=['POST'])
139-
async def chat():
80+
@router.post("/chat")
81+
async def chat(request: Request):
14082
thread_id = request.cookies.get('thread_id')
14183
agent_id = request.cookies.get('agent_id')
14284
thread = None
@@ -146,16 +88,18 @@ async def chat():
14688
try:
14789
thread = await bp.ai_client.agents.get_thread(thread_id)
14890
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)
15092
if thread is None:
15193
thread = await bp.ai_client.agents.create_thread()
15294

15395
thread_id = thread.id
15496
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}")
156100

157101
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)
159103

160104
message = await bp.ai_client.agents.create_message(
161105
thread_id=thread.id, role="user", content=user_message['message']
@@ -170,25 +114,24 @@ async def chat():
170114
'Content-Type': 'text/event-stream'
171115
}
172116

173-
response = Response(get_result(thread_id, agent_id), headers=headers)
117+
response = fastapi.responses.StreamingResponse(get_result(thread_id, agent_id), headers=headers)
174118
response.set_cookie('thread_id', thread_id)
175119
response.set_cookie('agent_id', agent_id)
176120
return response
177121

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')
182125
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")
184127

185128
try:
186129
# Read the file content asynchronously using asyncio.to_thread
187130
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)
189132

190133
except Exception as e:
191-
return jsonify({"error": str(e)}), 500
134+
return fastapi.responses.JSONResponse(content={"error": str(e)}, status_code=500)
192135

193136
def read_file(path):
194137
with open(path, 'r') as file:

src/api/shared.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from typing import Dict
2+
from azure.ai.projects.aio import AIProjectClient
3+
4+
5+
from azure.ai.projects.models import (
6+
Agent,
7+
VectorStore
8+
)
9+
10+
11+
class ChatBlueprint():
12+
ai_client: AIProjectClient
13+
agent: Agent
14+
files: Dict[str, str]
15+
vector_store: VectorStore
16+
17+
18+
bp = ChatBlueprint()
19+
20+
__all__ = ["bp"]
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)