Skip to content

Commit 38dd16c

Browse files
authored
Run black on all Python files (#632)
* Run black on all files * Fix acreate
1 parent 89a22ea commit 38dd16c

18 files changed

+678
-357
lines changed

.github/workflows/python-test.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,7 @@ jobs:
3737
pip install -r requirements-dev.txt
3838
- name: Lint with ruff
3939
run: ruff .
40+
- name: Check formatting with black
41+
run: black . --check --verbose
4042
- name: Run Python tests
4143
run: python3 -m pytest

.pre-commit-config.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ repos:
77
- id: end-of-file-fixer
88
- id: trailing-whitespace
99
- repo: https://github.com/astral-sh/ruff-pre-commit
10-
rev: v0.0.282
10+
rev: v0.0.289
1111
hooks:
1212
- id: ruff
13+
- repo: https://github.com/psf/black
14+
rev: 23.9.1
15+
hooks:
16+
- id: black

app/backend/app.py

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,24 @@
3737
CONFIG_CHAT_APPROACHES = "chat_approaches"
3838
CONFIG_BLOB_CONTAINER_CLIENT = "blob_container_client"
3939

40-
bp = Blueprint("routes", __name__, static_folder='static')
40+
bp = Blueprint("routes", __name__, static_folder="static")
41+
4142

4243
@bp.route("/")
4344
async def index():
4445
return await bp.send_static_file("index.html")
4546

47+
4648
@bp.route("/favicon.ico")
4749
async def favicon():
4850
return await bp.send_static_file("favicon.ico")
4951

52+
5053
@bp.route("/assets/<path:path>")
5154
async def assets(path):
5255
return await send_from_directory("static/assets", path)
5356

57+
5458
# Serve content files from blob storage from within the app to keep the example self-contained.
5559
# *** NOTE *** this assumes that the content files are public, or at least that all users of the app
5660
# can access all the files. This is also slow and memory hungry.
@@ -68,6 +72,7 @@ async def content_file(path):
6872
blob_file.seek(0)
6973
return await send_file(blob_file, mimetype=mime_type, as_attachment=False, attachment_filename=path)
7074

75+
7176
@bp.route("/ask", methods=["POST"])
7277
async def ask():
7378
if not request.is_json:
@@ -87,6 +92,7 @@ async def ask():
8792
logging.exception("Exception in /ask")
8893
return jsonify({"error": str(e)}), 500
8994

95+
9096
@bp.route("/chat", methods=["POST"])
9197
async def chat():
9298
if not request.is_json:
@@ -111,6 +117,7 @@ async def format_as_ndjson(r: AsyncGenerator[dict, None]) -> AsyncGenerator[str,
111117
async for event in r:
112118
yield json.dumps(event, ensure_ascii=False) + "\n"
113119

120+
114121
@bp.route("/chat_stream", methods=["POST"])
115122
async def chat_stream():
116123
if not request.is_json:
@@ -123,7 +130,7 @@ async def chat_stream():
123130
return jsonify({"error": "unknown approach"}), 400
124131
response_generator = impl.run_with_streaming(request_json["history"], request_json.get("overrides", {}))
125132
response = await make_response(format_as_ndjson(response_generator))
126-
response.timeout = None # type: ignore
133+
response.timeout = None # type: ignore
127134
return response
128135
except Exception as e:
129136
logging.exception("Exception in /chat")
@@ -134,13 +141,15 @@ async def chat_stream():
134141
async def ensure_openai_token():
135142
openai_token = current_app.config[CONFIG_OPENAI_TOKEN]
136143
if openai_token.expires_on < time.time() + 60:
137-
openai_token = await current_app.config[CONFIG_CREDENTIAL].get_token("https://cognitiveservices.azure.com/.default")
144+
openai_token = await current_app.config[CONFIG_CREDENTIAL].get_token(
145+
"https://cognitiveservices.azure.com/.default"
146+
)
138147
current_app.config[CONFIG_OPENAI_TOKEN] = openai_token
139148
openai.api_key = openai_token.token
140149

150+
141151
@bp.before_app_serving
142152
async def setup_clients():
143-
144153
# Replace these with your own values, either in environment variables or directly here
145154
AZURE_STORAGE_ACCOUNT = os.environ["AZURE_STORAGE_ACCOUNT"]
146155
AZURE_STORAGE_CONTAINER = os.environ["AZURE_STORAGE_CONTAINER"]
@@ -158,25 +167,24 @@ async def setup_clients():
158167
# just use 'az login' locally, and managed identity when deployed on Azure). If you need to use keys, use separate AzureKeyCredential instances with the
159168
# keys for each service
160169
# If you encounter a blocking error during a DefaultAzureCredential resolution, you can exclude the problematic credential by using a parameter (ex. exclude_shared_token_cache_credential=True)
161-
azure_credential = DefaultAzureCredential(exclude_shared_token_cache_credential = True)
170+
azure_credential = DefaultAzureCredential(exclude_shared_token_cache_credential=True)
162171

163172
# Set up clients for Cognitive Search and Storage
164173
search_client = SearchClient(
165174
endpoint=f"https://{AZURE_SEARCH_SERVICE}.search.windows.net",
166175
index_name=AZURE_SEARCH_INDEX,
167-
credential=azure_credential)
176+
credential=azure_credential,
177+
)
168178
blob_client = BlobServiceClient(
169-
account_url=f"https://{AZURE_STORAGE_ACCOUNT}.blob.core.windows.net",
170-
credential=azure_credential)
179+
account_url=f"https://{AZURE_STORAGE_ACCOUNT}.blob.core.windows.net", credential=azure_credential
180+
)
171181
blob_container_client = blob_client.get_container_client(AZURE_STORAGE_CONTAINER)
172182

173183
# Used by the OpenAI SDK
174184
openai.api_base = f"https://{AZURE_OPENAI_SERVICE}.openai.azure.com"
175185
openai.api_version = "2023-05-15"
176186
openai.api_type = "azure_ad"
177-
openai_token = await azure_credential.get_token(
178-
"https://cognitiveservices.azure.com/.default"
179-
)
187+
openai_token = await azure_credential.get_token("https://cognitiveservices.azure.com/.default")
180188
openai.api_key = openai_token.token
181189

182190
# Store on app.config for later use inside requests
@@ -193,21 +201,22 @@ async def setup_clients():
193201
AZURE_OPENAI_CHATGPT_MODEL,
194202
AZURE_OPENAI_EMB_DEPLOYMENT,
195203
KB_FIELDS_SOURCEPAGE,
196-
KB_FIELDS_CONTENT
204+
KB_FIELDS_CONTENT,
197205
),
198206
"rrr": ReadRetrieveReadApproach(
199207
search_client,
200208
AZURE_OPENAI_CHATGPT_DEPLOYMENT,
201209
AZURE_OPENAI_EMB_DEPLOYMENT,
202210
KB_FIELDS_SOURCEPAGE,
203-
KB_FIELDS_CONTENT
211+
KB_FIELDS_CONTENT,
204212
),
205-
"rda": ReadDecomposeAsk(search_client,
213+
"rda": ReadDecomposeAsk(
214+
search_client,
206215
AZURE_OPENAI_CHATGPT_DEPLOYMENT,
207216
AZURE_OPENAI_EMB_DEPLOYMENT,
208217
KB_FIELDS_SOURCEPAGE,
209-
KB_FIELDS_CONTENT
210-
)
218+
KB_FIELDS_CONTENT,
219+
),
211220
}
212221
current_app.config[CONFIG_CHAT_APPROACHES] = {
213222
"rrr": ChatReadRetrieveReadApproach(

app/backend/approaches/chatreadretrieveread.py

Lines changed: 94 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,21 @@ class ChatReadRetrieveReadApproach:
4141
If you cannot generate a search query, return just the number 0.
4242
"""
4343
query_prompt_few_shots = [
44-
{'role' : USER, 'content' : 'What are my health plans?' },
45-
{'role' : ASSISTANT, 'content' : 'Show available health plans' },
46-
{'role' : USER, 'content' : 'does my plan cover cardio?' },
47-
{'role' : ASSISTANT, 'content' : 'Health plan cardio coverage' }
44+
{"role": USER, "content": "What are my health plans?"},
45+
{"role": ASSISTANT, "content": "Show available health plans"},
46+
{"role": USER, "content": "does my plan cover cardio?"},
47+
{"role": ASSISTANT, "content": "Health plan cardio coverage"},
4848
]
4949

50-
def __init__(self, search_client: SearchClient, chatgpt_deployment: str, chatgpt_model: str, embedding_deployment: str, sourcepage_field: str, content_field: str):
50+
def __init__(
51+
self,
52+
search_client: SearchClient,
53+
chatgpt_deployment: str,
54+
chatgpt_model: str,
55+
embedding_deployment: str,
56+
sourcepage_field: str,
57+
content_field: str,
58+
):
5159
self.search_client = search_client
5260
self.chatgpt_deployment = chatgpt_deployment
5361
self.chatgpt_model = chatgpt_model
@@ -56,15 +64,17 @@ def __init__(self, search_client: SearchClient, chatgpt_deployment: str, chatgpt
5664
self.content_field = content_field
5765
self.chatgpt_token_limit = get_token_limit(chatgpt_model)
5866

59-
async def run_until_final_call(self, history: list[dict[str, str]], overrides: dict[str, Any], should_stream: bool = False) -> tuple:
67+
async def run_until_final_call(
68+
self, history: list[dict[str, str]], overrides: dict[str, Any], should_stream: bool = False
69+
) -> tuple:
6070
has_text = overrides.get("retrieval_mode") in ["text", "hybrid", None]
6171
has_vector = overrides.get("retrieval_mode") in ["vectors", "hybrid", None]
6272
use_semantic_captions = True if overrides.get("semantic_captions") and has_text else False
6373
top = overrides.get("top") or 3
6474
exclude_category = overrides.get("exclude_category") or None
6575
filter = "category ne '{}'".format(exclude_category.replace("'", "''")) if exclude_category else None
6676

67-
user_q = 'Generate search query for: ' + history[-1]["user"]
77+
user_q = "Generate search query for: " + history[-1]["user"]
6878

6979
# STEP 1: Generate an optimized keyword search query based on the chat history and the last question
7080
messages = self.get_messages_from_history(
@@ -73,89 +83,112 @@ async def run_until_final_call(self, history: list[dict[str, str]], overrides: d
7383
history,
7484
user_q,
7585
self.query_prompt_few_shots,
76-
self.chatgpt_token_limit - len(user_q)
77-
)
86+
self.chatgpt_token_limit - len(user_q),
87+
)
7888

7989
chat_completion = await openai.ChatCompletion.acreate(
8090
deployment_id=self.chatgpt_deployment,
8191
model=self.chatgpt_model,
8292
messages=messages,
8393
temperature=0.0,
8494
max_tokens=32,
85-
n=1)
95+
n=1,
96+
)
8697

8798
query_text = chat_completion.choices[0].message.content
8899
if query_text.strip() == "0":
89-
query_text = history[-1]["user"] # Use the last user input if we failed to generate a better query
100+
query_text = history[-1]["user"] # Use the last user input if we failed to generate a better query
90101

91102
# STEP 2: Retrieve relevant documents from the search index with the GPT optimized query
92103

93104
# If retrieval mode includes vectors, compute an embedding for the query
94105
if has_vector:
95-
query_vector = (await openai.Embedding.acreate(engine=self.embedding_deployment, input=query_text))["data"][0]["embedding"]
106+
embedding = await openai.Embedding.acreate(engine=self.embedding_deployment, input=query_text)
107+
query_vector = embedding["data"][0]["embedding"]
96108
else:
97109
query_vector = None
98110

99-
# Only keep the text query if the retrieval mode uses text, otherwise drop it
111+
# Only keep the text query if the retrieval mode uses text, otherwise drop it
100112
if not has_text:
101113
query_text = None
102114

103115
# Use semantic L2 reranker if requested and if retrieval mode is text or hybrid (vectors + text)
104116
if overrides.get("semantic_ranker") and has_text:
105-
r = await self.search_client.search(query_text,
106-
filter=filter,
107-
query_type=QueryType.SEMANTIC,
108-
query_language="en-us",
109-
query_speller="lexicon",
110-
semantic_configuration_name="default",
111-
top=top,
112-
query_caption="extractive|highlight-false" if use_semantic_captions else None,
113-
vector=query_vector,
114-
top_k=50 if query_vector else None,
115-
vector_fields="embedding" if query_vector else None)
117+
r = await self.search_client.search(
118+
query_text,
119+
filter=filter,
120+
query_type=QueryType.SEMANTIC,
121+
query_language="en-us",
122+
query_speller="lexicon",
123+
semantic_configuration_name="default",
124+
top=top,
125+
query_caption="extractive|highlight-false" if use_semantic_captions else None,
126+
vector=query_vector,
127+
top_k=50 if query_vector else None,
128+
vector_fields="embedding" if query_vector else None,
129+
)
116130
else:
117-
r = await self.search_client.search(query_text,
118-
filter=filter,
119-
top=top,
120-
vector=query_vector,
121-
top_k=50 if query_vector else None,
122-
vector_fields="embedding" if query_vector else None)
131+
r = await self.search_client.search(
132+
query_text,
133+
filter=filter,
134+
top=top,
135+
vector=query_vector,
136+
top_k=50 if query_vector else None,
137+
vector_fields="embedding" if query_vector else None,
138+
)
123139
if use_semantic_captions:
124-
results = [doc[self.sourcepage_field] + ": " + nonewlines(" . ".join([c.text for c in doc['@search.captions']])) async for doc in r]
140+
results = [
141+
doc[self.sourcepage_field] + ": " + nonewlines(" . ".join([c.text for c in doc["@search.captions"]]))
142+
async for doc in r
143+
]
125144
else:
126145
results = [doc[self.sourcepage_field] + ": " + nonewlines(doc[self.content_field]) async for doc in r]
127146
content = "\n".join(results)
128147

129-
follow_up_questions_prompt = self.follow_up_questions_prompt_content if overrides.get("suggest_followup_questions") else ""
148+
follow_up_questions_prompt = (
149+
self.follow_up_questions_prompt_content if overrides.get("suggest_followup_questions") else ""
150+
)
130151

131152
# STEP 3: Generate a contextual and content specific answer using the search results and chat history
132153

133154
# Allow client to replace the entire prompt, or to inject into the exiting prompt using >>>
134155
prompt_override = overrides.get("prompt_template")
135156
if prompt_override is None:
136-
system_message = self.system_message_chat_conversation.format(injected_prompt="", follow_up_questions_prompt=follow_up_questions_prompt)
157+
system_message = self.system_message_chat_conversation.format(
158+
injected_prompt="", follow_up_questions_prompt=follow_up_questions_prompt
159+
)
137160
elif prompt_override.startswith(">>>"):
138-
system_message = self.system_message_chat_conversation.format(injected_prompt=prompt_override[3:] + "\n", follow_up_questions_prompt=follow_up_questions_prompt)
161+
system_message = self.system_message_chat_conversation.format(
162+
injected_prompt=prompt_override[3:] + "\n", follow_up_questions_prompt=follow_up_questions_prompt
163+
)
139164
else:
140165
system_message = prompt_override.format(follow_up_questions_prompt=follow_up_questions_prompt)
141166

142167
messages = self.get_messages_from_history(
143168
system_message,
144169
self.chatgpt_model,
145170
history,
146-
history[-1]["user"]+ "\n\nSources:\n" + content, # Model does not handle lengthy system messages well. Moving sources to latest user conversation to solve follow up questions prompt.
147-
max_tokens=self.chatgpt_token_limit)
148-
msg_to_display = '\n\n'.join([str(message) for message in messages])
149-
150-
extra_info = {"data_points": results, "thoughts": f"Searched for:<br>{query_text}<br><br>Conversations:<br>" + msg_to_display.replace('\n', '<br>')}
171+
# Model does not handle lengthy system messages well.
172+
# Moved sources to latest user conversation to solve follow up questions prompt.
173+
history[-1]["user"] + "\n\nSources:\n" + content,
174+
max_tokens=self.chatgpt_token_limit,
175+
)
176+
msg_to_display = "\n\n".join([str(message) for message in messages])
177+
178+
extra_info = {
179+
"data_points": results,
180+
"thoughts": f"Searched for:<br>{query_text}<br><br>Conversations:<br>"
181+
+ msg_to_display.replace("\n", "<br>"),
182+
}
151183
chat_coroutine = openai.ChatCompletion.acreate(
152-
deployment_id=self.chatgpt_deployment,
153-
model=self.chatgpt_model,
154-
messages=messages,
155-
temperature=overrides.get("temperature") or 0.7,
156-
max_tokens=1024,
157-
n=1,
158-
stream=should_stream)
184+
deployment_id=self.chatgpt_deployment,
185+
model=self.chatgpt_model,
186+
messages=messages,
187+
temperature=overrides.get("temperature") or 0.7,
188+
max_tokens=1024,
189+
n=1,
190+
stream=should_stream,
191+
)
159192
return (extra_info, chat_coroutine)
160193

161194
async def run_without_streaming(self, history: list[dict[str, str]], overrides: dict[str, Any]) -> dict[str, Any]:
@@ -164,19 +197,29 @@ async def run_without_streaming(self, history: list[dict[str, str]], overrides:
164197
extra_info["answer"] = chat_content
165198
return extra_info
166199

167-
async def run_with_streaming(self, history: list[dict[str, str]], overrides: dict[str, Any]) -> AsyncGenerator[dict, None]:
200+
async def run_with_streaming(
201+
self, history: list[dict[str, str]], overrides: dict[str, Any]
202+
) -> AsyncGenerator[dict, None]:
168203
extra_info, chat_coroutine = await self.run_until_final_call(history, overrides, should_stream=True)
169204
yield extra_info
170205
async for event in await chat_coroutine:
171206
yield event
172207

173-
174-
def get_messages_from_history(self, system_prompt: str, model_id: str, history: list[dict[str, str]], user_conv: str, few_shots = [], max_tokens: int = 4096) -> list:
208+
def get_messages_from_history(
209+
self,
210+
system_prompt: str,
211+
model_id: str,
212+
history: list[dict[str, str]],
213+
user_conv: str,
214+
few_shots=[],
215+
max_tokens: int = 4096,
216+
) -> list:
175217
message_builder = MessageBuilder(system_prompt, model_id)
176218

177-
# Add examples to show the chat what responses we want. It will try to mimic any responses and make sure they match the rules laid out in the system message.
219+
# Add examples to show the chat what responses we want.
220+
# It will try to mimic any responses and make sure they match the rules laid out in the system message.
178221
for shot in few_shots:
179-
message_builder.append_message(shot.get('role'), shot.get('content'))
222+
message_builder.append_message(shot.get("role"), shot.get("content"))
180223

181224
user_content = user_conv
182225
append_index = len(few_shots) + 1

0 commit comments

Comments
 (0)