Tutorial 2: Crie um aplicativo de bate-papo simples com o Cortex Search¶
Introdução¶
Este tutorial descreve como usar o Cortex Search e a função COMPLETE (SNOWFLAKE.CORTEX) para configurar um chatbot Retrieval-Augmented Generation (RAG) no Snowflake.
O que você aprenderá¶
Crie um Cortex Search Service com base em um conjunto de dados baixado do Kaggle.
Crie um aplicativo Streamlit in Snowflake que permita consultar seu Cortex Search Service.
Pré-requisitos¶
Os seguintes pré-requisitos são necessários para concluir este tutorial:
Você tem uma conta Snowflake e um usuário com uma função que concede os privilégios necessários para criar um banco de dados, tabelas, objetos de warehouse virtual, Cortex Search Services e aplicativos Streamlit.
Consulte o Snowflake em 20 minutos para obter instruções para atender a estes requisitos.
Etapa 1: Configuração¶
Como obter dados da amostra¶
Você usará um conjunto de dados de amostra hospedado no Kaggle para este tutorial. O conjunto de dados Livros é uma coleção de nomes, títulos e descrições de livros. Você pode baixar o conjunto de dados no seguinte link:
O conjunto de dados completo pode ser encontrado no Kaggle Kaggle.
Nota
Em um configuração não tutorial, você traria seus próprios dados, possivelmente já em uma tabela Snowflake.
Criação do banco de dados, esquema, estágio e warehouse¶
Execute o código SQL a seguir para definir o banco de dados, o esquema e o warehouse necessários:
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;
Observe o seguinte:
A instrução
CREATE DATABASE
cria um banco de dados. O banco de dados inclui automaticamente um esquema chamado PUBLIC.A instrução
CREATE WAREHOUSE
cria um warehouse inicialmente suspenso.
Etapa 2: Carregamento de dados para o Snowflake¶
Primeiro, crie um estágio para armazenar os arquivos baixados do Kaggle. Esta estágio conterá o conjunto de dados dos livros.
CREATE OR REPLACE STAGE books_data_stage
DIRECTORY = (ENABLE = TRUE)
ENCRYPTION = (TYPE = 'SNOWFLAKE_SSE');
Agora carregue o conjunto de dados. Você pode carregar o conjunto de dados em Snowsight ou usando SQL. Para carregar em Snowsight:
Faça login no Snowsight.
Selecione Data no menu de navegação do lado esquerdo.
Selecione seu banco de dados
cortex_search_tutorial_db
.Selecione seu esquema
public
.Selecione Stages e
books_data_stage
.No canto superior direito, selecione o botão + Files.
Arraste e solte arquivos na UI ou selecione Browse para escolher um arquivo na janela de diálogo.
Selecione Upload para enviar seu arquivo,
BooksDatasetClean.csv
.Selecione os três pontos à direita do arquivo e selecione Load into table.
Nomeie a tabela
BOOKS_DATASET_RAW
e selecione Next.No painel esquerdo da caixa de diálogo de carregar de dados, escolha First line contains header no menu Header.
Em seguida selecione Load.
Etapa 3: Criação de uma UDF de divisão em partes¶
Alimentar documentos longos no Cortex Search não terá bom desempenho porque os modelos de recuperação funcionam melhor com partes pequenas de texto. Em seguida, crie uma UDF de Python para dividir o texto em partes. Volte para um editor SQL e execute o seguinte:
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
$$;
Etapa 4: Criação da tabela de partes¶
Crie uma tabela para armazenar as partes de texto extraídos das transcrições. Inclua o título e o palestrante na parte para fornecer contexto:
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
);
Verifique o conteúdo da tabela:
SELECT chunk, * FROM book_description_chunks LIMIT 10;
Etapa 5: Criação de um Cortex Search Service¶
Crie um Cortex Search Service na tabela para permitir que você pesquise nas partes em 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
);
Etapa 6: Criação de um aplicativo Streamlit¶
Você pode consulta o serviço com Python SDK (usando o pacote Python snowflake
). Este tutorial demonstra o uso do Python SDK em um aplicativo Streamlit in Snowflake.
Primeiro, certifique-se de que sua função de Snowsight UI global seja a mesma que a função usada para criar o serviço na etapa de criação do serviço.
Faça login no Snowsight.
Selecione Projects » Streamlit no menu de navegação do lado esquerdo.
Selecione + Streamlit App.
Importante: selecione o banco de dados
cortex_search_tutorial_db
e o esquemapublic
para o local do aplicativo.No painel esquerdo do editor Streamlit in Snowflake, selecione Packages e adicione
snowflake
(versão >= 0.8.0) para instalar o pacote em seu aplicativo.Substitua o código do aplicativo de exemplo pelo seguinte aplicativo Streamlit:
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()
Etapa 7: Teste do aplicativo¶
Insira uma consulta na caixa de texto para testar seu novo aplicativo. Alguns amostra de consultas que você pode tentar são:
Eu gosto de Harry Potter. Você pode recomendar mais livros que eu goste?
Can you recommend me books on Greek Mythology?
Etapa 7: limpeza¶
Limpeza (opcional)¶
Execute os seguintes comandos DROP <objeto> para retornar o sistema ao seu estado antes de iniciar o tutorial:
DROP DATABASE IF EXISTS cortex_search_tutorial_db;
DROP WAREHOUSE IF EXISTS cortex_search_tutorial_wh;
Descartar o banco de dados remove automaticamente todos os objetos do banco de dados filho, tais como tabelas.
Próximos passos¶
Parabéns! Você criou com sucesso um aplicativo de pesquisa simples em dados de texto no Snowflake. Você pode prosseguir para o Tutorial 3 para ver como criar um chatbot AI com o Cortex Search a partir de um conjunto de arquivos PDF.
Recursos adicionais¶
Continue aprendendo usando os seguintes recursos: