Skip to content

Commit 751abd1

Browse files
committed
More progress on multimodal approach
1 parent 2a73065 commit 751abd1

File tree

126 files changed

+1484
-1146
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

126 files changed

+1484
-1146
lines changed

.azdo/pipelines/azure-dev.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ steps:
121121
AZURE_CONTAINER_APPS_WORKLOAD_PROFILE: $(AZURE_CONTAINER_APPS_WORKLOAD_PROFILE)
122122
USE_CHAT_HISTORY_BROWSER: $(USE_CHAT_HISTORY_BROWSER)
123123
USE_MEDIA_DESCRIBER_AZURE_CU: $(USE_MEDIA_DESCRIBER_AZURE_CU)
124+
RAG_LLM_INPUTS_OVERRIDE: $(RAG_LLM_INPUTS_OVERRIDE)
125+
RAG_VECTOR_FIELDS_DEFAULT: $(RAG_VECTOR_FIELDS_DEFAULT)
124126
- task: AzureCLI@2
125127
displayName: Deploy Application
126128
inputs:

.github/copilot-instructions.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Adding new data
2+
3+
New files should be added to the `data` folder, and then either run scripts/prepdocs.sh or script/prepdocs.ps1 to ingest the data.
4+
5+
# Overall code layout
6+
7+
* app: Contains the main application code, including frontend and backend.
8+
* app/backend: Contains the Python backend code, written with Quart framework.
9+
* app/backend/approaches: Contains the different approaches
10+
* app/backend/approaches/approach.py: Base class for all approaches
11+
* app/backend/approaches/retrievethenread.py: Ask approach, just searches and answers
12+
* app/backend/approaches/chatreadretrieveread.py: Chat approach, includes query rewriting step first
13+
* app/backend/app.py: The main entry point for the backend application.
14+
* app/frontend: Contains the React frontend code, built with TypeScript, built with vite.
15+
* app/frontend/src/api: Contains the API client code for communicating with the backend.
16+
* app/frontend/src/components: Contains the React components for the frontend.
17+
* app/frontend/src/locales: Contains the translation files for internationalization.
18+
* app/frontend/src/pages: Contains the main pages of the application
19+
* infra: Contains the Bicep templates for provisioning Azure resources.
20+
* tests: Contains the test code, including e2e tests, app integration tests, and unit tests.
21+
22+
# Adding a new azd environment variable
23+
24+
An azd environment variable is stored by the azd CLI for each environment. It is passed to the "azd up" command and can configure both provisioning options and application settings.
25+
When adding new azd environment variables, update:
26+
27+
1. infra/main.parameters.json : Add the new parameter with a Bicep-friendly variable name and map to the new environment variable
28+
1. infra/main.bicep: Add the new Bicep parameter at the top, and add it to the `appEnvVariables` object
29+
1. azure.yaml: Add the new environment variable under pipeline config section
30+
1. .azdo/pipelines/azure-dev.yml: Add the new environment variable under `env` section
31+
1. .github/workflows/azure-dev.yml: Add the new environment variable under `env` section
32+
33+
# Adding a new setting to "Developer Settings" in RAG app
34+
35+
When adding a new developer setting, update:
36+
37+
* frontend:
38+
* app/frontend/src/api/models.ts : Add to ChatAppRequestOverrides
39+
* app/frontend/src/components/Settings.tsx : Add a UI element for the setting
40+
* app/frontend/src/locales/*/translations.json: Add a translation for the setting label/tooltip for all languages
41+
* app/frontend/src/pages/chat/Chat.tsx: Add the setting to the component, pass it to Settings
42+
* app/frontend/src/pages/ask/Ask.tsx: Add the setting to the component, pass it to Settings
43+
44+
* backend:
45+
* app/backend/approaches/chatreadretrieveread.py : Retrieve from overrides parameter
46+
* app/backend/approaches/retrievethenread.py : Retrieve from overrides parameter
47+
* app/backend/app.py: Some settings may need to sent down in the /config route.
48+
49+
# When adding tests for a new feature:
50+
51+
All tests are in the `tests` folder and use the pytest framework.
52+
There are three styles of tests:
53+
54+
* e2e tests: These use playwright to run the app in a browser and test the UI end-to-end. They are in e2e.py and they mock the backend using the snapshots from the app tests.
55+
* app integration tests: Mostly in test_app.py, these test the app's API endpoints and use mocks for services like Azure OpenAI and Azure Search.
56+
* unit tests: The rest of the tests are unit tests that test individual functions and methods. They are in test_*.py files.
57+
58+
When adding a new feature, add tests for it in the appropriate file.
59+
If the feature is a UI element, add an e2e test for it.
60+
If it is an API endpoint, add an app integration test for it.
61+
If it is a function or method, add a unit test for it.
62+
Use mocks from conftest.py to mock external services.

.github/workflows/azure-dev.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ jobs:
111111
USE_CHAT_HISTORY_BROWSER: ${{ vars.USE_CHAT_HISTORY_BROWSER }}
112112
USE_MEDIA_DESCRIBER_AZURE_CU: ${{ vars.USE_MEDIA_DESCRIBER_AZURE_CU }}
113113
USE_AI_PROJECT: ${{ vars.USE_AI_PROJECT }}
114+
RAG_LLM_INPUTS_OVERRIDE: ${{ vars.RAG_LLM_INPUTS_OVERRIDE }}
115+
RAG_VECTOR_FIELDS_DEFAULT: ${{ vars.RAG_VECTOR_FIELDS_DEFAULT }}
114116
steps:
115117
- name: Checkout
116118
uses: actions/checkout@v4

CONTRIBUTING.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ contact [[email protected]](mailto:[email protected]) with any additio
1717
- [Running unit tests](#running-unit-tests)
1818
- [Running E2E tests](#running-e2e-tests)
1919
- [Code style](#code-style)
20-
- [Adding new azd environment variables](#adding-new-azd-environment-variables)
21-
- [Adding new UI strings](#adding-new-ui-strings)
20+
- [Adding new features](#adding-new-features)
21+
- [Adding new azd environment variables](#adding-new-azd-environment-variables)
22+
- [Adding new UI strings](#adding-new-ui-strings)
2223

2324
## Submitting a Pull Request (PR)
2425

@@ -118,7 +119,15 @@ python -m black <path-to-file>
118119

119120
If you followed the steps above to install the pre-commit hooks, then you can just wait for those hooks to run `ruff` and `black` for you.
120121

121-
## Adding new azd environment variables
122+
## Adding new features
123+
124+
We recommend using GitHub Copilot Agent mode when adding new features,
125+
as this project includes [.github/copilot-instructions.md](.github/copilot-instructions.md) file
126+
that instructs Copilot on how to generate code for common code changes.
127+
128+
If you are not using Copilot Agent mode, consult both that file and suggestions below.
129+
130+
### Adding new azd environment variables
122131

123132
When adding new azd environment variables, please remember to update:
124133

@@ -128,7 +137,7 @@ When adding new azd environment variables, please remember to update:
128137
1. [ADO pipeline](.azdo/pipelines/azure-dev.yml).
129138
1. [Github workflows](.github/workflows/azure-dev.yml)
130139

131-
## Adding new UI strings
140+
### Adding new UI strings
132141

133142
When adding new UI strings, please remember to update all translations.
134143
For any translations that you generate with an AI tool,

app/backend/app.py

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
import mimetypes
66
import os
77
import time
8-
from collections.abc import AsyncGenerator
8+
from collections.abc import AsyncGenerator, Awaitable
99
from pathlib import Path
10-
from typing import Any, Union, cast
10+
from typing import Any, Callable, Union, cast
1111

1212
from azure.cognitiveservices.speech import (
1313
ResultReason,
@@ -72,6 +72,8 @@
7272
CONFIG_MULTIMODAL_ENABLED,
7373
CONFIG_OPENAI_CLIENT,
7474
CONFIG_QUERY_REWRITING_ENABLED,
75+
CONFIG_RAG_LLM_INPUTS_OVERRIDE,
76+
CONFIG_RAG_VECTOR_FIELDS_DEFAULT,
7577
CONFIG_REASONING_EFFORT_ENABLED,
7678
CONFIG_SEARCH_CLIENT,
7779
CONFIG_SEMANTIC_RANKER_DEPLOYED,
@@ -279,7 +281,7 @@ def auth_setup():
279281
def config():
280282
return jsonify(
281283
{
282-
"showMultimodalOption": current_app.config[CONFIG_MULTIMODAL_ENABLED],
284+
"showMultimodalOptions": current_app.config[CONFIG_MULTIMODAL_ENABLED],
283285
"showSemanticRankerOption": current_app.config[CONFIG_SEMANTIC_RANKER_DEPLOYED],
284286
"showQueryRewritingOption": current_app.config[CONFIG_QUERY_REWRITING_ENABLED],
285287
"showReasoningEffortOption": current_app.config[CONFIG_REASONING_EFFORT_ENABLED],
@@ -294,6 +296,8 @@ def config():
294296
"showChatHistoryBrowser": current_app.config[CONFIG_CHAT_HISTORY_BROWSER_ENABLED],
295297
"showChatHistoryCosmos": current_app.config[CONFIG_CHAT_HISTORY_COSMOS_ENABLED],
296298
"showAgenticRetrievalOption": current_app.config[CONFIG_AGENTIC_RETRIEVAL_ENABLED],
299+
"ragLlmInputsOverride": current_app.config[CONFIG_RAG_LLM_INPUTS_OVERRIDE],
300+
"ragVectorFieldsDefault": current_app.config[CONFIG_RAG_VECTOR_FIELDS_DEFAULT],
297301
}
298302
)
299303

@@ -432,6 +436,7 @@ async def setup_clients():
432436
# https://learn.microsoft.com/azure/ai-services/openai/api-version-deprecation#latest-ga-api-release
433437
AZURE_OPENAI_API_VERSION = os.getenv("AZURE_OPENAI_API_VERSION") or "2024-10-21"
434438
AZURE_VISION_ENDPOINT = os.getenv("AZURE_VISION_ENDPOINT", "")
439+
AZURE_OPENAI_API_KEY_OVERRIDE = os.getenv("AZURE_OPENAI_API_KEY_OVERRIDE")
435440
# Used only with non-Azure OpenAI deployments
436441
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
437442
OPENAI_ORGANIZATION = os.getenv("OPENAI_ORGANIZATION")
@@ -461,6 +466,8 @@ async def setup_clients():
461466
AZURE_SPEECH_SERVICE_VOICE = os.getenv("AZURE_SPEECH_SERVICE_VOICE") or "en-US-AndrewMultilingualNeural"
462467

463468
USE_MULTIMODAL = os.getenv("USE_MULTIMODAL", "").lower() == "true"
469+
RAG_LLM_INPUTS_OVERRIDE = os.getenv("RAG_LLM_INPUTS_OVERRIDE", "")
470+
RAG_VECTOR_FIELDS_DEFAULT = os.getenv("RAG_VECTOR_FIELDS_DEFAULT", "")
464471
USE_USER_UPLOAD = os.getenv("USE_USER_UPLOAD", "").lower() == "true"
465472
ENABLE_LANGUAGE_PICKER = os.getenv("ENABLE_LANGUAGE_PICKER", "").lower() == "true"
466473
USE_SPEECH_INPUT_BROWSER = os.getenv("USE_SPEECH_INPUT_BROWSER", "").lower() == "true"
@@ -477,6 +484,7 @@ async def setup_clients():
477484
# This assumes you use 'azd auth login' locally, and managed identity when deployed on Azure.
478485
# The managed identity is setup in the infra/ folder.
479486
azure_credential: Union[AzureDeveloperCliCredential, ManagedIdentityCredential]
487+
azure_ai_token_provider: Callable[[], Awaitable[str]]
480488
if RUNNING_ON_AZURE:
481489
current_app.logger.info("Setting up Azure credential using ManagedIdentityCredential")
482490
if AZURE_CLIENT_ID := os.getenv("AZURE_CLIENT_ID"):
@@ -497,6 +505,9 @@ async def setup_clients():
497505
else:
498506
current_app.logger.info("Setting up Azure credential using AzureDeveloperCliCredential for home tenant")
499507
azure_credential = AzureDeveloperCliCredential(process_timeout=60)
508+
azure_ai_token_provider = get_bearer_token_provider(
509+
azure_credential, "https://cognitiveservices.azure.com/.default"
510+
)
500511

501512
# Set the Azure credential in the app config for use in other parts of the app
502513
current_app.config[CONFIG_CREDENTIAL] = azure_credential
@@ -565,20 +576,21 @@ async def setup_clients():
565576
document_intelligence_service=os.getenv("AZURE_DOCUMENTINTELLIGENCE_SERVICE"),
566577
local_pdf_parser=os.getenv("USE_LOCAL_PDF_PARSER", "").lower() == "true",
567578
local_html_parser=os.getenv("USE_LOCAL_HTML_PARSER", "").lower() == "true",
568-
search_images=USE_MULTIMODAL,
579+
use_multimodal=USE_MULTIMODAL,
569580
)
570581
search_info = await setup_search_info(
571582
search_service=AZURE_SEARCH_SERVICE, index_name=AZURE_SEARCH_INDEX, azure_credential=azure_credential
572583
)
573584
text_embeddings_service = setup_embeddings_service(
574585
azure_credential=azure_credential,
575586
openai_host=OPENAI_HOST,
576-
openai_model_name=OPENAI_EMB_MODEL,
577-
openai_service=AZURE_OPENAI_SERVICE,
578-
openai_custom_url=AZURE_OPENAI_CUSTOM_URL,
579-
openai_deployment=AZURE_OPENAI_EMB_DEPLOYMENT,
580-
openai_dimensions=OPENAI_EMB_DIMENSIONS,
581-
openai_api_version=AZURE_OPENAI_API_VERSION,
587+
emb_model_name=OPENAI_EMB_MODEL,
588+
emb_model_dimensions=OPENAI_EMB_DIMENSIONS,
589+
azure_openai_service=AZURE_OPENAI_SERVICE,
590+
azure_openai_custom_url=AZURE_OPENAI_CUSTOM_URL,
591+
azure_openai_deployment=AZURE_OPENAI_EMB_DEPLOYMENT,
592+
azure_openai_api_version=AZURE_OPENAI_API_VERSION,
593+
azure_openai_key=clean_key_if_exists(AZURE_OPENAI_API_KEY_OVERRIDE),
582594
openai_key=clean_key_if_exists(OPENAI_API_KEY),
583595
openai_org=OPENAI_ORGANIZATION,
584596
disable_vectors=os.getenv("USE_VECTORS", "").lower() == "false",
@@ -617,18 +629,17 @@ async def setup_clients():
617629
if not AZURE_OPENAI_SERVICE:
618630
raise ValueError("AZURE_OPENAI_SERVICE must be set when OPENAI_HOST is azure")
619631
endpoint = f"https://{AZURE_OPENAI_SERVICE}.openai.azure.com"
620-
if api_key := os.getenv("AZURE_OPENAI_API_KEY_OVERRIDE"):
632+
if AZURE_OPENAI_API_KEY_OVERRIDE:
621633
current_app.logger.info("AZURE_OPENAI_API_KEY_OVERRIDE found, using as api_key for Azure OpenAI client")
622634
openai_client = AsyncAzureOpenAI(
623-
api_version=AZURE_OPENAI_API_VERSION, azure_endpoint=endpoint, api_key=api_key
635+
api_version=AZURE_OPENAI_API_VERSION, azure_endpoint=endpoint, api_key=AZURE_OPENAI_API_KEY_OVERRIDE
624636
)
625637
else:
626638
current_app.logger.info("Using Azure credential (passwordless authentication) for Azure OpenAI client")
627-
token_provider = get_bearer_token_provider(azure_credential, "https://cognitiveservices.azure.com/.default")
628639
openai_client = AsyncAzureOpenAI(
629640
api_version=AZURE_OPENAI_API_VERSION,
630641
azure_endpoint=endpoint,
631-
azure_ad_token_provider=token_provider,
642+
azure_ad_token_provider=azure_ai_token_provider,
632643
)
633644
elif OPENAI_HOST == "local":
634645
current_app.logger.info("OPENAI_HOST is local, setting up local OpenAI client for OPENAI_BASE_URL with no key")
@@ -673,6 +684,8 @@ async def setup_clients():
673684
current_app.config[CONFIG_CHAT_HISTORY_COSMOS_ENABLED] = USE_CHAT_HISTORY_COSMOS
674685
current_app.config[CONFIG_AGENTIC_RETRIEVAL_ENABLED] = USE_AGENTIC_RETRIEVAL
675686
current_app.config[CONFIG_MULTIMODAL_ENABLED] = USE_MULTIMODAL
687+
current_app.config[CONFIG_RAG_LLM_INPUTS_OVERRIDE] = RAG_LLM_INPUTS_OVERRIDE
688+
current_app.config[CONFIG_RAG_VECTOR_FIELDS_DEFAULT] = RAG_VECTOR_FIELDS_DEFAULT
676689

677690
prompt_manager = PromptyManager()
678691

@@ -699,6 +712,9 @@ async def setup_clients():
699712
query_speller=AZURE_SEARCH_QUERY_SPELLER,
700713
prompt_manager=prompt_manager,
701714
reasoning_effort=OPENAI_REASONING_EFFORT,
715+
vision_endpoint=AZURE_VISION_ENDPOINT,
716+
vision_token_provider=azure_ai_token_provider,
717+
multimodal_enabled=USE_MULTIMODAL,
702718
)
703719

704720
# ChatReadRetrieveReadApproach is used by /chat for multi-turn conversation
@@ -710,6 +726,7 @@ async def setup_clients():
710726
agent_client=agent_client,
711727
openai_client=openai_client,
712728
auth_helper=auth_helper,
729+
images_blob_container_client=image_blob_container_client,
713730
chatgpt_model=OPENAI_CHATGPT_MODEL,
714731
chatgpt_deployment=AZURE_OPENAI_CHATGPT_DEPLOYMENT,
715732
embedding_model=OPENAI_EMB_MODEL,
@@ -723,7 +740,8 @@ async def setup_clients():
723740
prompt_manager=prompt_manager,
724741
reasoning_effort=OPENAI_REASONING_EFFORT,
725742
vision_endpoint=AZURE_VISION_ENDPOINT,
726-
vision_token_provider=token_provider,
743+
vision_token_provider=azure_ai_token_provider,
744+
multimodal_enabled=USE_MULTIMODAL,
727745
)
728746

729747

0 commit comments

Comments
 (0)