Tutoriel 2 : Créer une application de chat simple avec Cortex Search¶
Introduction¶
Ce tutoriel décrit comment utiliser Cortex Search et la fonction COMPLETE (SNOWFLAKE.CORTEX) permettant de configurer un Chatbot à génération augmentée par récupération (RAG) dans Snowflake.
Ce que vous apprendrez¶
Créer un Cortex Search Service basé sur un ensemble de données téléchargé depuis Kaggle.
Créer une application Streamlit in Snowflake qui vous permet d’interroger votre Cortex Search Service.
Conditions préalables¶
Les prérequis suivants sont nécessaires à la réalisation de ce tutoriel :
Vous disposez d’un compte Snowflake et d’un utilisateur avec un rôle qui accorde les privilèges nécessaires pour créer une base de données, des tables, des objets d’entrepôt virtuel, des Cortex Search Services et des applications Streamlit.
Consultez les Snowflake en 20 minutes pour obtenir des instructions afin de répondre à ces exigences.
Étape 1 : Configuration¶
Obtenir les données d’échantillon¶
Vous utiliserez un exemple d’ensemble de données hébergé sur Kaggle pour ce tutoriel. L’ensemble de données Livres est une collection de noms, de titres et de descriptions de livres. Vous pouvez télécharger l’ensemble de données à partir du lien suivant :
L’ensemble de données complet est disponible sur Kaggle Kaggle.
Note
Dans un environnement non didactique, vous apporteriez vos propres données, éventuellement déjà présentes dans une table Snowflake.
Création de la base de données, du schéma, de la zone de préparation et de l’entrepôt¶
Exécutez le code SQL qui suit pour configurer la base de données, le schéma et l’entrepôt nécessaires :
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;
Remarques :
L’instruction
CREATE DATABASE
crée une base de données. La base de données comprend automatiquement un schéma nommé PUBLIC.L’instruction
CREATE WAREHOUSE
crée un entrepôt initialement suspendu.
Étape 2 : charger les données dans Snowflake¶
Créez d’abord une zone de préparation pour stocker les fichiers téléchargés depuis Kaggle. Cette zone de préparation contiendra l’ensemble de données des livres.
CREATE OR REPLACE STAGE books_data_stage
DIRECTORY = (ENABLE = TRUE)
ENCRYPTION = (TYPE = 'SNOWFLAKE_SSE');
Téléchargez maintenant l’ensemble de données. Vous pouvez télécharger l’ensemble de données dans Snowsight ou en utilisant SQL. Pour télécharger dans Snowsight :
Connectez-vous à Snowsight.
Sélectionnez Data dans le menu de navigation de gauche.
Sélectionnez votre base de données
cortex_search_tutorial_db
.Sélectionnez votre schéma
public
.Sélectionnez Stages et
books_data_stage
.En haut à droite, sélectionnez le bouton + Files.
Faites glisser et déposez les fichiers dans l’UI ou sélectionnez Browse pour choisir un fichier dans la fenêtre de dialogue.
Sélectionnez Upload pour charger votre fichier,
BooksDatasetClean.csv
Sélectionnez les trois points à droite du fichier et sélectionnez Load into table.
Nommez la table
BOOKS_DATASET_RAW
et sélectionnez Next.Dans le panneau de gauche de la boîte de dialogue de chargement des données, choisissez First line contains header dans le menu Header.
Sélectionnez ensuite Load.
Étape 3 : Créer un découpage UDF¶
L’alimentation de documents longs vers Cortex Search ne sera pas performante car les modèles de récupération fonctionnent mieux avec de petits morceaux de texte. Ensuite, créez un UDF Python pour fragmenter le texte. Revenez à un éditeur SQL et exécutez ce qui suit :
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
$$;
Étape 4 : Construire la table des morceaux¶
Créez une table pour stocker les morceaux de texte extraits des transcriptions. Incluez le titre et l’orateur dans le morceau pour fournir un contexte :
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
);
Vérifiez le contenu de la table :
SELECT chunk, * FROM book_description_chunks LIMIT 10;
Étape 5 : Créer un Cortex Search Service¶
Créez un Cortex Search Service sur la table pour vous permettre de rechercher parmi les morceaux dans le book_description_chunks
:
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
);
Étape 6 : Créer une application Streamlit¶
Vous pouvez interroger le service avec le SDK Python (en utilisant le Paquet Python snowflake
). Ce tutoriel montre comment utiliser le SDK Python dans une application Streamlit in Snowflake.
Tout d’abord, assurez-vous que votre rôle global de la Snowsight UI est le même que le rôle utilisé pour créer le service à l’étape de création du service.
Connectez-vous à Snowsight.
Sélectionnez Projects » Streamlit dans le menu de navigation de gauche.
Sélectionnez + Streamlit App.
Important : Sélectionnez la base de données
cortex_search_tutorial_db
et le schémapublic
pour l’emplacement de l’application.Dans le volet gauche de l’éditeur Streamlit in Snowflake, sélectionnez Packages et ajoutez
snowflake
(version> = 0.8.0) pour installer le paquet dans votre application.Remplacez l’exemple de code d’application par l’application Streamlit suivante :
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()
Étape 7 : Essayer l’application¶
Saisissez une requête dans la zone de texte pour tester votre nouvelle application. Voici quelques exemples de requêtes que vous pouvez essayer :
J'aime Harry Potter. Pouvez-vous me recommander d'autres livres que j'aimerais ?
Can you recommend me books on Greek Mythology?
Étape 7 : Nettoyage¶
Nettoyer (facultatif)¶
Exécutez les commandes DROP <objet> suivantes pour remettre votre système dans son état initial avant de commencer le tutoriel :
DROP DATABASE IF EXISTS cortex_search_tutorial_db;
DROP WAREHOUSE IF EXISTS cortex_search_tutorial_wh;
Détruire la base de données supprime automatiquement toutes les objets de base de données liés, par exemple les tables.
Prochaines étapes¶
Félicitations ! Vous avez réussi à créer une application de recherche simple sur des données textuelles dans Snowflake. Vous pouvez passer au Tutoriel 3 pour voir comment créer un Chatbot AI avec Cortex Search à partir d’un ensemble de fichiers PDF.
Ressources supplémentaires¶
Poursuivez l’entraînement en utilisant les ressources suivantes :