diff --git a/app/backend/app.py b/app/backend/app.py index 07d4d64027..927b961f7c 100644 --- a/app/backend/app.py +++ b/app/backend/app.py @@ -8,6 +8,7 @@ from pathlib import Path from typing import Any, AsyncGenerator, Dict, Union, cast +import aiohttp from azure.cognitiveservices.speech import ( ResultReason, SpeechConfig, @@ -133,6 +134,16 @@ async def content_file(path: str, auth_claims: Dict[str, Any]): if AZURE_ENFORCE_ACCESS_CONTROL is set to true, logged in users can only access files they have access to This is also slow and memory hungry. """ + # if the path looks like issue-NNN.html, fetch it from github.com/Azure-samples/azure-search-openai-demo/issues instead + if path.startswith("issue-") and path.endswith(".html"): + issue_id = path.split("-")[1].split(".")[0] + url = f"https://github.com/Azure-Samples/azure-search-openai-demo/issues/{issue_id}" + async with aiohttp.ClientSession() as session: + async with session.get(url) as response: + if response.status != 200: + abort(404) + return await response.text() + # Remove page number from path, filename-1.txt -> filename.txt # This shouldn't typically be necessary as browsers don't send hash fragments to servers if path.find("#page=") > 0: diff --git a/app/backend/approaches/approach.py b/app/backend/approaches/approach.py index 44a1d6380a..c0c9a3fea9 100644 --- a/app/backend/approaches/approach.py +++ b/app/backend/approaches/approach.py @@ -28,6 +28,14 @@ from core.authentication import AuthenticationHelper +@dataclass +class AzureAISearch: + aisearch_query: str + +@dataclass +class GitHubIssueSearch: + github_query: str + @dataclass class Document: id: Optional[str] @@ -204,6 +212,34 @@ async def search( return qualified_documents + + async def search_github_issues(self, github_issue_search: GitHubIssueSearch) -> list[Document]: + async with aiohttp.ClientSession() as session: + async with session.get(f'https://api.github.com/search/issues?q={github_issue_search.github_query}+repo:Azure-samples/azure-search-openai-demo+type:issue&per_page=10') as response: + if response.status == 200: + issues = (await response.json()).get("items", []) + documents = [] + # strip out image markdown from the body + for issue in issues: + body = issue["body"].replace("![", "").replace("](https://", "").replace(")", "") + # turn html_url like https://github.com/Azure-Samples/azure-search-openai-demo/issues/2358 into issue-2358.html + sourcefile = f"issue-{issue.get('number')}.html" + documents.append(Document( + id=issue.get("id"), + content=f"# {issue.get('title')}\n\n{body}", + sourcepage=sourcefile, + sourcefile=sourcefile, + embedding=[], + image_embedding=[], + category=None, + oids=[], + groups=[], + captions=[], + )) + return documents + else: + return [] + def get_sources_content( self, results: List[Document], use_semantic_captions: bool, use_image_citation: bool ) -> list[str]: diff --git a/app/backend/approaches/chatapproach.py b/app/backend/approaches/chatapproach.py index 1dc1b48171..5323f76e8b 100644 --- a/app/backend/approaches/chatapproach.py +++ b/app/backend/approaches/chatapproach.py @@ -1,11 +1,11 @@ import json import re from abc import ABC, abstractmethod -from typing import Any, AsyncGenerator, Optional +from typing import Any, AsyncGenerator, List, Optional, Union from openai.types.chat import ChatCompletion, ChatCompletionMessageParam -from approaches.approach import Approach +from approaches.approach import Approach, AzureAISearch, GitHubIssueSearch class ChatApproach(Approach, ABC): @@ -16,23 +16,27 @@ class ChatApproach(Approach, ABC): async def run_until_final_call(self, messages, overrides, auth_claims, should_stream) -> tuple: pass - def get_search_query(self, chat_completion: ChatCompletion, user_query: str): + def get_search_query(self, chat_completion: ChatCompletion, user_query: str) -> List[Union[AzureAISearch, GitHubIssueSearch]]: response_message = chat_completion.choices[0].message + search_queries = [] if response_message.tool_calls: for tool in response_message.tool_calls: if tool.type != "function": continue function = tool.function - if function.name == "search_sources": + if function.name == "azure_ai_search_docs": arg = json.loads(function.arguments) search_query = arg.get("search_query", self.NO_RESPONSE) if search_query != self.NO_RESPONSE: - return search_query - elif query_text := response_message.content: - if query_text.strip() != self.NO_RESPONSE: - return query_text - return user_query + search_queries.append(AzureAISearch(aisearch_query=search_query)) + elif function.name == "github_search_issues": + arg = json.loads(function.arguments) + search_query = arg.get("search_query", self.NO_RESPONSE) + if search_query != self.NO_RESPONSE: + search_queries.append(GitHubIssueSearch(github_query=search_query)) + + return search_queries def extract_followup_questions(self, content: Optional[str]): if content is None: diff --git a/app/backend/approaches/chatreadretrieveread.py b/app/backend/approaches/chatreadretrieveread.py index 7777b9a741..2caf8cebb0 100644 --- a/app/backend/approaches/chatreadretrieveread.py +++ b/app/backend/approaches/chatreadretrieveread.py @@ -12,7 +12,7 @@ from openai_messages_token_helper import build_messages, get_token_limit from approaches.approach import ThoughtStep -from approaches.chatapproach import ChatApproach +from approaches.chatapproach import ChatApproach, GitHubIssueSearch from approaches.promptmanager import PromptManager from core.authentication import AuthenticationHelper @@ -124,33 +124,40 @@ async def run_until_final_call( max_tokens=query_response_token_limit, # Setting too low risks malformed JSON, setting too high may affect performance n=1, tools=tools, + tool_choice="auto", seed=seed, ) - query_text = self.get_search_query(chat_completion, original_user_query) - - # STEP 2: Retrieve relevant documents from the search index with the GPT optimized query - - # If retrieval mode includes vectors, compute an embedding for the query - vectors: list[VectorQuery] = [] - if use_vector_search: - vectors.append(await self.compute_text_embedding(query_text)) - - results = await self.search( - top, - query_text, - filter, - vectors, - use_text_search, - use_vector_search, - use_semantic_ranker, - use_semantic_captions, - minimum_search_score, - minimum_reranker_score, - ) + search_queries = self.get_search_query(chat_completion, original_user_query) + results = [] + + for query in search_queries: + if isinstance(query, GitHubIssueSearch): + # Handle GitHub issue search + results.extend(await self.search_github_issues(query)) + else: + # Handle regular AI search query + + vectors: list[VectorQuery] = [] + if use_vector_search: + vectors.append(await self.compute_text_embedding(query.aisearch_query)) + + results.extend(await self.search( + top, + query.aisearch_query, + filter, + vectors, + use_text_search, + use_vector_search, + use_semantic_ranker, + use_semantic_captions, + minimum_search_score, + minimum_reranker_score, + )) # STEP 3: Generate a contextual and content specific answer using the search results and chat history text_sources = self.get_sources_content(results, use_semantic_captions, use_image_citation=False) + rendered_answer_prompt = self.prompt_manager.render_prompt( self.answer_prompt, self.get_system_prompt_variables(overrides.get("prompt_template")) @@ -186,7 +193,7 @@ async def run_until_final_call( ), ThoughtStep( "Search using generated search query", - query_text, + search_queries, { "use_semantic_captions": use_semantic_captions, "use_semantic_ranker": use_semantic_ranker, @@ -222,4 +229,5 @@ async def run_until_final_call( stream=should_stream, seed=seed, ) + return (extra_info, chat_coroutine) diff --git a/app/backend/approaches/prompts/chat_query_rewrite.prompty b/app/backend/approaches/prompts/chat_query_rewrite.prompty index 7738a85cd7..7809bd200e 100644 --- a/app/backend/approaches/prompts/chat_query_rewrite.prompty +++ b/app/backend/approaches/prompts/chat_query_rewrite.prompty @@ -14,26 +14,14 @@ sample: content: "The Northwind Health Plus plan includes coverage for emergency services, mental health and substance abuse coverage, and out-of-network services, which are not included in the Northwind Standard plan. [Benefit_Options.pdf#page=3]" --- system: -Below is a history of the conversation so far, and a new question asked by the user that needs to be answered by searching in a knowledge base. -You have access to Azure AI Search index with 100's of documents. -Generate a search query based on the conversation and the new question. -Do not include cited source filenames and document names e.g. info.txt or doc.pdf in the search query terms. -Do not include any text inside [] or <<>> in the search query terms. -Do not include any special characters like '+'. -If the question is not in English, translate the question to English before generating the search query. -If you cannot generate a search query, return just the number 0. - -user: -(EXAMPLE) How did crypto do last year? +Below is a history of the conversation so far, and a new question asked by the user about the azure-search-openai-demo open source project. +You have access to an Azure AI Search index with the documentation or to the GitHub issue tracker for the project documentation. -assistant: -Summarize Cryptocurrency Market Dynamics from last year - -user: -(EXAMPLE) What are my health plans? +Based on the conversation and the new question, suggest the optimal search query for the AI Search index or GitHub issue tracker. +If the question is not in English, translate the question to English before generating the search query. -assistant: -Show available health plans +If you cannot generate a search query for either AI Search or GitHub, return just the number 0. +If you think that it would help to search both, then recommend both functions be called. {% for message in past_messages %} {{ message["role"] }}: @@ -41,4 +29,4 @@ Show available health plans {% endfor %} user: -Generate search query for: {{ user_query }} +{{ user_query }} diff --git a/app/backend/approaches/prompts/chat_query_rewrite_tools.json b/app/backend/approaches/prompts/chat_query_rewrite_tools.json index cf1743483c..6c3216479c 100644 --- a/app/backend/approaches/prompts/chat_query_rewrite_tools.json +++ b/app/backend/approaches/prompts/chat_query_rewrite_tools.json @@ -1,17 +1,40 @@ -[{ +[ + { "type": "function", "function": { - "name": "search_sources", - "description": "Retrieve sources from the Azure AI Search index", - "parameters": { - "type": "object", - "properties": { - "search_query": { - "type": "string", - "description": "Query string to retrieve documents from azure search eg: 'Health care plan'" - } - }, - "required": ["search_query"] - } + "name": "azure_ai_search_docs", + "description": "Retrieve sources from the Azure AI Search index. Use this function for questions like 'does the repo support user-based access control?'", + "parameters": { + "type": "object", + "properties": { + "search_query": { + "type": "string", + "description": "Query string to retrieve documents from azure search eg: 'data access control'. Do not include cited source filenames and document names e.g. info.txt or doc.pdf in the search query terms. Do not include any text inside [] or <<>> in the search query terms. Do not include any special characters like '+'." + } + }, + "required": ["search_query"], + "additionalProperties": false + }, + "strict": true } -}] + }, + { + "type": "function", + "function": { + "name": "github_search_issues", + "description": "Retrieve issues from the azure-search-openai-demo issue tracker. Use this function for questions like 'what are the top errors with deployment?'", + "parameters": { + "type": "object", + "properties": { + "search_query": { + "type": "string", + "description": "Query string to retrieve issues from github eg: 'Deployment failure' - should only contain the search terms, does not need 'issue' or 'issues' in the search query." + } + }, + "required": ["search_query"], + "additionalProperties": false + }, + "strict": true + } + } +] diff --git a/app/frontend/src/components/AnalysisPanel/ThoughtProcess.tsx b/app/frontend/src/components/AnalysisPanel/ThoughtProcess.tsx index 4c3a62617e..b604f490c8 100644 --- a/app/frontend/src/components/AnalysisPanel/ThoughtProcess.tsx +++ b/app/frontend/src/components/AnalysisPanel/ThoughtProcess.tsx @@ -28,7 +28,7 @@ export const ThoughtProcess = ({ thoughts }: Props) => { ))} - {Array.isArray(t.description) ? ( + {Array.isArray(t.description) || typeof t.description === "object" ? ( {JSON.stringify(t.description, null, 2)} diff --git a/app/frontend/src/locales/en/translation.json b/app/frontend/src/locales/en/translation.json index 07f657da8b..66fecde2be 100644 --- a/app/frontend/src/locales/en/translation.json +++ b/app/frontend/src/locales/en/translation.json @@ -37,9 +37,9 @@ "chatEmptyStateTitle": "Chat with your data", "chatEmptyStateSubtitle": "Ask anything or try an example", "defaultExamples": { - "1": "What is included in my Northwind Health Plus plan that is not in standard?", - "2": "What happens in a performance review?", - "3": "What does a Product Manager do?", + "1": "summarize issues with manageacls.py?", + "2": "how to enable user-based access control?", + "3": "Summarize the available documentation and reported user issues around manageacls.py", "placeholder": "Type a new question (e.g. does my plan cover annual eye exams?)" }, "askTitle": "Ask your data", diff --git a/app/frontend/src/pages/chat/Chat.tsx b/app/frontend/src/pages/chat/Chat.tsx index e3c0cfd77f..838e704fec 100644 --- a/app/frontend/src/pages/chat/Chat.tsx +++ b/app/frontend/src/pages/chat/Chat.tsx @@ -45,7 +45,7 @@ const Chat = () => { const [seed, setSeed] = useState(null); const [minimumRerankerScore, setMinimumRerankerScore] = useState(0); const [minimumSearchScore, setMinimumSearchScore] = useState(0); - const [retrieveCount, setRetrieveCount] = useState(3); + const [retrieveCount, setRetrieveCount] = useState(5); const [retrievalMode, setRetrievalMode] = useState(RetrievalMode.Hybrid); const [useSemanticRanker, setUseSemanticRanker] = useState(true); const [shouldStream, setShouldStream] = useState(true); diff --git a/convertdocs.py b/convertdocs.py new file mode 100644 index 0000000000..b427e0fbe0 --- /dev/null +++ b/convertdocs.py @@ -0,0 +1,25 @@ +import os + +import pypandoc + + +def convert_md_to_html(directory): + # Ensure the output directory exists + html_output_dir = os.path.join(directory, 'html') + os.makedirs(html_output_dir, exist_ok=True) + + # Iterate over all files in the directory + for filename in os.listdir(directory): + if filename.endswith('.md'): + filepath = os.path.join(directory, filename) + base_filename = os.path.splitext(filename)[0] + + # Convert to HTML + html_output_path = os.path.join(html_output_dir, f'{base_filename}.html') + pypandoc.convert_file(filepath, 'html', outputfile=html_output_path) + print(f'Converted {filename} to {html_output_path}') + +if __name__ == '__main__': + # Specify the directory containing the Markdown files + directory = '.' + convert_md_to_html(directory) \ No newline at end of file diff --git a/data/README.html b/data/README.html new file mode 100644 index 0000000000..f62f7513ca --- /dev/null +++ b/data/README.html @@ -0,0 +1,35 @@ +

Additional documentation

+

Consult the main README for general +information about the project. These are advanced topics that are not +necessary for a basic deployment.

+ diff --git a/data/appservice.html b/data/appservice.html new file mode 100644 index 0000000000..baaf22fcda --- /dev/null +++ b/data/appservice.html @@ -0,0 +1,696 @@ +

RAG chat: Debugging +the app on App Service

+

When you run azd up or azd deploy, it +deploys your application to App Service, and displays the deployed +endpoint in the console.

+

If you encounter an error with that deployed app, you can debug the +deployment using the tips below.

+ +

Debugging failed +Azure App Service deployments

+

If you see a 500 error upon visiting your app after deployment, +something went wrong during either the deployment or the server start +script.

+

We recommend always waiting 10 minutes, to give the server time to +properly startup.

+

If you still see a 500 error after 10 minutes:

+
    +
  1. Check the +deployment logs
  2. +
  3. Look for errors in the +app logs
  4. +
  5. Look for errors in +Azure Monitor
  6. +
+

Checking the deployment +logs for errors

+

In the Azure portal, navigate to your App Service.

+

Select Deployment Center from the side navigation menu, then +select Logs. You should see a timestamped list of recent +deploys:

+
+ + +
+

Check whether the status of the most recent deploy is “Success +(Active)” or “Failed”. If it’s success, the deployment logs might still +reveal issues, and if it’s failed, the logs should certainly reveal the +issue.

+

Click the commit ID to open the logs for the most recent deploy. +First scroll down to see if any errors or warnings are reported at the +end. This is what you’ll hopefully see if all went well:

+
+ + +
+

Now scroll back up to find the timestamp with the label “Running oryx +build”. Oryx is the open +source tool that builds apps for App Service, Functions, and other +platforms, across all the supported MS languages. Click the Show +logs link next to that label. That will pop open detailed logs at +the bottom. Scroll down.

+
+ +Expand to see the logs for a successful Oryx build for the application. + +
Command: oryx build /tmp/zipdeploy/extracted -o /home/site/wwwroot --platform python --platform-version 3.11 -p virtualenv_name=antenv --log-file /tmp/build-debug.log  -i /tmp/8dc28dad0e10acb --compress-destination-dir | tee /tmp/oryx-build.log
+Operation performed by Microsoft Oryx, https://github.com/Microsoft/Oryx
+You can report issues at https://github.com/Microsoft/Oryx/issues
+
+Oryx Version: 0.2.20230508.1, Commit: 7fe2bf39b357dd68572b438a85ca50b5ecfb4592, ReleaseTagName: 20230508.1
+
+Build Operation ID: 7440a33100749a32
+Repository Commit : b09bff8b-da36-4d70-9e2f-c7b9131d85bc
+OS Type           : bullseye
+Image Type        : githubactions
+
+Detecting platforms...
+Detected following platforms:
+  python: 3.11.7
+Version '3.11.7' of platform 'python' is not installed. Generating script to install it...
+
+Using intermediate directory '/tmp/8dc28dad0e10acb'.
+
+Copying files to the intermediate directory...
+Done in 27 sec(s).
+
+Source directory     : /tmp/8dc28dad0e10acb
+Destination directory: /home/site/wwwroot
+
+
+Downloading and extracting 'python' version '3.11.7' to '/tmp/oryx/platforms/python/3.11.7'...
+Detected image debian flavor: bullseye.
+Downloaded in 5 sec(s).
+Verifying checksum...
+Extracting contents...
+performing sha512 checksum for: python...
+Done in 48 sec(s).
+
+image detector file exists, platform is python..
+OS detector file exists, OS is bullseye..
+Python Version: /tmp/oryx/platforms/python/3.11.7/bin/python3.11
+Creating directory for command manifest file if it does not exist
+Removing existing manifest file
+Python Virtual Environment: antenv
+Creating virtual environment...
+Activating virtual environment...
+Running pip install...
+[19:21:31+0000] Collecting aiofiles==23.2.1 (from -r requirements.txt (line 7))
+[19:21:31+0000]   Obtaining dependency information for aiofiles==23.2.1 from https://files.pythonhosted.org/packages/c5/19/5af6804c4cc0fed83f47bff6e413a98a36618e7d40185cd36e69737f3b0e/aiofiles-23.2.1-py3-none-any.whl.metadata
+[19:21:31+0000]   Downloading aiofiles-23.2.1-py3-none-any.whl.metadata (9.7 kB)
+[19:21:35+0000] Collecting aiohttp==3.9.3 (from -r requirements.txt (line 9))
+[19:21:35+0000]   Obtaining dependency information for aiohttp==3.9.3 from https://files.pythonhosted.org/packages/84/bb/74c9f32e1a76fab04b54ed6cd4b0dc4a07bd9dc6f3bb37f630149a9c3068/aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata
+[19:21:35+0000]   Downloading aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (7.4 kB)
+[19:21:35+0000] Collecting aiosignal==1.3.1 (from -r requirements.txt (line 11))
+[19:21:35+0000]   Downloading aiosignal-1.3.1-py3-none-any.whl (7.6 kB)
+[19:21:36+0000] Collecting annotated-types==0.6.0 (from -r requirements.txt (line 13))
+[19:21:36+0000]   Obtaining dependency information for annotated-types==0.6.0 from https://files.pythonhosted.org/packages/28/78/d31230046e58c207284c6b2c4e8d96e6d3cb4e52354721b944d3e1ee4aa5/annotated_types-0.6.0-py3-none-any.whl.metadata
+[19:21:36+0000]   Downloading annotated_types-0.6.0-py3-none-any.whl.metadata (12 kB)
+[19:21:36+0000] Collecting anyio==4.2.0 (from -r requirements.txt (line 15))
+[19:21:36+0000]   Obtaining dependency information for anyio==4.2.0 from https://files.pythonhosted.org/packages/bf/cd/d6d9bb1dadf73e7af02d18225cbd2c93f8552e13130484f1c8dcfece292b/anyio-4.2.0-py3-none-any.whl.metadata
+[19:21:36+0000]   Downloading anyio-4.2.0-py3-none-any.whl.metadata (4.6 kB)
+[19:21:36+0000] Collecting asgiref==3.7.2 (from -r requirements.txt (line 19))
+[19:21:36+0000]   Obtaining dependency information for asgiref==3.7.2 from https://files.pythonhosted.org/packages/9b/80/b9051a4a07ad231558fcd8ffc89232711b4e618c15cb7a392a17384bbeef/asgiref-3.7.2-py3-none-any.whl.metadata
+[19:21:36+0000]   Downloading asgiref-3.7.2-py3-none-any.whl.metadata (9.2 kB)
+[19:21:36+0000] Collecting attrs==23.2.0 (from -r requirements.txt (line 21))
+[19:21:36+0000]   Obtaining dependency information for attrs==23.2.0 from https://files.pythonhosted.org/packages/e0/44/827b2a91a5816512fcaf3cc4ebc465ccd5d598c45cefa6703fcf4a79018f/attrs-23.2.0-py3-none-any.whl.metadata
+[19:21:36+0000]   Downloading attrs-23.2.0-py3-none-any.whl.metadata (9.5 kB)
+[19:21:36+0000] Collecting azure-common==1.1.28 (from -r requirements.txt (line 23))
+[19:21:36+0000]   Downloading azure_common-1.1.28-py2.py3-none-any.whl (14 kB)
+[19:21:36+0000] Collecting azure-core==1.29.7 (from -r requirements.txt (line 27))
+[19:21:36+0000]   Obtaining dependency information for azure-core==1.29.7 from https://files.pythonhosted.org/packages/ff/29/dbc7182bc207530c7b5858d59f429158465f878845d64a038afc1aa61e35/azure_core-1.29.7-py3-none-any.whl.metadata
+[19:21:36+0000]   Downloading azure_core-1.29.7-py3-none-any.whl.metadata (36 kB)
+[19:21:36+0000] Collecting azure-core-tracing-opentelemetry==1.0.0b11 (from -r requirements.txt (line 37))
+[19:21:36+0000]   Obtaining dependency information for azure-core-tracing-opentelemetry==1.0.0b11 from https://files.pythonhosted.org/packages/e6/6e/3ef6dfba8e0faa4692caa6d103c721ccba6ac37a24744848a3a10bb3fe89/azure_core_tracing_opentelemetry-1.0.0b11-py3-none-any.whl.metadata
+[19:21:36+0000]   Downloading azure_core_tracing_opentelemetry-1.0.0b11-py3-none-any.whl.metadata (8.5 kB)
+[19:21:37+0000] Collecting azure-identity==1.15.0 (from -r requirements.txt (line 39))
+[19:21:37+0000]   Obtaining dependency information for azure-identity==1.15.0 from https://files.pythonhosted.org/packages/30/10/5dbf755b368d10a28d55b06ac1f12512a13e88874a23db82defdea9a8cd9/azure_identity-1.15.0-py3-none-any.whl.metadata
+[19:21:37+0000]   Downloading azure_identity-1.15.0-py3-none-any.whl.metadata (75 kB)
+[19:21:37+0000]      ━━━━━━━━━━━━━━━━��━━━━━━━━━━━━━━━━━━━━━━━ 75.4/75.4 kB 6.2 MB/s eta 0:00:00
+[19:21:37+0000] Collecting azure-keyvault-secrets==4.7.0 (from -r requirements.txt (line 41))
+[19:21:37+0000]   Downloading azure_keyvault_secrets-4.7.0-py3-none-any.whl (348 kB)
+[19:21:37+0000]      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 348.6/348.6 kB 19.6 MB/s eta 0:00:00
+[19:21:37+0000] Collecting azure-monitor-opentelemetry==1.2.0 (from -r requirements.txt (line 43))
+[19:21:37+0000]   Obtaining dependency information for azure-monitor-opentelemetry==1.2.0 from https://files.pythonhosted.org/packages/66/72/5a6bac11b8f3bd60825f19c144c4c770c46951165f8ee5fc10ab3eaadf59/azure_monitor_opentelemetry-1.2.0-py3-none-any.whl.metadata
+[19:21:37+0000]   Downloading azure_monitor_opentelemetry-1.2.0-py3-none-any.whl.metadata (19 kB)
+[19:21:37+0000] Collecting azure-monitor-opentelemetry-exporter==1.0.0b21 (from -r requirements.txt (line 45))
+[19:21:37+0000]   Obtaining dependency information for azure-monitor-opentelemetry-exporter==1.0.0b21 from https://files.pythonhosted.org/packages/4a/0d/18cb0da98b49c9a6724f6cae46a7e59b8325cda476bde13b64404a428ae8/azure_monitor_opentelemetry_exporter-1.0.0b21-py2.py3-none-any.whl.metadata
+[19:21:37+0000]   Downloading azure_monitor_opentelemetry_exporter-1.0.0b21-py2.py3-none-any.whl.metadata (31 kB)
+[19:21:37+0000] Collecting azure-search-documents==11.6.0b1 (from -r requirements.txt (line 47))
+[19:21:37+0000]   Obtaining dependency information for azure-search-documents==11.6.0b1 from https://files.pythonhosted.org/packages/7c/f6/b138d9a252f80db69c052c65410bc972dca375e29c71c472e27d0bae327d/azure_search_documents-11.6.0b1-py3-none-any.whl.metadata
+[19:21:37+0000]   Downloading azure_search_documents-11.6.0b1-py3-none-any.whl.metadata (23 kB)
+[19:21:37+0000] Collecting azure-storage-blob==12.19.0 (from -r requirements.txt (line 49))
+[19:21:37+0000]   Obtaining dependency information for azure-storage-blob==12.19.0 from https://files.pythonhosted.org/packages/f6/82/24b0d7cf67ea63af86f11092756b8fe2adc1d55323241dc4107f5f5748e2/azure_storage_blob-12.19.0-py3-none-any.whl.metadata
+[19:21:37+0000]   Downloading azure_storage_blob-12.19.0-py3-none-any.whl.metadata (26 kB)
+[19:21:37+0000] Collecting blinker==1.7.0 (from -r requirements.txt (line 51))
+[19:21:37+0000]   Obtaining dependency information for blinker==1.7.0 from https://files.pythonhosted.org/packages/fa/2a/7f3714cbc6356a0efec525ce7a0613d581072ed6eb53eb7b9754f33db807/blinker-1.7.0-py3-none-any.whl.metadata
+[19:21:37+0000]   Downloading blinker-1.7.0-py3-none-any.whl.metadata (1.9 kB)
+[19:21:37+0000] Collecting certifi==2023.11.17 (from -r requirements.txt (line 55))
+[19:21:37+0000]   Obtaining dependency information for certifi==2023.11.17 from https://files.pythonhosted.org/packages/64/62/428ef076be88fa93716b576e4a01f919d25968913e817077a386fcbe4f42/certifi-2023.11.17-py3-none-any.whl.metadata
+[19:21:37+0000]   Downloading certifi-2023.11.17-py3-none-any.whl.metadata (2.2 kB)
+[19:21:39+0000] Collecting cffi==1.16.0 (from -r requirements.txt (line 61))
+[19:21:39+0000]   Obtaining dependency information for cffi==1.16.0 from https://files.pythonhosted.org/packages/9b/89/a31c81e36bbb793581d8bba4406a8aac4ba84b2559301c44eef81f4cf5df/cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata
+[19:21:39+0000]   Downloading cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (1.5 kB)
+[19:21:40+0000] Collecting charset-normalizer==3.3.2 (from -r requirements.txt (line 63))
+[19:21:40+0000]   Obtaining dependency information for charset-normalizer==3.3.2 from https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata
+[19:21:40+0000]   Downloading charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (33 kB)
+[19:21:40+0000] Collecting click==8.1.7 (from -r requirements.txt (line 65))
+[19:21:40+0000]   Obtaining dependency information for click==8.1.7 from https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl.metadata
+[19:21:40+0000]   Downloading click-8.1.7-py3-none-any.whl.metadata (3.0 kB)
+[19:21:43+0000] Collecting cryptography==42.0.1 (from -r requirements.txt (line 70))
+[19:21:43+0000]   Obtaining dependency information for cryptography==42.0.1 from https://files.pythonhosted.org/packages/f8/46/2776ca9b602f79633fdf69824b5e18c94f2e0c5f09a94fc69e5b0887c14d/cryptography-42.0.1-cp39-abi3-manylinux_2_28_x86_64.whl.metadata
+[19:21:43+0000]   Downloading cryptography-42.0.1-cp39-abi3-manylinux_2_28_x86_64.whl.metadata (5.3 kB)
+[19:21:43+0000] Collecting deprecated==1.2.14 (from -r requirements.txt (line 78))
+[19:21:43+0000]   Obtaining dependency information for deprecated==1.2.14 from https://files.pythonhosted.org/packages/20/8d/778b7d51b981a96554f29136cd59ca7880bf58094338085bcf2a979a0e6a/Deprecated-1.2.14-py2.py3-none-any.whl.metadata
+[19:21:43+0000]   Downloading Deprecated-1.2.14-py2.py3-none-any.whl.metadata (5.4 kB)
+[19:21:43+0000] Collecting distro==1.9.0 (from -r requirements.txt (line 80))
+[19:21:43+0000]   Obtaining dependency information for distro==1.9.0 from https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl.metadata
+[19:21:44+0000]   Downloading distro-1.9.0-py3-none-any.whl.metadata (6.8 kB)
+[19:21:44+0000] Collecting ecdsa==0.18.0 (from -r requirements.txt (line 82))
+[19:21:44+0000]   Downloading ecdsa-0.18.0-py2.py3-none-any.whl (142 kB)
+[19:21:44+0000]      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 142.9/142.9 kB 3.7 MB/s eta 0:00:00
+[19:21:44+0000] Collecting fixedint==0.1.6 (from -r requirements.txt (line 84))
+[19:21:44+0000]   Downloading fixedint-0.1.6-py3-none-any.whl (12 kB)
+[19:21:45+0000] Collecting flask==3.0.1 (from -r requirements.txt (line 86))
+[19:21:45+0000]   Obtaining dependency information for flask==3.0.1 from https://files.pythonhosted.org/packages/bd/0e/63738e88e981ae57c23bad6c499898314a1110a4141f77d7bd929b552fb4/flask-3.0.1-py3-none-any.whl.metadata
+[19:21:45+0000]   Downloading flask-3.0.1-py3-none-any.whl.metadata (3.6 kB)
+[19:21:47+0000] Collecting frozenlist==1.4.1 (from -r requirements.txt (line 88))
+[19:21:47+0000]   Obtaining dependency information for frozenlist==1.4.1 from https://files.pythonhosted.org/packages/b3/c9/0bc5ee7e1f5cc7358ab67da0b7dfe60fbd05c254cea5c6108e7d1ae28c63/frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata
+[19:21:47+0000]   Downloading frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (12 kB)
+[19:21:47+0000] Collecting h11==0.14.0 (from -r requirements.txt (line 92))
+[19:21:47+0000]   Downloading h11-0.14.0-py3-none-any.whl (58 kB)
+[19:21:47+0000]      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 58.3/58.3 kB 2.5 MB/s eta 0:00:00
+[19:21:48+0000] Collecting h2==4.1.0 (from -r requirements.txt (line 98))
+[19:21:48+0000]   Downloading h2-4.1.0-py3-none-any.whl (57 kB)
+[19:21:48+0000]      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 57.5/57.5 kB 627.9 kB/s eta 0:00:00
+[19:21:48+0000] Collecting hpack==4.0.0 (from -r requirements.txt (line 100))
+[19:21:48+0000]   Downloading hpack-4.0.0-py3-none-any.whl (32 kB)
+[19:21:49+0000] Collecting httpcore==1.0.2 (from -r requirements.txt (line 102))
+[19:21:49+0000]   Obtaining dependency information for httpcore==1.0.2 from https://files.pythonhosted.org/packages/56/ba/78b0a99c4da0ff8b0f59defa2f13ca4668189b134bd9840b6202a93d9a0f/httpcore-1.0.2-py3-none-any.whl.metadata
+[19:21:49+0000]   Downloading httpcore-1.0.2-py3-none-any.whl.metadata (20 kB)
+[19:21:50+0000] Collecting httpx==0.26.0 (from -r requirements.txt (line 104))
+[19:21:50+0000]   Obtaining dependency information for httpx==0.26.0 from https://files.pythonhosted.org/packages/39/9b/4937d841aee9c2c8102d9a4eeb800c7dad25386caabb4a1bf5010df81a57/httpx-0.26.0-py3-none-any.whl.metadata
+[19:21:50+0000]   Downloading httpx-0.26.0-py3-none-any.whl.metadata (7.6 kB)
+[19:21:50+0000] Collecting hypercorn==0.16.0 (from -r requirements.txt (line 106))
+[19:21:50+0000]   Obtaining dependency information for hypercorn==0.16.0 from https://files.pythonhosted.org/packages/17/9e/700d764316399c20fbe8e98c6fff903b5d3f950043cc2fcbd0831a42c953/hypercorn-0.16.0-py3-none-any.whl.metadata
+[19:21:50+0000]   Downloading hypercorn-0.16.0-py3-none-any.whl.metadata (5.4 kB)
+[19:21:50+0000] Collecting hyperframe==6.0.1 (from -r requirements.txt (line 108))
+[19:21:50+0000]   Downloading hyperframe-6.0.1-py3-none-any.whl (12 kB)
+[19:21:51+0000] Collecting idna==3.6 (from -r requirements.txt (line 110))
+[19:21:51+0000]   Obtaining dependency information for idna==3.6 from https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl.metadata
+[19:21:51+0000]   Downloading idna-3.6-py3-none-any.whl.metadata (9.9 kB)
+[19:21:51+0000] Collecting importlib-metadata==6.11.0 (from -r requirements.txt (line 116))
+[19:21:51+0000]   Obtaining dependency information for importlib-metadata==6.11.0 from https://files.pythonhosted.org/packages/59/9b/ecce94952ab5ea74c31dcf9ccf78ccd484eebebef06019bf8cb579ab4519/importlib_metadata-6.11.0-py3-none-any.whl.metadata
+[19:21:51+0000]   Downloading importlib_metadata-6.11.0-py3-none-any.whl.metadata (4.9 kB)
+[19:21:52+0000] Collecting isodate==0.6.1 (from -r requirements.txt (line 118))
+[19:21:52+0000]   Downloading isodate-0.6.1-py2.py3-none-any.whl (41 kB)
+[19:21:52+0000]      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 41.7/41.7 kB 1.8 MB/s eta 0:00:00
+[19:21:52+0000] Collecting itsdangerous==2.1.2 (from -r requirements.txt (line 124))
+[19:21:52+0000]   Downloading itsdangerous-2.1.2-py3-none-any.whl (15 kB)
+[19:21:52+0000] Collecting jinja2==3.1.3 (from -r requirements.txt (line 128))
+[19:21:52+0000]   Obtaining dependency information for jinja2==3.1.3 from https://files.pythonhosted.org/packages/30/6d/6de6be2d02603ab56e72997708809e8a5b0fbfee080735109b40a3564843/Jinja2-3.1.3-py3-none-any.whl.metadata
+[19:21:52+0000]   Downloading Jinja2-3.1.3-py3-none-any.whl.metadata (3.3 kB)
+[19:21:53+0000] Collecting markupsafe==2.1.4 (from -r requirements.txt (line 132))
+[19:21:53+0000]   Obtaining dependency information for markupsafe==2.1.4 from https://files.pythonhosted.org/packages/d3/0a/c6dfffacc5a9a17c97019cb7cbec67e5abfb65c59a58ecba270fa224f88d/MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata
+[19:21:53+0000]   Downloading MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (3.0 kB)
+[19:21:53+0000] Collecting msal==1.26.0 (from -r requirements.txt (line 137))
+[19:21:53+0000]   Obtaining dependency information for msal==1.26.0 from https://files.pythonhosted.org/packages/b7/61/2756b963e84db6946e4b93a8e288595106286fc11c7129fcb869267ead67/msal-1.26.0-py2.py3-none-any.whl.metadata
+[19:21:53+0000]   Downloading msal-1.26.0-py2.py3-none-any.whl.metadata (11 kB)
+[19:21:54+0000] Collecting msal-extensions==1.1.0 (from -r requirements.txt (line 142))
+[19:21:54+0000]   Obtaining dependency information for msal-extensions==1.1.0 from https://files.pythonhosted.org/packages/78/8d/ecd0eb93196f25c722ba1b923fd54d190366feccfa5b159d48dacf2b1fee/msal_extensions-1.1.0-py3-none-any.whl.metadata
+[19:21:54+0000]   Downloading msal_extensions-1.1.0-py3-none-any.whl.metadata (7.7 kB)
+[19:21:54+0000] Collecting msrest==0.7.1 (from -r requirements.txt (line 144))
+[19:21:54+0000]   Downloading msrest-0.7.1-py3-none-any.whl (85 kB)
+[19:21:54+0000]      ━━━━━━━━━━━━━━━━━━━��━━━━━━━━━━━━━━━━━━━━ 85.4/85.4 kB 6.2 MB/s eta 0:00:00
+[19:21:59+0000] Collecting multidict==6.0.4 (from -r requirements.txt (line 146))
+[19:21:59+0000]   Downloading multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (117 kB)
+[19:21:59+0000]      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 117.4/117.4 kB 2.2 MB/s eta 0:00:00
+[19:22:05+0000] Collecting numpy==1.26.3 (from -r requirements.txt (line 150))
+[19:22:05+0000]   Obtaining dependency information for numpy==1.26.3 from https://files.pythonhosted.org/packages/5a/62/007b63f916aca1d27f5fede933fda3315d931ff9b2c28b9c2cf388cd8edb/numpy-1.26.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata
+[19:22:05+0000]   Downloading numpy-1.26.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (61 kB)
+[19:22:05+0000]      ━━━━━━━━━━━━━━━━━━━━━━━━━���━━━━━━━━━━━━━━ 61.2/61.2 kB 5.6 MB/s eta 0:00:00
+[19:22:05+0000] Collecting oauthlib==3.2.2 (from -r requirements.txt (line 155))
+[19:22:05+0000]   Downloading oauthlib-3.2.2-py3-none-any.whl (151 kB)
+[19:22:05+0000]      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 151.7/151.7 kB 7.9 MB/s eta 0:00:00
+[19:22:06+0000] Collecting openai[datalib]==1.10.0 (from -r requirements.txt (line 157))
+[19:22:06+0000]   Obtaining dependency information for openai[datalib]==1.10.0 from https://files.pythonhosted.org/packages/46/85/8681046cd9cc13a36ac76e4a1b047338c90dbeab2e9b14fb36de7f314c93/openai-1.10.0-py3-none-any.whl.metadata
+[19:22:06+0000]   Downloading openai-1.10.0-py3-none-any.whl.metadata (18 kB)
+[19:22:06+0000] Collecting opentelemetry-api==1.22.0 (from -r requirements.txt (line 159))
+[19:22:06+0000]   Obtaining dependency information for opentelemetry-api==1.22.0 from https://files.pythonhosted.org/packages/fc/2e/a8509051aa446783e24ee03d74bd268c07d5d25a8d48686cfcf3429d5d32/opentelemetry_api-1.22.0-py3-none-any.whl.metadata
+[19:22:06+0000]   Downloading opentelemetry_api-1.22.0-py3-none-any.whl.metadata (1.4 kB)
+[19:22:07+0000] Collecting opentelemetry-instrumentation==0.43b0 (from -r requirements.txt (line 177))
+[19:22:07+0000]   Obtaining dependency information for opentelemetry-instrumentation==0.43b0 from https://files.pythonhosted.org/packages/91/f0/4a9f7cbcc697273d847040a9e4f98ceb07b642e1fe5fed56a0fb6b567665/opentelemetry_instrumentation-0.43b0-py3-none-any.whl.metadata
+[19:22:07+0000]   Downloading opentelemetry_instrumentation-0.43b0-py3-none-any.whl.metadata (5.9 kB)
+[19:22:08+0000] Collecting opentelemetry-instrumentation-aiohttp-client==0.43b0 (from -r requirements.txt (line 191))
+[19:22:08+0000]   Obtaining dependency information for opentelemetry-instrumentation-aiohttp-client==0.43b0 from https://files.pythonhosted.org/packages/23/75/ce33cd15bc706b1e170e5ce65235a8418e3332ad543419b902a9d24f079f/opentelemetry_instrumentation_aiohttp_client-0.43b0-py3-none-any.whl.metadata
+[19:22:08+0000]   Downloading opentelemetry_instrumentation_aiohttp_client-0.43b0-py3-none-any.whl.metadata (2.2 kB)
+[19:22:08+0000] Collecting opentelemetry-instrumentation-asgi==0.43b0 (from -r requirements.txt (line 193))
+[19:22:08+0000]   Obtaining dependency information for opentelemetry-instrumentation-asgi==0.43b0 from https://files.pythonhosted.org/packages/71/cd/a0456c8e4441d9ef5b412a3ffdf97629a81adeb331f8bb645df4f9153dd8/opentelemetry_instrumentation_asgi-0.43b0-py3-none-any.whl.metadata
+[19:22:08+0000]   Downloading opentelemetry_instrumentation_asgi-0.43b0-py3-none-any.whl.metadata (2.1 kB)
+[19:22:08+0000] Collecting opentelemetry-instrumentation-dbapi==0.43b0 (from -r requirements.txt (line 197))
+[19:22:08+0000]   Obtaining dependency information for opentelemetry-instrumentation-dbapi==0.43b0 from https://files.pythonhosted.org/packages/6d/96/f46bffb40e71f0abd82ad24ecfa7a8e29b6abca631f7d049d80afee83ff9/opentelemetry_instrumentation_dbapi-0.43b0-py3-none-any.whl.metadata
+[19:22:08+0000]   Downloading opentelemetry_instrumentation_dbapi-0.43b0-py3-none-any.whl.metadata (1.9 kB)
+[19:22:09+0000] Collecting opentelemetry-instrumentation-django==0.43b0 (from -r requirements.txt (line 199))
+[19:22:09+0000]   Obtaining dependency information for opentelemetry-instrumentation-django==0.43b0 from https://files.pythonhosted.org/packages/11/66/a6b5aadb04b5daf002fcbe97bb6bc83416c53b81a608de0e9ad886c59643/opentelemetry_instrumentation_django-0.43b0-py3-none-any.whl.metadata
+[19:22:09+0000]   Downloading opentelemetry_instrumentation_django-0.43b0-py3-none-any.whl.metadata (2.3 kB)
+[19:22:09+0000] Collecting opentelemetry-instrumentation-fastapi==0.43b0 (from -r requirements.txt (line 201))
+[19:22:09+0000]   Obtaining dependency information for opentelemetry-instrumentation-fastapi==0.43b0 from https://files.pythonhosted.org/packages/1d/51/429d04b8694fec2f87184ced4beeab1dd6db194a9444b0a6fca1675338b2/opentelemetry_instrumentation_fastapi-0.43b0-py3-none-any.whl.metadata
+[19:22:09+0000]   Downloading opentelemetry_instrumentation_fastapi-0.43b0-py3-none-any.whl.metadata (2.3 kB)
+[19:22:10+0000] Collecting opentelemetry-instrumentation-flask==0.43b0 (from -r requirements.txt (line 203))
+[19:22:10+0000]   Obtaining dependency information for opentelemetry-instrumentation-flask==0.43b0 from https://files.pythonhosted.org/packages/21/eb/4b0d6f98d2767c7117ebe497bcc58f00e70cc6b4ce97b99bd3eccf3d6644/opentelemetry_instrumentation_flask-0.43b0-py3-none-any.whl.metadata
+[19:22:10+0000]   Downloading opentelemetry_instrumentation_flask-0.43b0-py3-none-any.whl.metadata (2.4 kB)
+[19:22:10+0000] Collecting opentelemetry-instrumentation-httpx==0.43b0 (from -r requirements.txt (line 205))
+[19:22:10+0000]   Obtaining dependency information for opentelemetry-instrumentation-httpx==0.43b0 from https://files.pythonhosted.org/packages/7e/ed/a8d3951650145d7d7997c83e35c59c02c8bf632c24ff2e07ab065ad7dd48/opentelemetry_instrumentation_httpx-0.43b0-py3-none-any.whl.metadata
+[19:22:10+0000]   Downloading opentelemetry_instrumentation_httpx-0.43b0-py3-none-any.whl.metadata (7.1 kB)
+[19:22:11+0000] Collecting opentelemetry-instrumentation-psycopg2==0.43b0 (from -r requirements.txt (line 207))
+[19:22:11+0000]   Obtaining dependency information for opentelemetry-instrumentation-psycopg2==0.43b0 from https://files.pythonhosted.org/packages/0a/4e/f2085da8254b0f019a5dd57f737395c39274a23c25bf3dfe4030a4169325/opentelemetry_instrumentation_psycopg2-0.43b0-py3-none-any.whl.metadata
+[19:22:11+0000]   Downloading opentelemetry_instrumentation_psycopg2-0.43b0-py3-none-any.whl.metadata (2.1 kB)
+[19:22:11+0000] Collecting opentelemetry-instrumentation-requests==0.43b0 (from -r requirements.txt (line 209))
+[19:22:11+0000]   Obtaining dependency information for opentelemetry-instrumentation-requests==0.43b0 from https://files.pythonhosted.org/packages/3b/a9/98618c6383cad51313f448412cadd0bed43634f0287eaf67a3e71a536f9c/opentelemetry_instrumentation_requests-0.43b0-py3-none-any.whl.metadata
+[19:22:11+0000]   Downloading opentelemetry_instrumentation_requests-0.43b0-py3-none-any.whl.metadata (2.7 kB)
+[19:22:12+0000] Collecting opentelemetry-instrumentation-urllib==0.43b0 (from -r requirements.txt (line 213))
+[19:22:12+0000]   Obtaining dependency information for opentelemetry-instrumentation-urllib==0.43b0 from https://files.pythonhosted.org/packages/29/8a/c184945b2628ed44b9357e0df84dfc0974efd4e1360b3d89d2180ebfb3c0/opentelemetry_instrumentation_urllib-0.43b0-py3-none-any.whl.metadata
+[19:22:12+0000]   Downloading opentelemetry_instrumentation_urllib-0.43b0-py3-none-any.whl.metadata (3.4 kB)
+[19:22:12+0000] Collecting opentelemetry-instrumentation-urllib3==0.43b0 (from -r requirements.txt (line 215))
+[19:22:12+0000]   Obtaining dependency information for opentelemetry-instrumentation-urllib3==0.43b0 from https://files.pythonhosted.org/packages/a0/54/3e6fc502e06d6c4cba23f314426951225f950b1af3c2e6decb780cd64ff1/opentelemetry_instrumentation_urllib3-0.43b0-py3-none-any.whl.metadata
+[19:22:12+0000]   Downloading opentelemetry_instrumentation_urllib3-0.43b0-py3-none-any.whl.metadata (3.6 kB)
+[19:22:13+0000] Collecting opentelemetry-instrumentation-wsgi==0.43b0 (from -r requirements.txt (line 217))
+[19:22:13+0000]   Obtaining dependency information for opentelemetry-instrumentation-wsgi==0.43b0 from https://files.pythonhosted.org/packages/4a/37/6315abd394778d76b9bf206980436a8539cc13ddcd0bced709f4d9c3d1e8/opentelemetry_instrumentation_wsgi-0.43b0-py3-none-any.whl.metadata
+[19:22:13+0000]   Downloading opentelemetry_instrumentation_wsgi-0.43b0-py3-none-any.whl.metadata (2.1 kB)
+[19:22:13+0000] Collecting opentelemetry-resource-detector-azure==0.1.3 (from -r requirements.txt (line 221))
+[19:22:13+0000]   Obtaining dependency information for opentelemetry-resource-detector-azure==0.1.3 from https://files.pythonhosted.org/packages/99/c4/6790b15d360d0a14c5fb3a754d713470758da8a3635d90502aabb52febe2/opentelemetry_resource_detector_azure-0.1.3-py3-none-any.whl.metadata
+[19:22:13+0000]   Downloading opentelemetry_resource_detector_azure-0.1.3-py3-none-any.whl.metadata (4.6 kB)
+[19:22:14+0000] Collecting opentelemetry-sdk==1.22.0 (from -r requirements.txt (line 223))
+[19:22:14+0000]   Obtaining dependency information for opentelemetry-sdk==1.22.0 from https://files.pythonhosted.org/packages/ff/94/588f49e0dd9a62ec46102736d2378330032a55e19c79ff7e4febea7ebed1/opentelemetry_sdk-1.22.0-py3-none-any.whl.metadata
+[19:22:14+0000]   Downloading opentelemetry_sdk-1.22.0-py3-none-any.whl.metadata (1.5 kB)
+[19:22:14+0000] Collecting opentelemetry-semantic-conventions==0.43b0 (from -r requirements.txt (line 227))
+[19:22:14+0000]   Obtaining dependency information for opentelemetry-semantic-conventions==0.43b0 from https://files.pythonhosted.org/packages/e0/26/69be0f1a56a362c68fa0c7632d841b1b8f29d809bc6b1b897387c9f46973/opentelemetry_semantic_conventions-0.43b0-py3-none-any.whl.metadata
+[19:22:14+0000]   Downloading opentelemetry_semantic_conventions-0.43b0-py3-none-any.whl.metadata (2.3 kB)
+[19:22:15+0000] Collecting opentelemetry-util-http==0.43b0 (from -r requirements.txt (line 241))
+[19:22:15+0000]   Obtaining dependency information for opentelemetry-util-http==0.43b0 from https://files.pythonhosted.org/packages/74/91/a87a59baeeb917a93f2cc86fa670cf533328d18a2d09b0cef4f65e8b83e9/opentelemetry_util_http-0.43b0-py3-none-any.whl.metadata
+[19:22:15+0000]   Downloading opentelemetry_util_http-0.43b0-py3-none-any.whl.metadata (2.5 kB)
+[19:22:15+0000] Collecting packaging==23.2 (from -r requirements.txt (line 252))
+[19:22:15+0000]   Obtaining dependency information for packaging==23.2 from https://files.pythonhosted.org/packages/ec/1a/610693ac4ee14fcdf2d9bf3c493370e4f2ef7ae2e19217d7a237ff42367d/packaging-23.2-py3-none-any.whl.metadata
+[19:22:15+0000]   Downloading packaging-23.2-py3-none-any.whl.metadata (3.2 kB)
+[19:22:20+0000] Collecting pandas==2.2.0 (from -r requirements.txt (line 256))
+[19:22:20+0000]   Obtaining dependency information for pandas==2.2.0 from https://files.pythonhosted.org/packages/5b/7e/9fd11ba8e86a8add8f2ff4e11c7111f65ec6fd1b547222160bb969e2bf5e/pandas-2.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata
+[19:22:20+0000]   Downloading pandas-2.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (19 kB)
+[19:22:21+0000] Collecting pandas-stubs==2.1.4.231227 (from -r requirements.txt (line 258))
+[19:22:21+0000]   Obtaining dependency information for pandas-stubs==2.1.4.231227 from https://files.pythonhosted.org/packages/c0/6d/c5c23926fcc7526a5df32a8f3b3540948be8dd4c25f4a097f9091d40535c/pandas_stubs-2.1.4.231227-py3-none-any.whl.metadata
+[19:22:21+0000]   Downloading pandas_stubs-2.1.4.231227-py3-none-any.whl.metadata (9.6 kB)
+[19:22:26+0000] Collecting pillow==10.2.0 (from -r requirements.txt (line 260))
+[19:22:26+0000]   Obtaining dependency information for pillow==10.2.0 from https://files.pythonhosted.org/packages/66/9c/2e1877630eb298bbfd23f90deeec0a3f682a4163d5ca9f178937de57346c/pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl.metadata
+[19:22:26+0000]   Downloading pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl.metadata (9.7 kB)
+[19:22:27+0000] Collecting portalocker==2.8.2 (from -r requirements.txt (line 262))
+[19:22:27+0000]   Obtaining dependency information for portalocker==2.8.2 from https://files.pythonhosted.org/packages/17/9e/87671efcca80ba6203811540ed1f9c0462c1609d2281d7b7f53cef05da3d/portalocker-2.8.2-py3-none-any.whl.metadata
+[19:22:27+0000]   Downloading portalocker-2.8.2-py3-none-any.whl.metadata (8.5 kB)
+[19:22:28+0000] Collecting priority==2.0.0 (from -r requirements.txt (line 264))
+[19:22:28+0000]   Downloading priority-2.0.0-py3-none-any.whl (8.9 kB)
+[19:22:28+0000] Collecting pyasn1==0.5.1 (from -r requirements.txt (line 266))
+[19:22:28+0000]   Obtaining dependency information for pyasn1==0.5.1 from https://files.pythonhosted.org/packages/d1/75/4686d2872bf2fc0b37917cbc8bbf0dd3a5cdb0990799be1b9cbf1e1eb733/pyasn1-0.5.1-py2.py3-none-any.whl.metadata
+[19:22:29+0000]   Downloading pyasn1-0.5.1-py2.py3-none-any.whl.metadata (8.6 kB)
+[19:22:29+0000] Collecting pycparser==2.21 (from -r requirements.txt (line 270))
+[19:22:29+0000]   Downloading pycparser-2.21-py2.py3-none-any.whl (118 kB)
+[19:22:29+0000]      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 118.7/118.7 kB 3.6 MB/s eta 0:00:00
+[19:22:31+0000] Collecting pydantic==2.6.0 (from -r requirements.txt (line 272))
+[19:22:31+0000]   Obtaining dependency information for pydantic==2.6.0 from https://files.pythonhosted.org/packages/e4/37/3ffe6e7daa1ea1b4bf5228807a92ccbae538cf57c0c50b93564c310c11a8/pydantic-2.6.0-py3-none-any.whl.metadata
+[19:22:31+0000]   Downloading pydantic-2.6.0-py3-none-any.whl.metadata (81 kB)
+[19:22:31+0000]      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 81.8/81.8 kB 3.6 MB/s eta 0:00:00
+[19:22:39+0000] Collecting pydantic-core==2.16.1 (from -r requirements.txt (line 274))
+[19:22:39+0000]   Obtaining dependency information for pydantic-core==2.16.1 from https://files.pythonhosted.org/packages/98/19/955b83b6e33b7ac27914860069a918fe49b29c13bc149dc7bb7c60954812/pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata
+[19:22:39+0000]   Downloading pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.5 kB)
+[19:22:39+0000] Collecting pyjwt[crypto]==2.8.0 (from -r requirements.txt (line 276))
+[19:22:39+0000]   Obtaining dependency information for pyjwt[crypto]==2.8.0 from https://files.pythonhosted.org/packages/2b/4f/e04a8067c7c96c364cef7ef73906504e2f40d690811c021e1a1901473a19/PyJWT-2.8.0-py3-none-any.whl.metadata
+[19:22:39+0000]   Downloading PyJWT-2.8.0-py3-none-any.whl.metadata (4.2 kB)
+[19:22:39+0000] Collecting python-dateutil==2.8.2 (from -r requirements.txt (line 278))
+[19:22:39+0000]   Downloading python_dateutil-2.8.2-py2.py3-none-any.whl (247 kB)
+[19:22:39+0000]      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 247.7/247.7 kB 19.2 MB/s eta 0:00:00
+[19:22:40+0000] Collecting python-jose[cryptography]==3.3.0 (from -r requirements.txt (line 280))
+[19:22:40+0000]   Downloading python_jose-3.3.0-py2.py3-none-any.whl (33 kB)
+[19:22:40+0000] Collecting pytz==2023.4 (from -r requirements.txt (line 282))
+[19:22:40+0000]   Obtaining dependency information for pytz==2023.4 from https://files.pythonhosted.org/packages/3b/dd/9b84302ba85ac6d3d3042d3e8698374838bde1c386b4adb1223d7a0efd4e/pytz-2023.4-py2.py3-none-any.whl.metadata
+[19:22:40+0000]   Downloading pytz-2023.4-py2.py3-none-any.whl.metadata (22 kB)
+[19:22:41+0000] Collecting quart==0.19.4 (from -r requirements.txt (line 284))
+[19:22:41+0000]   Obtaining dependency information for quart==0.19.4 from https://files.pythonhosted.org/packages/9a/2c/681b4fcecefd98627a90dd5aecdc6b57ba18c9ce07e173d86a0b1274f20b/quart-0.19.4-py3-none-any.whl.metadata
+[19:22:41+0000]   Downloading quart-0.19.4-py3-none-any.whl.metadata (5.7 kB)
+[19:22:42+0000] Collecting quart-cors==0.7.0 (from -r requirements.txt (line 288))
+[19:22:42+0000]   Obtaining dependency information for quart-cors==0.7.0 from https://files.pythonhosted.org/packages/60/fc/1ffe9042df05d48f5eaac4116708fee3f7bb18b696380cc4e3797c8fd510/quart_cors-0.7.0-py3-none-any.whl.metadata
+[19:22:42+0000]   Downloading quart_cors-0.7.0-py3-none-any.whl.metadata (9.4 kB)
+[19:22:50+0000] Collecting regex==2023.12.25 (from -r requirements.txt (line 290))
+[19:22:50+0000]   Obtaining dependency information for regex==2023.12.25 from https://files.pythonhosted.org/packages/8d/6b/2f6478814954c07c04ba60b78d688d3d7bab10d786e0b6c1db607e4f6673/regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata
+[19:22:50+0000]   Downloading regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (40 kB)
+[19:22:50+0000]      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 40.9/40.9 kB 504.8 kB/s eta 0:00:00
+[19:22:50+0000] Collecting requests==2.31.0 (from -r requirements.txt (line 292))
+[19:22:50+0000]   Obtaining dependency information for requests==2.31.0 from https://files.pythonhosted.org/packages/70/8e/0e2d847013cb52cd35b38c009bb167a1a26b2ce6cd6965bf26b47bc0bf44/requests-2.31.0-py3-none-any.whl.metadata
+[19:22:50+0000]   Downloading requests-2.31.0-py3-none-any.whl.metadata (4.6 kB)
+[19:22:51+0000] Collecting requests-oauthlib==1.3.1 (from -r requirements.txt (line 299))
+[19:22:51+0000]   Downloading requests_oauthlib-1.3.1-py2.py3-none-any.whl (23 kB)
+[19:22:51+0000] Collecting rsa==4.9 (from -r requirements.txt (line 301))
+[19:22:51+0000]   Downloading rsa-4.9-py3-none-any.whl (34 kB)
+[19:22:51+0000] Collecting six==1.16.0 (from -r requirements.txt (line 303))
+[19:22:52+0000]   Downloading six-1.16.0-py2.py3-none-any.whl (11 kB)
+[19:22:53+0000] Collecting sniffio==1.3.0 (from -r requirements.txt (line 309))
+[19:22:53+0000]   Downloading sniffio-1.3.0-py3-none-any.whl (10 kB)
+[19:22:53+0000] Collecting tenacity==8.2.3 (from -r requirements.txt (line 314))
+[19:22:53+0000]   Obtaining dependency information for tenacity==8.2.3 from https://files.pythonhosted.org/packages/f4/f1/990741d5bb2487d529d20a433210ffa136a367751e454214013b441c4575/tenacity-8.2.3-py3-none-any.whl.metadata
+[19:22:53+0000]   Downloading tenacity-8.2.3-py3-none-any.whl.metadata (1.0 kB)
+[19:22:54+0000] Collecting tiktoken==0.5.2 (from -r requirements.txt (line 316))
+[19:22:54+0000]   Obtaining dependency information for tiktoken==0.5.2 from https://files.pythonhosted.org/packages/fb/a9/237dc2db35e6ec0fb7dd63e3d10ebe0377559203bd2a87e12a4adbfc8585/tiktoken-0.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata
+[19:22:54+0000]   Downloading tiktoken-0.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.6 kB)
+[19:22:55+0000] Collecting tqdm==4.66.1 (from -r requirements.txt (line 318))
+[19:22:55+0000]   Obtaining dependency information for tqdm==4.66.1 from https://files.pythonhosted.org/packages/00/e5/f12a80907d0884e6dff9c16d0c0114d81b8cd07dc3ae54c5e962cc83037e/tqdm-4.66.1-py3-none-any.whl.metadata
+[19:22:55+0000]   Downloading tqdm-4.66.1-py3-none-any.whl.metadata (57 kB)
+[19:22:55+0000]      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 57.6/57.6 kB 2.4 MB/s eta 0:00:00
+[19:22:55+0000] Collecting types-pillow==10.2.0.20240206 (from -r requirements.txt (line 320))
+[19:22:55+0000]   Obtaining dependency information for types-pillow==10.2.0.20240206 from https://files.pythonhosted.org/packages/54/a1/9c24f95c637f5ed77f0a1de9077a06af018acc0d3ffe9bb0843abc13619c/types_Pillow-10.2.0.20240206-py3-none-any.whl.metadata
+[19:22:56+0000]   Downloading types_Pillow-10.2.0.20240206-py3-none-any.whl.metadata (1.6 kB)
+[19:22:56+0000] Collecting types-pytz==2023.4.0.20240130 (from -r requirements.txt (line 322))
+[19:22:56+0000]   Obtaining dependency information for types-pytz==2023.4.0.20240130 from https://files.pythonhosted.org/packages/83/cd/018e825d60d86c1798c7acccfcb3d7c31227793445e4b87423498e8c486d/types_pytz-2023.4.0.20240130-py3-none-any.whl.metadata
+[19:22:56+0000]   Downloading types_pytz-2023.4.0.20240130-py3-none-any.whl.metadata (1.5 kB)
+[19:22:57+0000] Collecting typing-extensions==4.9.0 (from -r requirements.txt (line 324))
+[19:22:57+0000]   Obtaining dependency information for typing-extensions==4.9.0 from https://files.pythonhosted.org/packages/b7/f4/6a90020cd2d93349b442bfcb657d0dc91eee65491600b2cb1d388bc98e6b/typing_extensions-4.9.0-py3-none-any.whl.metadata
+[19:22:57+0000]   Downloading typing_extensions-4.9.0-py3-none-any.whl.metadata (3.0 kB)
+[19:22:57+0000] Collecting tzdata==2023.4 (from -r requirements.txt (line 333))
+[19:22:57+0000]   Obtaining dependency information for tzdata==2023.4 from https://files.pythonhosted.org/packages/a3/fb/52b62131e21b24ee297e4e95ed41eba29647dad0e0051a92bb66b43c70ff/tzdata-2023.4-py2.py3-none-any.whl.metadata
+[19:22:57+0000]   Downloading tzdata-2023.4-py2.py3-none-any.whl.metadata (1.4 kB)
+[19:22:57+0000] Collecting urllib3==2.1.0 (from -r requirements.txt (line 335))
+[19:22:57+0000]   Obtaining dependency information for urllib3==2.1.0 from https://files.pythonhosted.org/packages/96/94/c31f58c7a7f470d5665935262ebd7455c7e4c7782eb525658d3dbf4b9403/urllib3-2.1.0-py3-none-any.whl.metadata
+[19:22:57+0000]   Downloading urllib3-2.1.0-py3-none-any.whl.metadata (6.4 kB)
+[19:22:58+0000] Collecting uvicorn==0.27.0.post1 (from -r requirements.txt (line 337))
+[19:22:58+0000]   Obtaining dependency information for uvicorn==0.27.0.post1 from https://files.pythonhosted.org/packages/c7/f3/29caa83f5795b20ed3aca357c648f3ae995ff6ff08e38b22387017abbdc5/uvicorn-0.27.0.post1-py3-none-any.whl.metadata
+[19:22:58+0000]   Downloading uvicorn-0.27.0.post1-py3-none-any.whl.metadata (6.4 kB)
+[19:22:59+0000] Collecting werkzeug==3.0.1 (from -r requirements.txt (line 339))
+[19:22:59+0000]   Obtaining dependency information for werkzeug==3.0.1 from https://files.pythonhosted.org/packages/c3/fc/254c3e9b5feb89ff5b9076a23218dafbc99c96ac5941e900b71206e6313b/werkzeug-3.0.1-py3-none-any.whl.metadata
+[19:22:59+0000]   Downloading werkzeug-3.0.1-py3-none-any.whl.metadata (4.1 kB)
+[19:23:00+0000] Collecting wrapt==1.16.0 (from -r requirements.txt (line 343))
+[19:23:00+0000]   Obtaining dependency information for wrapt==1.16.0 from https://files.pythonhosted.org/packages/6e/52/2da48b35193e39ac53cfb141467d9f259851522d0e8c87153f0ba4205fb1/wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata
+[19:23:00+0000]   Downloading wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.6 kB)
+[19:23:00+0000] Collecting wsproto==1.2.0 (from -r requirements.txt (line 350))
+[19:23:00+0000]   Downloading wsproto-1.2.0-py3-none-any.whl (24 kB)
+[19:23:03+0000] Collecting yarl==1.9.4 (from -r requirements.txt (line 352))
+[19:23:03+0000]   Obtaining dependency information for yarl==1.9.4 from https://files.pythonhosted.org/packages/9f/ea/94ad7d8299df89844e666e4aa8a0e9b88e02416cd6a7dd97969e9eae5212/yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata
+[19:23:03+0000]   Downloading yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (31 kB)
+[19:23:03+0000] Collecting zipp==3.17.0 (from -r requirements.txt (line 354))
+[19:23:03+0000]   Obtaining dependency information for zipp==3.17.0 from https://files.pythonhosted.org/packages/d9/66/48866fc6b158c81cc2bfecc04c480f105c6040e8b077bc54c634b4a67926/zipp-3.17.0-py3-none-any.whl.metadata
+[19:23:03+0000]   Downloading zipp-3.17.0-py3-none-any.whl.metadata (3.7 kB)
+[19:23:30+0000] Requirement already satisfied: setuptools>=16.0 in ./antenv/lib/python3.11/site-packages (from opentelemetry-instrumentation==0.43b0->-r requirements.txt (line 177)) (65.5.0)
+[19:23:51+0000] Downloading aiofiles-23.2.1-py3-none-any.whl (15 kB)
+[19:23:51+0000] Downloading aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.3 MB)
+[19:23:51+0000]    ━━━━━━━━━━━━━━━━━��━━━━━━━━━━━━━━━━━━━━━━ 1.3/1.3 MB 32.0 MB/s eta 0:00:00
+[19:23:51+0000] Downloading annotated_types-0.6.0-py3-none-any.whl (12 kB)
+[19:23:51+0000] Downloading anyio-4.2.0-py3-none-any.whl (85 kB)
+[19:23:51+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 85.5/85.5 kB 6.9 MB/s eta 0:00:00
+[19:23:51+0000] Downloading asgiref-3.7.2-py3-none-any.whl (24 kB)
+[19:23:51+0000] Downloading attrs-23.2.0-py3-none-any.whl (60 kB)
+[19:23:51+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 60.8/60.8 kB 5.9 MB/s eta 0:00:00
+[19:23:51+0000] Downloading azure_core-1.29.7-py3-none-any.whl (192 kB)
+[19:23:51+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 192.9/192.9 kB 6.3 MB/s eta 0:00:00
+[19:23:51+0000] Downloading azure_core_tracing_opentelemetry-1.0.0b11-py3-none-any.whl (10 kB)
+[19:23:51+0000] Downloading azure_identity-1.15.0-py3-none-any.whl (164 kB)
+[19:23:52+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 164.7/164.7 kB 5.7 MB/s eta 0:00:00
+[19:23:52+0000] Downloading azure_monitor_opentelemetry-1.2.0-py3-none-any.whl (20 kB)
+[19:23:52+0000] Downloading azure_monitor_opentelemetry_exporter-1.0.0b21-py2.py3-none-any.whl (78 kB)
+[19:23:52+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 78.5/78.5 kB 787.5 kB/s eta 0:00:00
+[19:23:52+0000] Downloading azure_search_documents-11.6.0b1-py3-none-any.whl (315 kB)
+[19:23:52+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 315.3/315.3 kB 5.6 MB/s eta 0:00:00
+[19:23:52+0000] Downloading azure_storage_blob-12.19.0-py3-none-any.whl (394 kB)
+[19:23:52+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 394.2/394.2 kB 19.4 MB/s eta 0:00:00
+[19:23:52+0000] Downloading blinker-1.7.0-py3-none-any.whl (13 kB)
+[19:23:52+0000] Downloading certifi-2023.11.17-py3-none-any.whl (162 kB)
+[19:23:52+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 162.5/162.5 kB 6.3 MB/s eta 0:00:00
+[19:23:52+0000] Downloading cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (464 kB)
+[19:23:52+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 464.8/464.8 kB 3.1 MB/s eta 0:00:00
+[19:23:52+0000] Downloading charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (140 kB)
+[19:23:52+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 140.3/140.3 kB 5.7 MB/s eta 0:00:00
+[19:23:52+0000] Downloading click-8.1.7-py3-none-any.whl (97 kB)
+[19:23:53+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 97.9/97.9 kB 831.8 kB/s eta 0:00:00
+[19:23:53+0000] Downloading cryptography-42.0.1-cp39-abi3-manylinux_2_28_x86_64.whl (4.6 MB)
+[19:23:53+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.6/4.6 MB 15.8 MB/s eta 0:00:00
+[19:23:53+0000] Downloading Deprecated-1.2.14-py2.py3-none-any.whl (9.6 kB)
+[19:23:53+0000] Downloading distro-1.9.0-py3-none-any.whl (20 kB)
+[19:23:53+0000] Downloading flask-3.0.1-py3-none-any.whl (101 kB)
+[19:23:53+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 101.2/101.2 kB 1.1 MB/s eta 0:00:00
+[19:23:53+0000] Downloading frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (272 kB)
+[19:23:53+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 272.3/272.3 kB 12.9 MB/s eta 0:00:00
+[19:23:53+0000] Downloading httpcore-1.0.2-py3-none-any.whl (76 kB)
+[19:23:53+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 76.9/76.9 kB 7.0 MB/s eta 0:00:00
+[19:23:53+0000] Downloading httpx-0.26.0-py3-none-any.whl (75 kB)
+[19:23:54+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 75.9/75.9 kB 20.7 MB/s eta 0:00:00
+[19:23:54+0000] Downloading hypercorn-0.16.0-py3-none-any.whl (59 kB)
+[19:23:54+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 59.9/59.9 kB 17.8 MB/s eta 0:00:00
+[19:23:54+0000] Downloading idna-3.6-py3-none-any.whl (61 kB)
+[19:23:54+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 61.6/61.6 kB 3.2 MB/s eta 0:00:00
+[19:23:54+0000] Downloading importlib_metadata-6.11.0-py3-none-any.whl (23 kB)
+[19:23:54+0000] Downloading Jinja2-3.1.3-py3-none-any.whl (133 kB)
+[19:23:54+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 133.2/133.2 kB 9.8 MB/s eta 0:00:00
+[19:23:54+0000] Downloading MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (28 kB)
+[19:23:54+0000] Downloading msal-1.26.0-py2.py3-none-any.whl (99 kB)
+[19:23:54+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 99.0/99.0 kB 10.0 MB/s eta 0:00:00
+[19:23:54+0000] Downloading msal_extensions-1.1.0-py3-none-any.whl (19 kB)
+[19:23:54+0000] Downloading numpy-1.26.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (18.3 MB)
+[19:23:55+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 18.3/18.3 MB 9.5 MB/s eta 0:00:00
+[19:23:55+0000] Downloading opentelemetry_api-1.22.0-py3-none-any.whl (57 kB)
+[19:23:55+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 57.9/57.9 kB 3.1 MB/s eta 0:00:00
+[19:23:55+0000] Downloading opentelemetry_instrumentation-0.43b0-py3-none-any.whl (28 kB)
+[19:23:55+0000] Downloading opentelemetry_instrumentation_aiohttp_client-0.43b0-py3-none-any.whl (11 kB)
+[19:23:55+0000] Downloading opentelemetry_instrumentation_asgi-0.43b0-py3-none-any.whl (14 kB)
+[19:23:55+0000] Downloading opentelemetry_instrumentation_dbapi-0.43b0-py3-none-any.whl (10 kB)
+[19:23:55+0000] Downloading opentelemetry_instrumentation_django-0.43b0-py3-none-any.whl (18 kB)
+[19:23:55+0000] Downloading opentelemetry_instrumentation_fastapi-0.43b0-py3-none-any.whl (11 kB)
+[19:23:55+0000] Downloading opentelemetry_instrumentation_flask-0.43b0-py3-none-any.whl (14 kB)
+[19:23:55+0000] Downloading opentelemetry_instrumentation_httpx-0.43b0-py3-none-any.whl (12 kB)
+[19:23:55+0000] Downloading opentelemetry_instrumentation_psycopg2-0.43b0-py3-none-any.whl (10 kB)
+[19:23:55+0000] Downloading opentelemetry_instrumentation_requests-0.43b0-py3-none-any.whl (12 kB)
+[19:23:55+0000] Downloading opentelemetry_instrumentation_urllib-0.43b0-py3-none-any.whl (11 kB)
+[19:23:55+0000] Downloading opentelemetry_instrumentation_urllib3-0.43b0-py3-none-any.whl (11 kB)
+[19:23:56+0000] Downloading opentelemetry_instrumentation_wsgi-0.43b0-py3-none-any.whl (13 kB)
+[19:23:56+0000] Downloading opentelemetry_resource_detector_azure-0.1.3-py3-none-any.whl (10 kB)
+[19:23:56+0000] Downloading opentelemetry_sdk-1.22.0-py3-none-any.whl (105 kB)
+[19:23:56+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 105.6/105.6 kB 1.6 MB/s eta 0:00:00
+[19:23:56+0000] Downloading opentelemetry_semantic_conventions-0.43b0-py3-none-any.whl (36 kB)
+[19:23:56+0000] Downloading opentelemetry_util_http-0.43b0-py3-none-any.whl (6.9 kB)
+[19:23:56+0000] Downloading packaging-23.2-py3-none-any.whl (53 kB)
+[19:23:56+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 53.0/53.0 kB 1.9 MB/s eta 0:00:00
+[19:23:56+0000] Downloading pandas-2.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (13.0 MB)
+[19:23:58+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 13.0/13.0 MB 7.7 MB/s eta 0:00:00
+[19:23:58+0000] Downloading pandas_stubs-2.1.4.231227-py3-none-any.whl (153 kB)
+[19:23:58+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 153.6/153.6 kB 8.1 MB/s eta 0:00:00
+[19:23:58+0000] Downloading pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl (4.5 MB)
+[19:23:58+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.5/4.5 MB 16.5 MB/s eta 0:00:00
+[19:23:58+0000] Downloading portalocker-2.8.2-py3-none-any.whl (17 kB)
+[19:23:58+0000] Downloading pyasn1-0.5.1-py2.py3-none-any.whl (84 kB)
+[19:23:58+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 84.9/84.9 kB 6.8 MB/s eta 0:00:00
+[19:23:58+0000] Downloading pydantic-2.6.0-py3-none-any.whl (394 kB)
+[19:23:58+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 394.2/394.2 kB 9.3 MB/s eta 0:00:00
+[19:23:59+0000] Downloading pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.2 MB)
+[19:23:59+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.2/2.2 MB 5.6 MB/s eta 0:00:00
+[19:23:59+0000] Downloading pytz-2023.4-py2.py3-none-any.whl (506 kB)
+[19:23:59+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 506.5/506.5 kB 7.1 MB/s eta 0:00:00
+[19:23:59+0000] Downloading quart-0.19.4-py3-none-any.whl (77 kB)
+[19:23:59+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 77.8/77.8 kB 6.5 MB/s eta 0:00:00
+[19:23:59+0000] Downloading quart_cors-0.7.0-py3-none-any.whl (8.0 kB)
+[19:23:59+0000] Downloading regex-2023.12.25-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (785 kB)
+[19:23:59+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 785.1/785.1 kB 13.7 MB/s eta 0:00:00
+[19:23:59+0000] Downloading requests-2.31.0-py3-none-any.whl (62 kB)
+[19:23:59+0000]    ━━━━━━━━━━━━━━━━━━━━━━━���━━━━━━━━━━━━━━━━ 62.6/62.6 kB 1.2 MB/s eta 0:00:00
+[19:23:59+0000] Downloading tenacity-8.2.3-py3-none-any.whl (24 kB)
+[19:23:59+0000] Downloading tiktoken-0.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.0 MB)
+[19:24:00+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.0/2.0 MB 15.3 MB/s eta 0:00:00
+[19:24:00+0000] Downloading tqdm-4.66.1-py3-none-any.whl (78 kB)
+[19:24:00+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 78.3/78.3 kB 6.9 MB/s eta 0:00:00
+[19:24:00+0000] Downloading types_Pillow-10.2.0.20240206-py3-none-any.whl (52 kB)
+[19:24:00+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 52.9/52.9 kB 1.1 MB/s eta 0:00:00
+[19:24:00+0000] Downloading types_pytz-2023.4.0.20240130-py3-none-any.whl (5.1 kB)
+[19:24:00+0000] Downloading typing_extensions-4.9.0-py3-none-any.whl (32 kB)
+[19:24:00+0000] Downloading tzdata-2023.4-py2.py3-none-any.whl (346 kB)
+[19:24:00+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 346.6/346.6 kB 11.7 MB/s eta 0:00:00
+[19:24:00+0000] Downloading urllib3-2.1.0-py3-none-any.whl (104 kB)
+[19:24:00+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 104.6/104.6 kB 1.2 MB/s eta 0:00:00
+[19:24:00+0000] Downloading uvicorn-0.27.0.post1-py3-none-any.whl (60 kB)
+[19:24:00+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 60.7/60.7 kB 5.7 MB/s eta 0:00:00
+[19:24:00+0000] Downloading werkzeug-3.0.1-py3-none-any.whl (226 kB)
+[19:24:00+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 226.7/226.7 kB 12.6 MB/s eta 0:00:00
+[19:24:00+0000] Downloading wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (80 kB)
+[19:24:00+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 80.7/80.7 kB 8.0 MB/s eta 0:00:00
+[19:24:00+0000] Downloading yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (328 kB)
+[19:24:00+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 328.1/328.1 kB 20.1 MB/s eta 0:00:00
+[19:24:00+0000] Downloading zipp-3.17.0-py3-none-any.whl (7.4 kB)
+[19:24:00+0000] Downloading openai-1.10.0-py3-none-any.whl (225 kB)
+[19:24:00+0000]    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 225.1/225.1 kB 11.7 MB/s eta 0:00:00
+[19:24:01+0000] Downloading PyJWT-2.8.0-py3-none-any.whl (22 kB)
+[19:24:07+0000] Installing collected packages: pytz, fixedint, azure-common, zipp, wrapt, urllib3, tzdata, typing-extensions, types-pytz, types-pillow, tqdm, tenacity, sniffio, six, regex, pyjwt, pycparser, pyasn1, priority, portalocker, pillow, packaging, opentelemetry-util-http, opentelemetry-semantic-conventions, oauthlib, numpy, multidict, markupsafe, itsdangerous, idna, hyperframe, hpack, h11, frozenlist, distro, click, charset-normalizer, certifi, blinker, attrs, asgiref, annotated-types, aiofiles, yarl, wsproto, werkzeug, uvicorn, rsa, requests, python-dateutil, pydantic-core, pandas-stubs, jinja2, isodate, importlib-metadata, httpcore, h2, ecdsa, deprecated, cffi, anyio, aiosignal, tiktoken, requests-oauthlib, python-jose, pydantic, pandas, opentelemetry-api, hypercorn, httpx, flask, cryptography, azure-core, aiohttp, quart, opentelemetry-sdk, opentelemetry-instrumentation, openai, msrest, azure-storage-blob, azure-search-documents, azure-keyvault-secrets, azure-core-tracing-opentelemetry, quart-cors, opentelemetry-resource-detector-azure, opentelemetry-instrumentation-wsgi, opentelemetry-instrumentation-urllib3, opentelemetry-instrumentation-urllib, opentelemetry-instrumentation-requests, opentelemetry-instrumentation-httpx, opentelemetry-instrumentation-dbapi, opentelemetry-instrumentation-asgi, opentelemetry-instrumentation-aiohttp-client, msal, azure-monitor-opentelemetry-exporter, opentelemetry-instrumentation-psycopg2, opentelemetry-instrumentation-flask, opentelemetry-instrumentation-fastapi, opentelemetry-instrumentation-django, msal-extensions, azure-monitor-opentelemetry, azure-identity
+[19:25:31+0000] Successfully installed aiofiles-23.2.1 aiohttp-3.9.3 aiosignal-1.3.1 annotated-types-0.6.0 anyio-4.2.0 asgiref-3.7.2 attrs-23.2.0 azure-common-1.1.28 azure-core-1.29.7 azure-core-tracing-opentelemetry-1.0.0b11 azure-identity-1.15.0 azure-keyvault-secrets-4.7.0 azure-monitor-opentelemetry-1.2.0 azure-monitor-opentelemetry-exporter-1.0.0b21 azure-search-documents-11.6.0b1 azure-storage-blob-12.19.0 blinker-1.7.0 certifi-2023.11.17 cffi-1.16.0 charset-normalizer-3.3.2 click-8.1.7 cryptography-42.0.1 deprecated-1.2.14 distro-1.9.0 ecdsa-0.18.0 fixedint-0.1.6 flask-3.0.1 frozenlist-1.4.1 h11-0.14.0 h2-4.1.0 hpack-4.0.0 httpcore-1.0.2 httpx-0.26.0 hypercorn-0.16.0 hyperframe-6.0.1 idna-3.6 importlib-metadata-6.11.0 isodate-0.6.1 itsdangerous-2.1.2 jinja2-3.1.3 markupsafe-2.1.4 msal-1.26.0 msal-extensions-1.1.0 msrest-0.7.1 multidict-6.0.4 numpy-1.26.3 oauthlib-3.2.2 openai-1.10.0 opentelemetry-api-1.22.0 opentelemetry-instrumentation-0.43b0 opentelemetry-instrumentation-aiohttp-client-0.43b0 opentelemetry-instrumentation-asgi-0.43b0 opentelemetry-instrumentation-dbapi-0.43b0 opentelemetry-instrumentation-django-0.43b0 opentelemetry-instrumentation-fastapi-0.43b0 opentelemetry-instrumentation-flask-0.43b0 opentelemetry-instrumentation-httpx-0.43b0 opentelemetry-instrumentation-psycopg2-0.43b0 opentelemetry-instrumentation-requests-0.43b0 opentelemetry-instrumentation-urllib-0.43b0 opentelemetry-instrumentation-urllib3-0.43b0 opentelemetry-instrumentation-wsgi-0.43b0 opentelemetry-resource-detector-azure-0.1.3 opentelemetry-sdk-1.22.0 opentelemetry-semantic-conventions-0.43b0 opentelemetry-util-http-0.43b0 packaging-23.2 pandas-2.2.0 pandas-stubs-2.1.4.231227 pillow-10.2.0 portalocker-2.8.2 priority-2.0.0 pyasn1-0.5.1 pycparser-2.21 pydantic-2.6.0 pydantic-core-2.16.1 pyjwt-2.8.0 python-dateutil-2.8.2 python-jose-3.3.0 pytz-2023.4 quart-0.19.4 quart-cors-0.7.0 regex-2023.12.25 requests-2.31.0 requests-oauthlib-1.3.1 rsa-4.9 six-1.16.0 sniffio-1.3.0 tenacity-8.2.3 tiktoken-0.5.2 tqdm-4.66.1 types-pillow-10.2.0.20240206 types-pytz-2023.4.0.20240130 typing-extensions-4.9.0 tzdata-2023.4 urllib3-2.1.0 uvicorn-0.27.0.post1 werkzeug-3.0.1 wrapt-1.16.0 wsproto-1.2.0 yarl-1.9.4 zipp-3.17.0
+
+[notice] A new release of pip is available: 23.2.1 -> 24.0
+[notice] To update, run: pip install --upgrade pip
+Not a vso image, so not writing build commands
+Preparing output...
+
+Copying files to destination directory '/tmp/_preCompressedDestinationDir'...
+Done in 48 sec(s).
+Compressing content of directory '/tmp/_preCompressedDestinationDir'...
+Copied the compressed output to '/home/site/wwwroot'
+
+Removing existing manifest file
+Creating a manifest file...
+Manifest file created.
+Copying .ostype to manifest output directory.
+
+Done in 522 sec(s).
+
+

Look for these important steps in the Oryx build:

+
    +
  • Detected following platforms: python: 3.11.7 That should +match your runtime in the App Service configuration.
  • +
  • Running pip install… That should install all the +requirements in your requirements.txt - if it didn’t find your +requirements.txt, then you won’t see the packages installed.
  • +
+

If you see all those steps in the Oryx build, then that’s a good sign +that the build went well, and you can move on to checking the App +Service logs.

+

Checking the app logs for +errors

+

Select Advanced Tools from the side nav:

+
+ + +
+

Select Go to open the Kudu website.

+

When the Kudu website loads, find the Current Docker Logs +link and select Download as zip next to it:

+
+ + +
+

In the downloaded zip file, find the filename that starts with the +most recent date and ends with “_default_docker.log”:

+
+ + +
+

Open that file to see the full logs, with the most recent logs at the +bottom.

+
+ +Here are the full logs for the app successfully starting: + +

+2024-02-08T19:30:27.900249002Z    _____
+2024-02-08T19:30:27.900282702Z   /  _  \ __________ _________   ____
+2024-02-08T19:30:27.900288002Z  /  /_\  \\___   /  |  \_  __ \_/ __ \
+2024-02-08T19:30:27.900291902Z /    |    \/    /|  |  /|  | \/\  ___/
+2024-02-08T19:30:27.900295502Z \____|__  /_____ \____/ |__|    \___  >
+2024-02-08T19:30:27.900299602Z         \/      \/                  \/
+2024-02-08T19:30:27.900303402Z A P P   S E R V I C E   O N   L I N U X
+2024-02-08T19:30:27.900307003Z
+2024-02-08T19:30:27.900310303Z Documentation: http://aka.ms/webapp-linux
+2024-02-08T19:30:27.900313903Z Python 3.11.4
+2024-02-08T19:30:27.900317303Z Note: Any data outside '/home' is not persisted
+2024-02-08T19:30:32.956710361Z Starting OpenBSD Secure Shell server: sshd.
+2024-02-08T19:30:33.441385332Z Site's appCommandLine: python3 -m gunicorn main:app
+2024-02-08T19:30:33.703536564Z Launching oryx with: create-script -appPath /home/site/wwwroot -output /opt/startup/startup.sh -virtualEnvName antenv -defaultApp /opt/defaultsite -userStartupCommand 'python3 -m gunicorn main:app'
+2024-02-08T19:30:33.703598264Z Found build manifest file at '/home/site/wwwroot/oryx-manifest.toml'. Deserializing it...
+2024-02-08T19:30:33.703605164Z Build Operation ID: 7440a33100749a32
+2024-02-08T19:30:33.703609765Z Oryx Version: 0.2.20230707.1, Commit: 0bd28e69919b5e8beba451e8677e3345f0be8361, ReleaseTagName: 20230707.1
+2024-02-08T19:30:33.712124127Z Output is compressed. Extracting it...
+2024-02-08T19:30:33.712151827Z Extracting '/home/site/wwwroot/output.tar.gz' to directory '/tmp/8dc28dad0e10acb'...
+2024-02-08T19:31:08.047051747Z App path is set to '/tmp/8dc28dad0e10acb'
+2024-02-08T19:31:08.073259604Z Writing output script to '/opt/startup/startup.sh'
+2024-02-08T19:31:08.431803481Z Using packages from virtual environment antenv located at /tmp/8dc28dad0e10acb/antenv.
+2024-02-08T19:31:08.431842281Z Updated PYTHONPATH to '/opt/startup/app_logs:/tmp/8dc28dad0e10acb/antenv/lib/python3.11/site-packages'
+2024-02-08T19:31:11.043306496Z [2024-02-08 19:31:11 +0000] [75] [INFO] Starting gunicorn 20.1.0
+2024-02-08T19:31:11.060556234Z [2024-02-08 19:31:11 +0000] [75] [INFO] Listening at: http://0.0.0.0:8000 (75)
+2024-02-08T19:31:11.060586534Z [2024-02-08 19:31:11 +0000] [75] [INFO] Using worker: uvicorn.workers.UvicornWorker
+2024-02-08T19:31:11.069707155Z [2024-02-08 19:31:11 +0000] [76] [INFO] Booting worker with pid: 76
+2024-02-08T19:31:11.188073718Z [2024-02-08 19:31:11 +0000] [77] [INFO] Booting worker with pid: 77
+2024-02-08T19:31:11.415802023Z [2024-02-08 19:31:11 +0000] [78] [INFO] Booting worker with pid: 78
+2024-02-08T19:32:20.509338341Z [2024-02-08 19:32:20 +0000] [76] [INFO] Started server process [76]
+2024-02-08T19:32:20.521167526Z [2024-02-08 19:32:20 +0000] [77] [INFO] Started server process [77]
+2024-02-08T19:32:20.521189626Z [2024-02-08 19:32:20 +0000] [77] [INFO] Waiting for application startup.
+2024-02-08T19:32:20.521207626Z [2024-02-08 19:32:20 +0000] [78] [INFO] Started server process [78]
+2024-02-08T19:32:20.521212726Z [2024-02-08 19:32:20 +0000] [78] [INFO] Waiting for application startup.
+2024-02-08T19:32:20.521217126Z [2024-02-08 19:32:20 +0000] [76] [INFO] Waiting for application startup.
+2024-02-08T19:32:20.726894213Z [2024-02-08 19:32:20 +0000] [76] [INFO] Application startup complete.
+2024-02-08T19:32:20.726936214Z [2024-02-08 19:32:20 +0000] [78] [INFO] Application startup complete.
+2024-02-08T19:32:20.726942614Z [2024-02-08 19:32:20 +0000] [77] [INFO] Application startup complete.
+
+

A few notable logs:

+
    +
  • 2024-02-08T19:30:33.441385332Z Site's appCommandLine: python3 -m gunicorn main:app +This log indicates that App Service was correctly configured with a +custom startup command to run the app.
  • +
  • [2024-02-08 19:31:11 +0000] [75] [INFO] Starting gunicorn 20.1.0 +That’s the start of the gunicorn server serving the app.
  • +
  • 2024-02-08T19:32:20.726942614Z [2024-02-08 19:32:20 +0000] [77] [INFO] Application startup complete. +At this point, the app has started successfully.
  • +
+

If you do not see any errors in those logs, then the app should be +running successfully. If you do see errors, then try looking in Azure +Monitor.

+

Checking Azure Monitor for +errors

+

By default, deployed apps use Application Insights to trace and log +errors. (If you explicitly opted out of Application Insights, then you +won’t have this feature.)

+

In the Azure Portal, navigate to the Application Insights for your +app.

+

To see any exceptions and server errors, navigate to the +Investigate -> Failures blade and browse through the +exceptions.

+
+ + +
+

Configuring log levels

+

By default, the deployed app only logs messages from packages with a +level of WARNING or higher, but logs all messages from the +app with a level of INFO or higher.

+

These lines of code in app/backend/app.py configure the +logging level:

+
# Set root level to WARNING to avoid seeing overly verbose logs from SDKS
+logging.basicConfig(level=logging.WARNING)
+# Set the app logger level to INFO by default
+default_level = "INFO"
+app.logger.setLevel(os.getenv("APP_LOG_LEVEL", default_level))
+

To change the default level, either change default_level +or set the APP_LOG_LEVEL environment variable to one of the +allowed +log levels: DEBUG, INFO, +WARNING, ERROR, CRITICAL.

+

If you need to log in a route handler, use the the global variable +current_app’s logger:

+
async def chat():
+    current_app.logger.info("Received /chat request")
+

Otherwise, use the logging module’s root logger:

+
logging.info("System message: %s", system_message)
+

If you’re having troubles finding the logs in App Service, read the +section above on checking +app logs or watch this video about +viewing App Service logs.

diff --git a/data/azd.html b/data/azd.html new file mode 100644 index 0000000000..9a98ed915e --- /dev/null +++ b/data/azd.html @@ -0,0 +1,77 @@ +

RAG chat: +Deploying with the Azure Developer CLI

+

This guide includes advanced topics that are not necessary for a +basic deployment. If you are new to the project, please consult the main +README for steps on deploying the +project.

+

📺 Watch: +Deployment of your chat app

+ +

How does azd up work?

+

The azd up command comes from the Azure +Developer CLI, and takes care of both provisioning the Azure +resources and deploying code to the selected Azure hosts.

+

The azd up command uses the azure.yaml file +combined with the infrastructure-as-code .bicep files in +the infra/ folder. The azure.yaml file for +this project declares several “hooks” for the prepackage step and +postprovision steps. The up command first runs the +prepackage hook which installs Node dependencies and builds +the React.JS-based JavaScript files. It then packages all the code (both +frontend and backend) into a zip file which it will deploy later.

+

Next, it provisions the resources based on main.bicep +and main.parameters.json. At that point, since there is no +default value for the OpenAI resource location, it asks you to pick a +location from a short list of available regions. Then it will send +requests to Azure to provision all the required resources. With +everything provisioned, it runs the postprovision hook to +process the local data and add it to an Azure AI Search index.

+

Finally, it looks at azure.yaml to determine the Azure +host and uploads the zip to Azure App Service. The azd up +command is now complete, but it may take another 5-10 minutes for the +App Service app to be fully available and working, especially for the +initial deploy.

+

Related commands are azd provision for just provisioning +(if infra files change) and azd deploy for just deploying +updated app code.

+

Configuring continuous +deployment

+

This repository includes both a GitHub Actions workflow and an Azure +DevOps pipeline for continuous deployment with every push to +main. The GitHub Actions workflow is the default, but you +can switch to Azure DevOps if you prefer.

+

More details are available in Learn.com: +Configure a pipeline and push updates

+

GitHub actions

+

After you have deployed the app once with azd up, you +can enable continuous deployment with GitHub Actions.

+

Run this command to set up a Service Principal account for CI +deployment and to store your azd environment variables in +GitHub Actions secrets:

+
azd pipeline config
+

You can trigger the “Deploy” workflow manually from your GitHub +actions, or wait for the next push to main.

+

If you change your azd environment variables at any time +(via azd env set or as a result of provisioning), re-run +that command in order to update the GitHub Actions secrets.

+

Azure DevOps

+

After you have deployed the app once with azd up, you +can enable continuous deployment with Azure DevOps.

+

Run this command to set up a Service Principal account for CI +deployment and to store your azd environment variables in +GitHub Actions secrets:

+
azd pipeline config --provider azdo
+

If you change your azd environment variables at any time +(via azd env set or as a result of provisioning), re-run +that command in order to update the GitHub Actions secrets.

diff --git a/data/azure_app_service.html b/data/azure_app_service.html new file mode 100644 index 0000000000..706465d4be --- /dev/null +++ b/data/azure_app_service.html @@ -0,0 +1,45 @@ +

RAG chat: Deploying on +Azure App Service

+

Due to a +limitation of the Azure Developer CLI (azd), there can +be only one host option in the azure.yaml +file. By default, host: containerapp is used and +host: appservice is commented out.

+

To deploy to Azure App Service, please follow the following +steps:

+
    +
  1. Comment out host: containerapp and uncomment +host: appservice in the azure.yaml file.

  2. +
  3. Login to your Azure account:

    +
    azd auth login
  4. +
  5. Create a new azd environment to store the deployment +parameters:

    +
    azd env new
    +

    Enter a name that will be used for the resource group. This will +create a new folder in the .azure folder, and set it as the +active environment for any calls to azd going +forward.

  6. +
  7. Set the deployment target to appservice:

    +
    azd env set DEPLOYMENT_TARGET appservice
  8. +
  9. (Optional) This is the point where you can customize the +deployment by setting other azd environment variables, in +order to use existing resources, enable optional features (such as auth or +vision), or deploy to free +tiers.

  10. +
  11. Provision the resources and deploy the code:

    +
    azd up
    +

    This will provision Azure resources and deploy this sample to those +resources, including building the search index based on the files found +in the ./data folder.

    +

    Important: Beware that the resources created by this +command will incur immediate costs, primarily from the AI Search +resource. These resources may accrue costs even if you interrupt the +command before it is fully executed. You can run azd down +or delete the resources manually to avoid unnecessary spending.

  12. +
diff --git a/data/azure_container_apps.html b/data/azure_container_apps.html new file mode 100644 index 0000000000..f3294ec2a2 --- /dev/null +++ b/data/azure_container_apps.html @@ -0,0 +1,62 @@ +

RAG chat: Deploying +on Azure Container Apps

+

Due to a +limitation of the Azure Developer CLI (azd), there can +be only one host option in the azure.yaml +file. By default, host: containerapp is used and +host: appservice is commented out.

+

However, if you have an older version of the repo, you may need to +follow these steps to deploy to Container Apps instead, or you can stick +with Azure App Service.

+

To deploy to Azure Container Apps, please follow the following +steps:

+
    +
  1. Comment out host: appservice and uncomment +host: containerapp in the azure.yaml file.

  2. +
  3. Login to your Azure account:

    +
    azd auth login
  4. +
  5. Create a new azd environment to store the deployment +parameters:

    +
    azd env new
    +

    Enter a name that will be used for the resource group. This will +create a new folder in the .azure folder, and set it as the +active environment for any calls to azd going +forward.

  6. +
  7. Set the deployment target to containerapps:

    +
    azd env set DEPLOYMENT_TARGET containerapps
  8. +
  9. (Optional) This is the point where you can customize the +deployment by setting other `azd1 environment variables, in order to use existing resources, enable optional features (such as auth or +vision), or deploy to free +tiers.

  10. +
  11. Provision the resources and deploy the code:

    +
    azd up
    +

    This will provision Azure resources and deploy this sample to those +resources, including building the search index based on the files found +in the ./data folder.

    +

    Important: Beware that the resources created by this +command will incur immediate costs, primarily from the AI Search +resource. These resources may accrue costs even if you interrupt the +command before it is fully executed. You can run azd down +or delete the resources manually to avoid unnecessary spending.

  12. +
+

Customizing Workload Profile

+

The default workload profile is Consumption. If you want to use a +dedicated workload profile like D4, please run:

+
azd env AZURE_CONTAINER_APPS_WORKLOAD_PROFILE D4
+

For a full list of workload profiles, please check here. +Please note dedicated workload profiles have a different billing model +than Consumption plan. Please check here +for details.

+

Private endpoints

+

Private endpoints is still in private preview for Azure Container +Apps and not supported for now.

diff --git a/data/customization.html b/data/customization.html new file mode 100644 index 0000000000..0bf2389b6b --- /dev/null +++ b/data/customization.html @@ -0,0 +1,309 @@ +

RAG chat: Customizing the +chat app

+

📺 Watch: (RAG +Deep Dive series) Customizing the app

+

This guide provides more details for customizing the RAG chat +app.

+ +

Using your own data

+

The Chat App is designed to work with any PDF documents. The sample +data is provided to help you get started quickly, but you can easily +replace it with your own data. You’ll want to first remove all the +existing data, then add your own. See the data ingestion guide for more details.

+

Customizing the UI

+

The frontend is built using React +and Fluent UI components. The +frontend components are stored in the app/frontend/src +folder. To modify the page title, header text, example questions, and +other UI elements, you can customize the +app/frontend/src/locales/{en/es/fr/jp/it}/translation.json +file for different languages(English is the default). The primary +strings and labels used throughout the application are defined within +these files.

+

Customizing the backend

+

The backend is built using Quart, a Python framework +for asynchronous web applications. The backend code is stored in the +app/backend folder. The frontend and backend communicate +using the AI Chat HTTP +Protocol.

+

Chat/Ask tabs

+

Typically, the primary backend code you’ll want to customize is the +app/backend/approaches folder, which contains the classes +powering the Chat and Ask tabs. Each class uses a different RAG +(Retrieval Augmented Generation) approach, which include system messages +that should be changed to match your data

+

Chat approach

+

The chat tab uses the approach programmed in chatreadretrieveread.py.

+
    +
  1. It calls the OpenAI ChatCompletion API to turn the user question +into a good search query, using the prompt and tools from chat_query_rewrite.prompty.
  2. +
  3. It queries Azure AI Search for search results for that query +(optionally using the vector embeddings for that query).
  4. +
  5. It then calls the OpenAI ChatCompletion API to answer the question +based on the sources, using the prompt from chat_answer_question.prompty. +That call includes the past message history as well (or as many messages +fit inside the model’s token limit).
  6. +
+

The prompts are currently tailored to the sample data since they +start with “Assistant helps the company employees with their healthcare +plan questions, and questions about the employee handbook.” Modify the +chat_query_rewrite.prompty +and chat_answer_question.prompty +prompts to match your data.

+
Chat with vision
+

If you followed the instructions in docs/gpt4v.md to enable a GPT Vision model and then +select “Use GPT vision model”, then the chat tab will use the +chatreadretrievereadvision.py approach instead. This +approach is similar to the chatreadretrieveread.py +approach, with a few differences:

+
    +
  1. Step 1 is the same as before, except it uses the GPT-4 Vision model +instead of the default GPT-3.5 model.
  2. +
  3. For this step, it also calculates a vector embedding for the user +question using the +Computer Vision vectorize text API, and passes that to the Azure AI +Search to compare against the imageEmbeddings fields in the +indexed documents. For each matching document, it downloads the image +blob and converts it to a base 64 encoding.
  4. +
  5. When it combines the search results and user question, it includes +the base 64 encoded images, and sends along both the text and images to +the GPT4 Vision model (similar to this documentation +example). The model generates a response that includes citations to +the images, and the UI renders the base64 encoded images when a citation +is clicked.
  6. +
+

The prompt for step 2 is currently tailored to the sample data since +it starts with “You are an intelligent assistant helping analyze the +Annual Financial Report of Contoso Ltd.”. Modify the chat_answer_question_vision.prompty +prompt to match your data.

+

Ask tab

+

The ask tab uses the approach programmed in retrievethenread.py.

+
    +
  1. It queries Azure AI Search for search results for the user question +(optionally using the vector embeddings for that question).
  2. +
  3. It then combines the search results and user question, and calls the +OpenAI ChatCompletion API to answer the question based on the sources, +using the prompt from ask_answer_question.prompty.
  4. +
+

The prompt for step 2 is currently tailored to the sample data since +it starts with “You are an intelligent assistant helping Contoso Inc +employees with their healthcare plan questions and employee handbook +questions.” Modify ask_answer_question.prompty +to match your data.

+

Ask with vision

+

If you followed the instructions in docs/gpt4v.md to enable the GPT-4 Vision model and +then select “Use GPT vision model”, then the ask tab will use the +retrievethenreadvision.py approach instead. This approach +is similar to the retrievethenread.py approach, with a few +differences:

+
    +
  1. For this step, it also calculates a vector embedding for the user +question using the +Computer Vision vectorize text API, and passes that to the Azure AI +Search to compare against the imageEmbeddings fields in the +indexed documents. For each matching document, it downloads the image +blob and converts it to a base 64 encoding.
  2. +
  3. When it combines the search results and user question, it includes +the base 64 encoded images, and sends along both the text and images to +the GPT4 Vision model (similar to this documentation +example). The model generates a response that includes citations to +the images, and the UI renders the base64 encoded images when a citation +is clicked.
  4. +
+

The prompt for step 2 is currently tailored to the sample data since +it starts with “You are an intelligent assistant helping analyze the +Annual Financial Report of Contoso Ltd”. Modify the ask_answer_question_vision.prompty +prompt to match your data.

+

Making settings overrides +permanent

+

The UI provides a “Developer Settings” menu for customizing the +approaches, like disabling semantic ranker or using vector search. Those +settings are passed in the “context” field of the request to the +backend, and are not saved permanently. However, if you find a setting +that you do want to make permanent, there are two approaches:

+
    +
  1. Change the defaults in the frontend. You’ll find the defaults in +Chat.tsx and Ask.tsx. For example, this line +of code sets the default retrieval mode to Hybrid:

    +
    const [retrievalMode, setRetrievalMode] = useState<RetrievalMode>(RetrievalMode.Hybrid);
    +

    You can change the default to Text by changing the code to:

    +
    const [retrievalMode, setRetrievalMode] = useState<RetrievalMode>(RetrievalMode.Text);
  2. +
  3. Change the overrides in the backend. Each of the approaches has a +run method that takes a context parameter, and +the first line of code extracts the overrides from that +context. That’s where you can override any of the settings. +For example, to change the retrieval mode to text:

    +
    overrides = context.get("overrides", {})
    +overrides["retrieval_mode"] = "text"
    +

    By changing the setting on the backend, you can safely remove the +Developer Settings UI from the frontend, if you don’t wish to expose +that to your users.

  4. +
+

Improving answer quality

+

Once you are running the chat app on your own data and with your own +tailored system prompt, the next step is to test the app with questions +and note the quality of the answers. If you notice any answers that +aren’t as good as you’d like, here’s a process for improving them.

+

Identify the problem point

+

The first step is to identify where the problem is occurring. For +example, if using the Chat tab, the problem could be:

+
    +
  1. OpenAI ChatCompletion API is not generating a good search query +based on the user question
  2. +
  3. Azure AI Search is not returning good search results for the +query
  4. +
  5. OpenAI ChatCompletion API is not generating a good answer based on +the search results and user question
  6. +
+

You can look at the “Thought process” tab in the chat app to see each +of those steps, and determine which one is the problem.

+

Improving OpenAI +ChatCompletion results

+

If the problem is with the ChatCompletion API calls (steps 1 or 3 +above), you can try changing the relevant prompt.

+

Once you’ve changed the prompt, make sure you ask the same question +multiple times to see if the overall quality has improved, and run an evaluation when you’re +satisfied with the changes. The ChatCompletion API can yield different +results every time, even for a temperature of 0.0, but especially for a +higher temperature than that (like our default of 0.7 for step 3).

+

You can also try changing the ChatCompletion parameters, like +temperature, to see if that improves results for your domain.

+

Improving Azure AI Search +results

+

If the problem is with Azure AI Search (step 2 above), the first step +is to check what search parameters you’re using. Generally, the best +results are found with hybrid search (text + vectors) plus the +additional semantic re-ranking step, and that’s what we’ve enabled by +default. There may be some domains where that combination isn’t optimal, +however. Check out this blog post which evaluates +AI search strategies for a better understanding of the differences, +or watch this RAG +Deep Dive video on AI Search.

+

Configuring parameters in the +app

+

You can change many of the search parameters in the “Developer +settings” in the frontend and see if results improve for your queries. +The most relevant options:

+
+ + +
+

Configuring +parameters in the Azure Portal

+

You may find it easier to experiment with search options with the +index explorer in the Azure Portal. Open up the Azure AI Search +resource, select the Indexes tab, and select the index there.

+

Then use the JSON view of the search explorer, and make sure you +specify the same options you’re using in the app. For example, this +query represents a search with semantic ranker configured:

+
{
+  "search": "eye exams",
+  "queryType": "semantic",
+  "semanticConfiguration": "default",
+  "queryLanguage": "en-us",
+  "speller": "lexicon",
+  "top": 3
+}
+

You can also use the highlight parameter to see what +text is being matched in the content field in the search +results.

+
{
+    "search": "eye exams",
+    "highlight": "content"
+    ...
+}
+
+ + +
+

The search explorer works well for testing text, but is harder to use +with vectors, since you’d also need to compute the vector embedding and +send it in. It is probably easier to use the app frontend for testing +vectors/hybrid search.

+

Other approaches to +improve search results

+

Here are additional ways for improving the search results:

+
    +
  • Adding additional metadata to the “content” field, like the document +title, so that it can be matched in the search results. Modify searchmanager.py +to include more text in the content field.
  • +
  • Making additional fields searchable by the full text search step. +For example, the “sourcepage” field is not currently searchable, but you +could make that into a SearchableField with +searchable=True in searchmanager.py. +A change like that requires re-building +the index.
  • +
  • Using function calling to search by particular fields, like +searching by the filename. See this blog post on function +calling for structured retrieval.
  • +
  • Using a different splitting strategy for the documents, or modifying +the existing ones, to improve the chunks that are indexed. You can find +the currently available splitters in textsplitter.py.
  • +
+

Evaluating answer quality

+

Once you’ve made changes to the prompts or settings, you’ll want to +rigorously evaluate the results to see if they’ve improved. Follow the +evaluation guide to learn how to run +evaluations, review results, and compare answers across runs.

diff --git a/data/data_ingestion.html b/data/data_ingestion.html new file mode 100644 index 0000000000..b843344202 --- /dev/null +++ b/data/data_ingestion.html @@ -0,0 +1,230 @@ +

RAG chat: Data ingestion

+

The azure-search-openai-demo project can set up a +full RAG chat app on Azure AI Search and OpenAI so that you can chat on +custom data, like internal enterprise data or domain-specific knowledge +sets. For full instructions on setting up the project, consult the main README, and then return here for detailed +instructions on the data ingestion component.

+

The chat app provides two ways to ingest data: manual indexing and +integrated vectorization. This document explains the differences between +the two approaches and provides an overview of the manual indexing +process.

+ +

Supported document formats

+

In order to ingest a document format, we need a tool that can turn it +into text. By default, the manual indexing uses Azure Document +Intelligence (DI in the table below), but we also have local parsers for +several formats. The local parsers are not as sophisticated as Azure +Document Intelligence, but they can be used to decrease charges.

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FormatManual indexingIntegrated Vectorization
PDFYes (DI or local with PyPDF)Yes
HTMLYes (DI or local with BeautifulSoup)Yes
DOCX, PPTX, XLSXYes (DI)Yes
Images (JPG, PNG, BPM, TIFF, HEIFF)Yes (DI)Yes
TXTYes (Local)Yes
JSONYes (Local)Yes
CSVYes (Local)Yes
+

The Blob indexer used by the Integrated Vectorization approach also +supports a few additional +formats.

+

Manual indexing process

+

The prepdocs.py +script is responsible for both uploading and indexing documents. The +typical usage is to call it using scripts/prepdocs.sh +(Mac/Linux) or scripts/prepdocs.ps1 (Windows), as these +scripts will set up a Python virtual environment and pass in the +required parameters based on the current azd environment. +You can pass additional arguments directly to the script, for example +scripts/prepdocs.ps1 --removeall. Whenever +azd up or azd provision is run, the script is +called automatically.

+
+ + +
+

The script uses the following steps to index documents:

+
    +
  1. If it doesn’t yet exist, create a new index in Azure AI Search.
  2. +
  3. Upload the PDFs to Azure Blob Storage.
  4. +
  5. Split the PDFs into chunks of text.
  6. +
  7. Upload the chunks to Azure AI Search. If using vectors (the +default), also compute the embeddings and upload those alongside the +text.
  8. +
+

Chunking

+

We’re often asked why we need to break up the PDFs into chunks when +Azure AI Search supports searching large documents.

+

Chunking allows us to limit the amount of information we send to +OpenAI due to token limits. By breaking up the content, it allows us to +easily find potential chunks of text that we can inject into OpenAI. The +method of chunking we use leverages a sliding window of text such that +sentences that end one chunk will start the next. This allows us to +reduce the chance of losing the context of the text.

+

If needed, you can modify the chunking algorithm in +app/backend/prepdocslib/textsplitter.py.

+

Enhancing +search functionality with data categorization

+

To enhance search functionality, categorize data during the ingestion +process with the --category argument, for example +scripts/prepdocs.ps1 --category ExampleCategoryName. This +argument specifies the category to which the data belongs, enabling you +to filter search results based on these categories.

+

After running the script with the desired category, ensure these +categories are added to the ‘Include Category’ dropdown list. This can +be found in the developer settings in Settings.tsx. +The default option for this dropdown is “All”. By including specific +categories, you can refine your search results more effectively.

+

Indexing additional +documents

+

To upload more PDFs, put them in the data/ folder and run +./scripts/prepdocs.sh or +./scripts/prepdocs.ps1.

+

A recent +change added checks to see what’s been uploaded before. The prepdocs +script now writes an .md5 file with an MD5 hash of each file that gets +uploaded. Whenever the prepdocs script is re-run, that hash is checked +against the current hash and the file is skipped if it hasn’t +changed.

+

Removing documents

+

You may want to remove documents from the index. For example, if +you’re using the sample data, you may want to remove the documents that +are already in the index before adding your own.

+

To remove all documents, use +./scripts/prepdocs.sh --removeall or +./scripts/prepdocs.ps1 --removeall.

+

You can also remove individual documents by using the +--remove flag. Open either scripts/prepdocs.sh +or scripts/prepdocs.ps1 and replace /data/* +with /data/YOUR-DOCUMENT-FILENAME-GOES-HERE.pdf. Then run +scripts/prepdocs.sh --remove or +scripts/prepdocs.ps1 --remove.

+

Integrated Vectorization

+

Azure AI Search includes an integrated +vectorization feature, a cloud-based approach to data ingestion. +Integrated vectorization takes care of document format cracking, data +extraction, chunking, vectorization, and indexing, all with Azure +technologies.

+

See this +notebook to understand the process of setting up integrated +vectorization. We have integrated that code into our +prepdocs script, so you can use it without needing to +understand the details.

+

You must first explicitly enable +integrated vectorization in the azd environment to use +this feature.

+

This feature cannot be used on existing index. You need to create a +new index or drop and recreate an existing index. In the newly created +index schema, a new field ‘parent_id’ is added. This is used internally +by the indexer to manage life cycle of chunks.

+

This feature is not supported in the free SKU for Azure AI +Search.

+

Indexing of additional +documents

+

To add additional documents to the index, first upload them to your +data source (Blob storage, by default). Then navigate to the Azure +portal, find the index, and run it. The Azure AI Search indexer will +identify the new documents and ingest them into the index.

+

Removal of documents

+

To remove documents from the index, remove them from your data source +(Blob storage, by default). Then navigate to the Azure portal, find the +index, and run it. The Azure AI Search indexer will take care of +removing those documents from the index.

+

Scheduled indexing

+

If you would like the indexer to run automatically, you can set it up +to run +on a schedule.

+

Debugging tips

+

If you are not sure if a file successfully uploaded, you can query +the index from the Azure Portal or from the REST API. Open the index and +paste the queries below into the search bar.

+

To see all the filenames uploaded to the index:

+
{
+  "search": "*",
+  "count": true,
+  "top": 1,
+  "facets": ["sourcefile"]
+}
+

To search for specific filenames:

+
{
+  "search": "*",
+  "count": true,
+  "top": 1,
+  "filter": "sourcefile eq 'employee_handbook.pdf'",
+  "facets": ["sourcefile"]
+}
diff --git a/data/deploy_existing.html b/data/deploy_existing.html new file mode 100644 index 0000000000..2a8cef80a3 --- /dev/null +++ b/data/deploy_existing.html @@ -0,0 +1,209 @@ +

RAG chat: +Deploying with existing Azure resources

+

If you already have existing Azure resources, or if you want to +specify the exact name of new Azure Resource, you can do so by setting +azd environment values. You should set these values before +running azd up. Once you’ve set them, return to the deployment steps.

+ +

Resource group

+
    +
  1. Run +azd env set AZURE_RESOURCE_GROUP {Name of existing resource group}
  2. +
  3. Run +azd env set AZURE_LOCATION {Location of existing resource group}
  4. +
+

OpenAI resource

+

Azure OpenAI

+
    +
  1. Run +azd env set AZURE_OPENAI_SERVICE {Name of existing OpenAI service}
  2. +
  3. Run +azd env set AZURE_OPENAI_RESOURCE_GROUP {Name of existing resource group that OpenAI service is provisioned to}
  4. +
  5. Run +azd env set AZURE_OPENAI_LOCATION {Location of existing OpenAI service}
  6. +
  7. Run +azd env set AZURE_OPENAI_CHATGPT_DEPLOYMENT {Name of existing chat deployment}. +Only needed if your chat deployment is not the default ‘chat’.
  8. +
  9. Run +azd env set AZURE_OPENAI_CHATGPT_MODEL {Model name of existing chat deployment}. +Only needed if your chat model is not the default ‘gpt-35-turbo’.
  10. +
  11. Run +azd env set AZURE_OPENAI_CHATGPT_DEPLOYMENT_VERSION {Version string for existing chat deployment}. +Only needed if your chat deployment model version is not the default +‘0125’. You definitely need to change this if you changed the +model.
  12. +
  13. Run +azd env set AZURE_OPENAI_CHATGPT_DEPLOYMENT_SKU {Name of SKU for existing chat deployment}. +Only needed if your chat deployment SKU is not the default ‘Standard’, +like if it is ‘GlobalStandard’ instead.
  14. +
  15. Run +azd env set AZURE_OPENAI_EMB_DEPLOYMENT {Name of existing embedding deployment}. +Only needed if your embeddings deployment is not the default +‘embedding’.
  16. +
  17. Run +azd env set AZURE_OPENAI_EMB_MODEL_NAME {Model name of existing embedding deployment}. +Only needed if your embeddings model is not the default +‘text-embedding-ada-002’.
  18. +
  19. Run +azd env set AZURE_OPENAI_EMB_DIMENSIONS {Dimensions for existing embedding deployment}. +Only needed if your embeddings model is not the default +‘text-embedding-ada-002’.
  20. +
  21. Run +azd env set AZURE_OPENAI_EMB_DEPLOYMENT_VERSION {Version string for existing embedding deployment}. +If your embeddings deployment is one of the ‘text-embedding-3’ models, +set this to the number 1.
  22. +
  23. This project does not use keys when authenticating to Azure +OpenAI. However, if your Azure OpenAI service must have key access +enabled for some reason (like for use by other projects), then run +azd env set AZURE_OPENAI_DISABLE_KEYS false. The default +value is true so you should only run the command if you +need key access.
  24. +
+

When you run azd up after and are prompted to select a +value for openAiResourceGroupLocation, make sure to select +the same location as the existing OpenAI resource group.

+

Openai.com OpenAI

+
    +
  1. Run azd env set OPENAI_HOST openai
  2. +
  3. Run +azd env set OPENAI_ORGANIZATION {Your OpenAI organization}
  4. +
  5. Run +azd env set OPENAI_API_KEY {Your OpenAI API key}
  6. +
  7. Run azd up
  8. +
+

You can retrieve your OpenAI key by checking your user page +and your organization by navigating to your +organization page. Learn more about creating an OpenAI free trial at +this link. Do not +check your key into source control.

+

When you run azd up after and are prompted to select a +value for openAiResourceGroupLocation, you can select any +location as it will not be used.

+

Azure AI Search resource

+
    +
  1. Run +azd env set AZURE_SEARCH_SERVICE {Name of existing Azure AI Search service}
  2. +
  3. Run +azd env set AZURE_SEARCH_SERVICE_RESOURCE_GROUP {Name of existing resource group with ACS service}
  4. +
  5. If that resource group is in a different location than the one +you’ll pick for the azd up step, then run +azd env set AZURE_SEARCH_SERVICE_LOCATION {Location of existing service}
  6. +
  7. If the search service’s SKU is not standard, then run +azd env set AZURE_SEARCH_SERVICE_SKU {Name of SKU}. If you +specify the free tier, then your app will no longer be able to use +semantic ranker. Be advised that search +SKUs cannot be changed. (See +other possible SKU values)
  8. +
  9. If you have an existing index that is set up with all the expected +fields, then run +azd env set AZURE_SEARCH_INDEX {Name of existing index}. +Otherwise, the azd up command will create a new index.
  10. +
+

You can also customize the search service (new or existing) for +non-English searches:

+
    +
  1. To configure the language of the search query to a value other than +“en-US”, run +azd env set AZURE_SEARCH_QUERY_LANGUAGE {Name of query language}. +(See +other possible values)
  2. +
  3. To turn off the spell checker, run +azd env set AZURE_SEARCH_QUERY_SPELLER none. Consult this +table to determine if spell checker is supported for your query +language.
  4. +
  5. To configure the name of the analyzer to use for a searchable text +field to a value other than “en.microsoft”, run +azd env set AZURE_SEARCH_ANALYZER_NAME {Name of analyzer name}. +(See +other possible values)
  6. +
+

Azure App +Service Plan and App Service resources

+
    +
  1. Run +azd env set AZURE_APP_SERVICE_PLAN {Name of existing Azure App Service Plan}
  2. +
  3. Run +azd env set AZURE_APP_SERVICE {Name of existing Azure App Service}.
  4. +
  5. Run +azd env set AZURE_APP_SERVICE_SKU {SKU of Azure App Service, defaults to B1}.
  6. +
+ +
    +
  1. Run +azd env set AZURE_APPLICATION_INSIGHTS {Name of existing Azure App Insights}.
  2. +
  3. Run +azd env set AZURE_APPLICATION_INSIGHTS_DASHBOARD {Name of existing Azure App Insights Dashboard}.
  4. +
  5. Run +azd env set AZURE_LOG_ANALYTICS {Name of existing Azure Log Analytics Workspace Name}.
  6. +
+

Azure Computer Vision +resources

+
    +
  1. Run +azd env set AZURE_COMPUTER_VISION_SERVICE {Name of existing Azure Computer Vision Service Name}
  2. +
  3. Run +azd env set AZURE_COMPUTER_VISION_RESOURCE_GROUP {Name of existing Azure Computer Vision Resource Group Name}
  4. +
  5. Run +azd env set AZURE_COMPUTER_VISION_LOCATION {Name of existing Azure Computer Vision Location}
  6. +
  7. Run +azd env set AZURE_COMPUTER_VISION_SKU {SKU of Azure Computer Vision service, defaults to F0}
  8. +
+

Azure Document +Intelligence resource

+

In order to support analysis of many document formats, this +repository uses a preview version of Azure Document Intelligence +(formerly Form Recognizer) that is only available in limited +regions. If your existing resource is in one of those regions, then +you can re-use it by setting the following environment variables:

+
    +
  1. Run +azd env set AZURE_DOCUMENTINTELLIGENCE_SERVICE {Name of existing Azure AI Document Intelligence service}
  2. +
  3. Run +azd env set AZURE_DOCUMENTINTELLIGENCE_LOCATION {Location of existing service}
  4. +
  5. Run +azd env set AZURE_DOCUMENTINTELLIGENCE_RESOURCE_GROUP {Name of resource group with existing service, defaults to main resource group}
  6. +
  7. Run +azd env set AZURE_DOCUMENTINTELLIGENCE_SKU {SKU of existing service, defaults to S0}
  8. +
+

Azure Speech resource

+
    +
  1. Run +azd env set AZURE_SPEECH_SERVICE {Name of existing Azure Speech service}
  2. +
  3. Run +azd env set AZURE_SPEECH_SERVICE_RESOURCE_GROUP {Name of existing resource group with speech service}
  4. +
  5. If that resource group is in a different location than the one +you’ll pick for the azd up step, then run +azd env set AZURE_SPEECH_SERVICE_LOCATION {Location of existing service}
  6. +
  7. If the speech service’s SKU is not “S0”, then run +azd env set AZURE_SPEECH_SERVICE_SKU {Name of SKU}.
  8. +
+

Other Azure resources

+

You can also use existing Azure AI Storage Accounts. See +./infra/main.parameters.json for list of environment +variables to pass to azd env set to configure those +existing resources.

diff --git a/data/deploy_features.html b/data/deploy_features.html new file mode 100644 index 0000000000..2e5568a5b8 --- /dev/null +++ b/data/deploy_features.html @@ -0,0 +1,395 @@ +

RAG chat: Enabling optional +features

+

This document covers optional features that can be enabled in the +deployed Azure resources. You should typically enable these features +before running azd up. Once you’ve set them, return to the +deployment steps.

+ +

Using GPT-4

+

(Instructions for GPT-4, GPT-4o, +and GPT-4o mini models are also included here.)

+

We generally find that most developers are able to get high-quality +answers using GPT-3.5. However, if you want to try GPT-4, GPT-4o, or +GPT-4o mini, you can do so by following these steps:

+

Execute the following commands inside your terminal:

+
    +
  1. To set the name of the deployment, run this command with a unique +name in your Azure OpenAI account. You can use any deployment name, as +long as it’s unique in your Azure OpenAI account.

    +
    azd env set AZURE_OPENAI_CHATGPT_DEPLOYMENT <your-deployment-name>
    +

    For example:

    +
    azd env set AZURE_OPENAI_CHATGPT_DEPLOYMENT chat4
  2. +
  3. To set the GPT model name to a gpt-4, +gpt-4o, or gpt-4o mini version from +the available +models, run this command with the appropriate GPT model name.

    +

    For GPT-4:

    +
    azd env set AZURE_OPENAI_CHATGPT_MODEL gpt-4
    +

    For GPT-4o:

    +
    azd env set AZURE_OPENAI_CHATGPT_MODEL gpt-4o
    +

    For GPT-4o mini:

    +
    azd env set AZURE_OPENAI_CHATGPT_MODEL gpt-4o-mini
  4. +
  5. To set the Azure OpenAI deployment SKU name, run this command +with the +desired SKU name.

    +
    azd env set AZURE_OPENAI_CHATGPT_DEPLOYMENT_SKU GlobalStandard
  6. +
  7. To set the Azure OpenAI deployment capacity, run this command +with the desired capacity.

    +
    azd env set AZURE_OPENAI_CHATGPT_DEPLOYMENT_CAPACITY 10
  8. +
  9. To set the Azure OpenAI deployment version from the available +versions, run this command with the appropriate version.

    +

    For GPT-4:

    +
    azd env set AZURE_OPENAI_CHATGPT_DEPLOYMENT_VERSION turbo-2024-04-09
    +

    For GPT-4o:

    +
    azd env set AZURE_OPENAI_CHATGPT_DEPLOYMENT_VERSION 2024-05-13
    +

    For GPT-4o mini:

    +
    azd env set AZURE_OPENAI_CHATGPT_DEPLOYMENT_VERSION 2024-07-18
  10. +
  11. To update the deployment with the new parameters, run this +command.

    +
    azd up
  12. +
+
+

[!NOTE] To revert back to GPT 3.5, run the following commands:

+
    +
  • azd env set AZURE_OPENAI_CHATGPT_DEPLOYMENT chat to set +the name of your old GPT 3.5 deployment.
  • +
  • azd env set AZURE_OPENAI_CHATGPT_MODEL gpt-35-turbo to +set the name of your old GPT 3.5 model.
  • +
  • azd env set AZURE_OPENAI_CHATGPT_DEPLOYMENT_CAPACITY 30 +to set the capacity of your old GPT 3.5 deployment.
  • +
  • azd env set AZURE_OPENAI_CHATGPT_DEPLOYMENT_SKU Standard +to set the Sku name back to Standard.
  • +
  • azd env set AZURE_OPENAI_CHATGPT_DEPLOYMENT_VERSION 0125 +to set the version number of your old GPT 3.5.
  • +
  • azd up to update the provisioned resources.
  • +
+

Note that this does not delete your GPT-4 deployment; it just makes +your application create a new or reuse an old GPT 3.5 deployment. If you +want to delete it, you can go to your Azure OpenAI studio and do so.

+
+

Using text-embedding-3 +models

+

By default, the deployed Azure web app uses the +text-embedding-ada-002 embedding model. If you want to use +one of the text-embedding-3 models, you can do so by following these +steps:

+
    +
  1. Run one of the following commands to set the desired model:

    +
    azd env set AZURE_OPENAI_EMB_MODEL_NAME text-embedding-3-small
    +
    azd env set AZURE_OPENAI_EMB_MODEL_NAME text-embedding-3-large
  2. +
  3. Specify the desired dimensions of the model: (from 256-3072, +model dependent)

    +
    azd env set AZURE_OPENAI_EMB_DIMENSIONS 256
  4. +
  5. Set the model version to “1” (the only version as of March +2024):

    +
    azd env set AZURE_OPENAI_EMB_DEPLOYMENT_VERSION 1
  6. +
  7. When prompted during azd up, make sure to select a +region for the OpenAI resource group location that supports the +text-embedding-3 models. There are limited +regions available.

  8. +
+

If you have already deployed:

+
    +
  • You’ll need to change the deployment name by running +azd env set AZURE_OPENAI_EMB_DEPLOYMENT <new-deployment-name>
  • +
  • You’ll need to create a new index, and re-index all of the data +using the new model. You can either delete the current index in the +Azure Portal, or create an index with a different name by running +azd env set AZURE_SEARCH_INDEX new-index-name. When you +next run azd up, the new index will be created and the data +will be re-indexed.
  • +
  • If your OpenAI resource is not in one of the supported regions, you +should delete openAiResourceGroupLocation from +.azure/YOUR-ENV-NAME/config.json. When running +azd up, you will be prompted to select a new region.
  • +
+
+

![NOTE] The text-embedding-3 models are not currently supported by +the integrated vectorization feature.

+
+

Enabling GPT-4 Turbo with +Vision

+

⚠️ This feature is not currently compatible with integrated +vectorization.

+

This section covers the integration of GPT-4 Vision with Azure AI +Search. Learn how to enhance your search capabilities with the power of +image and text indexing, enabling advanced search functionalities over +diverse document types. For a detailed guide on setup and usage, visit +our Enabling GPT-4 Turbo with Vision page.

+

Enabling +media description with Azure Content Understanding

+

⚠️ This feature is not currently compatible with integrated vectorization. +It is compatible with GPT vision integration, +but the features provide similar functionality.

+

By default, if your documents contain image-like figures, the data +ingestion process will ignore those figures, so users will not be able +to ask questions about them.

+

You can optionably enable the description of media content using +Azure Content Understanding. When enabled, the data ingestion process +will send figures to Azure Content Understanding and replace the figure +with the description in the indexed document.

+

To enable media description with Azure Content Understanding, +run:

+
azd env set USE_MEDIA_DESCRIBER_AZURE_CU true
+

If you have already run azd up, you will need to run +azd provision to create the new Content Understanding +service. If you have already indexed your documents and want to re-index +them with the media descriptions, first remove the existing +documents and then re-ingest the +data.

+

⚠️ This feature does not yet support DOCX, PPTX, or XLSX formats. If +you have figures in those formats, they will be ignored. Convert them +first to PDF or image formats to enable media description.

+

Enabling client-side chat +history

+

📺 Watch: (RAG +Deep Dive series) Storing chat history

+

This feature allows users to view the chat history of their +conversation, stored in the browser using IndexedDB. +That means the chat history will be available only on the device where +the chat was initiated. To enable browser-stored chat history, run:

+
azd env set USE_CHAT_HISTORY_BROWSER true
+

Enabling +persistent chat history with Azure Cosmos DB

+

📺 Watch: (RAG +Deep Dive series) Storing chat history

+

This feature allows authenticated users to view the chat history of +their conversations, stored in the server-side storage using Azure Cosmos +DB.This option requires that authentication be enabled. The chat +history will be persistent and accessible from any device where the user +logs in with the same account. To enable server-stored chat history, +run:

+
azd env set USE_CHAT_HISTORY_COSMOS true
+

When both the browser-stored and Cosmos DB options are enabled, +Cosmos DB will take precedence over browser-stored chat history.

+

Enabling language picker

+

You can optionally enable the language picker to allow users to +switch between different languages. Currently, it supports English, +Spanish, French, and Japanese.

+

To add support for additional languages, create new locale files and +update app/frontend/src/i18n/config.ts accordingly. To +enable language picker, run:

+
azd env set ENABLE_LANGUAGE_PICKER true
+

Enabling speech input/output

+

📺 Watch a +short video of speech input/output

+

You can optionally enable speech input/output by setting the azd +environment variables.

+

Speech Input

+

The speech input feature uses the browser’s built-in Speech +Recognition API. It may not work in all browser/OS combinations. To +enable speech input, run:

+
azd env set USE_SPEECH_INPUT_BROWSER true
+

Speech Output

+

The speech output feature uses Azure +Speech Service for speech-to-text. Additional costs will be incurred +for using the Azure Speech Service. See +pricing. To enable speech output, run:

+
azd env set USE_SPEECH_OUTPUT_AZURE true
+

To set the +voice for the speech output, run:

+
azd env set AZURE_SPEECH_SERVICE_VOICE en-US-AndrewMultilingualNeural
+

Alternatively you can use the browser’s built-in Speech +Synthesis API. It may not work in all browser/OS combinations. To +enable speech output, run:

+
azd env set USE_SPEECH_OUTPUT_BROWSER true
+

Enabling Integrated +Vectorization

+

⚠️ This feature is not currently compatible with the GPT vision integration.

+

Azure AI search recently introduced an integrated +vectorization feature in preview mode. This feature is a cloud-based +approach to data ingestion, which takes care of document format +cracking, data extraction, chunking, vectorization, and indexing, all +with Azure technologies.

+

To enable integrated vectorization with this sample:

+
    +
  1. If you’ve previously deployed, delete the existing search index. +🗑️

  2. +
  3. To enable the use of integrated vectorization, run:

    +
    azd env set USE_FEATURE_INT_VECTORIZATION true
  4. +
  5. If you’ve already deployed your app, then you can run just the +provision step:

    +
    azd provision
    +

    That will set up necessary RBAC roles and configure the integrated +vectorization feature on your search service.

    +

    If you haven’t deployed your app yet, then you should run the full +azd up after configuring all optional features.

  6. +
  7. You can view the resources such as the indexer and skillset in +Azure Portal and monitor the status of the vectorization +process.

  8. +
+

Enabling authentication

+

By default, the deployed Azure web app will have no authentication or +access restrictions enabled, meaning anyone with routable network access +to the web app can chat with your indexed data. If you’d like to +automatically setup authentication and user login as part of the +azd up process, see this +guide.

+

Alternatively, you can manually require authentication to your Azure +Active Directory by following the Add +app authentication tutorial and set it up against the deployed web +app.

+

To then limit access to a specific set of users or groups, you can +follow the steps from Restrict +your Microsoft Entra app to a set of users by changing “Assignment +Required?” option under the Enterprise Application, and then assigning +users/groups access. Users not granted explicit access will receive the +error message -AADSTS50105: Your administrator has configured the +application to block users unless they are specifically +granted (‘assigned’) access to the application.-

+

Enabling login +and document level access control

+

By default, the deployed Azure web app allows users to chat with all +your indexed data. You can enable an optional login system using Azure +Active Directory to restrict access to indexed data based on the logged +in user. Enable the optional login and document level access control +system by following this guide.

+

Enabling user document +upload

+

You can enable an optional user document upload system to allow users +to upload their own documents and chat with them. This feature requires +you to first enable login and document +level access control. Then you can enable the optional user document +upload system by setting an azd environment variable:

+

azd env set USE_USER_UPLOAD true

+

Then you’ll need to run azd up to provision an Azure +Data Lake Storage Gen2 account for storing the user-uploaded documents. +When the user uploads a document, it will be stored in a directory in +that account with the same name as the user’s Entra object id, and will +have ACLs associated with that directory. When the ingester runs, it +will also set the oids of the indexed chunks to the user’s +Entra object id.

+

If you are enabling this feature on an existing index, you should +also update your index to have the new storageUrl +field:

+
python ./scripts/manageacl.py  -v --acl-action enable_acls
+

And then update existing search documents with the storage URL of the +main Blob container:

+
python ./scripts/manageacl.py  -v --acl-action update_storage_urls --url <https://YOUR-MAIN-STORAGE-ACCOUNT.blob.core.windows.net/content/>
+

Going forward, all uploaded documents will have their +storageUrl set in the search index. This is necessary to +disambiguate user-uploaded documents from admin-uploaded documents.

+

Enabling CORS for an +alternate frontend

+

By default, the deployed Azure web app will only allow requests from +the same origin. To enable CORS for a frontend hosted on a different +origin, run:

+
    +
  1. Run +azd env set ALLOWED_ORIGIN https://<your-domain.com>
  2. +
  3. Run azd up
  4. +
+

For the frontend code, change BACKEND_URI in +api.ts to point at the deployed backend URL, so that all +fetch requests will be sent to the deployed backend.

+

For an alternate frontend that’s written in Web Components and +deployed to Static Web Apps, check out azure-search-openai-javascript +and its guide on using +a different backend. Both these repositories adhere to the same HTTP protocol for AI chat +apps.

+

Adding an OpenAI load +balancer

+

As discussed in more details in our productionizing guide, you may want to +consider implementing a load balancer between OpenAI instances if you +are consistently going over the TPM limit. Fortunately, this repository +is designed for easy integration with other repositories that create +load balancers for OpenAI instances. For seamless integration +instructions with this sample, please check:

+ +

Deploying with private +endpoints

+

It is possible to deploy this app with public access disabled, using +Azure private endpoints and private DNS Zones. For more details, read the private deployment guide. That +requires a multi-stage provisioning, so you will need to do more than +just azd up after setting the environment variables.

+

Using local parsers

+

If you want to decrease the charges by using local parsers instead of +Azure Document Intelligence, you can set environment variables before +running the data ingestion script. +Note that local parsers will generally be not as sophisticated.

+
    +
  1. Run azd env set USE_LOCAL_PDF_PARSER true to use the +local PDF parser.
  2. +
  3. Run azd env set USE_LOCAL_HTML_PARSER true to use the +local HTML parser.
  4. +
+

The local parsers will be used the next time you run the data +ingestion script. To use these parsers for the user document upload +system, you’ll need to run azd provision to update the web +app to use the local parsers.

diff --git a/data/deploy_freetrial.html b/data/deploy_freetrial.html new file mode 100644 index 0000000000..1638b5418a --- /dev/null +++ b/data/deploy_freetrial.html @@ -0,0 +1,40 @@ +

RAG chat: +Deploying with a free trial account

+

If you have just created an Azure free trial account and are using +the free trial credits, there are several modifications you need to +make, due to restrictions on the free trial account.

+

Follow these instructions before you run +azd up.

+

Accomodate for low OpenAI +quotas

+

The free trial accounts currently get a max of 1K TPM +(tokens-per-minute), whereas our Bicep templates try to allocate 30K +TPM.

+

To reduce the TPM allocation, run these commands:

+
azd env set AZURE_OPENAI_CHATGPT_DEPLOYMENT_CAPACITY 1
+azd env set AZURE_OPENAI_EMB_DEPLOYMENT_CAPACITY 1
+

Alternatively, if you have an OpenAI.com account, you can use that +instead:

+
azd env set OPENAI_HOST openai
+azd env set OPENAI_ORGANIZATION {Your OpenAI organization}
+azd env set OPENAI_API_KEY {Your OpenAI API key}
+

Accomodate for +Azure Container Apps restrictions

+

By default, this project deploys to Azure Container Apps, using a +remote build process that builds the Docker image in the cloud. +Unfortunately, free trial accounts cannot use that remote build +process.

+

You have two options:

+
    +
  1. Comment out or delete remoteBuild: true in +azure.yaml, and make sure you have Docker installed in your +environment.

  2. +
  3. Deploy using App Service instead:

    +
      +
    • Comment out host: containerapp and uncomment +host: appservice in the azure.yaml file.

    • +
    • Set the deployment target to appservice:

      +
      azd env set DEPLOYMENT_TARGET appservice
    • +
  4. +
diff --git a/data/deploy_lowcost.html b/data/deploy_lowcost.html new file mode 100644 index 0000000000..866b9c1a23 --- /dev/null +++ b/data/deploy_lowcost.html @@ -0,0 +1,122 @@ +

RAG chat: Deploying with +minimal costs

+

This AI RAG chat application is designed to be easily deployed using +the Azure Developer CLI, which provisions the infrastructure according +to the Bicep files in the infra folder. Those files +describe each of the Azure resources needed, and configures their SKU +(pricing tier) and other parameters. Many Azure services offer a free +tier, but the infrastructure files in this project do not +default to the free tier as there are often limitations in that +tier.

+

However, if your goal is to minimize costs while prototyping your +application, follow the steps below before running +azd up. Once you’ve gone through these steps, return to the +deployment steps.

+

📺 Live stream: +Deploying from a free account

+
    +
  1. Log in to your Azure account using the Azure Developer CLI:

    +
    azd auth login
  2. +
  3. Create a new azd environment for the free resource group:

    +
    azd env new
    +

    Enter a name that will be used for the resource group. This will +create a new folder in the .azure folder, and set it as the +active environment for any calls to azd going +forward.

  4. +
  5. Switch from Azure Container Apps to the free tier of Azure App +Service:

    +

    Azure Container Apps has a consumption-based pricing model that is +very low cost, but it is not free, plus Azure Container Registry costs a +small amount each month.

    +

    To deploy to App Service instead:

    +
      +
    • Comment out host: containerapp and uncomment +host: appservice in the azure.yaml file.

    • +
    • Set the deployment target to appservice:

      +
      azd env set DEPLOYMENT_TARGET appservice
    • +
    • Set the App Service SKU to the free tier:

      +
      azd env set AZURE_APP_SERVICE_SKU F1
    • +
    +

    Limitation: You are only allowed a certain number of free App Service +instances per region. If you have exceeded your limit in a region, you +will get an error during the provisioning stage. If that happens, you +can run azd down, then azd env new to create a +new environment with a new region.

  6. +
  7. Use the free tier of Azure AI Search:

    +
    azd env set AZURE_SEARCH_SERVICE_SKU free
    +

    Limitations:

    +
      +
    1. You are only allowed one free search service across all regions. If +you have one already, either delete that service or follow instructions +to reuse your existing search +service.
    2. +
    3. The free tier does not support semantic ranker, so the app UI will +no longer display the option to use the semantic ranker. Note that will +generally result in decreased +search relevance.
    4. +
  8. +
  9. Use the free tier of Azure Document Intelligence (used in +analyzing files):

    +
    azd env set AZURE_DOCUMENTINTELLIGENCE_SKU F0
    +

    Limitation for PDF files:

    +

    The free tier will only scan the first two pages of each PDF. In our +sample documents, those first two pages are just title pages, so you +won’t be able to get answers from the documents. You can either use your +own documents that are only 2-pages long, or you can use a local Python +package for PDF parsing by setting:

    +
    azd env set USE_LOCAL_PDF_PARSER true
    +

    Limitation for HTML files:

    +

    The free tier will only scan the first two pages of each HTML file. +So, you might not get very accurate answers from the files. You can +either use your own files that are only 2-pages long, or you can use a +local Python package for HTML parsing by setting:

    +
    azd env set USE_LOCAL_HTML_PARSER true
  10. +
  11. Use the free tier of Azure Cosmos DB:

    +
    azd env set AZURE_COSMOSDB_SKU free
    +

    Limitation: You can have only one free Cosmos DB account. To keep +your account free of charge, ensure that you do not exceed the free tier +limits. For more information, see the Azure +Cosmos DB lifetime free tier.

  12. +
  13. ⚠️ This step is currently only possible if you’re deploying to +App Service (see +issue 2281):

    +

    Turn off Azure Monitor (Application Insights):

    +
    azd env set AZURE_USE_APPLICATION_INSIGHTS false
    +

    Application Insights is quite inexpensive already, so turning this +off may not be worth the costs saved, but it is an option for those who +want to minimize costs.

  14. +
  15. Use OpenAI.com instead of Azure OpenAI: This should not be +necessary, as the costs are same for both services, but you may need +this step if your account does not have access to Azure OpenAI for some +reason.

    +
    azd env set OPENAI_HOST openai
    +azd env set OPENAI_ORGANIZATION {Your OpenAI organization}
    +azd env set OPENAI_API_KEY {Your OpenAI API key}
    +

    Both Azure OpenAI and openai.com OpenAI accounts will incur costs, +based on tokens used, but the costs are fairly low for the amount of +sample data (less than $10).

  16. +
  17. Disable vector search:

    +
    azd env set USE_VECTORS false
    +

    By default, the application computes vector embeddings for documents +during the data ingestion phase, and then computes a vector embedding +for user questions asked in the application. Those computations require +an embedding model, which incurs costs per tokens used. The costs are +fairly low, so the benefits of vector search would typically outweigh +the costs, but it is possible to disable vector support. If you do so, +the application will fall back to a keyword search, which is less +accurate.

  18. +
  19. Once you’ve made the desired customizations, follow the steps in +the README to run +azd up. We recommend using “eastus” as the region, for +availability reasons.

  20. +
+

Reducing costs locally

+

To save costs for local development, you could use an +OpenAI-compatible model. Follow steps in local development +guide.

diff --git a/data/deploy_private.html b/data/deploy_private.html new file mode 100644 index 0000000000..a22c4d8fb4 --- /dev/null +++ b/data/deploy_private.html @@ -0,0 +1,93 @@ + +

RAG chat: Deploying with +private access

+

📺 Watch: (RAG +Deep Dive series) Private network deployment

+

The azure-search-openai-demo project can set up a +full RAG chat app on Azure AI Search and OpenAI so that you can chat on +custom data, like internal enterprise data or domain-specific knowledge +sets. For full instructions on setting up the project, consult the main README, and then return here for detailed +instructions on configuring private endpoints.

+

⚠️ This feature is not yet compatible with Azure Container Apps, so +you will need to deploy to Azure App +Service instead.

+

If you want to disable public access when deploying the Chat App, you +can do so by setting azd environment values.

+

Before you begin

+

Deploying with public access disabled adds additional cost to your +deployment. Please see pricing for the following products:

+
    +
  1. Private +Endpoints +
      +
    1. The exact number of private endpoints created depends on the optional features used.
    2. +
  2. +
  3. Private +DNS Zones
  4. +
+

Environment +variables controlling private access

+
    +
  1. AZURE_PUBLIC_NETWORK_ACCESS: Controls the value of +public network access on supported Azure resources. Valid values are +‘Enabled’ or ‘Disabled’. +
      +
    1. When public network access is ‘Enabled’, Azure resources are open to +the internet.
    2. +
    3. When public network access is ‘Disabled’, Azure resources are only +accessible over a virtual network.
    4. +
  2. +
  3. AZURE_USE_PRIVATE_ENDPOINT: Controls deployment of private +endpoints which connect Azure resources to the virtual network. +
      +
    1. When set to ‘true’, ensures private endpoints are deployed for +connectivity even when AZURE_PUBLIC_NETWORK_ACCESS is +‘Disabled’.
    2. +
    3. Note that private endpoints do not make the chat app accessible from +the internet. Connections must be initiated from inside the virtual +network.
    4. +
  4. +
+ +
    +
  1. Deploy the app with private endpoints enabled and public access +enabled.
  2. +
+
azd env set AZURE_USE_PRIVATE_ENDPOINT true
+azd env set AZURE_PUBLIC_NETWORK_ACCESS Enabled
+azd up
+
    +
  1. Validate that you can connect to the chat app and it’s working as +expected from the internet.
  2. +
  3. Re-provision the app with public access disabled.
  4. +
+
azd env set AZURE_PUBLIC_NETWORK_ACCESS Disabled
+azd provision
+
    +
  1. Log into your network using a tool like Azure VPN +Gateway and validate that you can connect to the chat app from +inside the network.
  2. +
diff --git a/data/deploy_troubleshooting.html b/data/deploy_troubleshooting.html new file mode 100644 index 0000000000..38e534bd00 --- /dev/null +++ b/data/deploy_troubleshooting.html @@ -0,0 +1,35 @@ +

RAG chat: Troubleshooting +deployment

+

If you are experiencing an error when deploying the RAG chat solution +using the deployment steps, this +guide will help you troubleshoot common issues.

+
    +
  1. You’re attempting to create resources in regions not enabled for +Azure OpenAI (e.g. East US 2 instead of East US), or where the model +you’re trying to use isn’t enabled. See this matrix of model +availability.

  2. +
  3. You’ve exceeded a quota, most often number of resources per +region. See this article on quotas +and limits.

  4. +
  5. You’re getting “same resource name not allowed” conflicts. That’s +likely because you’ve run the sample multiple times and deleted the +resources you’ve been creating each time, but are forgetting to purge +them. Azure keeps resources for 48 hours unless you purge from soft +delete. See this +article on purging resources.

  6. +
  7. You see CERTIFICATE_VERIFY_FAILED when the +prepdocs.py script runs. That’s typically due to incorrect +SSL certificates setup on your machine. Try the suggestions in this StackOverflow +answer.

  8. +
  9. After running azd up and visiting the website, you +see a ‘404 Not Found’ in the browser. Wait 10 minutes and try again, as +it might be still starting up. Then try running azd deploy +and wait again. If you still encounter errors with the deployed app and +are deploying to App Service, consult the guide on debugging App Service +deployments. Please file an issue if the logs don’t help you resolve +the error.

  10. +
diff --git a/data/evaluation.html b/data/evaluation.html new file mode 100644 index 0000000000..500bcda7ea --- /dev/null +++ b/data/evaluation.html @@ -0,0 +1,131 @@ +

Evaluating the RAG answer +quality

+

📺 Watch: (RAG +Deep Dive series) Evaluating RAG answer quality

+

Follow these steps to evaluate the quality of the answers generated +by the RAG flow.

+ +

Deploy an evaluation model

+
    +
  1. Run this command to tell azd to deploy a GPT-4 level +model for evaluation:

    +
    azd env set USE_EVAL true
  2. +
  3. Set the capacity to the highest possible value to ensure that the +evaluation runs relatively quickly. Even with a high capacity, it can +take a long time to generate ground truth data and run bulk +evaluations.

    +
    azd env set AZURE_OPENAI_EVAL_DEPLOYMENT_CAPACITY 100
    +

    By default, that will provision a gpt-4o model, version +2024-08-06. To change those settings, set the azd +environment variables AZURE_OPENAI_EVAL_MODEL and +AZURE_OPENAI_EVAL_MODEL_VERSION to the desired +values.

  4. +
  5. Then, run the following command to provision the model:

    +
    azd provision
  6. +
+

Setup the evaluation +environment

+

Make a new Python virtual environment and activate it. This is +currently required due to incompatibilities between the dependencies of +the evaluation script and the main project.

+
python -m venv .evalenv
+
source .evalenv/bin/activate
+

Install all the dependencies for the evaluation script by running the +following command:

+
pip install -r evals/requirements.txt
+

Generate ground truth data

+

Modify the search terms and tasks in +evals/generate_config.json to match your domain.

+

Generate ground truth data by running the following command:

+
python evals/generate_ground_truth.py --numquestions=200 --numsearchdocs=1000
+

The options are:

+
    +
  • numquestions: The number of questions to generate. We +suggest at least 200.
  • +
  • numsearchdocs: The number of documents (chunks) to +retrieve from your search index. You can leave off the option to fetch +all documents, but that will significantly increase time it takes to +generate ground truth data. You may want to at least start with a +subset.
  • +
  • kgfile: An existing RAGAS knowledge base JSON file, +which is usually ground_truth_kg.json. You may want to +specify this if you already created a knowledge base and just want to +tweak the question generation steps.
  • +
  • groundtruthfile: The file to write the generated ground +truth answwers. By default, this is +evals/ground_truth.jsonl.
  • +
+

🕰️ This may take a long time, possibly several hours, depending on +the size of the search index.

+

Review the generated data in evals/ground_truth.jsonl +after running that script, removing any question/answer pairs that don’t +seem like realistic user input.

+

Run bulk evaluation

+

Review the configuration in evals/eval_config.json to +ensure that everything is correctly setup. You may want to adjust the +metrics used. See the +ai-rag-chat-evaluator README for more information on the available +metrics.

+

By default, the evaluation script will evaluate every question in the +ground truth data. Run the evaluation script by running the following +command:

+
python evals/evaluate.py
+

The options are:

+
    +
  • numquestions: The number of questions to evaluate. By +default, this is all questions in the ground truth data.
  • +
  • resultsdir: The directory to write the evaluation +results. By default, this is a timestamped folder in +evals/results. This option can also be specified in +eval_config.json.
  • +
  • targeturl: The URL of the running application to +evaluate. By default, this is http://localhost:50505. This +option can also be specified in eval_config.json.
  • +
+

🕰️ This may take a long time, possibly several hours, depending on +the number of ground truth questions, and the TPM capacity of the +evaluation model, and the number of GPT metrics requested.

+

Review the evaluation +results

+

The evaluation script will output a summary of the evaluation +results, inside the evals/results directory.

+

You can see a summary of results across all evaluation runs by +running the following command:

+
python -m evaltools summary evals/results
+

Compare answers to the ground truth by running the following +command:

+
python -m evaltools diff evals/results/baseline/
+

Compare answers across two runs by running the following command:

+
python -m evaltools diff evals/results/baseline/ evals/results/SECONDRUNHERE
+

Run bulk evaluation on a PR

+

This repository includes a GitHub Action workflow +evaluate.yaml that can be used to run the evaluation on the +changes in a PR.

+

In order for the workflow to run successfully, you must first set up +continuous integration for the +repository.

+

To run the evaluation on the changes in a PR, a repository member can +post a /evaluate comment to the PR. This will trigger the +evaluation workflow to run the evaluation on the PR changes and will +post the results to the PR.

diff --git a/data/gpt4v.html b/data/gpt4v.html new file mode 100644 index 0000000000..cc52c482e6 --- /dev/null +++ b/data/gpt4v.html @@ -0,0 +1,88 @@ +

RAG chat: +Using GPT vision model with RAG approach

+

📺 Watch: (RAG +Deep Dive series) Multimedia data ingestion

+

This repository includes an optional feature that uses the GPT vision +model to generate responses based on retrieved content. This feature is +useful for answering questions based on the visual content of documents, +such as photos and charts.

+

How it works

+

When this feature is enabled, the following changes are made to the +application:

+
    +
  • Search index: We added a new field to the Azure AI +Search index to store the embedding returned by the multimodal Azure AI +Vision API (while keeping the existing field that stores the OpenAI text +embeddings).
  • +
  • Data ingestion: In addition to our usual PDF +ingestion flow, we also convert each PDF document page to an image, +store that image with the filename rendered on top, and add the +embedding to the index.
  • +
  • Question answering: We search the index using both +the text and multimodal embeddings. We send both the text and the image +to gpt-4o, and ask it to answer the question based on both kinds of +sources.
  • +
  • Citations: The frontend displays both image sources +and text sources, to help users understand how the answer was +generated.
  • +
+

For more details on how this feature works, read this +blog post or watch this +video.

+

Using the feature

+

Prerequisites

+ +

Deployment

+
    +
  1. Enable GPT vision approach:

    +

    First, make sure you do not have integrated vectorization +enabled, since that is currently incompatible:

    +
    azd env set USE_FEATURE_INT_VECTORIZATION false
    +

    Then set the environment variable for enabling vision support:

    +
    azd env set USE_GPT4V true
    +

    When set, that flag will provision a Azure AI Vision resource and +gpt-4o model, upload image versions of PDFs to Blob storage, upload +embeddings of images in a new imageEmbedding field, and +enable the vision approach in the UI.

  2. +
  3. Clean old deployments (optional): Run +azd down --purge for a fresh setup.

  4. +
  5. Start the application: Execute +azd up to build, provision, deploy, and initiate document +preparation.

  6. +
  7. Try out the feature:

    +
      +
    • Access the developer options in the web app and select “Use GPT +vision model”.
    • +
    • New sample questions will show up in the UI that are based on the +sample financial document.
    • +
    • Try out a question and see the answer generated by the GPT vision +model.
    • +
    • Check the ‘Thought process’ and ‘Supporting content’ tabs.
    • +
  8. +
diff --git a/data/localdev.html b/data/localdev.html new file mode 100644 index 0000000000..43e3f0b1c8 --- /dev/null +++ b/data/localdev.html @@ -0,0 +1,124 @@ +

RAG chat: Local +development of chat app

+

After deploying the app to Azure, you may want to continue +development locally. This guide explains how to run the app locally, +including hot reloading and debugging.

+ +

Running +development server from the command line

+

You can only run locally after having successfully +run the azd up command. If you haven’t yet, follow the +steps in Azure deployment +above.

+
    +
  1. Run azd auth login
  2. +
  3. Start the server:
  4. +
+

Windows:

+
./app/start.ps1
+

Linux/Mac:

+
./app/start.sh
+

VS Code: Run the “VS Code Task: Start App” task.

+

Hot reloading frontend +and backend files

+

When you run ./start.ps1 or ./start.sh, the +backend files will be watched and reloaded automatically. However, the +frontend files will not be watched and reloaded automatically.

+

To enable hot reloading of frontend files, open a new terminal and +navigate to the frontend directory:

+
cd app/frontend
+

Then run:

+
npm run dev
+

You should see:

+
> frontend@0.0.0 dev
+> vite
+
+
+  VITE v4.5.1  ready in 957 ms
+
+  ➜  Local:   http://localhost:5173/
+  ➜  Network: use --host to expose
+  ➜  press h to show help
+

Navigate to the URL shown in the terminal (in this case, +http://localhost:5173/). This local server will watch and +reload frontend files. All backend requests will be routed to the Python +server according to vite.config.ts.

+

Then, whenever you make changes to frontend files, the changes will +be automatically reloaded, without any browser refresh needed.

+

Using VS Code “Run and Debug”

+

This project includes configurations defined in +.vscode/launch.json that allow you to run and debug the app +directly from VS Code:

+
    +
  • “Backend (Python)”: Starts the Python backend server, defaulting to +port 50505.
  • +
  • “Frontend”: Starts the frontend server using Vite, typically at port +5173.
  • +
  • “Frontend & Backend”: A compound configuration that starts both +the frontend and backend servers.
  • +
+

When you run these configurations, you can set breakpoints in your +code and debug as you would in a normal VS Code debugging session.

+

Using a local +OpenAI-compatible API

+

You may want to save costs by developing against a local LLM server, +such as llamafile. Note +that a local LLM will generally be slower and not as sophisticated.

+

Once the local LLM server is running and serving an OpenAI-compatible +endpoint, set these environment variables:

+
azd env set USE_VECTORS false
+azd env set OPENAI_HOST local
+azd env set OPENAI_BASE_URL <your local endpoint>
+azd env set AZURE_OPENAI_CHATGPT_MODEL local-model-name
+

Then restart the local development server. You should now be able to +use the “Ask” tab.

+

⚠️ Limitations:

+
    +
  • The “Chat” tab will only work if the local language model supports +function calling.
  • +
  • Your search mode must be text only (no vectors), since the search +index is only populated with OpenAI-generated embeddings, and the local +OpenAI host can’t generate those.
  • +
  • The conversation history will be truncated using the GPT tokenizers, +which may not be the same as the local model’s tokenizer, so if you have +a long conversation, you may end up with token limit errors.
  • +
+
+

[!NOTE] You must set OPENAI_HOST back to a non-local +value (“azure”, “azure_custom”, or “openai”) before running +azd up or azd provision, since the deployed +backend can’t access your local server.

+
+

Using Ollama server

+

For example, to point at a local Ollama server running the +llama3.1:8b model:

+
azd env set OPENAI_HOST local
+azd env set OPENAI_BASE_URL http://localhost:11434/v1
+azd env set AZURE_OPENAI_CHATGPT_MODEL llama3.1:8b
+azd env set USE_VECTORS false
+

If you’re running the app inside a VS Code Dev Container, use this +local URL instead:

+
azd env set OPENAI_BASE_URL http://host.docker.internal:11434/v1
+

Using llamafile server

+

To point at a local llamafile server running on its default port:

+
azd env set OPENAI_HOST local
+azd env set OPENAI_BASE_URL http://localhost:8080/v1
+azd env set USE_VECTORS false
+

Llamafile does not require a model name to be specified.

+

If you’re running the app inside a VS Code Dev Container, use this +local URL instead:

+
azd env set OPENAI_BASE_URL http://host.docker.internal:8080/v1
diff --git a/data/login_and_acl.html b/data/login_and_acl.html new file mode 100644 index 0000000000..35cda45c12 --- /dev/null +++ b/data/login_and_acl.html @@ -0,0 +1,689 @@ + +

RAG +chat: Setting up optional login and document level access control

+

📺 Watch: (RAG +Deep Dive series) Login and access control

+

The azure-search-openai-demo project can set up a +full RAG chat app on Azure AI Search and OpenAI so that you can chat on +custom data, like internal enterprise data or domain-specific knowledge +sets. For full instructions on setting up the project, consult the main README, and then return here for detailed +instructions on configuring login and access control.

+

Table of Contents

+ +

This guide demonstrates how to add an optional login and document +level access control system to the sample. This system can be used to +restrict access to indexed data to specific users based on what Microsoft +Entra groups they are a part of, or their user +object id.

+
+ + +
+

Requirements

+

IMPORTANT: In order to add optional login and +document level access control, you’ll need the following in addition to +the normal sample requirements

+ +

Setting up Microsoft +Entra applications

+

Two Microsoft Entra applications must be registered in order to make +the optional login and document level access control system work +correctly. One app is for the client UI. The client UI is implemented as +a single +page application. The other app is for the API server. The API +server uses a confidential +client to call the Microsoft Graph +API.

+

Automatic Setup

+

The easiest way to setup the two apps is to use the azd +CLI. We’ve written scripts that will automatically create the two apps +and configure them for use with the sample. To trigger the automatic +setup, run the following commands:

+
    +
  1. Run azd env set AZURE_USE_AUTHENTICATION true to enable +the login UI and use App Service authentication by default.
  2. +
  3. Ensure access control is enabled on your search index. If your index +doesn’t exist yet, run prepdocs with +AZURE_USE_AUTHENTICATION set to true. If your +index already exists, run +python ./scripts/manageacl.py --acl-action enable_acls.
  4. +
  5. (Optional) To require access control when using the app, run +azd env set AZURE_ENFORCE_ACCESS_CONTROL true. +Authentication is always required to search on documents with access +control assigned, regardless of if unauthenticated access is enabled or +not.
  6. +
  7. (Optional) To allow authenticated users to search on documents that +have no access controls assigned, even when access control is required, +run +azd env set AZURE_ENABLE_GLOBAL_DOCUMENT_ACCESS true.
  8. +
  9. (Optional) To allow unauthenticated users to use the app, even when +access control is enforced, run +azd env set AZURE_ENABLE_UNAUTHENTICATED_ACCESS true. +AZURE_ENABLE_GLOBAL_DOCUMENT_ACCESS should also be set to +true if you want unauthenticated users to be able to search on documents +with no access control.
  10. +
  11. Run +azd env set AZURE_AUTH_TENANT_ID <YOUR-TENANT-ID> to +set the tenant ID associated with authentication.
  12. +
  13. If your auth tenant ID is different from your currently logged in +tenant ID, run +azd auth login --tenant-id <YOUR-TENANT-ID> to login +to the authentication tenant simultaneously.
  14. +
  15. Run azd up to deploy the app.
  16. +
+

Manual Setup

+

The following instructions explain how to setup the two apps using +the Azure Portal.

+

Server App

+
    +
  • Sign in to the Azure +portal.

  • +
  • Select the Microsoft Entra ID service.

  • +
  • In the left hand menu, select Application +Registrations.

  • +
  • Select New Registration.

    +
      +
    • In the Name section, enter a meaningful application +name. This name will be displayed to users of the app, for example +Azure Search OpenAI Chat API.
    • +
    • Under Supported account types, select +Accounts in this organizational directory only.
    • +
  • +
  • Select Register to create the +application

  • +
  • In the app’s registration screen, find the Application +(client) ID.

    +
      +
    • Run the following azd command to save this ID: +azd env set AZURE_SERVER_APP_ID <Application (client) ID>.
    • +
  • +
  • Microsoft Entra supports three types of credentials to +authenticate an app using the client +credentials: passwords (app secrets), certificates, and federated +identity credentials. For a higher level of security, either certificates +or federated identity credentials are recommended. This sample currently +uses an app secret for ease of provisioning.

  • +
  • Select Certificates & secrets in the left +hand menu.

  • +
  • In the Client secrets section, select +New client secret.

    +
      +
    • Type a description, for example +Azure Search OpenAI Chat Key.
    • +
    • Select one of the available key durations.
    • +
    • The generated key value will be displayed after you select +Add.
    • +
    • Copy the generated key value and run the following azd +command to save this ID: +azd env set AZURE_SERVER_APP_SECRET <generated key value>.
    • +
  • +
  • Select API Permissions in the left hand menu. By +default, the delegated +User.Read permission should be present. This permission +is required to read the signed-in user’s profile to get the security +information used for document level access control. If this permission +is not present, it needs to be added to the application.

    +
      +
    • Select Add a permission, and then Microsoft +Graph.
    • +
    • Select Delegated permissions.
    • +
    • Search for and and select User.Read.
    • +
    • Select Add permissions.
    • +
  • +
  • Select Expose an API in the left hand menu. The +server app works by using the On +Behalf Of Flow, which requires the server app to expose at least 1 +API.

    +
      +
    • The application must define a URI to expose APIs. Select +Add next to Application ID URI. +
        +
      • By default, the Application ID URI is set to +api://<application client id>. Accept the default by +selecting Save.
      • +
    • +
    • Under Scopes defined by this API, select +Add a scope.
    • +
    • Fill in the values as indicated: +
        +
      • For Scope name, use +access_as_user.
      • +
      • For Who can consent?, select Admins and +users.
      • +
      • For Admin consent display name, type Access +Azure Search OpenAI Chat API.
      • +
      • For Admin consent description, type Allows +the app to access Azure Search OpenAI Chat API as the signed-in +user..
      • +
      • For User consent display name, type Access +Azure Search OpenAI Chat API.
      • +
      • For User consent description, type Allow +the app to access Azure Search OpenAI Chat API on your +behalf.
      • +
      • Leave State set to Enabled.
      • +
      • Select Add scope at the bottom to save the +scope.
      • +
    • +
  • +
  • (Optional) Enable group claims. Include which Microsoft Entra +groups the user is part of as part of the login in the optional +claims. The groups are used for optional +security filtering in the search results.

    +
      +
    • In the left hand menu, select Token +configuration
    • +
    • Under Optional claims, select Add groups +claim
    • +
    • Select which group +types to include in the claim. Note that a overage +claim will be emitted if the user is part of too many groups. In +this case, the API server will use the Microsoft +Graph to list the groups the user is part of instead of relying on +the groups in the claim.
    • +
    • Select Add to save your changes
    • +
  • +
+

Client App

+
    +
  • Sign in to the Azure +portal.
  • +
  • Select the Microsoft Entra ID service.
  • +
  • In the left hand menu, select Application +Registrations.
  • +
  • Select New Registration. +
      +
    • In the Name section, enter a meaningful application +name. This name will be displayed to users of the app, for example +Azure Search OpenAI Chat Web App.
    • +
    • Under Supported account types, select +Accounts in this organizational directory only.
    • +
    • Under Redirect URI (optional) section, select +Single-page application (SPA) in the combo-box and enter +the following redirect URI: +
        +
      • If you are running the sample locally, add the endpoints +http://localhost:50505/redirect and +http://localhost:5173/redirect
      • +
      • If you are running the sample on Azure, add the endpoints provided +by azd up: +https://<your-endpoint>.azurewebsites.net/redirect.
      • +
      • If you are running the sample from Github Codespaces, add the +Codespaces endpoint: +https://<your-codespace>-50505.app.github.dev/redirect
      • +
    • +
  • +
  • Select Register to create the application
  • +
  • In the app’s registration screen, find the Application +(client) ID. +
      +
    • Run the following azd command to save this ID: +azd env set AZURE_CLIENT_APP_ID <Application (client) ID>.
    • +
  • +
  • In the left hand menu, select Authentication. +
      +
    • Under Web, add a redirect URI with the endpoint provided by +azd up: +https://<your-endpoint>.azurewebsites.net/.auth/login/aad/callback.
    • +
    • Under Implicit grant and hybrid flows, select +ID Tokens (used for implicit and hybrid flows)
    • +
    • Select Save
    • +
  • +
  • In the left hand menu, select API permissions. You +will add permission to access the access_as_user API on +the server app. This permission is required for the On +Behalf Of Flow to work. +
      +
    • Select Add a permission, and then My +APIs.
    • +
    • In the list of applications, select your server application +Azure Search OpenAI Chat API
    • +
    • Ensure Delegated permissions is selected.
    • +
    • In the Select permissions section, select the +access_as_user permission
    • +
    • Select Add permissions.
    • +
  • +
  • Stay in the API permissions section and select +Add a permission. +
      +
    • Select Microsoft Graph.
    • +
    • Select Delegated permissions.
    • +
    • Search for and select User.Read.
    • +
    • Select Add permissions.
    • +
  • +
+

Configure Server +App Known Client Applications

+

Consent from the user must be obtained for use of the client and +server app. The client app can prompt the user for consent through a +dialog when they log in. The server app has no ability to show a dialog +for consent. Client apps can be added +to the list of known clients to access the server app, so a consent +dialog is shown for the server app.

+
    +
  • Navigate to the server app registration
  • +
  • In the left hand menu, select Manifest
  • +
  • Replace "knownClientApplications": [] with +"knownClientApplications": ["<client application id>"]
  • +
  • Select Save
  • +
+

Testing

+

If you are running setup for the first time, ensure you have run +azd env set AZURE_ADLS_GEN2_STORAGE_ACCOUNT <YOUR-STORAGE_ACCOUNT> +before running azd up. If you do not set this environment +variable, your index will not be initialized with access control support +when prepdocs is run for the first time. To manually enable +access control in your index, use the manual setup script.

+

Ensure you run azd env set AZURE_USE_AUTHENTICATION to +enable the login UI once you have setup the two Microsoft Entra apps +before you deploy or run the application. The login UI will not appear +unless all required +environment variables have been setup.

+

In both the chat and ask a question modes, under Developer +settings optional Use oid security filter and +Use groups security filter checkboxes will appear. The +oid (User ID) filter maps to the oids field in the search +index and the groups (Group ID) filter maps to the groups +field in the search index. If AZURE_ENFORCE_ACCESS_CONTROL +has been set, then both the Use oid security filter and +Use groups security filter options are always enabled +and cannot be disabled.

+

Programmatic Access +with Authentication

+

If you want to use the chat endpoint without the UI and still use +authentication, you must disable App +Service built-in authentication and use only the app’s MSAL-based +authentication flow. Ensure the +AZURE_DISABLE_APP_SERVICES_AUTHENTICATION environment +variable is set before deploying.

+

Get an access token that can be used for calling the chat API using +the following code:

+
from azure.identity import DefaultAzureCredential
+import os
+
+token = DefaultAzureCredential().get_token(f"api://{os.environ['AZURE_SERVER_APP_ID']}/access_as_user", tenant_id=os.getenv('AZURE_AUTH_TENANT_ID', os.getenv('AZURE_TENANT_ID')))
+
+print(token.token)
+

Troubleshooting

+
    +
  • If your primary tenant restricts the ability to create Entra +applications, you’ll need to use a separate tenant to create the Entra +applications. You can create a new tenant by following these +instructions. Then run +azd env set AZURE_AUTH_TENANT_ID <YOUR-AUTH-TENANT-ID> +before running azd up.
  • +
  • If any Entra apps need to be recreated, you can avoid redeploying +the app by changing +the app settings in the portal. Any of the required environment +variables can be changed. Once the environment variables have been +changed, restart the web app.
  • +
  • It’s possible a consent dialog will not appear when you log into the +app for the first time. If this consent dialog doesn’t appear, you will +be unable to use the security filters because the API server app does +not have permission to read your authorization information. A consent +dialog can be forced to appear by adding +"prompt": "consent" to the loginRequest +property in authentication.py
  • +
  • It’s possible that your tenant admin has placed a restriction on +consent to apps with unverified +publishers. In this case, only admins may consent to the client and +server apps, and normal user accounts are unable to use the login system +until the admin consents on behalf of the entire organization.
  • +
  • It’s possible that your tenant admin requires admin +approval of all new apps. Regardless of whether you select the +delegated or admin permissions, the app will not work without tenant +admin consent. See this guide for granting +consent to an app.
  • +
+

Adding data with +document level access control

+

The sample supports 2 main strategies for adding data with document +level access control.

+ +

Using the Add Documents API

+

Manually enable document level access control on a search index and +manually set access control values using the manageacl.py script.

+

Prior to running the script:

+
    +
  • Run azd up or use azd env set to manually +set the AZURE_SEARCH_SERVICE and +AZURE_SEARCH_INDEX azd environment variables
  • +
  • Activate the Python virtual environment for your shell session
  • +
+

The script supports the following commands. All commands support +-v for verbose logging.

+
    +
  • python ./scripts/manageacl.py --acl-action enable_acls: +Creates the required oids (User ID) and groups +(Group IDs) security +filter fields for document level access control on your index, as +well as the storageUrl field for storing the Blob storage +URL. Does nothing if these fields already exist.

    +

    Example usage:

    +
    python ./scripts/manageacl.py -v --acl-action enable_acls
  • +
  • python ./scripts/manageacl.py --acl-type [oids or groups]--acl-action view --url [https://url.pdf]: +Prints access control values associated with either User IDs or Group +IDs for the document at the specified URL.

    +

    Example to view all Group IDs:

    +
    python ./scripts/manageacl.py -v --acl-type groups --acl-action view --url https://st12345.blob.core.windows.net/content/Benefit_Options.pdf
  • +
  • python ./scripts/manageacl.py --acl-type [oids or groups]--acl-action add --acl [ID of group or user] --url [https://url.pdf]: +Adds an access control value associated with either User IDs or Group +IDs for the document at the specified URL.

    +

    Example to add a Group ID:

    +
    python ./scripts/manageacl.py -v --acl-type groups --acl-action add --acl xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --url https://st12345.blob.core.windows.net/content/Benefit_Options.pdf
  • +
  • python ./scripts/manageacl.py --acl-type [oids or groups]--acl-action remove_all --url [https://url.pdf]: +Removes all access control values associated with either User IDs or +Group IDs for a specific document.

    +

    Example to remove all Group IDs:

    +
    python ./scripts/manageacl.py -v --acl-type groups --acl-action remove_all --url https://st12345.blob.core.windows.net/content/Benefit_Options.pdf
  • +
  • python ./scripts/manageacl.py --url [https://url.pdf] --acl-type [oids or groups]--acl-action remove --acl [ID of group or user]: +Removes an access control value associated with either User IDs or Group +IDs for a specific document.

    +

    Example to remove a specific User ID:

    +
    python ./scripts/manageacl.py -v --acl-type oids --acl-action remove --acl xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --url https://st12345.blob.core.windows.net/content/Benefit_Options.pdf
  • +
+

Azure Data Lake Storage Gen2 +Setup

+

Azure +Data Lake Storage Gen2 implements an access +control model that can be used for document level access control. +The adlsgen2setup.py script +uploads the sample data included in the data folder +to a Data Lake Storage Gen2 storage account. The Storage +Blob Data Owner role is required to use the script.

+

In order to use this script, an existing Data Lake Storage Gen2 +storage account is required. Run +azd env set AZURE_ADLS_GEN2_STORAGE_ACCOUNT <your-storage-account> +prior to running the script.

+

Then run the script inside your Python environment:

+
python /scripts/adlsgen2setup.py './data/*' --data-access-control './scripts/sampleacls.json' -v
+

The script performs the following steps:

+ +

In order to use the sample access control, you need to join these +groups in your Microsoft Entra tenant.

+

Note that this optional script may not work in Codespaces if your +administrator has applied a Conditional +Access policy to your tenant.

+

Azure Data Lake Storage +Gen2 Prep Docs

+

Once a Data Lake Storage Gen2 storage account has been setup with +sample data and access control lists, prepdocs.py can be used to +automatically process PDFs in the storage account and store them with +their access +control lists in the search index.

+

To run this script with a Data Lake Storage Gen2 account, first set +the following environment variables:

+
    +
  • AZURE_ADLS_GEN2_STORAGE_ACCOUNT: Name of existing Data +Lake Storage Gen2 storage account.
  • +
  • (Optional) AZURE_ADLS_GEN2_FILESYSTEM: Name of existing +Data Lake Storage Gen2 filesystem / container in the storage account. If +empty, gptkbcontainer is used.
  • +
  • (Optional) AZURE_ADLS_GEN2_FILESYSTEM_PATH: Specific +path in the Data Lake Storage Gen2 filesystem / container to process. +Only PDFs contained in this path will be processed.
  • +
+

Once the environment variables are set, run the script using the +following command: /scripts/prepdocs.ps1 or +/scripts/prepdocs.sh.

+

Environment variables +reference

+

The following environment variables are used to setup the optional +login and document level access control:

+
    +
  • AZURE_USE_AUTHENTICATION: Enables Entra ID login and +document level access control. Set to true before running +azd up.
  • +
  • AZURE_ENFORCE_ACCESS_CONTROL: Enforces Entra ID based +login and document level access control on documents with access control +assigned. Set to true before running azd up. If +AZURE_ENFORCE_ACCESS_CONTROL is enabled and +AZURE_ENABLE_UNAUTHENTICATED_ACCESS is not enabled, then +authentication is required to use the app.
  • +
  • AZURE_ENABLE_GLOBAL_DOCUMENT_ACCESS: Allows users to +search on documents that have no access controls assigned
  • +
  • AZURE_ENABLE_UNAUTHENTICATED_ACCESS: Allows +unauthenticated users to access the chat app, even when +AZURE_ENFORCE_ACCESS_CONTROL is enabled. +AZURE_ENABLE_GLOBAL_DOCUMENT_ACCESS should be set to true +to allow unauthenticated users to search on documents that have no +access control assigned. Unauthenticated users cannot search on +documents with access control assigned.
  • +
  • AZURE_DISABLE_APP_SERVICES_AUTHENTICATION: Disables use +of built-in authentication for App Services. An authentication flow +based on the MSAL SDKs is used instead. Useful when you want to provide +programmatic access to the chat endpoints with authentication.
  • +
  • AZURE_SERVER_APP_ID: (Required) Application ID of the +Microsoft Entra app for the API server.
  • +
  • AZURE_SERVER_APP_SECRET: Client +secret used by the API server to authenticate using the Microsoft +Entra server app.
  • +
  • AZURE_CLIENT_APP_ID: Application ID of the Microsoft +Entra app for the client UI.
  • +
  • AZURE_AUTH_TENANT_ID: Tenant +ID associated with the Microsoft Entra tenant used for login and +document level access control. Defaults to AZURE_TENANT_ID +if not defined.
  • +
  • AZURE_ADLS_GEN2_STORAGE_ACCOUNT: (Optional) Name of +existing Data +Lake Storage Gen2 storage account for storing sample data with access +control lists. Only used with the optional Data Lake Storage Gen2 setup and prep docs +scripts.
  • +
  • AZURE_ADLS_GEN2_FILESYSTEM: (Optional) Name of existing +Data +Lake Storage Gen2 filesystem for storing sample data with access +control lists. Only used with the optional Data Lake Storage Gen2 setup and prep docs +scripts.
  • +
  • AZURE_ADLS_GEN2_FILESYSTEM_PATH: (Optional) Name of +existing path in a Data +Lake Storage Gen2 filesystem for storing sample data with access +control lists. Only used with the optional Data Lake Storage Gen2 prep docs +script.
  • +
+

Authentication behavior +by environment

+

This application uses an in-memory token cache. User sessions are +only available in memory while the application is running. When the +application server is restarted, all users will need to log-in +again.

+

The following table describes the impact of the +AZURE_USE_AUTHENTICATION and +AZURE_ENFORCE_ACCESS_CONTROL variables depending on the +environment you are deploying the application in:

+ ++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AZURE_USE_AUTHENTICATIONAZURE_ENFORCE_ACCESS_CONTROLEnvironmentDefault Behavior
TrueFalseApp ServicesUse integrated auth
Login page blocks access to app
+User can opt-into access control in developer settings
Allows +unrestricted access to sources
TrueTrueApp ServicesUse integrated auth
Login page blocks access to app
+User must use access control
TrueFalseLocal or CodespacesDo not use integrated auth
Can use app without login
+User can opt-into access control in developer settings
Allows +unrestricted access to sources
TrueTrueLocal or CodespacesDo not use integrated auth
Cannot use app without login +
Behavior is chat box is greyed out with default “Please login +message”
User must use login button to make chat box usable +
User must use access control when logged in
FalseFalseAllNo login or access control
FalseTrueAllInvalid setting
diff --git a/data/monitoring.html b/data/monitoring.html new file mode 100644 index 0000000000..ab9016f90b --- /dev/null +++ b/data/monitoring.html @@ -0,0 +1,73 @@ +

RAG chat: +Monitoring with Application Insights

+

By default, deployed apps use Application Insights for the tracing of +each request, along with the logging of errors.

+ +

Performance

+

To see the performance data, go to the Application Insights resource +in your resource group, click on the “Investigate -> Performance” +blade and navigate to any HTTP request to see the timing data. To +inspect the performance of chat requests, use the “Drill into Samples” +button to see end-to-end traces of all the API calls made for any chat +request:

+
+Tracing screenshot + +
+

Failures

+

To see any exceptions and server errors, navigate to the “Investigate +-> Failures” blade and use the filtering tools to locate a specific +exception. You can see Python stack traces on the right-hand side.

+

Dashboard

+

You can see chart summaries on a dashboard by running the following +command:

+
azd monitor
+

You can modify the contents of that dashboard by updating +infra/backend-dashboard.bicep, which is a Bicep file that +defines the dashboard contents and layout.

+

Customizing the traces

+

The tracing is done using these OpenTelemetry Python packages:

+ +

Those packages are configured in the app.py file:

+
if os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING"):
+    configure_azure_monitor()
+    # This tracks HTTP requests made by aiohttp:
+    AioHttpClientInstrumentor().instrument()
+    # This tracks HTTP requests made by httpx:
+    HTTPXClientInstrumentor().instrument()
+    # This tracks OpenAI SDK requests:
+    OpenAIInstrumentor().instrument()
+    # This middleware tracks app route requests:
+    app.asgi_app = OpenTelemetryMiddleware(app.asgi_app)
+

You can pass in parameters to configure_azure_monitor() +to customize the tracing, like to add custom span processors. You can +also set OpenTelemetry +environment variables to customize the tracing, like to set the +sampling rate. See the azure-monitor-opentelemetry +documentation for more details.

+

By default, opentelemetry-instrumentation-openai +traces all requests made to the OpenAI API, including the messages and +responses. To disable that for privacy reasons, set the +TRACELOOP_TRACE_CONTENT=false environment variable.

+

To set environment variables, update appEnvVariables in +infra/main.bicep and re-run azd up.

diff --git a/data/other_samples.html b/data/other_samples.html new file mode 100644 index 0000000000..715f29e12c --- /dev/null +++ b/data/other_samples.html @@ -0,0 +1,139 @@ +

RAG chat: Alternative RAG +chat samples

+

There are an increasingly large number of ways to build RAG chat +apps.

+

Other language samples

+

Inspired by this repo, there are similar apps for other +languages:

+ +

They do not all support the same features as this repo, but they +provide a good starting point for building a RAG chat app in your +preferred language.

+

Other Python samples

+

Another popular repository for this use case is here: https://github.com/Microsoft/sample-app-aoai-chatGPT/

+

That repository is designed for use by customers using Azure OpenAI +studio and Azure Portal for setup. It also includes azd +support for folks who want to deploy it completely from scratch.

+

The primary differences:

+
    +
  • This repository includes multiple RAG (retrieval-augmented +generation) approaches that chain the results of multiple API calls (to +Azure OpenAI and ACS) together in different ways. The other repository +uses only the built-in data sources option for the ChatCompletions API, +which uses a RAG approach on the specified ACS index. That should work +for most uses, but if you needed more flexibility, this sample may be a +better option.
  • +
  • This repository is also a bit more experimental in other ways, since +it’s not tied to the Azure OpenAI Studio like the other repository.
  • +
+

Feature comparison:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Featureazure-search-openai-demosample-app-aoai-chatGPT
RAG approachMultiple approachesOnly via ChatCompletion API data_sources
Vector support✅ Yes✅ Yes
Data ingestion✅ Yes (Many +formats)✅ Yes (Many +formats)
Persistent chat history✅ Yes✅ Yes
User feedback❌ No✅ Yes
GPT-4-vision✅ Yes❌ No
Auth + ACL✅ Yes✅ Yes
User upload✅ Yes❌ No
Speech I/O✅ Yes❌ No
+

Technology comparison:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Techazure-search-openai-demosample-app-aoai-chatGPT
FrontendReactReact
BackendPython (Quart)Python (Quart)
Vector DBAzure AI SearchAzure AI Search, CosmosDB Mongo vCore, ElasticSearch, Pinecone, +AzureML
DeploymentAzure Developer CLI (azd)Azure Portal, az, azd
diff --git a/data/productionizing.html b/data/productionizing.html new file mode 100644 index 0000000000..3e1bf5f62e --- /dev/null +++ b/data/productionizing.html @@ -0,0 +1,157 @@ +

RAG chat: Productionizing the +app

+

This sample is designed to be a starting point for your own +production application, but you should do a thorough review of the +security and performance before deploying to production. Here are some +things to consider:

+ +

Azure resource configuration

+

OpenAI Capacity

+

The default TPM (tokens per minute) is set to 30K. That is equivalent +to approximately 30 conversations per minute (assuming 1K per user +message/response). You can increase the capacity by changing the +chatGptDeploymentCapacity and +embeddingDeploymentCapacity parameters in +infra/main.bicep to your account’s maximum capacity. You +can also view the Quotas tab in Azure +OpenAI studio to understand how much capacity you have.

+

If the maximum TPM isn’t enough for your expected load, you have a +few options:

+ +

Azure Storage

+

The default storage account uses the Standard_LRS SKU. +To improve your resiliency, we recommend using Standard_ZRS +for production deployments, which you can specify using the +sku property under the storage module in +infra/main.bicep.

+ +

The default search service uses the “Basic” SKU with the free +semantic ranker option, which gives you 1000 free queries a month. After +1000 queries, you will get an error message about exceeding the semantic +ranker free capacity.

+
    +
  • Assuming your app will experience more than 1000 questions per +month, you should upgrade the semantic ranker SKU from “free” to +“standard” SKU:

    +
    azd env set AZURE_SEARCH_SEMANTIC_RANKER standard
    +

    Or disable semantic search entirely:

    +
    azd env set AZURE_SEARCH_SEMANTIC_RANKER disabled
  • +
  • The search service can handle fairly large indexes, but it does +have per-SKU limits on storage sizes, maximum vector dimensions, etc. +You may want to upgrade the SKU to either a Standard or Storage +Optimized SKU, depending on your expected load. However, you cannot +change the SKU of an existing search service, so you will need to +re-index the data or manually copy it over. You can change the SKU by +setting the AZURE_SEARCH_SERVICE_SKU azd environment +variable to an +allowed SKU.

    +
    azd env set AZURE_SEARCH_SERVICE_SKU standard
    +

    See the Azure +AI Search service limits documentation for more details.

  • +
  • If you see errors about search service capacity being exceeded, +you may find it helpful to increase the number of replicas by changing +replicaCount in +infra/core/search/search-services.bicep or manually scaling +it from the Azure Portal.

  • +
+

Azure App Service

+

The default app service plan uses the Basic SKU with 1 +CPU core and 1.75 GB RAM. We recommend using a Premium level SKU, +starting with 1 CPU core. You can use auto-scaling rules or scheduled +scaling rules, and scale up the maximum/minimum based on load.

+

Additional security measures

+ +

Load testing

+

We recommend running a loadtest for your expected number of users. +You can use the locust tool with +the locustfile.py in this sample or set up a loadtest with +Azure Load Testing.

+

To use locust, first install the dev requirements that includes +locust:

+
python -m pip install -r requirements-dev.txt
+

Or manually install locust:

+
python -m pip install locust
+

Then run the locust command, specifying the name of the User class to +use from locustfile.py. We’ve provided a +ChatUser class that simulates a user asking questions and +receiving answers, as well as a ChatVisionUser to simulate +a user asking questions with the GPT-4 vision +mode enabled.

+
locust ChatUser
+

Open the locust UI at http://localhost:8089/, the URI +displayed in the terminal.

+

Start a new test with the URI of your website, +e.g. https://my-chat-app.azurewebsites.net. Do not +end the URI with a slash. You can start by pointing at your localhost if +you’re concerned more about load on OpenAI/AI Search than the host +platform.

+

For the number of users and spawn rate, we recommend starting with 20 +users and 1 users/second. From there, you can keep increasing the number +of users to simulate your expected load.

+

Here’s an example loadtest for 50 users and a spawn rate of 1 per +second:

+
+ + +
+

After each test, check the local or App Service logs to see if there +are any errors.

+

Evaluation

+

Before you make your chat app available to users, you’ll want to +rigorously evaluate the answer quality. You can use tools in the AI RAG +Chat evaluator repository to run evaluations, review results, and +compare answers across runs.

diff --git a/data/sharing_environments.html b/data/sharing_environments.html new file mode 100644 index 0000000000..d3a08fd66d --- /dev/null +++ b/data/sharing_environments.html @@ -0,0 +1,27 @@ +

RAG chat: Sharing +deployment environments

+

If you’ve deployed the RAG chat solution already following the steps +in the deployment guide, you may +want to share the environment with a colleague. Either you or they can +follow these steps:

+
    +
  1. Install the Azure +CLI
  2. +
  3. Run azd init -t azure-search-openai-demo or clone this +repository.
  4. +
  5. Run azd env refresh -e {environment name} They will +need the azd environment name, subscription ID, and location to run this +command. You can find those values in your +.azure/{env name}/.env file. This will populate their azd +environment’s .env file with all the settings needed to run +the app locally.
  6. +
  7. Set the environment variable AZURE_PRINCIPAL_ID either +in that .env file or in the active shell to their Azure ID, +which they can get with az ad signed-in-user show.
  8. +
  9. Run ./scripts/roles.ps1 or +.scripts/roles.sh to assign all of the necessary roles to +the user. If they do not have the necessary permission to create roles +in the subscription, then you may need to run this script for them. Once +the script runs, they should be able to run the app locally.
  10. +