Skip to content

Commit 28848fd

Browse files
committed
CosmosDB progress
1 parent fbb5135 commit 28848fd

File tree

3 files changed

+82
-25
lines changed

3 files changed

+82
-25
lines changed

app/backend/chat_history/cosmosdb.py

Lines changed: 70 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import logging
12
import os
23
import time
34
from typing import Any, Dict, Union
45

6+
from azure.cosmos import exceptions
57
from azure.cosmos.aio import ContainerProxy, CosmosClient
8+
from azure.cosmos.partition_key import PartitionKey
69
from azure.identity.aio import AzureDeveloperCliCredential, ManagedIdentityCredential
710
from quart import Blueprint, current_app, jsonify, request
811

@@ -15,9 +18,19 @@
1518
from decorators import authenticated
1619
from error import error_response
1720

21+
logger = logging.getLogger("scripts")
22+
1823
chat_history_cosmosdb_bp = Blueprint("chat_history_cosmos", __name__, static_folder="static")
1924

2025

26+
def make_partition_key(entra_id, session_id=None):
27+
if entra_id and session_id:
28+
# Need multihash for hierachical partitioning
29+
return PartitionKey(path=["/entra_id", "/session_id"], kind="MultiHash")
30+
else:
31+
return PartitionKey(path="/entra_id")
32+
33+
2134
@chat_history_cosmosdb_bp.post("/chat_history")
2235
@authenticated
2336
async def post_chat_history(auth_claims: Dict[str, Any]):
@@ -34,44 +47,69 @@ async def post_chat_history(auth_claims: Dict[str, Any]):
3447

3548
try:
3649
request_json = await request.get_json()
37-
id = request_json.get("id")
50+
session_id = request_json.get("id")
3851
answers = request_json.get("answers")
3952
title = answers[0][0][:50] + "..." if len(answers[0][0]) > 50 else answers[0][0]
4053
timestamp = int(time.time() * 1000)
4154

4255
# Insert the session item:
43-
await container.upsert_item(
44-
{
45-
"id": id,
46-
"session_id": id,
47-
"entra_oid": entra_oid,
48-
"type": "session",
49-
"title": title,
50-
"timestamp": timestamp,
51-
}
52-
)
53-
56+
session_item = {
57+
"id": id,
58+
"session_id": session_id,
59+
"entra_oid": entra_oid,
60+
"type": "session",
61+
"title": title,
62+
"timestamp": timestamp,
63+
}
64+
65+
message_items = []
5466
# Now insert a message item for each question/response pair:
5567
for ind, message_pair in enumerate(zip(answers[::2], answers[1::2])):
56-
# TODO: Can I do a batch upsert?
57-
await container.upsert_item(
68+
# The id: what if you delete a message and then add a new one? The id will be the same.
69+
# If we had delete mechanism, and you deleted item 5 in a history, then item 6 would still hang around
70+
# and youd have two of item 6.
71+
# abc-0
72+
# abc-1
73+
# abc-2 <-- DELETE
74+
# abc-3
75+
# One approach would be to delete EVERYTHING, then upsert everything.
76+
# Another approach would be to delete item plus everything after, then upsert everything after.
77+
# Or: Change the frontend?
78+
# We can do this first, and change the frontend after
79+
message_items.append(
5880
{
59-
"id": f"{id}-{ind}",
81+
"id": f"{session_id}-{ind}",
6082
"session_id": id,
6183
"entra_oid": entra_oid,
6284
"type": "message",
6385
"question": message_pair[0],
6486
"response": message_pair[1],
65-
"timestamp": timestamp,
87+
"timestamp": timestamp, # <-- This is the timestamp of the session, not the message
6688
}
6789
)
6890

91+
batch_operations = [("upsert", tuple([session_item] + message_items), {})]
92+
93+
try:
94+
# Run that list of operations
95+
batch_results = container.execute_item_batch(
96+
batch_operations=batch_operations, partition_key=make_partition_key(entra_oid, session_id)
97+
)
98+
# Batch results are returned as a list of item operation results - or raise a CosmosBatchOperationError if
99+
# one of the operations failed within your batch request.
100+
print(f"\nResults for the batch operations: {batch_results}\n")
101+
except exceptions.CosmosBatchOperationError as e:
102+
error_operation_index = e.error_index
103+
error_operation_response = e.operation_responses[error_operation_index]
104+
error_operation = batch_operations[error_operation_index]
105+
logger.error(f"Batch operation failed: {error_operation_response} for operation {error_operation}")
106+
return jsonify({"error": "Batch operation failed"}), 400
69107
return jsonify({}), 201
70108
except Exception as error:
71109
return error_response(error, "/chat_history")
72110

73111

74-
@chat_history_cosmosdb_bp.post("/chat_history/items")
112+
@chat_history_cosmosdb_bp.get("/chat_history/items")
75113
@authenticated
76114
async def get_chat_history(auth_claims: Dict[str, Any]):
77115
if not current_app.config[CONFIG_CHAT_HISTORY_COSMOS_ENABLED]:
@@ -86,14 +124,15 @@ async def get_chat_history(auth_claims: Dict[str, Any]):
86124
return jsonify({"error": "User OID not found"}), 401
87125

88126
try:
89-
request_json = await request.get_json()
90-
count = request_json.get("count", 20)
91-
continuation_token = request_json.get("continuation_token")
127+
# get the count and continuation token from the request URL
128+
count = request.args.get("count", 10)
129+
continuation_token = request.args.get("continuation_token")
92130

93131
res = container.query_items(
132+
# TODO: do we need distinct? per Mark's code - Mark says no!
94133
query="SELECT c.id, c.entra_oid, c.title, c.timestamp FROM c WHERE c.entra_oid = @entra_oid AND c.type = @type ORDER BY c.timestamp DESC",
95134
parameters=[dict(name="@entra_oid", value=entra_oid), dict(name="@type", value="session")],
96-
partition_key=entra_oid,
135+
partition_key=make_partition_key(entra_oid),
97136
max_item_count=count,
98137
)
99138

@@ -142,6 +181,14 @@ async def get_chat_history_session(auth_claims: Dict[str, Any], item_id: str):
142181
return jsonify({"error": "User OID not found"}), 401
143182

144183
try:
184+
res = container.query_items(
185+
# TODO: do we need distinct? per Mark's code
186+
query="SELECT c.id, c.entra_oid, c.title, c.timestamp FROM c WHERE c.session_id = @session_id ORDER BY c.timestamp DESC",
187+
parameters=[dict(name="@entra_oid", value=entra_oid), dict(name="@session_id", value=item_id)],
188+
partition_key=make_partition_key(entra_oid, item_id),
189+
# max_item_count=?
190+
)
191+
145192
res = await container.read_item(item=item_id, partition_key=entra_oid)
146193
return (
147194
jsonify(
@@ -175,6 +222,8 @@ async def delete_chat_history_session(auth_claims: Dict[str, Any], item_id: str)
175222

176223
try:
177224
await container.delete_item(item=item_id, partition_key=entra_oid)
225+
# Delete session, and all the message items associated with it
226+
# TODO: Delete all the message items as well
178227
return jsonify({}), 204
179228
except Exception as error:
180229
return error_response(error, f"/chat_history/items/{item_id}")

app/frontend/src/api/api.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,15 @@ export async function postChatHistoryApi(item: any, idToken: string): Promise<an
145145

146146
export async function getChatHistoryListApi(count: number, continuationToken: string | undefined, idToken: string): Promise<HistoryListApiResponse> {
147147
const headers = await getHeaders(idToken);
148-
const response = await fetch("/chat_history/items", {
149-
method: "POST",
150-
headers: { ...headers, "Content-Type": "application/json" },
151-
body: JSON.stringify({ count: count, continuation_token: continuationToken })
148+
const url = new URL("/chat_history/items", BACKEND_URI);
149+
url.searchParams.append("count", count.toString());
150+
if (continuationToken) {
151+
url.searchParams.append("continuationToken", continuationToken);
152+
}
153+
154+
const response = await fetch(url.toString(), {
155+
method: "GET",
156+
headers: { ...headers, "Content-Type": "application/json" }
152157
});
153158

154159
if (!response.ok) {

infra/main.bicep

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,9 @@ module cosmosDb 'br/public:avm/res/document-db/database-account:0.6.1' = if (use
815815
{
816816
path: '/timestamp/?'
817817
}
818+
{
819+
path: '/type/?'
820+
}
818821
]
819822
excludedPaths: [
820823
{

0 commit comments

Comments
 (0)