55import time
66
77import openai
8+ from azure .identity import DefaultAzureCredential
9+ from azure .search .documents import SearchClient
10+ from azure .storage .blob import BlobServiceClient
11+ from flask import (
12+ Blueprint ,
13+ Flask ,
14+ abort ,
15+ current_app ,
16+ jsonify ,
17+ request ,
18+ send_file ,
19+ send_from_directory ,
20+ )
21+
822from approaches .chatreadretrieveread import ChatReadRetrieveReadApproach
923from approaches .readdecomposeask import ReadDecomposeAsk
1024from approaches .readretrieveread import ReadRetrieveReadApproach
1125from approaches .retrievethenread import RetrieveThenReadApproach
12- from azure .identity import DefaultAzureCredential
13- from azure .search .documents import SearchClient
14- from azure .storage .blob import BlobServiceClient
15- from flask import Flask , abort , jsonify , request , send_file
1626
1727# Replace these with your own values, either in environment variables or directly here
18- AZURE_STORAGE_ACCOUNT = os .environ .get ("AZURE_STORAGE_ACCOUNT" ) or "mystorageaccount"
19- AZURE_STORAGE_CONTAINER = os .environ .get ("AZURE_STORAGE_CONTAINER" ) or "content"
20- AZURE_SEARCH_SERVICE = os .environ .get ("AZURE_SEARCH_SERVICE" ) or "gptkb"
21- AZURE_SEARCH_INDEX = os .environ .get ("AZURE_SEARCH_INDEX" ) or "gptkbindex"
22- AZURE_OPENAI_SERVICE = os .environ .get ("AZURE_OPENAI_SERVICE" ) or "myopenai"
23- AZURE_OPENAI_GPT_DEPLOYMENT = os .environ .get ("AZURE_OPENAI_GPT_DEPLOYMENT" ) or "davinci"
24- AZURE_OPENAI_CHATGPT_DEPLOYMENT = os .environ .get ("AZURE_OPENAI_CHATGPT_DEPLOYMENT" ) or "chat"
25- AZURE_OPENAI_CHATGPT_MODEL = os .environ .get ("AZURE_OPENAI_CHATGPT_MODEL" ) or "gpt-35-turbo"
26- AZURE_OPENAI_EMB_DEPLOYMENT = os .environ .get ("AZURE_OPENAI_EMB_DEPLOYMENT" ) or "embedding"
27-
28- KB_FIELDS_CONTENT = os .environ .get ("KB_FIELDS_CONTENT" ) or "content"
29- KB_FIELDS_CATEGORY = os .environ .get ("KB_FIELDS_CATEGORY" ) or "category"
30- KB_FIELDS_SOURCEPAGE = os .environ .get ("KB_FIELDS_SOURCEPAGE" ) or "sourcepage"
31-
32- # Use the current user identity to authenticate with Azure OpenAI, Cognitive Search and Blob Storage (no secrets needed,
33- # just use 'az login' locally, and managed identity when deployed on Azure). If you need to use keys, use separate AzureKeyCredential instances with the
34- # keys for each service
35- # If you encounter a blocking error during a DefaultAzureCredntial resolution, you can exclude the problematic credential by using a parameter (ex. exclude_shared_token_cache_credential=True)
36- azure_credential = DefaultAzureCredential (exclude_shared_token_cache_credential = True )
37-
38- # Used by the OpenAI SDK
39- openai .api_type = "azure"
40- openai .api_base = f"https://{ AZURE_OPENAI_SERVICE } .openai.azure.com"
41- openai .api_version = "2023-05-15"
42-
43- # Comment these two lines out if using keys, set your API key in the OPENAI_API_KEY environment variable instead
44- openai .api_type = "azure_ad"
45- openai_token = azure_credential .get_token ("https://cognitiveservices.azure.com/.default" )
46- openai .api_key = openai_token .token
47-
48- # Set up clients for Cognitive Search and Storage
49- search_client = SearchClient (
50- endpoint = f"https://{ AZURE_SEARCH_SERVICE } .search.windows.net" ,
51- index_name = AZURE_SEARCH_INDEX ,
52- credential = azure_credential )
53- blob_client = BlobServiceClient (
54- account_url = f"https://{ AZURE_STORAGE_ACCOUNT } .blob.core.windows.net" ,
55- credential = azure_credential )
56- blob_container = blob_client .get_container_client (AZURE_STORAGE_CONTAINER )
57-
58- # Various approaches to integrate GPT and external knowledge, most applications will use a single one of these patterns
59- # or some derivative, here we include several for exploration purposes
60- ask_approaches = {
61- "rtr" : RetrieveThenReadApproach (search_client , AZURE_OPENAI_CHATGPT_DEPLOYMENT , AZURE_OPENAI_CHATGPT_MODEL , AZURE_OPENAI_EMB_DEPLOYMENT , KB_FIELDS_SOURCEPAGE , KB_FIELDS_CONTENT ),
62- "rrr" : ReadRetrieveReadApproach (search_client , AZURE_OPENAI_GPT_DEPLOYMENT , AZURE_OPENAI_EMB_DEPLOYMENT , KB_FIELDS_SOURCEPAGE , KB_FIELDS_CONTENT ),
63- "rda" : ReadDecomposeAsk (search_client , AZURE_OPENAI_GPT_DEPLOYMENT , AZURE_OPENAI_EMB_DEPLOYMENT , KB_FIELDS_SOURCEPAGE , KB_FIELDS_CONTENT )
64- }
65-
66- chat_approaches = {
67- "rrr" : ChatReadRetrieveReadApproach (search_client ,
68- AZURE_OPENAI_CHATGPT_DEPLOYMENT ,
69- AZURE_OPENAI_CHATGPT_MODEL ,
70- AZURE_OPENAI_EMB_DEPLOYMENT ,
71- KB_FIELDS_SOURCEPAGE ,
72- KB_FIELDS_CONTENT )
73- }
74-
75- app = Flask (__name__ )
76-
77- @app .route ("/" , defaults = {"path" : "index.html" })
78- @app .route ("/<path:path>" )
79- def static_file (path ):
80- return app .send_static_file (path )
28+ AZURE_STORAGE_ACCOUNT = os .getenv ("AZURE_STORAGE_ACCOUNT" , "mystorageaccount" )
29+ AZURE_STORAGE_CONTAINER = os .getenv ("AZURE_STORAGE_CONTAINER" , "content" )
30+ AZURE_SEARCH_SERVICE = os .getenv ("AZURE_SEARCH_SERVICE" , "gptkb" )
31+ AZURE_SEARCH_INDEX = os .getenv ("AZURE_SEARCH_INDEX" , "gptkbindex" )
32+ AZURE_OPENAI_SERVICE = os .getenv ("AZURE_OPENAI_SERVICE" , "myopenai" )
33+ AZURE_OPENAI_GPT_DEPLOYMENT = os .getenv ("AZURE_OPENAI_GPT_DEPLOYMENT" , "davinci" )
34+ AZURE_OPENAI_CHATGPT_DEPLOYMENT = os .getenv ("AZURE_OPENAI_CHATGPT_DEPLOYMENT" , "chat" )
35+ AZURE_OPENAI_CHATGPT_MODEL = os .getenv ("AZURE_OPENAI_CHATGPT_MODEL" , "gpt-35-turbo" )
36+ AZURE_OPENAI_EMB_DEPLOYMENT = os .getenv ("AZURE_OPENAI_EMB_DEPLOYMENT" , "embedding" )
37+
38+ KB_FIELDS_CONTENT = os .getenv ("KB_FIELDS_CONTENT" , "content" )
39+ KB_FIELDS_CATEGORY = os .getenv ("KB_FIELDS_CATEGORY" , "category" )
40+ KB_FIELDS_SOURCEPAGE = os .getenv ("KB_FIELDS_SOURCEPAGE" , "sourcepage" )
41+
42+ CONFIG_OPENAI_TOKEN = "openai_token"
43+ CONFIG_CREDENTIAL = "azure_credential"
44+ CONFIG_ASK_APPROACHES = "ask_approaches"
45+ CONFIG_CHAT_APPROACHES = "chat_approaches"
46+ CONFIG_BLOB_CLIENT = "blob_client"
47+
48+
49+ bp = Blueprint ("routes" , __name__ , static_folder = 'static' )
50+
51+ @bp .route ("/" )
52+ def index ():
53+ return bp .send_static_file ("index.html" )
54+
55+ @bp .route ("/favicon.ico" )
56+ def favicon ():
57+ return bp .send_static_file ("favicon.ico" )
58+
59+ @bp .route ("/assets/<path:path>" )
60+ def assets (path ):
61+ return send_from_directory ("static/assets" , path )
8162
8263# Serve content files from blob storage from within the app to keep the example self-contained.
8364# *** NOTE *** this assumes that the content files are public, or at least that all users of the app
8465# can access all the files. This is also slow and memory hungry.
85- @app .route ("/content/<path>" )
66+ @bp .route ("/content/<path>" )
8667def content_file (path ):
68+ blob_container = current_app .config [CONFIG_BLOB_CLIENT ].get_container_client (AZURE_STORAGE_CONTAINER )
8769 blob = blob_container .get_blob_client (path ).download_blob ()
8870 if not blob .properties or not blob .properties .has_key ("content_settings" ):
8971 abort (404 )
@@ -95,13 +77,13 @@ def content_file(path):
9577 blob_file .seek (0 )
9678 return send_file (blob_file , mimetype = mime_type , as_attachment = False , download_name = path )
9779
98- @app .route ("/ask" , methods = ["POST" ])
80+ @bp .route ("/ask" , methods = ["POST" ])
9981def ask ():
100- if not request .json :
101- return jsonify ({"error" : "request must be json" }), 400
82+ if not request .is_json :
83+ return jsonify ({"error" : "request must be json" }), 415
10284 approach = request .json ["approach" ]
10385 try :
104- impl = ask_approaches .get (approach )
86+ impl = current_app . config [ CONFIG_ASK_APPROACHES ] .get (approach )
10587 if not impl :
10688 return jsonify ({"error" : "unknown approach" }), 400
10789 r = impl .run (request .json ["question" ], request .json .get ("overrides" ) or {})
@@ -110,13 +92,13 @@ def ask():
11092 logging .exception ("Exception in /ask" )
11193 return jsonify ({"error" : str (e )}), 500
11294
113- @app .route ("/chat" , methods = ["POST" ])
95+ @bp .route ("/chat" , methods = ["POST" ])
11496def chat ():
115- if not request .json :
116- return jsonify ({"error" : "request must be json" }), 400
97+ if not request .is_json :
98+ return jsonify ({"error" : "request must be json" }), 415
11799 approach = request .json ["approach" ]
118100 try :
119- impl = chat_approaches .get (approach )
101+ impl = current_app . config [ CONFIG_CHAT_APPROACHES ] .get (approach )
120102 if not impl :
121103 return jsonify ({"error" : "unknown approach" }), 400
122104 r = impl .run (request .json ["history" ], request .json .get ("overrides" ) or {})
@@ -125,12 +107,89 @@ def chat():
125107 logging .exception ("Exception in /chat" )
126108 return jsonify ({"error" : str (e )}), 500
127109
128- @app .before_request
110+ @bp .before_request
129111def ensure_openai_token ():
130- global openai_token
112+ openai_token = current_app . config [ CONFIG_OPENAI_TOKEN ]
131113 if openai_token .expires_on < time .time () + 60 :
132- openai_token = azure_credential .get_token ("https://cognitiveservices.azure.com/.default" )
114+ openai_token = current_app .config [CONFIG_CREDENTIAL ].get_token ("https://cognitiveservices.azure.com/.default" )
115+ current_app .config [CONFIG_OPENAI_TOKEN ] = openai_token
133116 openai .api_key = openai_token .token
134117
118+
119+ def create_app ():
120+ app = Flask (__name__ )
121+
122+ # Use the current user identity to authenticate with Azure OpenAI, Cognitive Search and Blob Storage (no secrets needed,
123+ # just use 'az login' locally, and managed identity when deployed on Azure). If you need to use keys, use separate AzureKeyCredential instances with the
124+ # keys for each service
125+ # If you encounter a blocking error during a DefaultAzureCredntial resolution, you can exclude the problematic credential by using a parameter (ex. exclude_shared_token_cache_credential=True)
126+ azure_credential = DefaultAzureCredential (exclude_shared_token_cache_credential = True )
127+
128+ # Set up clients for Cognitive Search and Storage
129+ search_client = SearchClient (
130+ endpoint = f"https://{ AZURE_SEARCH_SERVICE } .search.windows.net" ,
131+ index_name = AZURE_SEARCH_INDEX ,
132+ credential = azure_credential )
133+ blob_client = BlobServiceClient (
134+ account_url = f"https://{ AZURE_STORAGE_ACCOUNT } .blob.core.windows.net" ,
135+ credential = azure_credential )
136+
137+ # Used by the OpenAI SDK
138+ openai .api_type = "azure"
139+ openai .api_base = f"https://{ AZURE_OPENAI_SERVICE } .openai.azure.com"
140+ openai .api_version = "2023-05-15"
141+
142+ # Comment these two lines out if using keys, set your API key in the OPENAI_API_KEY environment variable instead
143+ openai .api_type = "azure_ad"
144+ openai_token = azure_credential .get_token (
145+ "https://cognitiveservices.azure.com/.default"
146+ )
147+ openai .api_key = openai_token .token
148+
149+ # Store on app.config for later use inside requests
150+ app .config [CONFIG_OPENAI_TOKEN ] = openai_token
151+ app .config [CONFIG_CREDENTIAL ] = azure_credential
152+ app .config [CONFIG_BLOB_CLIENT ] = blob_client
153+ # Various approaches to integrate GPT and external knowledge, most applications will use a single one of these patterns
154+ # or some derivative, here we include several for exploration purposes
155+ app .config [CONFIG_ASK_APPROACHES ] = {
156+ "rtr" : RetrieveThenReadApproach (
157+ search_client ,
158+ AZURE_OPENAI_CHATGPT_DEPLOYMENT ,
159+ AZURE_OPENAI_CHATGPT_MODEL ,
160+ AZURE_OPENAI_EMB_DEPLOYMENT ,
161+ KB_FIELDS_SOURCEPAGE ,
162+ KB_FIELDS_CONTENT
163+ ),
164+ "rrr" : ReadRetrieveReadApproach (
165+ search_client ,
166+ AZURE_OPENAI_GPT_DEPLOYMENT ,
167+ AZURE_OPENAI_EMB_DEPLOYMENT ,
168+ KB_FIELDS_SOURCEPAGE ,
169+ KB_FIELDS_CONTENT
170+ ),
171+ "rda" : ReadDecomposeAsk (search_client ,
172+ AZURE_OPENAI_GPT_DEPLOYMENT ,
173+ AZURE_OPENAI_EMB_DEPLOYMENT ,
174+ KB_FIELDS_SOURCEPAGE ,
175+ KB_FIELDS_CONTENT
176+ )
177+ }
178+ app .config [CONFIG_CHAT_APPROACHES ] = {
179+ "rrr" : ChatReadRetrieveReadApproach (
180+ search_client ,
181+ AZURE_OPENAI_CHATGPT_DEPLOYMENT ,
182+ AZURE_OPENAI_CHATGPT_MODEL ,
183+ AZURE_OPENAI_EMB_DEPLOYMENT ,
184+ KB_FIELDS_SOURCEPAGE ,
185+ KB_FIELDS_CONTENT ,
186+ )
187+ }
188+
189+ app .register_blueprint (bp )
190+
191+ return app
192+
135193if __name__ == "__main__" :
194+ app = create_app ()
136195 app .run ()
0 commit comments