DR Você pode resolver automaticamente tarefas típicas de processamento de linguagem natural (classificação, análise de sentimento, etc.) para seus dados de texto usando LLM por um preço tão baixo quanto US$ 10 por 1 milhão de linhas (depende da tarefa e do modelo), permanecendo em seu ambiente dbt. Instruções, detalhes e código estão abaixo Se você estiver usando dbt como camada de transformação, poderá ter uma situação em que deseja extrair informações significativas de dados de texto não estruturados. Esses dados podem incluir comentários de clientes, títulos, descrições, fontes/meios do Google Analytics, etc. Você pode querer categorizá-los em grupos ou buscar sentimentos e tons. As possíveis soluções seriam Aplique modelos de aprendizado de máquina (ou chame um LLM) fora do fluxo dbt Defina categorizações simples dentro de modelos dbt usando instruções CASE WHEN Predefina categorias antecipadamente e carregue-as em sua camada de banco de dados bruto ou aproveite a funcionalidade de semente dbt À medida que os modelos dbt do Python estão evoluindo, há mais uma solução: você pode manter essas tarefas de processamento de linguagem natural dentro do seu ambiente dbt como um dos modelos dbt. Se isso for útil para você, veja abaixo um guia passo a passo sobre como usar a API OpenAI em seu projeto dbt. Você pode reproduzir tudo deste guia em seu ambiente, tendo o código e a amostra de dados do repositório GitHub (veja links no final). Configurar ambiente Se você já possui um projeto e dados dbt ou não deseja reproduzir os resultados, vá para (4) ou pule esta seção completamente. Caso contrário, você precisará do seguinte: . Configure o projeto dbt Documentos oficiais Você pode simplesmente clonar aquele que preparei para este guia no . GitHub Não se esqueça de criar/atualizar seu arquivo profiles.yml. . Eu usei floco de neve. Infelizmente, não existe uma versão gratuita, mas eles oferecem um . Configure o banco de dados teste gratuito de 30 dias Atualmente, os modelos dbt Python funcionam apenas com Snowflake, Databricks e BigQuery (sem PostgreSQL). Portanto, este tutorial deve funcionar para qualquer um deles, embora alguns detalhes possam variar Preparar dados de origem Como conjunto de dados, usei metadados de um pacote R publicados no repositório TidyTuesday. Você pode baixá-lo . Os detalhes sobre o conjunto de dados estão aqui aqui Alternativamente, você pode usar uma versão leve do meu repositório aqui Faça upload para seu banco de dados. Atualize o arquivo no projeto dbt para corresponder ao seu banco de dados e aos nomes do esquema. source.yml Obtenha a chave da API OpenAI Siga as instruções de início rápido dos . documentos oficiais Não: não é gratuito, mas é pré-pago. Portanto, com o conjunto de dados de teste de 10 linhas, não será cobrado mais de US$ 1 durante seus experimentos. Para ser extremamente cuidadoso, estabeleça um limite de gastos. Configure a integração de acesso externo no Snowflake Isso se aplica apenas se você usar o Snowflake. Se isso não for feito, os modelos dbt Python não poderão acessar nenhuma API na Internet (incluindo a API OpenAI). Siga as . instruções oficiais Armazene a chave da API OpenAI nesta integração. Crie uma lista de categorias Em primeiro lugar, se você estiver resolvendo uma tarefa de classificação, precisará de categorias (também conhecidas como classes) para usar em seu prompt LLM. Basicamente, você dirá: “Tenho uma lista dessas categorias, você poderia definir a qual delas pertence este texto?” Algumas opções aqui: Crie uma lista de categorias predefinidas manualmente É adequado se você precisar de categorias estáveis e previsíveis. Não se esqueça de adicionar “Outros” aqui, para que o LLM tenha essas opções quando for incerto. Peça ao LLM em seu prompt para sugerir um nome de categoria sempre que usar a categoria “Outros”. Faça upload de uma lista predefinida para a camada bruta do banco de dados ou como um CSV em seu projeto dbt (utilizando ). dbt seed Alimente uma amostra de seus dados ao LLM e peça-lhe que crie N categorias. A mesma abordagem da anterior, mas estamos recebendo ajuda com a lista. Se você usa GPT, é melhor usar sementes aqui para reprodutibilidade. Vá sem categorias predefinidas e deixe o LLM fazer o trabalho em qualquer lugar. Isso pode levar a resultados menos previsíveis. Ao mesmo tempo, é bom o suficiente se você concordar com uma margem de aleatoriedade. No caso de uso do GPT, é melhor colocar temperatura = 0 para evitar resultados diferentes caso seja necessário executar novamente. Nesta postagem do blog, irei com a 3ª opção. Crie um modelo dbt Python para chamar a API OpenAI Agora, vamos ao cerne desta postagem e criar um modelo dbt que pegará novos dados de texto da tabela upstream, alimentará a API OpenAI e salvará a categoria na tabela. Conforme mencionado acima, usarei o conjunto de dados de pacotes R. R é uma linguagem de programação altamente popular na análise de dados. Este conjunto de dados contém informações sobre os pacotes R do projeto CRAN, como versão, licença, autor, título, descrição, etc. Estamos interessados no campo , pois vamos criar uma categoria para cada pacote com base em seu título. title Prepare a base para o modelo A configuração do dbt pode ser passada através do método . dbt.config(...) Existem argumentos adicionais em dbt.config, por exemplo, é um requisito de pacote. packages O modelo dbt Python pode fazer referência aos modelos upstream ou dbt.ref('...') dbt.source('...') Deve retornar um DataFrame. Seu banco de dados irá salvá-lo como uma tabela. import os import openai import pandas as pd COL_TO_CATEGORIZE = 'title' def model(dbt, session): import _snowflake dbt.config( packages=['pandas', 'openai'], ) df = dbt.ref('package').to_pandas() df.drop_duplicates(subset=[COL_TO_CATEGORIZE], inplace=True) return df Conecte-se à API OpenAI Precisamos passar e para o dbt.config. Ele conterá a referência secreta armazenada em sua integração de acesso externo Snowflake. secrets external_access_integrations Observação: este recurso foi lançado há apenas alguns dias e está disponível apenas na versão beta dbt 1.8.0-b3 dbt.config( packages=['pandas', 'openai'], secrets={'openai_key': 'openai_key', 'openai_org': 'openai_org'}, external_access_integrations=['openai_external_access_integration'], ) client = openai.OpenAI( api_key=_snowflake.get_generic_secret_string('openai_key'), organization=_snowflake.get_generic_secret_string('openai_org'), ) Torne o modelo dbt incremental e desative as atualizações completas. Esta parte é essencial para manter baixos os custos da API OpenAI. Isso impedirá que o mesmo texto seja categorizado várias vezes. Caso contrário, você enviará dados completos para o OpenAI sempre que executar , o que pode ocorrer várias vezes ao dia. dbt run Estamos adicionando , , , a dbt.config materialized='incremental' incremental_strategy='append' full_refresh = False Agora, a verificação completa será apenas para a primeira execução do dbt e, para as execuções posteriores (não importa a atualização incremental ou completa), ela categorizará apenas o delta. Se quiser ser mais cuidadoso, você pode pré-processar um pouco seus dados para reduzir o número de entradas exclusivas, mas evite pré-processar demais, pois os LLMs funcionam melhor com linguagem natural. dbt.config( materialized='incremental', incremental_strategy='append', full_refresh = False, packages=['pandas', 'openai'], secrets={'openai_key': 'openai_key', 'openai_org': 'openai_org'}, external_access_integrations=['openai_external_access_integration'], ) if dbt.is_incremental: pass Adicionar lógica de incrementalidade Na execução incremental (devido à nossa configuração, significa em qualquer execução, exceto a primeira), precisamos remover todos os títulos já categorizados. Podemos fazer isso simplesmente usando . Semelhante aos modelos incrementais normais. dbt.this if dbt.is_incremental: categorized_query = f''' SELECT DISTINCT "{ COL_TO_CATEGORIZE }" AS primary_key FROM { dbt.this } WHERE "category" IS NOT NULL ''' categorized = [row.PRIMARY_KEY for row in session.sql(categorized_query).collect()] df = df.loc[~df[COL_TO_CATEGORIZE].isin(categorized), :] Chame a API OpenAI em lotes Para reduzir custos, é melhor enviar dados para a API OpenAI em lotes. O prompt do sistema pode ser 5 vezes maior que o texto que precisamos classificar. Se enviarmos o prompt do sistema separadamente para cada título, isso levará a um uso de token muito maior para coisas repetitivas. O lote não deve ser grande, no entanto. Com lotes grandes, o GPT começa a produzir resultados menos estáveis. Pelas minhas experiências, tamanho do lote = 5 funciona bem o suficiente. Além disso, para garantir que a resposta não exceda o tamanho relevante, adicionei a restrição . max_tokens BATCH_SIZE = 5 n_rows = df.shape[0] categories = [None for idx in range(n_rows)] for idx in range(0, n_rows, BATCH_SIZE): df_sliced = df.iloc[idx:idx+BATCH_SIZE, :] user_prompt = f'```{ "|".join(df_sliced[COL_TO_CATEGORIZE].to_list()) }```' chat_completion = client.chat.completions.create( messages=[ {'role': 'system', 'content': SYSTEM_PROMPT}, {'role': 'user', 'content': user_prompt} ], model='gpt-3.5-turbo', temperature=0, max_tokens=10*BATCH_SIZE + 2*BATCH_SIZE, ) gpt_response = chat_completion.choices[0].message.content gpt_response = [category.strip() for category in gpt_response.split('|')] categories[idx:idx + len(gpt_response)] = gpt_response df['category'] = categories df.dropna(subset=['category'], inplace=True) É hora de falar sobre uma solicitação para LLM. Isso é o que eu consegui: Você receberá uma lista de títulos de pacotes CRAN R entre colchetes ```. Os títulos serão separados por "|" sinal. Crie uma categoria para cada título. Retorne apenas nomes de categorias separados por "|" sinal. Mantenha as instruções diretas ao ponto. Use a técnica ``` para evitar injeções de SQL. Seja claro no formato do resultado. No meu caso, pedi "|" como um separador para entradas e saídas Código final do modelo dbt import os import openai import pandas as pd SYSTEM_PROMPT = '''You will be provided a list of CRAN R package titles in ``` brackets. Titles will be separated by "|" sign. Come up with a category for each title. Return only category names separated by "|" sign. ''' COL_TO_CATEGORIZE = 'title' BATCH_SIZE = 5 def model(dbt, session): import _snowflake dbt.config( materialized='incremental', incremental_strategy='append', full_refresh = False, packages=['pandas', 'openai'], secrets={'openai_key': 'openai_key', 'openai_org': 'openai_org'}, external_access_integrations=['openai_external_access_integration'], ) client = openai.OpenAI( api_key=_snowflake.get_generic_secret_string('openai_key'), organization=_snowflake.get_generic_secret_string('openai_org'), ) df = dbt.ref('package').to_pandas() df.drop_duplicates(subset=[COL_TO_CATEGORIZE], inplace=True) if dbt.is_incremental: categorized_query = f''' SELECT DISTINCT "{ COL_TO_CATEGORIZE }" AS primary_key FROM { dbt.this } WHERE "category" IS NOT NULL ''' categorized = [row.PRIMARY_KEY for row in session.sql(categorized_query).collect()] df = df.loc[~df[COL_TO_CATEGORIZE].isin(categorized), :] n_rows = df.shape[0] categories = [None for idx in range(n_rows)] for idx in range(0, n_rows, BATCH_SIZE): df_sliced = df.iloc[idx:idx+BATCH_SIZE, :] user_prompt = f'```{ "|".join(df_sliced[COL_TO_CATEGORIZE].to_list()) }```' chat_completion = client.chat.completions.create( messages=[ {'role': 'system', 'content': SYSTEM_PROMPT}, {'role': 'user', 'content': user_prompt} ], model='gpt-3.5-turbo', temperature=0, max_tokens=10*BATCH_SIZE + 2*BATCH_SIZE, ) gpt_response = chat_completion.choices[0].message.content gpt_response = [category.strip() for category in gpt_response.split('|')] categories[idx:idx + len(gpt_response)] = gpt_response df['category'] = categories df.dropna(subset=['category'], inplace=True) return df Estimativas de custos Os preços da API OpenAI estão listados . Eles cobram pelo número de tokens solicitados e devolvidos. O token é uma instância correlacionada a vários caracteres da sua solicitação. Existem pacotes de código aberto para avaliar vários tokens para um determinado texto. Por exemplo, . Se você quiser avaliá-lo manualmente, o lugar certo é um tokenizer oficial da OpenAI . aqui Tiktoken aqui Em nosso conjunto de dados, existem cerca de 18 mil títulos. Aproximadamente, é igual a 320 mil tokens de entrada (180 mil títulos e 140 mil prompt do sistema se usarmos tamanho de lote = 5) e 50 mil tokens de saída. Dependendo do modelo, os custos da verificação completa serão: : . Preço: entrada: tokens de US$ 10/1 milhão; saída: tokens de US$ 30/1 milhão. GPT-4 Turbo $ 4,7 : Preço: entrada: tokens de US$ 30/1 milhão; saída: tokens de US$ 60/1 milhão. GPT-4 $ 12,6. : Preço: entrada: tokens de US$ 0,5/1 milhão; saída: tokens de US$ 1,5/1 milhão. GPT-3.5 Turbo $ 0,2. Resultados O modelo dbt funcionou perfeitamente. Categorizei com sucesso todos os pacotes de 18K sem lacunas. O modelo provou ser econômico e protegido contra múltiplas execuções de dbt. Publiquei o painel de resultados no Tableau Public . Sinta-se à vontade para brincar com ele, baixar os dados e criar o que desejar. aqui Alguns detalhes interessantes que encontrei: A primeira categoria é (1.190 pacotes ou 6%). Acho que isso prova a popularidade do R como ferramenta de visualização, especialmente com pacotes como Shiny, Plotly e outros. Data Visualization As duas categorias com maior crescimento em 2023 foram e . Parece que o R começou a ser usado mais como ferramenta de processamento de dados. Data Import Data Processing O maior crescimento ano a ano entre as 30 principais categorias foi em 2019. Dois anos após o famoso artigo "Atenção é tudo que você precisa" e meio ano após o lançamento do GPT-1 :) Natural Language Processing Mais ideias Podemos usar uma abordagem alternativa – os . embeddings GPT É muito mais barato. Mas mais pesado de engenharia porque você mesmo deve fazer a parte de classificação (fique ligado, pois explorarei essa opção em um dos próximos posts). Certamente, faz sentido remover esta parte do dbt e enviá-la para funções de nuvem ou qualquer infra-estrutura que você usar. Ao mesmo tempo, se você quiser mantê-lo sob controle, esta postagem oferece cobertura. Evite adicionar qualquer lógica ao modelo. Deve fazer um trabalho – ligar para o LLM e salvar o resultado. Isso o ajudará a evitar executá-lo novamente. Há grandes chances de você estar usando muitos ambientes em seu projeto dbt. Você precisa estar atento e evitar executar esse modelo repetidamente em cada ambiente de desenvolvedor em cada solicitação pull. Para fazer isso, você pode incorporar lógica com if dbt.config.get("target_name") == 'dev' A resposta com um delimitador pode ser instável. Por exemplo, o GPT pode retornar menos elementos do que o esperado e será difícil mapear os títulos iniciais para a lista de categorias. Para superar isso, adicione em sua solicitação para exigir saída JSON. Veja os . response_format={ "type": "json_object" } documentos oficiais Com a saída JSON, você pode solicitar imediatamente uma resposta no formato {"title": "category"} e, em seguida, mapeá-la para seus valores iniciais. Observe que será mais caro, pois aumentará o tamanho da resposta. Estranhamente, a qualidade da classificação caiu drasticamente quando mudei para JSON para GPT 3.5 Turbo. Existe uma alternativa no Snowflake - usando a função . Confira uma de Joel Labes no blog do dbt. cortex.complete() ótima postagem É isso! Diz-me o que pensas. Ligações Código completo no GitHub: link Painel público do Tableau: link Conjunto de dados TidyTuesday R: link