|
1 2 3 4 5 6 7 8 |
SELECT u.name, COUNT(o.id) AS total_orders FROM `commerce`.sales.users AS u JOIN `commerce`.sales.orders AS o ON u.id = o.user_id WHERE o.status = "completed" AND DATE_DIFF_STR(NOW_STR(), o.order_date, "day") <= 30 GROUP BY u.name ORDER BY total_orders DESC LIMIT 5; |
A consulta acima fornece insights valiosos dos seus dados armazenados no Couchbase sobre os cinco principais usuários que geraram os pedidos mais concluídos nos últimos 30 dias. Mas e se você não for um desenvolvedor avançado de SQL++ e precisar das respostas até as 23h para um relatório? Então, você precisará esperar que um desenvolvedor escreva uma consulta SQL++ e obtenha as respostas.
Como alternativa, considere um caso em que você precise fazer uma depuração ad hoc para responder a perguntas como:
- Há algum documento que não contenha a data de entrega do pedido?
- Isso significa que o pedido foi cancelado? Ou o pedido foi colocado no lugar errado e nunca foi entregue? Ou estava tudo bem, mas simplesmente deixamos de adicionar o valor order_delivered no campo?
Nesse caso, você não precisa apenas pesquisar o campo order_delivered, mas também examinar order_cancelled ou investigar os comentários para descobrir se ele foi extraviado etc. Portanto, a consulta a ser escrita não é simples nem direta.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
SELECT o.orderId, o.orderDate, o.order_cancelled, o.order_delivered, o.comments, CASE WHEN o.order_cancelled = TRUE THEN "Order was cancelled" WHEN ANY c IN o.comments SATISFIES LOWER(c) LIKE "%misplac%" OR LOWER(c) LIKE "%lost%" THEN "Order may have been misplaced" WHEN ANY c IN o.comments SATISFIES LOWER(c) LIKE "%deliver%" THEN "Delivered but field not updated" ELSE "Reason unknown — investigate" END AS reason FROM `commerce`.`sales`.`orders` AS o WHERE o.order_delivered IS MISSING OR o.order_delivered IS NULL; |
Nesses casos, seria útil se você tivesse um assistente confiável disponível 24 horas por dia, 7 dias por semana, para obter todas essas respostas. O UDF descrito neste blog é um desses assistentes. Ele aceita suas perguntas da forma mais natural possível e retorna os resultados em JSON. Nos bastidores, ele se conecta a um modelo de sua escolha, juntamente com sua chave de API, para converter suas ideias em SQL++ e, em seguida, executá-las. E tudo o que você precisa para chamar esse assistente é usar o UDF.
|
1 2 3 4 5 6 7 |
SELECT NL2SQL( [“`commerce`.`sales`.`orders`”], "Are there any documents where the order_delivered date is missing?and if so why?", "", "https://api.openai.com/v1/chat/completions", "gpt-4o-2024-05-13" ) ; |
Como funciona
1. Configure a biblioteca.
Primeiro, você cria uma biblioteca JavaScript usada pelo UDF.
Biblioteca:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
/* input: keyspaces: an array of strings, each string represents a keyspaces "bucket.scope.collection" with proper escaping using grave-accent quote wherever required prompt: users natural language request apikey: your openai api key model: string representing the model's name, see https://platform.openai.com/docs/api-reference/completions/create#completions-create-model for more details output: chat-completions api response with the generated sql statement */ function inferencer(k) { var infq = N1QL("SELECT t.properties FROM(INFER "+k+ ") as t") ; var res=[] for(const doc of infq) { res.push(doc) } return res[0]; } function nl2sql(keyspaces, prompt, apikey, modelapi, model) { collectionSchema = {} for(const k in keyspaces) { c = inferencer(keyspaces[k]) collectionSchema[keyspaces[k]] = c } collectionSchemaStr = JSON.stringify(collectionSchema) promptContent = `Information:\nCollection's schema: ${collectionSchemaStr}\n\nPrompt: \"${prompt}\"\n\nThe query context is set.\n\nBased on the above Information, write valid SQL++ and return only the statement and no explanation. For retrieval, use aliases. Use UNNEST from the FROM clause when appropriate. \n\nIf you're sure the Prompt can't be used to generate a query, first say \"#ERR:\" and then explain why not.` data = {"messages":[{"role":"system","content":"You are a Couchbase Capella expert. Your task is to create valid queries to retrieve or create data based on the provided Information.\n\nApproach this task step-by-step and take your time."},{"role":"user","content":promptContent}], "model": model, "temperature":0, "max_tokens":1024, "stream":false} var dataStr = JSON.stringify(data) .replace(/\\/g, "\\\\") // escape backslashes .replace(/"/g, '\\"'); // escape quotes var completionsurl = modelapi var q= `SELECT CURL("${completionsurl}", { "request": "POST", "header": ["Authorization: Bearer ${apikey}", "Content-type: application/json"], "data": "${dataStr}" }) AS result;` var completionsq = N1QL(q); var res = [] for(const doc of completionsq) { res.push(doc); } try { content = res[0].result.choices[0].message.content } catch(e) { return res; } stmt = content.trim().substring(7, content.length-4) isSelect = (stmt.substring(0,6).toLowerCase())==="select" if(isSelect === false){ return { "generated_statement": stmt } } var runq = N1QL(stmt); var rrun = [] for(const doc of runq) { rrun.push(doc) } return { "generated_statement": stmt, "results": rrun } } |
2. Faça o upload da biblioteca.
Execute o comando curl depois de copiar o código da biblioteca fornecida em um arquivo, ou seja, usingailib.js.
|
1 2 |
curl -X POST https://localhost:9499/evaluator/v1/libraries/usingailib --data-binary @usingailib.js -u Administrator:password |
3. Criar o UDF.
Use o comando create function abaixo para criar o UDF depois de criar a biblioteca:
|
1 2 |
CREATE OR REPLACE FUNCTION NL2SQL(keyspaces, prompt, apikey, modelapi, model) LANGUAGE JAVASCRIPT AS "nl2sql" AT "usingailib"; |
O NL2SQL() agora atua como seu tradutor multilíngue entre a linguagem humana e o mecanismo de consulta do Couchbase. Basta fornecer a ele algum contexto e uma solicitação em linguagem natural, e ele retorna uma resposta.
Como o UDF pensa
Por trás disso, ele usa seu modelo preferido ao invocar o UDF para entender sua intenção e gerar uma consulta que o Couchbase pode executar.
A vantagem de usar a API de conclusão de bate-papo significa que você pode simplesmente conectar um modelo de outros provedores que estejam em conformidade com a mesma especificação de API. Você pode usar seu próprio LLM privado ou os conhecidos da Open AI, Gemini, Claude, etc.
O UDF invocado requer as seguintes informações de você:
- Espaços-chave - Uma matriz de strings, cada uma representando um espaço-chave do Couchbase (bucket.scope.collection). Use aspas com acento grave quando necessário para escapar de nomes especiais (como
amostra de viagem.inventory.route). Isso informa ao UDF onde procurar seus dados. - imediato - Sua solicitação em inglês simples (ou em qualquer outro idioma).
Exemplo: “Mostre-me todos os usuários que fizeram uma compra nas últimas 24 horas”.” - apikey - Sua chave de API usada para autenticação com o ponto de extremidade do modelo.
- ponto final do modelo - Por exemplo, URL de conclusões de bate-papo em conformidade com o Open AI.
- modelo - O nome do modelo que você deseja usar do provedor.
Por exemplo, “gpt-4o-2024-05-13”
Há também várias funções disponíveis na biblioteca:
inferidor()
Antes de gerar uma consulta, o UDF tenta primeiro entender seus dados. A função auxiliar inferencer() chama a instrução INFER do Couchbase para recuperar o esquema de uma coleção:
|
1 2 3 4 5 6 7 8 |
function inferencer(k) { var infq = N1QL("SELECT t.properties FROM (INFER " + k + ") AS t"); var res = []; for (const doc of infq) { res.push(doc); } return res[0]; } |
Esse esquema é usado para ajudar a IA a entender que tipo de dados está contido em cada coleção.
A função principal: nl2sql()
- Coleta todos os esquemas para os espaços-chave fornecidos usando o inferencer(). Constrói um prompt que inclui: o esquema inferido, sua consulta em linguagem natural e um prompt do Couchbase para estimular o LLM.
- Envia para o LLM.
- Extrai o SQL++ gerado da resposta do modelo.
- Executa-o diretamente se for um comando SELECT e retorna o comando SQL++ gerado e os resultados da consulta.
O motivo para não executar instruções que não sejam de seleção é que você não quer que esse UDF insira, atualize ou exclua documentos em uma coleção sem que você o verifique. Portanto, o comando SQL++ permite que você o execute depois de ter sido verificado.
Exemplo de caso de uso:
|
1 2 3 4 5 6 |
SELECT default:NL2SQL( ["`travel-sample`.inventory.hotel"], "Give me hotels in San Francisco that have free parking and free breakfast and a rating of more than 3", "", "https://api.openai.com/v1/chat/completions", "gpt-4o-2024-05-13" ); |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
Result: [{ "$1": { "generated_statement": "SELECT h.name, h.address, h.city, h.state, h.country, h.free_parking, h.free_breakfast, r.ratings.Overall\nFROM `travel-sample`.inventory.hotel AS h\nUNNEST h.reviews AS r\nWHERE h.city = \"San Francisco\"\n AND h.free_parking = true\n AND h.free_breakfast = true\n AND r.ratings.Overall > 3;", "results": [{ "Overall": 4, "address": "520 Church St", "city": "San Francisco", "country": "United States", "free_breakfast": true, "free_parking": true, "name": "Parker House", "state": "California" }, { "Overall": 4, "address": "520 Church St", "city": "San Francisco", "country": "United States", "free_breakfast": true, "free_parking": true, "name": "Parker House", "state": "California" }, { "Overall": 5, "address": "520 Church St", "city": "San Francisco", "country": "United States", "free_breakfast": true, "free_parking": true, "name": "Parker House", "state": "California" }, { "Overall": 4, "address": "520 Church St", "city": "San Francisco", "country": "United States", "free_breakfast": true, "free_parking": true, "name": "Parker House", "state": "California" }, { "Overall": 5, "address": "465 Grant Ave", "city": "San Francisco", "country": "United States", "free_breakfast": true, "free_parking": true, "name": "Grant Plaza Hotel", "state": "California" }, { "Overall": 5, "address": "465 Grant Ave", "city": "San Francisco", "country": "United States", "free_breakfast": true, "free_parking": true, "name": "Grant Plaza Hotel", "state": "California" }, ... |
Experimentação com modelos de outros provedores
O próximo exemplo usa a API compatível com Open AI da Gemini. Basta alterar o URL do provedor do modelo da API Open AI anterior para a API da Gemini. Além disso, certifique-se de alterar o parâmetro do modelo para um modelo que ele reconheça. Obviamente, você também precisa atualizar a chave api da chave do Open AI para a chave do Gemini.
|
1 2 3 4 5 6 7 |
SELECT NL2SQL( ["`travel-sample`.inventory.hotel"], "Show me hotels in France", "", "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", "gemini-2.0-flash" )as p; |
O resultado é ilustrado a seguir:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
[{ "p": { "generated_statement": "SELECT h.name AS hotel_name, h.city AS hotel_city\nFROM `travel-sample`.inventory.hotel AS h\nWHERE h.country = \"France\";", "results": [{ "hotel_city": "Giverny", "hotel_name": "The Robins" }, { "hotel_city": "Giverny", "hotel_name": "Le Clos Fleuri" }, … { "hotel_city": "Ferney-Voltaire", "hotel_name": "Hotel Formule 1" } ] } }] |
Conclusão
Este blog oferece uma visão geral de como você pode aproveitar a IA para interagir com seus dados no Couchbase. Com esse UDF, a consulta em linguagem natural se torna uma realidade, sem necessidade de conhecimento em SQL++. Ele é agnóstico em relação ao modelo e seguro para consultas de produção.
E isso é apenas o começo. No futuro, esperamos estendê-lo para:
- Imagem → SQL++
- Voz → SQL++
- Pipelines do tipo agente
... todos executados dentro dos fluxos de trabalho do Couchbase.
Referências
Capella IQ: https://docs.couchbase.com/cloud/get-started/capella-iq/get-started-with-iq.html
APIs de conclusão de bate-papo:
https://platform.openai.com/docs/api-reference/chat
https://ai.google.dev/gemini-api/docs/openai#rest