Skip to content

Commit 8a58ddf

Browse files
committed
changes needed for user upload
1 parent 0d6e1ad commit 8a58ddf

File tree

12 files changed

+266
-112
lines changed

12 files changed

+266
-112
lines changed

app/backend/app.py

Lines changed: 11 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
CONFIG_CREDENTIAL,
6767
CONFIG_DEFAULT_REASONING_EFFORT,
6868
CONFIG_IMAGE_BLOB_CONTAINER_CLIENT, # Added this line
69+
CONFIG_IMAGE_DATALAKE_CLIENT,
6970
CONFIG_INGESTER,
7071
CONFIG_LANGUAGE_PICKER_ENABLED,
7172
CONFIG_MULTIMODAL_ENABLED,
@@ -354,7 +355,6 @@ async def speech():
354355
async def upload(auth_claims: dict[str, Any]):
355356
request_files = await request.files
356357
if "file" not in request_files:
357-
# If no files were included in the request, return an error response
358358
return jsonify({"message": "No file part in the request", "status": "failed"}), 400
359359

360360
user_oid = auth_claims["oid"]
@@ -372,10 +372,8 @@ async def delete_uploaded(auth_claims: dict[str, Any]):
372372
request_json = await request.get_json()
373373
filename = request_json.get("filename")
374374
user_oid = auth_claims["oid"]
375-
user_blob_container_client: FileSystemClient = current_app.config[CONFIG_USER_BLOB_CONTAINER_CLIENT]
376-
user_directory_client = user_blob_container_client.get_directory_client(user_oid)
377-
file_client = user_directory_client.get_file_client(filename)
378-
await file_client.delete_file()
375+
adls_manager = AdlsBlobManager(current_app.config[CONFIG_USER_BLOB_CONTAINER_CLIENT])
376+
await adls_manager.remove_blob(filename, user_oid)
379377
ingester = current_app.config[CONFIG_INGESTER]
380378
await ingester.remove_file(filename, user_oid)
381379
return jsonify({"message": f"File {filename} deleted successfully"}), 200
@@ -388,31 +386,8 @@ async def list_uploaded(auth_claims: dict[str, Any]):
388386
Only returns files directly in the user's directory, not in subdirectories.
389387
Excludes image files and the images directory."""
390388
user_oid = auth_claims["oid"]
391-
user_blob_container_client: FileSystemClient = current_app.config[CONFIG_USER_BLOB_CONTAINER_CLIENT]
392-
files = []
393-
try:
394-
all_paths = user_blob_container_client.get_paths(path=user_oid)
395-
async for path in all_paths:
396-
# Split path into parts (user_oid/filename or user_oid/directory/files)
397-
path_parts = path.name.split("/", 1)
398-
if len(path_parts) != 2:
399-
continue
400-
401-
filename = path_parts[1]
402-
# Only include files that are:
403-
# 1. Directly in the user's directory (no additional slashes)
404-
# 2. Not image files
405-
# 3. Not in a directory containing 'images'
406-
if (
407-
"/" not in filename
408-
and not any(filename.lower().endswith(ext) for ext in [".png", ".jpg", ".jpeg", ".gif", ".bmp"])
409-
and "images" not in filename
410-
):
411-
files.append(filename)
412-
except ResourceNotFoundError as error:
413-
if error.status_code != 404:
414-
current_app.logger.exception("Error listing uploaded files", error)
415-
# Return empty list for 404 (no directory) as this is expected for new users
389+
adls_manager = AdlsBlobManager(current_app.config[CONFIG_USER_BLOB_CONTAINER_CLIENT])
390+
files = await adls_manager.list_blobs(user_oid)
416391
return jsonify(files), 200
417392

418393

@@ -691,7 +666,8 @@ async def setup_clients():
691666
agent_client=agent_client,
692667
openai_client=openai_client,
693668
auth_helper=auth_helper,
694-
images_blob_container_client=image_blob_container_client,
669+
image_blob_container_client=image_blob_container_client,
670+
image_datalake_client=user_blob_container_client,
695671
chatgpt_model=OPENAI_CHATGPT_MODEL,
696672
chatgpt_deployment=AZURE_OPENAI_CHATGPT_DEPLOYMENT,
697673
embedding_model=OPENAI_EMB_MODEL,
@@ -718,7 +694,8 @@ async def setup_clients():
718694
agent_client=agent_client,
719695
openai_client=openai_client,
720696
auth_helper=auth_helper,
721-
images_blob_container_client=image_blob_container_client,
697+
image_blob_container_client=image_blob_container_client,
698+
image_datalake_client=user_blob_container_client,
722699
chatgpt_model=OPENAI_CHATGPT_MODEL,
723700
chatgpt_deployment=AZURE_OPENAI_CHATGPT_DEPLOYMENT,
724701
embedding_model=OPENAI_EMB_MODEL,
@@ -745,6 +722,8 @@ async def close_clients():
745722
await current_app.config[CONFIG_USER_BLOB_CONTAINER_CLIENT].close()
746723
if current_app.config.get(CONFIG_IMAGE_BLOB_CONTAINER_CLIENT):
747724
await current_app.config[CONFIG_IMAGE_BLOB_CONTAINER_CLIENT].close()
725+
if current_app.config.get(CONFIG_IMAGE_DATALAKE_CLIENT):
726+
await current_app.config[CONFIG_IMAGE_DATALAKE_CLIENT].close()
748727

749728

750729
def create_app():

app/backend/approaches/approach.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
VectorQuery,
2525
)
2626
from azure.storage.blob.aio import ContainerClient
27+
from azure.storage.filedatalake.aio import FileSystemClient
2728
from openai import AsyncOpenAI, AsyncStream
2829
from openai.types import CompletionUsage
2930
from openai.types.chat import (
@@ -175,7 +176,8 @@ def __init__(
175176
multimodal_enabled: bool = False,
176177
vision_endpoint: Optional[str] = None,
177178
vision_token_provider: Optional[Callable[[], Awaitable[str]]] = None,
178-
images_blob_container_client: Optional[ContainerClient] = None,
179+
image_blob_container_client: Optional[ContainerClient] = None,
180+
image_datalake_client: Optional[FileSystemClient] = None,
179181
):
180182
self.search_client = search_client
181183
self.openai_client = openai_client
@@ -193,7 +195,23 @@ def __init__(
193195
self.multimodal_enabled = multimodal_enabled
194196
self.vision_endpoint = vision_endpoint
195197
self.vision_token_provider = vision_token_provider
196-
self.images_blob_container_client = images_blob_container_client
198+
self.image_blob_container_client = image_blob_container_client
199+
self.image_datalake_client = image_datalake_client
200+
201+
def get_storage_client_for_url(self, url: str) -> Optional[Union[ContainerClient, FileSystemClient]]:
202+
"""
203+
Determines which storage client to use for a given URL.
204+
205+
Args:
206+
url: The URL or path of the image
207+
208+
Returns:
209+
Either the ContainerClient for Blob Storage or FileSystemClient for Data Lake Storage,
210+
based on the URL pattern. Returns None if no matching client is available.
211+
"""
212+
if ".dfs.core.windows.net" in url and self.image_datalake_client:
213+
return self.image_datalake_client
214+
return self.image_blob_container_client
197215

198216
def get_default_llm_inputs(self) -> str:
199217
"""
@@ -363,7 +381,11 @@ async def run_agentic_retrieval(
363381
return response, results
364382

365383
async def get_sources_content(
366-
self, results: list[Document], use_semantic_captions: bool, use_image_sources: bool
384+
self,
385+
results: list[Document],
386+
use_semantic_captions: bool,
387+
use_image_sources: bool,
388+
user_oid: Optional[str] = None,
367389
) -> tuple[list[str], list[str], list[str]]:
368390
"""
369391
Extracts text and image sources from the search results.
@@ -395,14 +417,13 @@ def nonewlines(s: str) -> str:
395417
text_sources.append(f"{citation}: {nonewlines(doc.content or '')}")
396418

397419
if use_image_sources and hasattr(doc, "images") and doc.images:
398-
if self.images_blob_container_client is None:
399-
raise ValueError("The images blob container client must be set to use image sources.")
400420
for img in doc.images:
401421
# Skip if we've already processed this URL
402-
if img["url"] in seen_urls:
422+
if img["url"] in seen_urls or not img["url"]:
403423
continue
404424
seen_urls.add(img["url"])
405-
url = await download_blob_as_base64(self.images_blob_container_client, img["url"])
425+
storage_client = self.get_storage_client_for_url(img["url"])
426+
url = await download_blob_as_base64(storage_client, img["url"], user_oid=user_oid)
406427
if url:
407428
image_sources.append(url)
408429
citations.append(self.get_image_citation(doc.sourcepage or "", img["url"]))

app/backend/approaches/chatreadretrieveread.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from azure.search.documents.aio import SearchClient
88
from azure.search.documents.models import VectorQuery
99
from azure.storage.blob.aio import ContainerClient
10+
from azure.storage.filedatalake.aio import FileSystemClient
1011
from openai import AsyncOpenAI, AsyncStream
1112
from openai.types.chat import (
1213
ChatCompletion,
@@ -61,7 +62,8 @@ def __init__(
6162
multimodal_enabled: bool = False,
6263
vision_endpoint: Optional[str] = None,
6364
vision_token_provider: Optional[Callable[[], Awaitable[str]]] = None,
64-
images_blob_container_client: Optional[ContainerClient] = None,
65+
image_blob_container_client: Optional[ContainerClient] = None,
66+
image_datalake_client: Optional[FileSystemClient] = None,
6567
):
6668
self.search_client = search_client
6769
self.search_index_name = search_index_name
@@ -70,7 +72,8 @@ def __init__(
7072
self.agent_client = agent_client
7173
self.openai_client = openai_client
7274
self.auth_helper = auth_helper
73-
self.images_blob_container_client = images_blob_container_client
75+
self.image_blob_container_client = image_blob_container_client
76+
self.image_datalake_client = image_datalake_client
7477
self.chatgpt_model = chatgpt_model
7578
self.chatgpt_deployment = chatgpt_deployment
7679
self.embedding_deployment = embedding_deployment
@@ -300,6 +303,7 @@ async def run_search_approach(
300303
VectorFieldType.TEXT_AND_IMAGE_EMBEDDINGS,
301304
]
302305
use_image_sources = llm_inputs_enum in [LLMInputType.TEXT_AND_IMAGES, LLMInputType.IMAGES]
306+
use_text_sources = llm_inputs_enum in [LLMInputType.TEXT_AND_IMAGES, LLMInputType.TEXTS]
303307

304308
original_user_query = messages[-1]["content"]
305309
if not isinstance(original_user_query, str):
@@ -354,11 +358,11 @@ async def run_search_approach(
354358

355359
# STEP 3: Generate a contextual and content specific answer using the search results and chat history
356360
text_sources, image_sources, citations = await self.get_sources_content(
357-
results, use_semantic_captions, use_image_sources=use_image_sources
361+
results, use_semantic_captions, use_image_sources=use_image_sources, user_oid=auth_claims["oid"]
358362
)
359363

360364
extra_info = ExtraInfo(
361-
DataPoints(text=text_sources, images=image_sources, citations=citations),
365+
DataPoints(text=text_sources if use_text_sources else [], images=image_sources, citations=citations),
362366
thoughts=[
363367
self.format_thought_step_for_chatcompletion(
364368
title="Prompt to generate search query",
@@ -417,19 +421,20 @@ async def run_agentic_retrieval_approach(
417421
results_merge_strategy=results_merge_strategy,
418422
)
419423

420-
# Determine if we should use image sources based on overrides or defaults
424+
# Determine if we should use text/image sources based on overrides or defaults
421425
llm_inputs = overrides.get("llm_inputs")
422426
if llm_inputs is None:
423427
llm_inputs = self.get_default_llm_inputs()
424428
llm_inputs_enum = LLMInputType(llm_inputs) if llm_inputs is not None else None
425429
use_image_sources = llm_inputs_enum in [LLMInputType.TEXT_AND_IMAGES, LLMInputType.IMAGES]
430+
use_text_sources = llm_inputs_enum in [LLMInputType.TEXT_AND_IMAGES, LLMInputType.TEXTS]
426431

427432
text_sources, image_sources, citations = await self.get_sources_content(
428-
results, use_semantic_captions=False, use_image_sources=use_image_sources
433+
results, use_semantic_captions=False, use_image_sources=use_image_sources, user_oid=auth_claims["oid"]
429434
)
430435

431436
extra_info = ExtraInfo(
432-
DataPoints(text=text_sources, images=image_sources, citations=citations),
437+
DataPoints(text=text_sources if use_text_sources else [], images=image_sources, citations=citations),
433438
thoughts=[
434439
ThoughtStep(
435440
"Use agentic retrieval",

app/backend/approaches/prompts/ask_answer_question.prompty

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ Answer the following question using only the data provided in the sources below.
2020
Each source has a name followed by colon and the actual information, always include the source name for each fact you use in the response.
2121
If you cannot answer using the sources below, say you don't know. Use below example to answer.
2222
{% if image_sources %}
23-
Each image source has the original document file name in the top left corner of the image with coordinates (10,10) pixels and is in the format Document:<document_name.ext#page=N>.
24-
The filename of the actual image is in the top right corner of the image and is in the format Figure:<image_name.png>.
23+
Each image source has the document file name in the top left corner of the image with coordinates (10,10) pixels with format <filename.ext#page=N>,
24+
and the image figure name is right-aligned in the top right corner of the image.
25+
The filename of the actual image is in the top right corner of the image and is in the format <figureN_N.png>.
2526
Each text source starts in a new line and has the file name followed by colon and the actual information.
2627
Always include the source document filename for each fact you use in the response in the format: [document_name.ext#page=N].
2728
If you are referencing an image, add the image filename in the format: [document_name.ext#page=N(image_name.png)].

app/backend/approaches/prompts/chat_answer_question.prompty

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ Answer ONLY with the facts listed in the list of sources below. If there isn't e
2525
If the question is not in English, answer in the language used in the question.
2626
Each source has a name followed by colon and the actual information, always include the source name for each fact you use in the response. Use square brackets to reference the source, for example [info1.txt]. Don't combine sources, list each source separately, for example [info1.txt][info2.pdf].
2727
{% if include_images %}
28-
Each image source has the file name in the top left corner of the image with coordinates (10,10) pixels and is in the format SourceFileName:<file_name>
28+
Each image source has the document file name in the top left corner of the image with coordinates (10,10) pixels with format <filename.ext#page=N>,
29+
and the image figure name is right-aligned in the top right corner of the image.
30+
The filename of the actual image is in the top right corner of the image and is in the format <figureN_N.png>.
2931
Each text source starts in a new line and has the file name followed by colon and the actual information
3032
Always include the source name from the image or text for each fact you use in the response in the format: [filename]
3133
Answer the following question using only the data provided in the sources below.
@@ -62,4 +64,4 @@ Sources:
6264
{% for text_source in text_sources %}
6365
{{ text_source }}
6466
{% endfor %}
65-
{% endif %}
67+
{% endif %}

app/backend/approaches/retrievethenread.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from azure.search.documents.aio import SearchClient
66
from azure.search.documents.models import VectorQuery
77
from azure.storage.blob.aio import ContainerClient
8+
from azure.storage.filedatalake.aio import FileSystemClient
89
from openai import AsyncOpenAI
910
from openai.types.chat import ChatCompletion, ChatCompletionMessageParam
1011

@@ -52,7 +53,8 @@ def __init__(
5253
multimodal_enabled: bool = False,
5354
vision_endpoint: Optional[str] = None,
5455
vision_token_provider: Optional[Callable[[], Awaitable[str]]] = None,
55-
images_blob_container_client: Optional[ContainerClient] = None,
56+
image_blob_container_client: Optional[ContainerClient] = None,
57+
image_datalake_client: Optional[FileSystemClient] = None,
5658
):
5759
self.search_client = search_client
5860
self.search_index_name = search_index_name
@@ -62,7 +64,8 @@ def __init__(
6264
self.chatgpt_deployment = chatgpt_deployment
6365
self.openai_client = openai_client
6466
self.auth_helper = auth_helper
65-
self.images_blob_container_client = images_blob_container_client
67+
self.image_blob_container_client = image_blob_container_client
68+
self.image_datalake_client = image_datalake_client
6669
self.chatgpt_model = chatgpt_model
6770
self.embedding_model = embedding_model
6871
self.embedding_dimensions = embedding_dimensions
@@ -200,7 +203,7 @@ async def run_search_approach(
200203
)
201204

202205
text_sources, image_sources, citations = await self.get_sources_content(
203-
results, use_semantic_captions, use_image_sources=use_image_sources
206+
results, use_semantic_captions, use_image_sources=use_image_sources, user_oid=auth_claims["oid"]
204207
)
205208

206209
return ExtraInfo(
@@ -261,7 +264,7 @@ async def run_agentic_retrieval_approach(
261264
use_image_sources = llm_inputs_enum in [LLMInputType.TEXT_AND_IMAGES, LLMInputType.IMAGES]
262265

263266
text_sources, image_sources, citations = await self.get_sources_content(
264-
results, use_semantic_captions=False, use_image_sources=use_image_sources
267+
results, use_semantic_captions=False, use_image_sources=use_image_sources, user_oid=auth_claims["oid"]
265268
)
266269

267270
extra_info = ExtraInfo(

app/backend/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
CONFIG_CHAT_APPROACH = "chat_approach"
55
CONFIG_BLOB_CONTAINER_CLIENT = "blob_container_client"
66
CONFIG_IMAGE_BLOB_CONTAINER_CLIENT = "image_blob_container_client"
7+
CONFIG_IMAGE_DATALAKE_CLIENT = "image_datalake_client"
78
CONFIG_USER_UPLOAD_ENABLED = "user_upload_enabled"
89
CONFIG_USER_BLOB_CONTAINER_CLIENT = "user_blob_container_client"
910
CONFIG_AUTH_CLIENT = "auth_client"

0 commit comments

Comments
 (0)