Skip to content

Commit 10a2c49

Browse files
committed
Move to new azure-search-documents package
1 parent d08040f commit 10a2c49

File tree

11 files changed

+212
-208
lines changed

11 files changed

+212
-208
lines changed

app/backend/app.py

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,9 @@ async def ask(auth_claims: dict[str, Any]):
188188
try:
189189
approach: Approach = cast(Approach, current_app.config[CONFIG_ASK_APPROACH])
190190
r = await approach.run(
191-
request_json["messages"], context=context, session_state=request_json.get("session_state")
191+
request_json["messages"],
192+
context=context,
193+
session_state=request_json.get("session_state"),
192194
)
193195
return jsonify(r)
194196
except Exception as error:
@@ -329,7 +331,10 @@ async def speech():
329331
+ "#"
330332
+ current_app.config[CONFIG_SPEECH_SERVICE_TOKEN].token
331333
)
332-
speech_config = SpeechConfig(auth_token=auth_token, region=current_app.config[CONFIG_SPEECH_SERVICE_LOCATION])
334+
speech_config = SpeechConfig(
335+
auth_token=auth_token,
336+
region=current_app.config[CONFIG_SPEECH_SERVICE_LOCATION],
337+
)
333338
speech_config.speech_synthesis_voice_name = current_app.config[CONFIG_SPEECH_SERVICE_VOICE]
334339
speech_config.speech_synthesis_output_format = SpeechSynthesisOutputFormat.Audio16Khz32KBitRateMonoMp3
335340
synthesizer = SpeechSynthesizer(speech_config=speech_config, audio_config=None)
@@ -339,7 +344,9 @@ async def speech():
339344
elif result.reason == ResultReason.Canceled:
340345
cancellation_details = result.cancellation_details
341346
current_app.logger.error(
342-
"Speech synthesis canceled: %s %s", cancellation_details.reason, cancellation_details.error_details
347+
"Speech synthesis canceled: %s %s",
348+
cancellation_details.reason,
349+
cancellation_details.error_details,
343350
)
344351
raise Exception("Speech synthesis canceled. Check logs for details.")
345352
else:
@@ -363,11 +370,22 @@ async def upload(auth_claims: dict[str, Any]):
363370
adls_manager: AdlsBlobManager = current_app.config[CONFIG_USER_BLOB_MANAGER]
364371
file_url = await adls_manager.upload_blob(file, file.filename, user_oid)
365372
ingester: UploadUserFileStrategy = current_app.config[CONFIG_INGESTER]
366-
await ingester.add_file(File(content=file, url=file_url, acls={"oids": [user_oid]}), user_oid=user_oid)
373+
await ingester.add_file(
374+
File(content=file, url=file_url, acls={"oids": [user_oid]}),
375+
user_oid=user_oid,
376+
)
367377
return jsonify({"message": "File uploaded successfully"}), 200
368378
except Exception as error:
369379
current_app.logger.error("Error uploading file: %s", error)
370-
return jsonify({"message": "Error uploading file, check server logs for details.", "status": "failed"}), 500
380+
return (
381+
jsonify(
382+
{
383+
"message": "Error uploading file, check server logs for details.",
384+
"status": "failed",
385+
}
386+
),
387+
500,
388+
)
371389

372390

373391
@bp.post("/delete_uploaded")
@@ -471,7 +489,7 @@ async def setup_clients():
471489
USE_CHAT_HISTORY_BROWSER = os.getenv("USE_CHAT_HISTORY_BROWSER", "").lower() == "true"
472490
USE_CHAT_HISTORY_COSMOS = os.getenv("USE_CHAT_HISTORY_COSMOS", "").lower() == "true"
473491
USE_AGENTIC_RETRIEVAL = os.getenv("USE_AGENTIC_RETRIEVAL", "").lower() == "true"
474-
ENABLE_AGENTIC_RETRIEVAL_SOURCE_DATA = os.getenv("ENABLE_AGENTIC_RETRIEVAL_SOURCE_DATA", "").lower() == "true"
492+
# TODO: ENABLE_AGENTIC_RETRIEVAL_SOURCE_DATA = os.getenv("ENABLE_AGENTIC_RETRIEVAL_SOURCE_DATA", "").lower() == "true"
475493

