Skip to content

Commit 44bfc40

Browse files
committed
Reconfigure the additional API endpoints in a separate blueprint
1 parent 0189dfc commit 44bfc40

File tree

4 files changed

+182
-145
lines changed

4 files changed

+182
-145
lines changed

app/backend/app.py

Lines changed: 5 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
SpeechSynthesizer,
1717
)
1818
from azure.core.exceptions import ResourceNotFoundError
19-
from azure.cosmos.aio import ContainerProxy, CosmosClient
2019
from azure.identity.aio import (
2120
AzureDeveloperCliCredential,
2221
ManagedIdentityCredential,
@@ -54,6 +53,7 @@
5453
from approaches.chatreadretrievereadvision import ChatReadRetrieveReadVisionApproach
5554
from approaches.retrievethenread import RetrieveThenReadApproach
5655
from approaches.retrievethenreadvision import RetrieveThenReadVisionApproach
56+
from chat_history.cosmosdb import chat_history_cosmosdb_bp
5757
from config import (
5858
CONFIG_ASK_APPROACH,
5959
CONFIG_ASK_VISION_APPROACH,
@@ -63,7 +63,6 @@
6363
CONFIG_CHAT_HISTORY_BROWSER_ENABLED,
6464
CONFIG_CHAT_HISTORY_COSMOS_ENABLED,
6565
CONFIG_CHAT_VISION_APPROACH,
66-
CONFIG_COSMOS_HISTORY_CONTAINER,
6766
CONFIG_CREDENTIAL,
6867
CONFIG_GPT4V_DEPLOYED,
6968
CONFIG_INGESTER,
@@ -407,129 +406,6 @@ async def list_uploaded(auth_claims: dict[str, Any]):
407406
return jsonify(files), 200
408407

409408

410-
@bp.post("/chat_history")
411-
@authenticated
412-
async def post_chat_history(auth_claims: Dict[str, Any]):
413-
if not current_app.config[CONFIG_CHAT_HISTORY_COSMOS_ENABLED]:
414-
return jsonify({"error": "Chat history not enabled"}), 405
415-
416-
container = cast(ContainerProxy, current_app.config[CONFIG_COSMOS_HISTORY_CONTAINER])
417-
if not container:
418-
return jsonify({"error": "Chat history not enabled"}), 405
419-
420-
entra_oid = auth_claims.get("oid")
421-
if not entra_oid:
422-
return jsonify({"error": "User OID not found"}), 401
423-
424-
try:
425-
request_json = await request.get_json()
426-
id = request_json.get("id")
427-
answers = request_json.get("answers")
428-
title = answers[0][0][:50] + "..." if len(answers[0][0]) > 50 else answers[0][0]
429-
430-
await container.upsert_item({"id": id, "entra_oid": entra_oid, "title": title, "answers": answers})
431-
432-
return jsonify({}), 201
433-
except Exception as error:
434-
return error_response(error, "/chat_history")
435-
436-
437-
@bp.post("/chat_history/items")
438-
@authenticated
439-
async def get_chat_history(auth_claims: Dict[str, Any]):
440-
if not current_app.config[CONFIG_CHAT_HISTORY_COSMOS_ENABLED]:
441-
return jsonify({"error": "Chat history not enabled"}), 405
442-
443-
container = cast(ContainerProxy, current_app.config[CONFIG_COSMOS_HISTORY_CONTAINER])
444-
if not container:
445-
return jsonify({"error": "Chat history not enabled"}), 405
446-
447-
entra_oid = auth_claims.get("oid")
448-
if not entra_oid:
449-
return jsonify({"error": "User OID not found"}), 401
450-
451-
try:
452-
request_json = await request.get_json()
453-
count = request_json.get("count", 20)
454-
continuation_token = request_json.get("continuation_token")
455-
456-
res = container.query_items(
457-
query="SELECT c.id, c.entra_oid, c.title, c._ts FROM c WHERE c.entra_oid = @entra_oid ORDER BY c._ts DESC",
458-
parameters=[dict(name="@entra_oid", value=entra_oid)],
459-
max_item_count=count,
460-
)
461-
462-
# set the continuation token for the next page
463-
pager = res.by_page(continuation_token)
464-
465-
# Get the first page, and the continuation token
466-
try:
467-
page = await pager.__anext__()
468-
continuation_token = pager.continuation_token # type: ignore
469-
470-
items = []
471-
async for item in page:
472-
items.append(item)
473-
474-
# If there are no page, StopAsyncIteration is raised
475-
except StopAsyncIteration:
476-
items = []
477-
continuation_token = None
478-
479-
return jsonify({"items": items, "continuation_token": continuation_token}), 200
480-
481-
except Exception as error:
482-
return error_response(error, "/chat_history/items")
483-
484-
485-
@bp.get("/chat_history/items/<path>")
486-
@authenticated_path
487-
async def get_chat_history_session(path: str, auth_claims: Dict[str, Any]):
488-
if not current_app.config[CONFIG_CHAT_HISTORY_COSMOS_ENABLED]:
489-
return jsonify({"error": "Chat history not enabled"}), 405
490-
491-
container = cast(ContainerProxy, current_app.config[CONFIG_COSMOS_HISTORY_CONTAINER])
492-
if not container:
493-
return jsonify({"error": "Chat history not enabled"}), 405
494-
495-
if not path:
496-
return jsonify({"error": "Invalid path"}), 400
497-
498-
entra_oid = auth_claims.get("oid")
499-
if not entra_oid:
500-
return jsonify({"error": "User OID not found"}), 401
501-
502-
try:
503-
res = await container.read_item(item=path, partition_key=entra_oid)
504-
return jsonify(res), 200
505-
except Exception as error:
506-
return error_response(error, f"/chat_history/items/{path}")
507-
508-
509-
@bp.delete("/chat_history/items/<path>")
510-
@authenticated_path
511-
async def delete_chat_history_session(path: str, auth_claims: Dict[str, Any]):
512-
if not current_app.config[CONFIG_CHAT_HISTORY_COSMOS_ENABLED]:
513-
return jsonify({"error": "Chat history not enabled"}), 405
514-
515-
container = cast(ContainerProxy, current_app.config[CONFIG_COSMOS_HISTORY_CONTAINER])
516-
if not container:
517-
return jsonify({"error": "Chat history not enabled"}), 405
518-
519-
if not path:
520-
return jsonify({"error": "Invalid path"}), 400
521-
522-
entra_oid = auth_claims.get("oid")
523-
if not entra_oid:
524-
return jsonify({"error": "User OID not found"}), 401
525-
526-
try:
527-
await container.delete_item(item=path, partition_key=entra_oid)
528-
return jsonify({}), 200
529-
except Exception as error:
530-
return error_response(error, f"/chat_history/items/{path}")
531-
532-
533409
@bp.before_app_serving
534410
async def setup_clients():
535411
# Replace these with your own values, either in environment variables or directly here
@@ -585,12 +461,8 @@ async def setup_clients():
585461
USE_SPEECH_INPUT_BROWSER = os.getenv("USE_SPEECH_INPUT_BROWSER", "").lower() == "true"
586462
USE_SPEECH_OUTPUT_BROWSER = os.getenv("USE_SPEECH_OUTPUT_BROWSER", "").lower() == "true"
587463
USE_SPEECH_OUTPUT_AZURE = os.getenv("USE_SPEECH_OUTPUT_AZURE", "").lower() == "true"
588-
589464
USE_CHAT_HISTORY_BROWSER = os.getenv("USE_CHAT_HISTORY_BROWSER", "").lower() == "true"
590465
USE_CHAT_HISTORY_COSMOS = os.getenv("USE_CHAT_HISTORY_COSMOS", "").lower() == "true"
591-
AZURE_COSMOSDB_ACCOUNT = os.getenv("AZURE_COSMOSDB_ACCOUNT")
592-
AZURE_CHAT_HISTORY_DATABASE = os.getenv("AZURE_CHAT_HISTORY_DATABASE")
593-
AZURE_CHAT_HISTORY_CONTAINER = os.getenv("AZURE_CHAT_HISTORY_CONTAINER")
594466

595467
# WEBSITE_HOSTNAME is always set by App Service, RUNNING_IN_PRODUCTION is set in main.bicep
596468
RUNNING_ON_AZURE = os.getenv("WEBSITE_HOSTNAME") is not None or os.getenv("RUNNING_IN_PRODUCTION") is not None
@@ -620,6 +492,9 @@ async def setup_clients():
620492
current_app.logger.info("Setting up Azure credential using AzureDeveloperCliCredential for home tenant")
621493
azure_credential = AzureDeveloperCliCredential(process_timeout=60)
622494

495+
# Set the Azure credential in the app config for use in other parts of the app
496+
current_app.config[CONFIG_CREDENTIAL] = azure_credential
497+
623498
# Set up clients for AI Search and Storage
624499
search_client = SearchClient(
625500
endpoint=f"https://{AZURE_SEARCH_SERVICE}.search.windows.net",
@@ -694,21 +569,6 @@ async def setup_clients():
694569
)
695570
current_app.config[CONFIG_INGESTER] = ingester
696571

697-
if USE_CHAT_HISTORY_COSMOS:
698-
current_app.logger.info("USE_CHAT_HISTORY_COSMOS is true, setting up CosmosDB client")
699-
if not AZURE_COSMOSDB_ACCOUNT:
700-
raise ValueError("AZURE_COSMOSDB_ACCOUNT must be set when USE_CHAT_HISTORY_COSMOS is true")
701-
if not AZURE_CHAT_HISTORY_DATABASE:
702-
raise ValueError("AZURE_CHAT_HISTORY_DATABASE must be set when USE_CHAT_HISTORY_COSMOS is true")
703-
if not AZURE_CHAT_HISTORY_CONTAINER:
704-
raise ValueError("AZURE_CHAT_HISTORY_CONTAINER must be set when USE_CHAT_HISTORY_COSMOS is true")
705-
cosmos_client = CosmosClient(
706-
url=f"https://{AZURE_COSMOSDB_ACCOUNT}.documents.azure.com:443/", credential=azure_credential
707-
)
708-
cosmos_db = cosmos_client.get_database_client(AZURE_CHAT_HISTORY_DATABASE)
709-
cosmos_container = cosmos_db.get_container_client(AZURE_CHAT_HISTORY_CONTAINER)
710-
current_app.config[CONFIG_COSMOS_HISTORY_CONTAINER] = cosmos_container
711-
712572
# Used by the OpenAI SDK
713573
openai_client: AsyncOpenAI
714574

@@ -723,7 +583,6 @@ async def setup_clients():
723583
current_app.config[CONFIG_SPEECH_SERVICE_VOICE] = AZURE_SPEECH_VOICE
724584
# Wait until token is needed to fetch for the first time
725585
current_app.config[CONFIG_SPEECH_SERVICE_TOKEN] = None
726-
current_app.config[CONFIG_CREDENTIAL] = azure_credential
727586

728587
if OPENAI_HOST.startswith("azure"):
729588
api_version = os.getenv("AZURE_OPENAI_API_VERSION") or "2024-03-01-preview"
@@ -867,6 +726,7 @@ async def close_clients():
867726
def create_app():
868727
app = Quart(__name__)
869728
app.register_blueprint(bp)
729+
app.register_blueprint(chat_history_cosmosdb_bp)
870730

871731
if os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING"):
872732
app.logger.info("APPLICATIONINSIGHTS_CONNECTION_STRING is set, enabling Azure Monitor")

app/backend/chat_history/__init__.py

Whitespace-only changes.
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import os
2+
from typing import Any, Dict, Union, cast
3+
4+
from azure.cosmos.aio import ContainerProxy, CosmosClient
5+
from azure.identity.aio import AzureDeveloperCliCredential, ManagedIdentityCredential
6+
from quart import Blueprint, current_app, jsonify, request
7+
8+
from config import (
9+
CONFIG_CHAT_HISTORY_COSMOS_ENABLED,
10+
CONFIG_COSMOS_HISTORY_CLIENT,
11+
CONFIG_COSMOS_HISTORY_CONTAINER,
12+
CONFIG_CREDENTIAL,
13+
)
14+
from decorators import authenticated, authenticated_path
15+
from error import error_response
16+
17+
chat_history_cosmosdb_bp = Blueprint("chat_history_cosmos", __name__, static_folder="static")
18+
19+
20+
@chat_history_cosmosdb_bp.post("/chat_history")
21+
@authenticated
22+
async def post_chat_history(auth_claims: Dict[str, Any]):
23+
if not current_app.config[CONFIG_CHAT_HISTORY_COSMOS_ENABLED]:
24+
return jsonify({"error": "Chat history not enabled"}), 405
25+
26+
container = cast(ContainerProxy, current_app.config[CONFIG_COSMOS_HISTORY_CONTAINER])
27+
if not container:
28+
return jsonify({"error": "Chat history not enabled"}), 405
29+
30+
entra_oid = auth_claims.get("oid")
31+
if not entra_oid:
32+
return jsonify({"error": "User OID not found"}), 401
33+
34+
try:
35+
request_json = await request.get_json()
36+
id = request_json.get("id")
37+
answers = request_json.get("answers")
38+
title = answers[0][0][:50] + "..." if len(answers[0][0]) > 50 else answers[0][0]
39+
40+
await container.upsert_item({"id": id, "entra_oid": entra_oid, "title": title, "answers": answers})
41+
42+
return jsonify({}), 201
43+
except Exception as error:
44+
return error_response(error, "/chat_history")
45+
46+
47+
@chat_history_cosmosdb_bp.post("/chat_history/items")
48+
@authenticated
49+
async def get_chat_history(auth_claims: Dict[str, Any]):
50+
if not current_app.config[CONFIG_CHAT_HISTORY_COSMOS_ENABLED]:
51+
return jsonify({"error": "Chat history not enabled"}), 405
52+
53+
container = cast(ContainerProxy, current_app.config[CONFIG_COSMOS_HISTORY_CONTAINER])
54+
if not container:
55+
return jsonify({"error": "Chat history not enabled"}), 405
56+
57+
entra_oid = auth_claims.get("oid")
58+
if not entra_oid:
59+
return jsonify({"error": "User OID not found"}), 401
60+
61+
try:
62+
request_json = await request.get_json()
63+
count = request_json.get("count", 20)
64+
continuation_token = request_json.get("continuation_token")
65+
66+
res = container.query_items(
67+
query="SELECT c.id, c.entra_oid, c.title, c._ts FROM c WHERE c.entra_oid = @entra_oid ORDER BY c._ts DESC",
68+
parameters=[dict(name="@entra_oid", value=entra_oid)],
69+
max_item_count=count,
70+
)
71+
72+
# set the continuation token for the next page
73+
pager = res.by_page(continuation_token)
74+
75+
# Get the first page, and the continuation token
76+
try:
77+
page = await pager.__anext__()
78+
continuation_token = pager.continuation_token # type: ignore
79+
80+
items = []
81+
async for item in page:
82+
items.append(item)
83+
84+
# If there are no page, StopAsyncIteration is raised
85+
except StopAsyncIteration:
86+
items = []
87+
continuation_token = None
88+
89+
return jsonify({"items": items, "continuation_token": continuation_token}), 200
90+
91+
except Exception as error:
92+
return error_response(error, "/chat_history/items")
93+
94+
95+
@chat_history_cosmosdb_bp.get("/chat_history/items/<path>")
96+
@authenticated_path
97+
async def get_chat_history_session(path: str, auth_claims: Dict[str, Any]):
98+
if not current_app.config[CONFIG_CHAT_HISTORY_COSMOS_ENABLED]:
99+
return jsonify({"error": "Chat history not enabled"}), 405
100+
101+
container = cast(ContainerProxy, current_app.config[CONFIG_COSMOS_HISTORY_CONTAINER])
102+
if not container:
103+
return jsonify({"error": "Chat history not enabled"}), 405
104+
105+
if not path:
106+
return jsonify({"error": "Invalid path"}), 400
107+
108+
entra_oid = auth_claims.get("oid")
109+
if not entra_oid:
110+
return jsonify({"error": "User OID not found"}), 401
111+
112+
try:
113+
res = await container.read_item(item=path, partition_key=entra_oid)
114+
return jsonify(res), 200
115+
except Exception as error:
116+
return error_response(error, f"/chat_history/items/{path}")
117+
118+
119+
@chat_history_cosmosdb_bp.delete("/chat_history/items/<path>")
120+
@authenticated_path
121+
async def delete_chat_history_session(path: str, auth_claims: Dict[str, Any]):
122+
if not current_app.config[CONFIG_CHAT_HISTORY_COSMOS_ENABLED]:
123+
return jsonify({"error": "Chat history not enabled"}), 405
124+
125+
container = cast(ContainerProxy, current_app.config[CONFIG_COSMOS_HISTORY_CONTAINER])
126+
if not container:
127+
return jsonify({"error": "Chat history not enabled"}), 405
128+
129+
if not path:
130+
return jsonify({"error": "Invalid path"}), 400
131+
132+
entra_oid = auth_claims.get("oid")
133+
if not entra_oid:
134+
return jsonify({"error": "User OID not found"}), 401
135+
136+
try:
137+
await container.delete_item(item=path, partition_key=entra_oid)
138+
return jsonify({}), 200
139+
except Exception as error:
140+
return error_response(error, f"/chat_history/items/{path}")
141+
142+
143+
@chat_history_cosmosdb_bp.before_app_serving
144+
async def setup_clients():
145+
USE_CHAT_HISTORY_COSMOS = os.getenv("USE_CHAT_HISTORY_COSMOS", "").lower() == "true"
146+
AZURE_COSMOSDB_ACCOUNT = os.getenv("AZURE_COSMOSDB_ACCOUNT")
147+
AZURE_CHAT_HISTORY_DATABASE = os.getenv("AZURE_CHAT_HISTORY_DATABASE")
148+
AZURE_CHAT_HISTORY_CONTAINER = os.getenv("AZURE_CHAT_HISTORY_CONTAINER")
149+
150+
azure_credential = cast(
151+
Union[AzureDeveloperCliCredential, ManagedIdentityCredential], current_app.config[CONFIG_CREDENTIAL]
152+
)
153+
154+
if USE_CHAT_HISTORY_COSMOS:
155+
current_app.logger.info("USE_CHAT_HISTORY_COSMOS is true, setting up CosmosDB client")
156+
if not AZURE_COSMOSDB_ACCOUNT:
157+
raise ValueError("AZURE_COSMOSDB_ACCOUNT must be set when USE_CHAT_HISTORY_COSMOS is true")
158+
if not AZURE_CHAT_HISTORY_DATABASE:
159+
raise ValueError("AZURE_CHAT_HISTORY_DATABASE must be set when USE_CHAT_HISTORY_COSMOS is true")
160+
if not AZURE_CHAT_HISTORY_CONTAINER:
161+
raise ValueError("AZURE_CHAT_HISTORY_CONTAINER must be set when USE_CHAT_HISTORY_COSMOS is true")
162+
cosmos_client = CosmosClient(
163+
url=f"https://{AZURE_COSMOSDB_ACCOUNT}.documents.azure.com:443/", credential=azure_credential
164+
)
165+
cosmos_db = cosmos_client.get_database_client(AZURE_CHAT_HISTORY_DATABASE)
166+
cosmos_container = cosmos_db.get_container_client(AZURE_CHAT_HISTORY_CONTAINER)
167+
168+
current_app.config[CONFIG_COSMOS_HISTORY_CLIENT] = cosmos_client
169+
current_app.config[CONFIG_COSMOS_HISTORY_CONTAINER] = cosmos_container
170+
171+
172+
@chat_history_cosmosdb_bp.after_app_serving
173+
async def close_clients():
174+
cosmos_client = cast(CosmosClient, current_app.config.get(CONFIG_COSMOS_HISTORY_CLIENT))
175+
if cosmos_client:
176+
await cosmos_client.close()

app/backend/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@
2424
CONFIG_SPEECH_SERVICE_VOICE = "speech_service_voice"
2525
CONFIG_CHAT_HISTORY_BROWSER_ENABLED = "chat_history_browser_enabled"
2626
CONFIG_CHAT_HISTORY_COSMOS_ENABLED = "chat_history_cosmos_enabled"
27+
CONFIG_COSMOS_HISTORY_CLIENT = "cosmos_history_client"
2728
CONFIG_COSMOS_HISTORY_CONTAINER = "cosmos_history_container"

0 commit comments

Comments
 (0)