forked from openai/openai-cookbook
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
initial commit for Azure RAG cookbook (openai#1272)
Co-authored-by: juston <96567547+justonf@users.noreply.github.com>
- Loading branch information
1 parent
f6ea13e
commit 5f55266
Showing
64 changed files
with
13,182 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
bin | ||
obj | ||
csx | ||
.vs | ||
edge | ||
Publish | ||
|
||
*.user | ||
*.suo | ||
*.cscfg | ||
*.Cache | ||
project.lock.json | ||
|
||
/packages | ||
/TestResults | ||
|
||
/tools/NuGet.exe | ||
/App_Data | ||
/secrets | ||
/data | ||
.secrets | ||
appsettings.json | ||
local.settings.json | ||
|
||
node_modules | ||
dist | ||
vector_database_wikipedia_articles_embedded | ||
|
||
# Local python packages | ||
.python_packages/ | ||
|
||
# Python Environments | ||
.env | ||
.venv | ||
env/ | ||
venv/ | ||
ENV/ | ||
env.bak/ | ||
venv.bak/ | ||
|
||
# Byte-compiled / optimized / DLL files | ||
__pycache__/ | ||
*.py[cod] | ||
*$py.class | ||
|
||
# Azurite artifacts | ||
__blobstorage__ | ||
__queuestorage__ | ||
__azurite_db*__.json | ||
vector_database_wikipedia_articles_embedded |
5 changes: 5 additions & 0 deletions
5
examples/chatgpt/rag-quickstart/azure/.vscode/extensions.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"recommendations": [ | ||
"ms-azuretools.vscode-azurefunctions" | ||
] | ||
} |
1,214 changes: 1,214 additions & 0 deletions
1,214
...ag-quickstart/azure/Azure_AI_Search_with_Azure_Functions_and_GPT_Actions_in_ChatGPT.ipynb
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
import azure.functions as func | ||
import json | ||
import logging | ||
from azure.search.documents import SearchClient | ||
from azure.search.documents.indexes import SearchIndexClient | ||
from azure.core.credentials import AzureKeyCredential | ||
from openai import OpenAI | ||
import os | ||
from azure.search.documents.models import ( | ||
VectorizedQuery | ||
) | ||
|
||
# Initialize the Azure Function App | ||
app = func.FunctionApp() | ||
|
||
def generate_embeddings(text): | ||
# Check if text is provided | ||
if not text: | ||
logging.error("No text provided in the query string.") | ||
return func.HttpResponse( | ||
"Please provide text in the query string.", | ||
status_code=400 | ||
) | ||
|
||
try: | ||
# Initialize OpenAI client | ||
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) | ||
logging.info("OpenAI client initialized successfully.") | ||
|
||
# Generate embeddings using OpenAI API | ||
response = client.embeddings.create( | ||
input=text, | ||
model=os.getenv("EMBEDDINGS_MODEL") | ||
) | ||
logging.info("Embeddings created successfully.") | ||
|
||
# Extract the embedding from the response | ||
embedding = response.data[0].embedding | ||
logging.debug(f"Generated embedding: {embedding}") | ||
|
||
return embedding | ||
except Exception as e: | ||
logging.error(f"Error generating embeddings: {str(e)}") | ||
return func.HttpResponse( | ||
f"Error generating embeddings: {str(e)}", | ||
status_code=500 | ||
) | ||
|
||
|
||
@app.route(route="vector_similarity_search", auth_level=func.AuthLevel.ANONYMOUS) | ||
def vector_similarity_search(req: func.HttpRequest) -> func.HttpResponse: | ||
logging.info("Received request for vector similarity search.") | ||
try: | ||
# Parse the request body as JSON | ||
req_body = req.get_json() | ||
logging.info("Request body parsed successfully.") | ||
except ValueError: | ||
logging.error("Invalid JSON in request body.") | ||
return func.HttpResponse( | ||
"Invalid JSON in request body.", | ||
status_code=400 | ||
) | ||
|
||
# Extract parameters from the request body | ||
search_service_endpoint = req_body.get('search_service_endpoint') | ||
index_name = req_body.get('index_name') | ||
query = req_body.get('query') | ||
k_nearest_neighbors = req_body.get('k_nearest_neighbors') | ||
search_column = req_body.get('search_column') | ||
use_hybrid_query = req_body.get('use_hybrid_query') | ||
|
||
logging.info(f"Parsed request parameters: search_service_endpoint={search_service_endpoint}, index_name={index_name}, query={query}, k_nearest_neighbors={k_nearest_neighbors}, search_column={search_column}, use_hybrid_query={use_hybrid_query}") | ||
|
||
# Generate embeddings for the query | ||
embeddings = generate_embeddings(query) | ||
logging.info(f"Generated embeddings: {embeddings}") | ||
|
||
# Check for required parameters | ||
if not (search_service_endpoint and index_name and query): | ||
logging.error("Missing required parameters in request body.") | ||
return func.HttpResponse( | ||
"Please provide search_service_endpoint, index_name, and query in the request body.", | ||
status_code=400 | ||
) | ||
try: | ||
# Create a vectorized query | ||
vector_query = VectorizedQuery(vector=embeddings, k_nearest_neighbors=float(k_nearest_neighbors), fields=search_column) | ||
logging.info("Vector query generated successfully.") | ||
except Exception as e: | ||
logging.error(f"Error generating vector query: {str(e)}") | ||
return func.HttpResponse( | ||
f"Error generating vector query: {str(e)}", | ||
status_code=500 | ||
) | ||
|
||
try: | ||
# Initialize the search client | ||
search_client = SearchClient( | ||
endpoint=search_service_endpoint, | ||
index_name=index_name, | ||
credential=AzureKeyCredential(os.getenv("SEARCH_SERVICE_API_KEY")) | ||
) | ||
logging.info("Search client created successfully.") | ||
|
||
# Initialize the index client and get the index schema | ||
index_client = SearchIndexClient(endpoint=search_service_endpoint, credential=AzureKeyCredential(os.getenv("SEARCH_SERVICE_API_KEY"))) | ||
index_schema = index_client.get_index(index_name) | ||
for field in index_schema.fields: | ||
logging.info(f"Field: {field.name}, Type: {field.type}") | ||
# Filter out non-vector fields | ||
non_vector_fields = [field.name for field in index_schema.fields if field.type not in ["Edm.ComplexType", "Collection(Edm.ComplexType)","Edm.Vector","Collection(Edm.Single)"]] | ||
|
||
logging.info(f"Non-vector fields in the index: {non_vector_fields}") | ||
except Exception as e: | ||
logging.error(f"Error creating search client: {str(e)}") | ||
return func.HttpResponse( | ||
f"Error creating search client: {str(e)}", | ||
status_code=500 | ||
) | ||
|
||
# Determine if hybrid query should be used | ||
search_text = query if use_hybrid_query else None | ||
|
||
try: | ||
# Perform the search | ||
results = search_client.search( | ||
search_text=search_text, | ||
vector_queries=[vector_query], | ||
select=non_vector_fields, | ||
top=3 | ||
) | ||
logging.info("Search performed successfully.") | ||
except Exception as e: | ||
logging.error(f"Error performing search: {str(e)}") | ||
return func.HttpResponse( | ||
f"Error performing search: {str(e)}", | ||
status_code=500 | ||
) | ||
|
||
try: | ||
# Extract relevant data from results and put it into a list of dictionaries | ||
response_data = [result for result in results] | ||
response_data = json.dumps(response_data) | ||
logging.info("Search results processed successfully.") | ||
except Exception as e: | ||
logging.error(f"Error processing search results: {str(e)}") | ||
return func.HttpResponse( | ||
f"Error processing search results: {str(e)}", | ||
status_code=500 | ||
) | ||
|
||
logging.info("Returning search results.") | ||
return func.HttpResponse(response_data, mimetype="application/json") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"version": "2.0", | ||
"logging": { | ||
"applicationInsights": { | ||
"samplingSettings": { | ||
"isEnabled": true, | ||
"excludedTypes": "Request" | ||
} | ||
} | ||
}, | ||
"extensionBundle": { | ||
"id": "Microsoft.Azure.Functions.ExtensionBundle", | ||
"version": "[4.*, 5.0.0)" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# Do not include azure-functions-worker in this file | ||
# The Python Worker is managed by the Azure Functions platform | ||
# Manually managing azure-functions-worker may cause unexpected issues | ||
|
||
azure-functions | ||
azure-search-documents | ||
azure-identity | ||
openai | ||
azure-mgmt-search | ||
pandas | ||
azure-mgmt-resource | ||
azure-mgmt-storage | ||
azure-mgmt-web | ||
python-dotenv | ||
pyperclip | ||
PyPDF2 | ||
tiktoken |
19 changes: 19 additions & 0 deletions
19
examples/chatgpt/rag-quickstart/azure/vector_similarity_search/function.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"scriptFile": "__init__.py", | ||
"bindings": [ | ||
{ | ||
"authLevel": "Anonymous", | ||
"type": "httpTrigger", | ||
"direction": "in", | ||
"name": "req", | ||
"methods": [ | ||
"post" | ||
] | ||
}, | ||
{ | ||
"type": "http", | ||
"direction": "out", | ||
"name": "$return" | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
|
||
# Action authentication | ||
|
||
Actions offer different authentication schemas to accommodate various use cases. To specify the authentication schema for your action, use the GPT editor and select "None", "API Key", or "OAuth". | ||
|
||
By default, the authentication method for all actions is set to "None", but you can change this and allow different actions to have different authentication methods. | ||
|
||
## No authentication | ||
|
||
We support flows without authentication for applications where users can send requests directly to your API without needing an API key or signing in with OAuth. | ||
|
||
Consider using no authentication for initial user interactions as you might experience a user drop off if they are forced to sign into an application. You can create a "signed out" experience and then move users to a "signed in" experience by enabling a separate action. | ||
|
||
## API key authentication | ||
|
||
Just like how a user might already be using your API, we allow API key authentication through the GPT editor UI. We encrypt the secret key when we store it in our database to keep your API key secure. | ||
|
||
This approach is useful if you have an API that takes slightly more consequential actions than the no authentication flow but does not require an individual user to sign in. Adding API key authentication can protect your API and give you more fine-grained access controls along with visibility into where requests are coming from. | ||
|
||
## OAuth | ||
|
||
Actions allow OAuth sign in for each user. This is the best way to provide personalized experiences and make the most powerful actions available to users. A simple example of the OAuth flow with actions will look like the following: | ||
|
||
- To start, select "Authentication" in the GPT editor UI, and select "OAuth". | ||
- You will be prompted to enter the OAuth client ID, client secret, authorization URL, token URL, and scope. | ||
- The client ID and secret can be simple text strings but should [follow OAuth best practices](https://www.oauth.com/oauth2-servers/client-registration/client-id-secret/). | ||
- We store an encrypted version of the client secret, while the client ID is available to end users. | ||
- OAuth requests will include the following information: `request={'grant_type': 'authorization_code', 'client_id': 'YOUR_CLIENT_ID', 'client_secret': 'YOUR_CLIENT_SECRET', 'code': 'abc123', 'redirect_uri': 'https://chatgpt.com/aip/g-some_gpt_id/oauth/callback'}` | ||
- In order for someone to use an action with OAuth, they will need to send a message that invokes the action and then the user will be presented with a "Sign in to [domain]" button in the ChatGPT UI. | ||
- The `authorization_url` endpoint should return a response that looks like: | ||
`{ "access_token": "example_token", "token_type": "bearer", "refresh_token": "example_token", "expires_in": 59 }` | ||
- During the user sign in process, ChatGPT makes a request to your `authorization_url` using the specified `authorization_content_type`, we expect to get back an access token and optionally a [refresh token](https://auth0.com/learn/refresh-tokens) which we use to periodically fetch a new access token. | ||
- Each time a user makes a request to the action, the user’s token will be passed in the Authorization header: (“Authorization”: “[Bearer/Basic] [user’s token]”). | ||
- We require that OAuth applications make use of the [state parameter](https://auth0.com/docs/secure/attack-protection/state-parameters#set-and-compare-state-parameter-values) for security reasons. |
Oops, something went wrong.