476494
# WEBSITE_HOSTNAME is always set by App Service, RUNNING_IN_PRODUCTION is set in main.bicep
477495
RUNNING_ON_AZURE = os.getenv("WEBSITE_HOSTNAME") is not None or os.getenv("RUNNING_IN_PRODUCTION") is not None
@@ -487,15 +505,17 @@ async def setup_clients():
487505
# ManagedIdentityCredential should use AZURE_CLIENT_ID if set in env, but its not working for some reason,
488506
# so we explicitly pass it in as the client ID here. This is necessary for user-assigned managed identities.
489507
current_app.logger.info(
490-
"Setting up Azure credential using ManagedIdentityCredential with client_id %s", AZURE_CLIENT_ID
508+
"Setting up Azure credential using ManagedIdentityCredential with client_id %s",
509+
AZURE_CLIENT_ID,
491510
)
492511
azure_credential = ManagedIdentityCredential(client_id=AZURE_CLIENT_ID)
493512
else:
494513
current_app.logger.info("Setting up Azure credential using ManagedIdentityCredential")
495514
azure_credential = ManagedIdentityCredential()
496515
elif AZURE_TENANT_ID:
497516
current_app.logger.info(
498-
"Setting up Azure credential using AzureDeveloperCliCredential with tenant_id %s", AZURE_TENANT_ID
517+
"Setting up Azure credential using AzureDeveloperCliCredential with tenant_id %s",
518+
AZURE_TENANT_ID,
499519
)
500520
azure_credential = AzureDeveloperCliCredential(tenant_id=AZURE_TENANT_ID, process_timeout=60)
501521
else:
@@ -515,7 +535,9 @@ async def setup_clients():
515535
credential=azure_credential,
516536
)
517537
agent_client = KnowledgeAgentRetrievalClient(
518-
endpoint=AZURE_SEARCH_ENDPOINT, agent_name=AZURE_SEARCH_AGENT, credential=azure_credential
538+
endpoint=AZURE_SEARCH_ENDPOINT,
539+
agent_name=AZURE_SEARCH_AGENT,
540+
credential=azure_credential,
519541
)
520542

521543
# Set up the global blob storage manager (used for global content/images, but not user uploads)
@@ -600,7 +622,9 @@ async def setup_clients():
600622
openai_deployment=AZURE_OPENAI_CHATGPT_DEPLOYMENT if OPENAI_HOST == OpenAIHost.AZURE else None,
601623
)
602624
search_info = await setup_search_info(
603-
search_service=AZURE_SEARCH_SERVICE, index_name=AZURE_SEARCH_INDEX, azure_credential=azure_credential
625+
search_service=AZURE_SEARCH_SERVICE,
626+
index_name=AZURE_SEARCH_INDEX,
627+
azure_credential=azure_credential,
604628
)
605629
text_embeddings_service = setup_embeddings_service(
606630
azure_credential=azure_credential,
@@ -690,7 +714,6 @@ async def setup_clients():
690714
query_speller=AZURE_SEARCH_QUERY_SPELLER,
691715
prompt_manager=prompt_manager,
692716
reasoning_effort=OPENAI_REASONING_EFFORT,
693-
hydrate_references=ENABLE_AGENTIC_RETRIEVAL_SOURCE_DATA,
694717
multimodal_enabled=USE_MULTIMODAL,
695718
image_embeddings_client=image_embeddings_client,
696719
global_blob_manager=global_blob_manager,
@@ -718,7 +741,6 @@ async def setup_clients():
718741
query_speller=AZURE_SEARCH_QUERY_SPELLER,
719742
prompt_manager=prompt_manager,
720743
reasoning_effort=OPENAI_REASONING_EFFORT,
721-
hydrate_references=ENABLE_AGENTIC_RETRIEVAL_SOURCE_DATA,
722744
multimodal_enabled=USE_MULTIMODAL,
723745
image_embeddings_client=image_embeddings_client,
724746
global_blob_manager=global_blob_manager,

app/backend/approaches/approach.py

Lines changed: 30 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@
66

77
from azure.search.documents.agent.aio import KnowledgeAgentRetrievalClient
88
from azure.search.documents.agent.models import (
9-
KnowledgeAgentAzureSearchDocReference,
10-
KnowledgeAgentIndexParams,
119
KnowledgeAgentMessage,
1210
KnowledgeAgentMessageTextContent,
1311
KnowledgeAgentRetrievalRequest,
1412
KnowledgeAgentRetrievalResponse,
15-
KnowledgeAgentSearchActivityRecord,
13+
KnowledgeAgentSearchIndexReference,
14+
SearchIndexKnowledgeSourceParams,
1615
)
1716
from azure.search.documents.aio import SearchClient
1817
from azure.search.documents.models import (
@@ -162,7 +161,6 @@ def __init__(
162161
openai_host: str,
163162
prompt_manager: PromptManager,
164163
reasoning_effort: Optional[str] = None,
165-
hydrate_references: bool = False,
166164
multimodal_enabled: bool = False,
167165
image_embeddings_client: Optional[ImageEmbeddings] = None,
168166
global_blob_manager: Optional[BlobManager] = None,
@@ -180,7 +178,6 @@ def __init__(
180178
self.openai_host = openai_host
181179
self.prompt_manager = prompt_manager
182180
self.reasoning_effort = reasoning_effort
183-
self.hydrate_references = hydrate_references
184181
self.include_token_usage = True
185182
self.multimodal_enabled = multimodal_enabled
186183
self.image_embeddings_client = image_embeddings_client
@@ -275,139 +272,58 @@ async def run_agentic_retrieval(
275272
search_index_name: str,
276273
top: Optional[int] = None,
277274
filter_add_on: Optional[str] = None,
278-
minimum_reranker_score: Optional[float] = None,
279-
max_docs_for_reranker: Optional[int] = None,
280275
results_merge_strategy: Optional[str] = None,
281276
) -> tuple[KnowledgeAgentRetrievalResponse, list[Document]]:
282277
# STEP 1: Invoke agentic retrieval
283278
response = await agent_client.retrieve(
284279
retrieval_request=KnowledgeAgentRetrievalRequest(
285280
messages=[
286281
KnowledgeAgentMessage(
287-
role=str(msg["role"]), content=[KnowledgeAgentMessageTextContent(text=str(msg["content"]))]
282+
role=str(msg["role"]),
283+
content=[KnowledgeAgentMessageTextContent(text=str(msg["content"]))],
288284
)
289285
for msg in messages
290286
if msg["role"] != "system"
291287
],
292-
target_index_params=[
293-
KnowledgeAgentIndexParams(
294-
index_name=search_index_name,
295-
reranker_threshold=minimum_reranker_score,
296-
max_docs_for_reranker=max_docs_for_reranker,
288+
knowledge_source_params=[
289+
SearchIndexKnowledgeSourceParams(
290+
knowledge_source_name="default-knowledge-source",
297291
filter_add_on=filter_add_on,
298-
include_reference_source_data=True,
299292
)
300293
],
301294
)
302295
)
303296

304-
# Map activity id -> agent's internal search query
305-
activities = response.activity
306-
activity_mapping: dict[int, str] = (
307-
{
308-
activity.id: activity.query.search
309-
for activity in activities
310-
if (
311-
isinstance(activity, KnowledgeAgentSearchActivityRecord)
312-
and activity.query
313-
and activity.query.search is not None
314-
)
315-
}
316-
if activities
317-
else {}
318-
)
319-
320297
# No refs? we're done
321298
if not (response and response.references):
322299
return response, []
323300

324301
# Extract references
325-
refs = [r for r in response.references if isinstance(r, KnowledgeAgentAzureSearchDocReference)]
302+
refs = [r for r in response.references if isinstance(r, KnowledgeAgentSearchIndexReference)]
326303

327304
documents: list[Document] = []
328305

329-
if self.hydrate_references:
330-
# Hydrate references to get full documents
331-
documents = await self.hydrate_agent_references(
332-
references=refs,
333-
top=top,
334-
)
335-
else:
336-
# Create documents from reference source data
337-
for ref in refs:
338-
if ref.source_data:
339-
documents.append(
340-
Document(
341-
id=ref.doc_key,
342-
content=ref.source_data.get("content"),
343-
sourcepage=ref.source_data.get("sourcepage"),
344-
)
345-
)
346-
if top and len(documents) >= top:
347-
break
348-
349-
# Build mappings for agent queries and sorting
350-
ref_to_activity: dict[str, int] = {}
351-
doc_to_ref_id: dict[str, str] = {}
306+
# Create documents from reference source data
352307
for ref in refs:
353-
if ref.doc_key:
354-
ref_to_activity[ref.doc_key] = ref.activity_source
355-
doc_to_ref_id[ref.doc_key] = ref.id
356-
357-
# Inject agent search queries into all documents
358-
for doc in documents:
359-
if doc.id and doc.id in ref_to_activity:
360-
activity_id = ref_to_activity[doc.id]
361-
doc.search_agent_query = activity_mapping.get(activity_id, "")
362-
363-
# Apply sorting strategy to the documents
364-
if results_merge_strategy == "interleaved": # Use interleaved reference order
365-
documents = sorted(
366-
documents,
367-
key=lambda d: int(doc_to_ref_id.get(d.id, 0)) if d.id and doc_to_ref_id.get(d.id) else 0,
368-
)
369-
# else: Default - preserve original order
308+
if ref.source_data:
309+
documents.append(
310+
Document(
311+
id=ref.source_data.get("id"),
312+
content=ref.source_data.get("content"),
313+
category=ref.source_data.get("category"),
314+
sourcepage=ref.source_data.get("sourcepage"),
315+
sourcefile=ref.source_data.get("sourcefile"),
316+
oids=ref.source_data.get("oids"),
317+
groups=ref.source_data.get("groups"),
318+
reranker_score=ref.reranker_score,
319+
images=ref.source_data.get("images"),
320+
)
321+
)
322+
if top and len(documents) >= top:
323+
break
370324

371325
return response, documents
372326

373-
async def hydrate_agent_references(
374-
self,
375-
references: list[KnowledgeAgentAzureSearchDocReference],
376-
top: Optional[int],
377-
) -> list[Document]:
378-
doc_keys: set[str] = set()
379-
380-
for ref in references:
381-
if not ref.doc_key:
382-
continue
383-
doc_keys.add(ref.doc_key)
384-
if top and len(doc_keys) >= top:
385-
break
386-
387-
if not doc_keys:
388-
return []
389-
390-
# Build search filter only on unique doc IDs
391-
id_csv = ",".join(doc_keys)
392-
id_filter = f"search.in(id, '{id_csv}', ',')"
393-
394-
# Fetch full documents
395-
hydrated_docs: list[Document] = await self.search(
396-
top=len(doc_keys),
397-
query_text=None,
398-
filter=id_filter,
399-
vectors=[],
400-
use_text_search=False,
401-
use_vector_search=False,
402-
use_semantic_ranker=False,
403-
use_semantic_captions=False,
404-
minimum_search_score=None,
405-
minimum_reranker_score=None,
406-
use_query_rewriting=False,
407-
)
408-
409-
return hydrated_docs
410-
411327
async def get_sources_content(
412328
self,
413329
results: list[Document],
@@ -535,7 +451,11 @@ async def compute_multimodal_embedding(self, q: str):
535451
if not self.image_embeddings_client:
536452
raise ValueError("Approach is missing an image embeddings client for multimodal queries")
537453
multimodal_query_vector = await self.image_embeddings_client.create_embedding_for_text(q)
538-
return VectorizedQuery(vector=multimodal_query_vector, k_nearest_neighbors=50, fields="images/embedding")
454+
return VectorizedQuery(
455+
vector=multimodal_query_vector,
456+
k_nearest_neighbors=50,
457+
fields="images/embedding",
458+
)
539459

540460
def get_system_prompt_variables(self, override_prompt: Optional[str]) -> dict[str, str]:
541461
# Allows client to replace the entire prompt, or to inject into the existing prompt using >>>

app/backend/approaches/chatreadretrieveread.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ def __init__(
5757
query_speller: str,
5858
prompt_manager: PromptManager,
5959
reasoning_effort: Optional[str] = None,
60-
hydrate_references: bool = False,
6160
multimodal_enabled: bool = False,
6261
image_embeddings_client: Optional[ImageEmbeddings] = None,
6362
global_blob_manager: Optional[BlobManager] = None,
@@ -85,7 +84,6 @@ def __init__(
8584
self.query_rewrite_tools = self.prompt_manager.load_tools("chat_query_rewrite_tools.json")
8685
self.answer_prompt = self.prompt_manager.load_prompt("chat_answer_question.prompty")
8786
self.reasoning_effort = reasoning_effort
88-
self.hydrate_references = hydrate_references
8987
self.include_token_usage = True
9088
self.multimodal_enabled = multimodal_enabled
9189
self.image_embeddings_client = image_embeddings_client
@@ -388,13 +386,9 @@ async def run_agentic_retrieval_approach(
388386
overrides: dict[str, Any],
389387
auth_claims: dict[str, Any],
390388
):
391-
minimum_reranker_score = overrides.get("minimum_reranker_score", 0)
392389
search_index_filter = self.build_filter(overrides, auth_claims)
393390
top = overrides.get("top", 3)
394-
max_subqueries = overrides.get("max_subqueries", 10)
395391
results_merge_strategy = overrides.get("results_merge_strategy", "interleaved")
396-
# 50 is the amount of documents that the reranker can process per query
397-
max_docs_for_reranker = max_subqueries * 50
398392
send_text_sources = overrides.get("send_text_sources", True)
399393
send_image_sources = overrides.get("send_image_sources", True)
400394

@@ -404,8 +398,6 @@ async def run_agentic_retrieval_approach(
404398
search_index_name=self.search_index_name,
405399
top=top,
406400
filter_add_on=search_index_filter,
407-
minimum_reranker_score=minimum_reranker_score,
408-
max_docs_for_reranker=max_docs_for_reranker,
409401
results_merge_strategy=results_merge_strategy,
410402
)
411403

@@ -425,8 +417,6 @@ async def run_agentic_retrieval_approach(
425417
"Use agentic retrieval",
426418
messages,
427419
{
428-
"reranker_threshold": minimum_reranker_score,
429-
"max_docs_for_reranker": max_docs_for_reranker,
430420
"results_merge_strategy": results_merge_strategy,
431421
"filter": search_index_filter,
432422
},

0 commit comments

Comments
 (0)