Tutorial 2: Eine einfache Chat-Anwendung mit Cortex Search erstellen¶
Einführung¶
In diesem Tutorial wird beschreiben, wie Sie mit Cortex Search und der Funktion COMPLETE (SNOWFLAKE.CORTEX) einen Retrieval-Augmented Generation-Chatbot (RAG) in Snowflake einrichten.
Lerninhalte¶
Erstellen eines Cortex Search Service auf der Grundlage eines von Kaggle heruntergeladenen Datensets.
Erstellen einer Streamlit in Snowflake-App, mit der Sie Ihren Cortex Search Service abfragen können
Voraussetzungen¶
Die folgenden Voraussetzungen müssen erfüllt sein, um dieses Tutorial abzuschließen:
Sie haben ein Snowflake-Konto sowie einen Benutzer mit einer Rolle, die die erforderlichen Berechtigungen zum Erstellen einer Datenbank sowie Tabellen, virtuellen Warehouse-Objekten, Cortex Search Services und Streamlit-Apps erteilt.
Eine Anleitung, wie Sie diese Anforderungen erfüllen können, finden Sie unter Snowflake in 20 Minuten.
Schritt 1: Setup¶
Abrufen der Beispieldaten¶
Für dieses Tutorial werden Sie ein Beispiel-Datenset verwenden, das auf Kaggle gehostet wird. Das Datenset „Books“ ist eine Sammlung von Buchnamen, Titeln und Beschreibungen. Sie können das Datenset über den folgenden Link herunterladen:
Das vollständige Datenset finden Sie auf Kaggle Kaggle.
Bemerkung
Außerhalb einer Tutorial-Umgebung würden Sie Ihre eigenen Daten mitbringen, möglicherweise bereits in einer Snowflake-Tabelle.
Datenbank, Schema, Stagingbereich und Warehouse erstellen¶
Führen Sie den folgenden SQL-Code aus, um die benötigte Datenbank, das Schema und das Warehouse einzurichten:
CREATE DATABASE IF NOT EXISTS cortex_search_tutorial_db;
CREATE OR REPLACE WAREHOUSE cortex_search_tutorial_wh WITH
WAREHOUSE_SIZE='X-SMALL'
AUTO_SUSPEND = 120
AUTO_RESUME = TRUE
INITIALLY_SUSPENDED=TRUE;
USE WAREHOUSE cortex_search_tutorial_wh;
Beachten Sie Folgendes:
Die
CREATE DATABASE
-Anweisung erstellt eine Datenbank. Die Datenbank enthält automatisch ein Schema mit dem Namen PUBLIC.Die
CREATE WAREHOUSE
-Anweisung erstellt ein Warehouse, das zunächst angehalten ist.
Schritt 2: Daten in Snowflake laden¶
Erstellen Sie zunächst einen Stagingbereich, um die von Kaggle heruntergeladenen Dateien zu speichern. Dieser Stagingbereich enthält das Buchdatenset.
CREATE OR REPLACE STAGE books_data_stage
DIRECTORY = (ENABLE = TRUE)
ENCRYPTION = (TYPE = 'SNOWFLAKE_SSE');
Laden Sie jetzt das Datenset hoch. Sie können das Datenset über die Snowsight oder über SQL hochladen. Hochladen über die Snowsight:
Melden Sie sich bei Snowsight an.
Wählen Sie im Navigationsmenü auf der linken Seite Data aus.
Wählen Sie Ihre Datenbank
cortex_search_tutorial_db
aus.Wählen Sie Ihr Schema
public
aus.Wählen Sie Stages und dann
books_data_stage
aus.Wählen Sie oben rechts die Schaltfläche + Files aus.
Ziehen Sie Dateien per Drag & Drop in die UI oder wählen Sie Browse, um eine Datei aus dem Dialogfenster auszuwählen.
Wählen Sie Upload aus, um Ihre Datei,
BooksDatasetClean.csv
, hochzuladen.Klicken Sie auf die drei Punkte rechts neben der Datei und wählen Sie Load into table aus.
Benennen Sie die Tabelle
BOOKS_DATASET_RAW
und wählen Sie Next aus.Wählen Sie im linken Bereich des Dialogs „Daten laden“ First line contains header aus dem Menü Header aus.
Wählen Sie dann Load aus.
Schritt 3: Eine Chunking-UDF erstellen¶
Das Einspeisen langer Dokumente in Cortex Search ist nicht leistungsfähig, da Abrufmodelle am besten mit kleinen Textblöcken funktionieren. Als Nächstes erstellen Sie einen Python-UDF, um den Text in Blöcke zu unterteilen. Navigieren Sie zurück zu einem SQL-Editor und führen Sie Folgendes aus:
CREATE OR REPLACE FUNCTION cortex_search_tutorial_db.public.books_chunk(
description string, title string, authors string, category string, publisher string
)
returns table (chunk string, title string, authors string, category string, publisher string)
language python
runtime_version = '3.9'
handler = 'text_chunker'
packages = ('snowflake-snowpark-python','langchain')
as
$$
from langchain.text_splitter import RecursiveCharacterTextSplitter
import copy
from typing import Optional
class text_chunker:
def process(self, description: Optional[str], title: str, authors: str, category: str, publisher: str):
if description == None:
description = "" # handle null values
text_splitter = RecursiveCharacterTextSplitter(
chunk_size = 2000,
chunk_overlap = 300,
length_function = len
)
chunks = text_splitter.split_text(description)
for chunk in chunks:
yield (title + "\n" + authors + "\n" + chunk, title, authors, category, publisher) # always chunk with title
$$;
Schritt 4: Tabelle mit Blöcken erstellen¶
Erstellen Sie eine Tabelle, um die aus den Transkripten extrahierten Textblöcke zu speichern. Fügen Sie den Titel und den Sprecher in den Block ein, um Kontext bereitzustellen:
CREATE TABLE cortex_search_tutorial_db.public.book_description_chunks AS (
SELECT
books.*,
t.CHUNK as CHUNK
FROM cortex_search_tutorial_db.public.books_dataset_raw books,
TABLE(cortex_search_tutorial_db.public.books_chunk(books.description, books.title, books.authors, books.category, books.publisher)) t
);
Überprüfen Sie den Tabelleninhalt:
SELECT chunk, * FROM book_description_chunks LIMIT 10;
Schritt 5: Einen Cortex Search Service erstellen¶
Erstellen Sie einen Cortex Search Service auf der Tabelle, um die Blöcke in book_description_chunks
zu durchsuchen:
CREATE CORTEX SEARCH SERVICE cortex_search_tutorial_db.public.books_dataset_service
ON CHUNK
WAREHOUSE = cortex_search_tutorial_wh
TARGET_LAG = '1 hour'
AS (
SELECT *
FROM cortex_search_tutorial_db.public.book_description_chunks
);
Schritt 6: Eine Streamlit-App erstellen¶
Sie können den Dienst mit dem Python-SDK (unter Verwendung des snowflake
-Python-Pakets) abfragen. Dieses Tutorial zeigt die Verwendung des Python-SDK in einer Streamlit in Snowflake-Anwendung.
Vergewissern Sie sich zunächst, dass Ihre globale Snowsight UI-Rolle mit der Rolle übereinstimmt, mit der Sie den Dienst im Schritt „Dienst erstellen“ erstellt haben.
Melden Sie sich bei Snowsight an.
Wählen Sie im Navigationsmenü auf der linken Seite Projects » Streamlit aus.
Wählen Sie + Streamlit App aus.
Wichtig: Wählen Sie die Datenbank
cortex_search_tutorial_db
und das Schemapublic
für den Speicherort der App aus.Wählen Sie im linken Bereich des Streamlit in Snowflake-Editors Packages aus und fügen Sie
snowflake
(Version >= 0.8.0) hinzu, um das Paket in Ihrer Anwendung zu installieren.Ersetzen Sie den Code der Beispielanwendung durch die folgende Streamlit-App:
import streamlit as st from snowflake.core import Root # requires snowflake>=0.8.0 from snowflake.snowpark.context import get_active_session MODELS = [ "mistral-large", "snowflake-arctic", "llama3-70b", "llama3-8b", ] def init_messages(): """ Initialize the session state for chat messages. If the session state indicates that the conversation should be cleared or if the "messages" key is not in the session state, initialize it as an empty list. """ if st.session_state.clear_conversation or "messages" not in st.session_state: st.session_state.messages = [] def init_service_metadata(): """ Initialize the session state for cortex search service metadata. Query the available cortex search services from the Snowflake session and store their names and search columns in the session state. """ if "service_metadata" not in st.session_state: services = session.sql("SHOW CORTEX SEARCH SERVICES;").collect() service_metadata = [] if services: for s in services: svc_name = s["name"] svc_search_col = session.sql( f"DESC CORTEX SEARCH SERVICE {svc_name};" ).collect()[0]["search_column"] service_metadata.append( {"name": svc_name, "search_column": svc_search_col} ) st.session_state.service_metadata = service_metadata def init_config_options(): """ Initialize the configuration options in the Streamlit sidebar. Allow the user to select a cortex search service, clear the conversation, toggle debug mode, and toggle the use of chat history. Also provide advanced options to select a model, the number of context chunks, and the number of chat messages to use in the chat history. """ st.sidebar.selectbox( "Select cortex search service:", [s["name"] for s in st.session_state.service_metadata], key="selected_cortex_search_service", ) st.sidebar.button("Clear conversation", key="clear_conversation") st.sidebar.toggle("Debug", key="debug", value=False) st.sidebar.toggle("Use chat history", key="use_chat_history", value=True) with st.sidebar.expander("Advanced options"): st.selectbox("Select model:", MODELS, key="model_name") st.number_input( "Select number of context chunks", value=5, key="num_retrieved_chunks", min_value=1, max_value=10, ) st.number_input( "Select number of messages to use in chat history", value=5, key="num_chat_messages", min_value=1, max_value=10, ) st.sidebar.expander("Session State").write(st.session_state) def query_cortex_search_service(query): """ Query the selected cortex search service with the given query and retrieve context documents. Display the retrieved context documents in the sidebar if debug mode is enabled. Return the context documents as a string. Args: query (str): The query to search the cortex search service with. Returns: str: The concatenated string of context documents. """ db, schema = session.get_current_database(), session.get_current_schema() cortex_search_service = ( root.databases[db] .schemas[schema] .cortex_search_services[st.session_state.selected_cortex_search_service] ) context_documents = cortex_search_service.search( query, columns=[], limit=st.session_state.num_retrieved_chunks ) results = context_documents.results service_metadata = st.session_state.service_metadata search_col = [s["search_column"] for s in service_metadata if s["name"] == st.session_state.selected_cortex_search_service][0] context_str = "" for i, r in enumerate(results): context_str += f"Context document {i+1}: {r[search_col]} \n" + "\n" if st.session_state.debug: st.sidebar.text_area("Context documents", context_str, height=500) return context_str def get_chat_history(): """ Retrieve the chat history from the session state limited to the number of messages specified by the user in the sidebar options. Returns: list: The list of chat messages from the session state. """ start_index = max( 0, len(st.session_state.messages) - st.session_state.num_chat_messages ) return st.session_state.messages[start_index : len(st.session_state.messages) - 1] def complete(model, prompt): """ Generate a completion for the given prompt using the specified model. Args: model (str): The name of the model to use for completion. prompt (str): The prompt to generate a completion for. Returns: str: The generated completion. """ return session.sql("SELECT snowflake.cortex.complete(?,?)", (model, prompt)).collect()[0][0] def make_chat_history_summary(chat_history, question): """ Generate a summary of the chat history combined with the current question to extend the query context. Use the language model to generate this summary. Args: chat_history (str): The chat history to include in the summary. question (str): The current user question to extend with the chat history. Returns: str: The generated summary of the chat history and question. """ prompt = f""" [INST] Based on the chat history below and the question, generate a query that extend the question with the chat history provided. The query should be in natural language. Answer with only the query. Do not add any explanation. <chat_history> {chat_history} </chat_history> <question> {question} </question> [/INST] """ summary = complete(st.session_state.model_name, prompt) if st.session_state.debug: st.sidebar.text_area( "Chat history summary", summary.replace("$", "\$"), height=150 ) return summary def create_prompt(user_question): """ Create a prompt for the language model by combining the user question with context retrieved from the cortex search service and chat history (if enabled). Format the prompt according to the expected input format of the model. Args: user_question (str): The user's question to generate a prompt for. Returns: str: The generated prompt for the language model. """ if st.session_state.use_chat_history: chat_history = get_chat_history() if chat_history != []: question_summary = make_chat_history_summary(chat_history, user_question) prompt_context = query_cortex_search_service(question_summary) else: prompt_context = query_cortex_search_service(user_question) else: prompt_context = query_cortex_search_service(user_question) chat_history = "" prompt = f""" [INST] You are a helpful AI chat assistant with RAG capabilities. When a user asks you a question, you will also be given context provided between <context> and </context> tags. Use that context with the user's chat history provided in the between <chat_history> and </chat_history> tags to provide a summary that addresses the user's question. Ensure the answer is coherent, concise, and directly relevant to the user's question. If the user asks a generic question which cannot be answered with the given context or chat_history, just say "I don't know the answer to that question. Don't saying things like "according to the provided context". <chat_history> {chat_history} </chat_history> <context> {prompt_context} </context> <question> {user_question} </question> [/INST] Answer: """ return prompt def main(): st.title(f":speech_balloon: Chatbot with Snowflake Cortex") init_service_metadata() init_config_options() init_messages() icons = {"assistant": "❄️", "user": "👤"} # Display chat messages from history on app rerun for message in st.session_state.messages: with st.chat_message(message["role"], avatar=icons[message["role"]]): st.markdown(message["content"]) disable_chat = ( "service_metadata" not in st.session_state or len(st.session_state.service_metadata) == 0 ) if question := st.chat_input("Ask a question...", disabled=disable_chat): # Add user message to chat history st.session_state.messages.append({"role": "user", "content": question}) # Display user message in chat message container with st.chat_message("user", avatar=icons["user"]): st.markdown(question.replace("$", "\$")) # Display assistant response in chat message container with st.chat_message("assistant", avatar=icons["assistant"]): message_placeholder = st.empty() question = question.replace("'", "") with st.spinner("Thinking..."): generated_response = complete( st.session_state.model_name, create_prompt(question) ) message_placeholder.markdown(generated_response) st.session_state.messages.append( {"role": "assistant", "content": generated_response} ) if __name__ == "__main__": session = get_active_session() root = Root(session) main()
Schritt 7: App testen¶
Geben Sie eine Abfrage in das Textfeld ein, um Ihre neue App zu testen. Hier sind einige Beispielabfragen, die Sie ausprobieren können:
Ich mag Harry Potter. Können Sie mir weitere Bücher empfehlen, die mir gefallen werden?
Can you recommend me books on Greek Mythology?
Schritt 7: Bereinigen¶
Bereinigen (optional)¶
Führen Sie die folgenden DROP <Objekt>-Befehle aus, um Ihr System in den Zustand zu versetzen, bevor Sie das Tutorial begonnen haben:
DROP DATABASE IF EXISTS cortex_search_tutorial_db;
DROP WAREHOUSE IF EXISTS cortex_search_tutorial_wh;
Wenn Sie die Datenbank löschen, werden automatisch alle untergeordneten Datenbankobjekte wie Tabellen entfernt.
Nächste Schritte¶
Herzlichen Glückwunsch! Sie haben erfolgreich eine einfache Such-App für Textdaten in Snowflake erstellt. Sie können mit Tutorial 3 fortfahren, um zu erfahren, wie Sie mit Cortex Search aus einer Gruppe von PDF Dateien einen AI-Chatbot erstellen.
Zusätzliche Ressourcen¶
Setzen Sie die Einarbeitung mithilfe der folgenden Ressourcen fort: