Skip to content

Commit 62f3618

Browse files
committed
Lab changes
1 parent 6597e8b commit 62f3618

File tree

6 files changed

+336
-8
lines changed

6 files changed

+336
-8
lines changed

.snowflake/.DS_Store

6 KB
Binary file not shown.

.snowflake/config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ enable_snowflake_projects = true
88

99
[connections.default]
1010
role = "ATTENDEE_ROLE"
11-
account = "REPLACEME"
11+
account = "SFSEHOL-SUMMIT25_DATA_PROJECTS_CI_CD_MJCSIA"
1212
user = "service_user"
1313
schema = "DEV"
1414
database = "UTILS"

.snowflake/environment.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# This file is used to install packages used by the Streamlit App.
2+
# For more details, refer to https://docs.snowflake.com/en/developer-guide/streamlit/create-streamlit-sql#label-streamlit-install-packages-manual
3+
4+
channels:
5+
- snowflake
6+
dependencies:
7+
- streamlit=1.35.0
8+
- snowflake=0.12.1

.snowflake/ui.py

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
import streamlit as st
2+
from snowflake.core import Root # requires snowflake>=0.8.0
3+
from snowflake.snowpark.context import get_active_session
4+
import time
5+
6+
MODELS = [
7+
"mistral-large",
8+
"snowflake-arctic",
9+
"llama3-70b",
10+
"llama3-8b",
11+
]
12+
13+
def init_messages():
14+
"""
15+
Initialize the session state for chat messages. If the session state indicates that the
16+
conversation should be cleared or if the "messages" key is not in the session state,
17+
initialize it as an empty list.
18+
"""
19+
if st.session_state.clear_conversation or "messages" not in st.session_state:
20+
st.session_state.messages = []
21+
22+
def init_service_metadata():
23+
"""
24+
Initialize the session state for cortex search service metadata. Query the available
25+
cortex search services from the Snowflake session and store their names and search
26+
columns in the session state.
27+
"""
28+
if "service_metadata" not in st.session_state:
29+
services = session.sql("SHOW CORTEX SEARCH SERVICES IN DATABASE MOVIES;").collect()
30+
service_metadata = []
31+
if services:
32+
for s in services:
33+
svc_name = s["name"]
34+
svc_search_col = session.sql(
35+
f"DESC CORTEX SEARCH SERVICE MOVIES.DATA.{svc_name};"
36+
).collect()[0]["search_column"]
37+
service_metadata.append(
38+
{"name": svc_name, "search_column": svc_search_col}
39+
)
40+
41+
st.session_state.service_metadata = service_metadata
42+
43+
def init_config_options():
44+
"""
45+
Initialize the configuration options in the Streamlit sidebar. Allow the user to select
46+
a cortex search service, clear the conversation, toggle debug mode, and toggle the use of
47+
chat history. Also provide advanced options to select a model, the number of context chunks,
48+
and the number of chat messages to use in the chat history.
49+
"""
50+
st.sidebar.selectbox(
51+
"Select cortex search service:",
52+
[s["name"] for s in st.session_state.service_metadata],
53+
key="selected_cortex_search_service",
54+
)
55+
56+
st.sidebar.button("Clear conversation", key="clear_conversation")
57+
st.sidebar.toggle("Debug", key="debug", value=False)
58+
st.sidebar.toggle("Use chat history", key="use_chat_history", value=True)
59+
60+
with st.sidebar.expander("Advanced options"):
61+
st.selectbox("Select model:", MODELS, key="model_name")
62+
st.number_input(
63+
"Select number of context chunks",
64+
value=5,
65+
key="num_retrieved_chunks",
66+
min_value=1,
67+
max_value=10,
68+
)
69+
st.number_input(
70+
"Select number of messages to use in chat history",
71+
value=5,
72+
key="num_chat_messages",
73+
min_value=1,
74+
max_value=10,
75+
)
76+
77+
st.sidebar.expander("Session State").write(st.session_state)
78+
79+
def query_cortex_search_service(query):
80+
"""
81+
Query the selected cortex search service with the given query and retrieve context documents.
82+
Display the retrieved context documents in the sidebar if debug mode is enabled. Return the
83+
context documents as a string.
84+
85+
Args:
86+
query (str): The query to search the cortex search service with.
87+
88+
Returns:
89+
str: The concatenated string of context documents.
90+
"""
91+
db, schema = 'movies', 'data'
92+
93+
cortex_search_service = (
94+
root.databases[db]
95+
.schemas[schema]
96+
.cortex_search_services[st.session_state.selected_cortex_search_service]
97+
)
98+
99+
context_documents = cortex_search_service.search(
100+
query, columns=[], limit=st.session_state.num_retrieved_chunks
101+
)
102+
results = context_documents.results
103+
104+
service_metadata = st.session_state.service_metadata
105+
search_col = [s["search_column"] for s in service_metadata
106+
if s["name"] == st.session_state.selected_cortex_search_service][0]
107+
108+
context_str = ""
109+
for i, r in enumerate(results):
110+
context_str += f"Context document {i+1}: {r[search_col]} \n" + "\n"
111+
112+
if st.session_state.debug:
113+
st.sidebar.text_area("Context documents", context_str, height=500)
114+
115+
return context_str
116+
117+
def get_chat_history():
118+
"""
119+
Retrieve the chat history from the session state limited to the number of messages specified
120+
by the user in the sidebar options.
121+
122+
Returns:
123+
list: The list of chat messages from the session state.
124+
"""
125+
start_index = max(
126+
0, len(st.session_state.messages) - st.session_state.num_chat_messages
127+
)
128+
return st.session_state.messages[start_index : len(st.session_state.messages) - 1]
129+
130+
def complete(model, prompt):
131+
"""
132+
Generate a completion for the given prompt using the specified model.
133+
134+
Args:
135+
model (str): The name of the model to use for completion.
136+
prompt (str): The prompt to generate a completion for.
137+
138+
Returns:
139+
str: The generated completion.
140+
"""
141+
return session.sql("SELECT snowflake.cortex.complete(?,?)", (model, prompt)).collect()[0][0]
142+
143+
def make_chat_history_summary(chat_history, question):
144+
"""
145+
Generate a summary of the chat history combined with the current question to extend the query
146+
context. Use the language model to generate this summary.
147+
148+
Args:
149+
chat_history (str): The chat history to include in the summary.
150+
question (str): The current user question to extend with the chat history.
151+
152+
Returns:
153+
str: The generated summary of the chat history and question.
154+
"""
155+
prompt = f"""
156+
[INST]
157+
Based on the chat history below and the question, generate a query that extend the question
158+
with the chat history provided. The query should be in natural language.
159+
Answer with only the query. Do not add any explanation.
160+
161+
<chat_history>
162+
{chat_history}
163+
</chat_history>
164+
<question>
165+
{question}
166+
</question>
167+
[/INST]
168+
"""
169+
170+
summary = complete(st.session_state.model_name, prompt)
171+
172+
if st.session_state.debug:
173+
st.sidebar.text_area(
174+
"Chat history summary", summary.replace("$", "\$"), height=150
175+
)
176+
177+
return summary
178+
179+
def create_prompt(user_question):
180+
"""
181+
Create a prompt for the language model by combining the user question with context retrieved
182+
from the cortex search service and chat history (if enabled). Format the prompt according to
183+
the expected input format of the model.
184+
185+
Args:
186+
user_question (str): The user's question to generate a prompt for.
187+
188+
Returns:
189+
str: The generated prompt for the language model.
190+
"""
191+
if st.session_state.use_chat_history:
192+
chat_history = get_chat_history()
193+
if chat_history != []:
194+
question_summary = make_chat_history_summary(chat_history, user_question)
195+
prompt_context = query_cortex_search_service(question_summary)
196+
else:
197+
prompt_context = query_cortex_search_service(user_question)
198+
else:
199+
prompt_context = query_cortex_search_service(user_question)
200+
chat_history = ""
201+
202+
prompt = f"""
203+
[INST]
204+
You are a helpful Movie Recommendation chatbot with RAG capabilities. When a user asks you a question about movie recommendations you will recommend movies that are similar to the one they ask about or are in the same genre
205+
. Use that context with the user's chat history provided in the between <chat_history> and </chat_history> tags
206+
to provide a summary that addresses the user's question. Ensure the answer is coherent, concise,
207+
and directly relevant to the user's question.
208+
209+
If the user asks a generic question which cannot be answered with the given context or chat_history,
210+
just say "I don't know the answer to that question.
211+
212+
Don't saying things like "according to the provided context".
213+
214+
<chat_history>
215+
{chat_history}
216+
</chat_history>
217+
<context>
218+
{prompt_context}
219+
</context>
220+
<question>
221+
{user_question}
222+
</question>
223+
[/INST]
224+
Answer:
225+
"""
226+
return prompt
227+
228+
def init_chunking():
229+
st.markdown("We have not detected a Cortex Service or a chunked table. Please click below to set this up")
230+
if st.button('Prepare Service'):
231+
try:
232+
with st.spinner("Preparing service... please wait."):
233+
session.sql("BEGIN \
234+
call CORTEX_APP_INSTANCE.CORE.TABLE_CHUNKER();\
235+
call CORTEX_APP_INSTANCE.CORE.CREATE_CORTEX_SEARCH();\
236+
END").collect()
237+
238+
st.success("Table chunked and Cortex Service created. Reloading...")
239+
240+
# Reload service metadata
241+
st.session_state.pop("service_metadata", None) # Clear old metadata if exists
242+
243+
# Re-fetch service metadata
244+
init_service_metadata()
245+
246+
if st.session_state.service_metadata and st.session_state.service_metadata:
247+
st.session_state.selected_cortex_search_service = st.session_state.service_metadata[0]["name"]
248+
249+
250+
# Rerun the app cleanly
251+
st.rerun()
252+
except Exception as e:
253+
st.error(f"An error occurred: {e}")
254+
255+
def ensure_service_ready():
256+
"""
257+
Ensure that a Cortex Search Service exists and is selected.
258+
If no service exists, create one and rerun the app.
259+
"""
260+
init_service_metadata()
261+
262+
# If no service exists yet, prepare one
263+
if "service_metadata" not in st.session_state or not st.session_state.service_metadata:
264+
init_chunking()
265+
st.stop()
266+
267+
# If no selection made yet, select the first service
268+
if "selected_cortex_search_service" not in st.session_state:
269+
st.session_state.selected_cortex_search_service = st.session_state.service_metadata[0]["name"]
270+
271+
272+
def main():
273+
st.title(f":movie_camera: Snowflake CineBot")
274+
275+
ensure_service_ready()
276+
init_config_options()
277+
init_messages()
278+
279+
# If the underlying service has not been created, disable the chat and display config
280+
disable_chat = (
281+
"service_metadata" not in st.session_state
282+
or len(st.session_state.service_metadata) == 0
283+
)
284+
285+
if disable_chat:
286+
init_chunking()
287+
288+
icons = {"assistant": "❄️", "user": "👤"}
289+
290+
# Display chat messages from history on app rerun
291+
for message in st.session_state.messages:
292+
with st.chat_message(message["role"], avatar=icons[message["role"]]):
293+
st.markdown(message["content"])
294+
295+
if question := st.chat_input("Ask a question...", disabled=disable_chat):
296+
# Add user message to chat history
297+
st.session_state.messages.append({"role": "user", "content": question})
298+
# Display user message in chat message container
299+
with st.chat_message("user", avatar=icons["user"]):
300+
st.markdown(question.replace("$", "\$"))
301+
302+
# Display assistant response in chat message container
303+
with st.chat_message("assistant", avatar=icons["assistant"]):
304+
message_placeholder = st.empty()
305+
question = question.replace("'", "")
306+
with st.spinner("Thinking..."):
307+
generated_response = complete(
308+
st.session_state.model_name, create_prompt(question)
309+
)
310+
message_placeholder.markdown(generated_response)
311+
312+
st.session_state.messages.append(
313+
{"role": "assistant", "content": generated_response}
314+
)
315+
316+
if __name__ == "__main__":
317+
session = get_active_session()
318+
root = Root(session)
319+
main()

steps/definitions/account_objects.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ define warehouse ANALYST_WH_{{ ENV }}
77
AUTO_SUSPEND = 1200;
88

99
define warehouse DATA_SCIENCE_WH_{{ ENV }}
10-
WAREHOUSE_SIZE = LARGE
10+
WAREHOUSE_SIZE = MEDIUM
1111
AUTO_SUSPEND = 60;

steps/definitions/schema_objects.sql

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,19 +95,20 @@ DEFINE TABLE {{DB}}_{{ENV}}_DB.{{SCHEMA}}.DIAGNOSIS_D
9595
--Views
9696
--NOTE - The CREATE OR ALTER VIEW command doesn’t support changing a view definition once a view is created.
9797
--This limitation is inherited from the ALTER VIEW command.
98-
DEFINE VIEW {{DB}}_{{ENV}}_DB.MEMBERSHIP.TOP_100_MEMBERS_BY_CLAIM_COUNT
98+
DEFINE VIEW {{ DB }}_{{ ENV }}_DB.MEMBERSHIP.TOP_100_MEMBERS_BY_PAID_AMOUNT
9999
AS
100100
SELECT TOP 100 COUNT(DISTINCT C.CLAIM_ID) AS CLAIM_COUNT
101101
, D.DIAGNOSIS_CODE
102102
, M.MEMBER_FIRST_NAME
103103
, SUM(PAID_AMOUNT) AS TOTAL_PAID_AMOUNT
104-
FROM GOLD_{{ENV}}_DB.CLAIMS.CLAIM_F C
105-
JOIN GOLD_{{ENV}}_DB.REFERENCE.DIAGNOSIS_D D
104+
FROM GOLD_{{ ENV }}_DB.CLAIMS.CLAIM_F C
105+
JOIN GOLD_{{ ENV }}_DB.REFERENCE.DIAGNOSIS_D D
106106
ON C.DIAGNOSIS_ID = D.DIAGNOSIS_ID
107-
JOIN GOLD_{{ENV}}_DB.MEMBERSHIP.MEMBER_D M
107+
JOIN GOLD_{{ ENV }}_DB.MEMBERSHIP.MEMBER_D M
108108
ON C.MEMBER_ID = M.MEMBER_ID
109-
JOIN GOLD_{{ENV}}_DB.CLAIMS.CLAIM_LINE_F CL
109+
JOIN GOLD_{{ ENV }}_DB.CLAIMS.CLAIM_LINE_F CL
110+
ON C.CLAIM_ID = CL.CLAIM_ID
110111
GROUP BY ALL
111-
ORDER BY CLAIM_COUNT DESC
112+
ORDER BY TOTAL_PAID_AMOUNT DESC
112113
;
113114

0 commit comments

Comments
 (0)