From 315f4d8121933ecef5842628f4cb912f20a9c83a Mon Sep 17 00:00:00 2001 From: Chris Nivera Date: Wed, 9 Oct 2024 14:27:18 -0700 Subject: [PATCH 1/4] Update README.md --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b24f4720..82a55ab9 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,12 @@ SELECT CURRENT_ACCOUNT_LOCATOR(); ``` B. To find the `SNOWFLAKE_HOST` for your -account, [follow these instructions](https://docs.snowflake.com/en/user-guide/organizations-connect#connecting-with-a-url). +account, [follow these instructions](https://docs.snowflake.com/en/user-guide/organizations-connect#connecting-with-a-url). The easiest way to find your account URL is to click the `Copy account URL` button from the Account panel in Snowsight: -* Currently we recommend you to look under the `Account locator (legacy)` method of connection for better compatibility - on API. +![CleanShot 2024-10-09 at 14 25 13](https://github.com/user-attachments/assets/b1715c57-9571-4c65-92fb-e5d43afa871b) + +However, if you have trouble authenticating with this URL, you can try building the URL manually: +* Currently we recommend you to look under the `Account locator (legacy)` method of connection for better compatibility on API. * It typically follows format of: `...snowflakecomputing.com`. Ensure that you omit the `https://` prefix. * `SNOWFLAKE_HOST` is required if you are using the Streamlit app, but may not be required for the CLI tool depending on From dca309032ba158a68d9242b01fbbed6015a00aee Mon Sep 17 00:00:00 2001 From: Chris Nivera Date: Wed, 9 Oct 2024 14:57:42 -0700 Subject: [PATCH 2/4] Add debug options for chatting (#176) For users trying to debug failures, it is often helpful to provide a request ID. Request IDs are not currently shown anywhere in the app. This PR adds a debug mode setting to the chat window that, when enabled, showcases request IDs for all assistant messages. This will make it easier to provide the relevant info to engineers. https://github.com/user-attachments/assets/e7c00459-35cc-4039-a386-a2d796c1ac9a --- admin_apps/journeys/iteration.py | 46 ++++++++++++++++++++++++++++---- admin_apps/shared_utils.py | 4 +++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/admin_apps/journeys/iteration.py b/admin_apps/journeys/iteration.py index aca66942..3c4682f9 100644 --- a/admin_apps/journeys/iteration.py +++ b/admin_apps/journeys/iteration.py @@ -119,8 +119,12 @@ def process_message(_conn: SnowflakeConnection, prompt: str) -> None: with st.spinner("Generating response..."): response = send_message(_conn=_conn, prompt=prompt) content = response["message"]["content"] - display_content(conn=_conn, content=content) - st.session_state.messages.append({"role": "assistant", "content": content}) + # Grab the request ID from the response and stash it in the chat message object. + request_id = response["request_id"] + display_content(conn=_conn, content=content, request_id=request_id) + st.session_state.messages.append( + {"role": "assistant", "content": content, "request_id": request_id} + ) def show_expr_for_ref(message_index: int) -> None: @@ -234,11 +238,11 @@ def add_verified_query(question: str, sql: str) -> None: def display_content( conn: SnowflakeConnection, content: List[Dict[str, Any]], + request_id: Optional[str], message_index: Optional[int] = None, ) -> None: """Displays a content item for a message. For generated SQL, allow user to add to verified queries directly or edit then add.""" message_index = message_index or len(st.session_state.messages) - sql = "" question = "" for item in content: if item["type"] == "text": @@ -292,6 +296,11 @@ def display_content( ): edit_verified_query(conn, sql, question, message_index) + # If debug mode is enabled, we render the request ID. Note that request IDs are currently only plumbed + # through for assistant messages, as we obtain the request ID as part of the Analyst response. + if request_id and st.session_state.chat_debug: + st.caption(f"Request ID: {request_id}") + def chat_and_edit_vqr(_conn: SnowflakeConnection) -> None: messages = st.container(height=600, border=False) @@ -325,7 +334,12 @@ def chat_and_edit_vqr(_conn: SnowflakeConnection) -> None: with messages: with st.chat_message(message["role"]): display_content( - conn=_conn, content=message["content"], message_index=message_index + conn=_conn, + content=message["content"], + message_index=message_index, + request_id=message.get( + "request_id" + ), # Safe get since user messages have no request IDs ) chat_placeholder = ( @@ -664,6 +678,25 @@ def set_up_requirements() -> None: st.rerun() +@st.dialog("Chat Settings", width="small") +def chat_settings_dialog() -> None: + """ + Dialog that allows user to toggle on/off certain settings about the chat experience. + """ + + debug = st.toggle( + "Debug mode", + value=st.session_state.chat_debug, + help="Enable debug mode to see additional information (e.g. request ID).", + ) + + # TODO: This is where multiturn toggle will go. + + if st.button("Save"): + st.session_state.chat_debug = debug + st.rerun() + + VALIDATE_HELP = """Save and validate changes to the active semantic model in this app. This is useful so you can then play with it in the chat panel on the right side.""" @@ -714,6 +747,9 @@ def show() -> None: yaml_editor(editor_contents) with chat_container: - st.markdown("**Chat**") + header_row = row([0.85, 0.15], vertical_align="center") + header_row.markdown("**Chat**") + if header_row.button("Settings"): + chat_settings_dialog() # We still initialize an empty connector and pass it down in order to propagate the connector auth token. chat_and_edit_vqr(get_snowflake_connection()) diff --git a/admin_apps/shared_utils.py b/admin_apps/shared_utils.py index 1901061c..5d8400e2 100644 --- a/admin_apps/shared_utils.py +++ b/admin_apps/shared_utils.py @@ -169,6 +169,10 @@ def init_session_states() -> None: if "last_validated_model" not in st.session_state: st.session_state.last_validated_model = semantic_model_pb2.SemanticModel() + # Chat display settings. + if "chat_debug" not in st.session_state: + st.session_state.chat_debug = False + # initialize session states for the chat page. if "messages" not in st.session_state: # messages store all chat histories From 79b7c7874a7823e9c16e4a0e0291289f442a7eb2 Mon Sep 17 00:00:00 2001 From: Chris Nivera Date: Thu, 10 Oct 2024 12:48:24 -0700 Subject: [PATCH 3/4] Update CODEOWNERS --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 038f8e1f..5f4ec304 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,3 @@ -* @sfc-gh-cnivera @sfc-gh-jsummer @sfc-gh-twhite @sfc-gh-yayin -/admin_apps/ @sfc-gh-cnivera @sfc-gh-yayin @sfc-gh-jsummer +* @sfc-gh-cnivera @sfc-gh-jsummer @sfc-gh-twhite +/admin_apps/ @sfc-gh-cnivera @sfc-gh-jsummer /semantic_model_generator/ @sfc-gh-nsehrawat @sfc-gh-rehuang @sfc-gh-dasilva @sfc-gh-cnivera @sfc-gh-yayin From b1c4ce7ec9e7648ecbd6c11df415e0fe12b10053 Mon Sep 17 00:00:00 2001 From: Chris Nivera Date: Thu, 10 Oct 2024 14:46:05 -0700 Subject: [PATCH 4/4] Optional multiturn support (#178) This PR establishes multiturn support for users that have this flag enabled. Similar to joins, since we cannot read Snowflake parameters from this app, we need to rely on the user manually giving the go-ahead. For multiturn, this is done in the "chat settings" panel. In the recording below, note that when multiturn is enabled, Analyst can contextualize questions in the context of previous responses. https://github.com/user-attachments/assets/a63f38e3-ca1d-4518-a46f-be57c669e1de --- admin_apps/journeys/iteration.py | 50 +++++++++++++++++++++----------- admin_apps/shared_utils.py | 2 ++ 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/admin_apps/journeys/iteration.py b/admin_apps/journeys/iteration.py index 3c4682f9..b1c01f10 100644 --- a/admin_apps/journeys/iteration.py +++ b/admin_apps/journeys/iteration.py @@ -78,18 +78,21 @@ def pretty_print_sql(sql: str) -> str: @st.cache_data(ttl=60, show_spinner=False) -def send_message(_conn: SnowflakeConnection, prompt: str) -> Dict[str, Any]: - """Calls the REST API and returns the response.""" +def send_message( + _conn: SnowflakeConnection, messages: list[dict[str, str]] +) -> Dict[str, Any]: + """ + Calls the REST API with a list of messages and returns the response. + Args: + _conn: SnowflakeConnection, used to grab the token for auth. + messages: list of chat messages to pass to the Analyst API. + + Returns: The raw ChatMessage response from Analyst. + """ request_body = { - "messages": [ - { - "role": "user", - "content": [{"type": "text", "text": prompt}], - }, - ], + "messages": messages, "semantic_model": proto_to_yaml(st.session_state.semantic_model), } - host = st.session_state.host_name resp = requests.post( API_ENDPOINT.format( @@ -110,20 +113,25 @@ def send_message(_conn: SnowflakeConnection, prompt: str) -> Dict[str, Any]: def process_message(_conn: SnowflakeConnection, prompt: str) -> None: """Processes a message and adds the response to the chat.""" - st.session_state.messages.append( - {"role": "user", "content": [{"type": "text", "text": prompt}]} - ) + user_message = {"role": "user", "content": [{"type": "text", "text": prompt}]} + st.session_state.messages.append(user_message) with st.chat_message("user"): st.markdown(prompt) with st.chat_message("assistant"): with st.spinner("Generating response..."): - response = send_message(_conn=_conn, prompt=prompt) + # Depending on whether multiturn is enabled, we either send just the user message or the entire chat history. + request_messages = ( + st.session_state.messages[1:] # Skip the welcome message + if st.session_state.multiturn + else [user_message] + ) + response = send_message(_conn=_conn, messages=request_messages) content = response["message"]["content"] # Grab the request ID from the response and stash it in the chat message object. request_id = response["request_id"] display_content(conn=_conn, content=content, request_id=request_id) st.session_state.messages.append( - {"role": "assistant", "content": content, "request_id": request_id} + {"role": "analyst", "content": content, "request_id": request_id} ) @@ -320,7 +328,7 @@ def chat_and_edit_vqr(_conn: SnowflakeConnection) -> None: if "messages" not in st.session_state or len(st.session_state.messages) == 0: st.session_state.messages = [ { - "role": "assistant", + "role": "analyst", "content": [ { "type": "text", @@ -332,7 +340,10 @@ def chat_and_edit_vqr(_conn: SnowflakeConnection) -> None: for message_index, message in enumerate(st.session_state.messages): with messages: - with st.chat_message(message["role"]): + # To get the handy robot icon on assistant messages, the role needs to be "assistant" or "ai". + # However, the Analyst API uses "analyst" as the role, so we need to convert it at render time. + render_role = "assistant" if message["role"] == "analyst" else "user" + with st.chat_message(render_role): display_content( conn=_conn, content=message["content"], @@ -690,10 +701,15 @@ def chat_settings_dialog() -> None: help="Enable debug mode to see additional information (e.g. request ID).", ) - # TODO: This is where multiturn toggle will go. + multiturn = st.toggle( + "Multiturn", + value=st.session_state.multiturn, + help="Enable multiturn mode to allow the chat to remember context. Note that your account must have the correct parameters enabled to use this feature.", + ) if st.button("Save"): st.session_state.chat_debug = debug + st.session_state.multiturn = multiturn st.rerun() diff --git a/admin_apps/shared_utils.py b/admin_apps/shared_utils.py index 5d8400e2..1e77c3f3 100644 --- a/admin_apps/shared_utils.py +++ b/admin_apps/shared_utils.py @@ -172,6 +172,8 @@ def init_session_states() -> None: # Chat display settings. if "chat_debug" not in st.session_state: st.session_state.chat_debug = False + if "multiturn" not in st.session_state: + st.session_state.multiturn = False # initialize session states for the chat page. if "messages" not in st.session_state: