Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Learn why MongoDB was selected as a leader in the 2024 Gartner® Magic Quadrant™
Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Produtoschevron-right
Atlaschevron-right

Melhore os resultados de pesquisa do seu aplicativo com o ajuste automático

Isa Torres, Ethan Steininger5 min read • Published Oct 20, 2021 • Updated Aug 14, 2024
AtlasPesquisa
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Historicamente, a única maneira de melhorar a relevância da consulta de pesquisa do seu aplicativo é por meio de intervenção manual. Por exemplo, você pode introduzir oaumento de pontuação para multiplicar uma pontuação básica de relevância na presença de campos específicos. Isso garante que as pesquisas em que uma chave presente em alguns campos tenham um peso maior do que em outras. Isso, no entanto, é fixo por natureza. Os resultados são dinâmicos, mas a lógica em si não muda.
O projeto a seguir mostrará como aproveitar sinônimos para criar um loop de feedback auto-ajustável, a fim de fornecer resultados de pesquisa cada vez mais relevantes aos usuários, tudo sem modelos complexos de aprendizado de máquina!

Exemplo

Temos um aplicativo de busca de alimentos em que um usuário pesquisa por “Romanian Food.”. Supondo que estejamos registrando os dados de sequência de cliques de cada usuário (sua interação passo a passo com nosso aplicativo), podemos dar uma olhada nesse “sequence” e compará-lo com outros resultados que produziram um forte CTA (call to action): um checkout bem-sucedido.
Outro usuário pesquisou por "German Cuisine " e ele tinha uma sequência de cliques muito semelhante. Bem, podemos criar um script que analise os fluxos de cliques desses usuários (e de outros usuários), identificando similaridades, podemos dizer ao script para anexá-lo a um documento de sinônimos que contenha "German, " "Romanian, " e outros cozinhas mais comuns, como “Hungarian.”
Aqui está um fluxo de trabalho do que pretendemos realizar:
diagrama do que estamos procurando realizar

Tutorial

Etapa 1: registrar a atividade de fluxo de cliques do usuário

Em nossa camada de aplicativo, à medida que os eventos são disparados, nós os registramos em uma coleção de clickstreams, como:
1[{
2 "session_id": "1",
3 "event_id": "search_query",
4 "metadata": {
5 "search_value": "romanian food"
6 },
7 "timestamp": "1"
8 },
9 {
10 "session_id": "1",
11 "event_id": "add_to_cart",
12 "product_category":"eastern european cuisine",
13 "timestamp": "2"
14 },
15 {
16 "session_id": "1",
17 "event_id": "checkout",
18 "timestamp": "3"
19 },
20 {
21 "session_id": "1",
22 "event_id": "payment_success",
23 "timestamp": "4"
24 },
25 {
26 "session_id": "2",
27 "event_id": "search_query",
28 "metadata": {
29 "search_value": "hungarian food"
30 },
31 "timestamp": "1"
32 },
33 {
34 "session_id": "2",
35 "event_id": "add_to_cart",
36 "product_category":"eastern european cuisine",
37 "timestamp": "2"
38 }
39]
Nesta lista simplificada de eventos, podemos concluir que {"session_id":"1"} pesquisou "romanian food, " o que levou a uma taxa de conversão mais alta, deployment_success, em comparação com {"session_id":"2"}, que pesquisou "hungarian food " e parou após o evento add_to_cart. Você mesmo pode importar esses dados usando sample_data.json.
Vamos preparar os dados para nosso script search_tuner.

Etapa 2: Crie uma exibição que agrupe por session_id e filtre a presença de pesquisas

A propósito, não há problema que apenas alguns documentos tenham um campo de metadados. Nosso operador $group pode identificar de forma inteligente os que fazem e os que não fazem isso.
1[
2 # first we sort by timestamp to get everything in the correct sequence of events,
3 # as that is what we'll be using to draw logical correlations
4 {
5 '$sort': {
6 'timestamp': 1
7 }
8 },
9 # next, we'll group by a unique session_id, include all the corresponding events, and begin
10 # the filter for determining if a search_query exists
11 {
12 '$group': {
13 '_id': '$session_id',
14 'events': {
15 '$push': '$$ROOT'
16 },
17 'isSearchQueryPresent': {
18 '$sum': {
19 '$cond': [
20 {
21 '$eq': [
22 '$event_id', 'search_query'
23 ]
24 }, 1, 0
25 ]
26 }
27 }
28 }
29 },
30 # we hide session_ids where there is no search query
31 # then create a new field, an array called searchQuery, which we'll use to parse
32 {
33 '$match': {
34 'isSearchQueryPresent': {
35 '$gte': 1
36 }
37 }
38 },
39 {
40 '$unset': 'isSearchQueryPresent'
41 },
42 {
43 '$set': {
44 'searchQuery': '$events.metadata.search_value'
45 }
46 }
47]
Vamos criar a visualização criando a query, depois acessando o Compass e adicionando-a como uma nova collection chamada group_by_session_id_and_search_query:
captura de tela da criação de uma visualização no compass
Captura de tela da visualização no Compass
Veja como será:
1[
2 {
3 "session_id": "1",
4 "events": [
5 {
6 "event_id": "search_query",
7 "search_value": "romanian food"
8 },
9 {
10 "event_id": "add_to_cart",
11 "context": {
12 "cuisine": "eastern european cuisine"
13 }
14 },
15 {
16 "event_id": "checkout"
17 },
18 {
19 "event_id": "payment_success"
20 }
21 ],
22 "searchQuery": "romanian food"
23 }, {
24 "session_id": "2",
25 "events": [
26 {
27 "event_id": "search_query",
28 "search_value": "hungarian food"
29 },
30 {
31 "event_id": "add_to_cart",
32 "context": {
33 "cuisine": "eastern european cuisine"
34 }
35 },
36 {
37 "event_id": "checkout"
38 }
39 ],
40 "searchQuery": "hungarian food"
41 },
42 {
43 "session_id": "3",
44 "events": [
45 {
46 "event_id": "search_query",
47 "search_value": "italian food"
48 },
49 {
50 "event_id": "add_to_cart",
51 "context": {
52 "cuisine": "western european cuisine"
53 }
54 }
55 ],
56 "searchQuery": "sad food"
57 }
58]

Etapa 3: Crie um trabalho agendado que compare fluxos de cliques semelhantes e envie os sinônimos resultantes para a collection de sinônimos

1// Provide a success indicator to determine which session we want to
2// compare any incomplete sessions with
3const successIndicator = "payment_success"
4
5// what percentage similarity between two sets of click/event streams
6// we'd accept to be determined as similar enough to produce a synonym
7// relationship
8const acceptedConfidence = .9
9
10// boost the confidence score when the following values are present
11// in the eventstream
12const eventBoosts = {
13 successIndicator: .1
14}
15
16/**
17 * Enrich sessions with a flattened event list to make comparison easier.
18 * Determine if the session is to be considered successful based on the success indicator.
19 * @param {*} eventList List of events in a session.
20 * @returns {any} Calculated values used to determine if an incomplete session is considered to
21 * be related to a successful session.
22 */
23const enrichEvents = (eventList) => {
24 return {
25 eventSequence: eventList.map(event => { return event.event_id }).join(';'),
26 isSuccessful: eventList.some(event => { return event.event_id === successIndicator })
27 }
28}
29
30/**
31 * De-duplicate common tokens in two strings
32 * @param {*} str1
33 * @param {*} str2
34 * @returns Returns an array with the provided strings with the common tokens removed
35 */
36const dedupTokens = (str1, str2) => {
37 const splitToken = ' '
38 const tokens1 = str1.split(splitToken)
39 const tokens2 = str2.split(splitToken)
40 const dupedTokens = tokens1.filter(token => { return tokens2.includes(token)});
41 const dedupedStr1 = tokens1.filter(token => { return !dupedTokens.includes(token)});
42 const dedupedStr2 = tokens2.filter(token => { return !dupedTokens.includes(token)});
43
44 return [ dedupedStr1.join(splitToken), dedupedStr2.join(splitToken) ]
45}
46
47const findMatchingIndex = (synonyms, results) => {
48 let matchIndex = -1
49 for(let i = 0; i < results.length; i++) {
50 for(const synonym of synonyms) {
51 if(results[i].synonyms.includes(synonym)){
52 matchIndex = i;
53 break;
54 }
55 }
56 }
57 return matchIndex;
58}
59/**
60 * Inspect the context of two matching sessions.
61 * @param {*} successfulSession
62 * @param {*} incompleteSession
63 */
64const processMatch = (successfulSession, incompleteSession, results) => {
65 console.log(`=====\nINSPECTING POTENTIAL MATCH: ${ successfulSession.searchQuery} = ${incompleteSession.searchQuery}`);
66 let contextMatch = true;
67
68 // At this point we can assume that the sequence of events is the same, so we can
69 // use the same index when comparing events
70 for(let i = 0; i < incompleteSession.events.length; i++) {
71 // if we have a context, let's compare the kv pairs in the context of
72 // the incomplete session with the successful session
73 if(incompleteSession.events[i].context){
74 const eventWithContext = incompleteSession.events[i]
75 const contextKeys = Object.keys(eventWithContext.context)
76
77 try {
78 for(const key of contextKeys) {
79 if(successfulSession.events[i].context[key] !== eventWithContext.context[key]){
80 // context is not the same, not a match, let's get out of here
81 contextMatch = false
82 break;
83 }
84 }
85 } catch (error) {
86 contextMatch = false;
87 console.log(`Something happened, probably successful session didn't have a context for an event.`);
88 }
89 }
90 }
91
92 // Update results
93 if(contextMatch){
94 console.log(`VALIDATED`);
95 const synonyms = dedupTokens(successfulSession.searchQuery, incompleteSession.searchQuery, true)
96 const existingMatchingResultIndex = findMatchingIndex(synonyms, results)
97 if(existingMatchingResultIndex >= 0){
98 const synonymSet = new Set([...synonyms, ...results[existingMatchingResultIndex].synonyms])
99 results[existingMatchingResultIndex].synonyms = Array.from(synonymSet)
100 }
101 else{
102 const result = {
103 "mappingType": "equivalent",
104 "synonyms": synonyms
105 }
106 results.push(result)
107 }
108
109 }
110 else{
111 console.log(`NOT A MATCH`);
112 }
113
114 return results;
115}
116
117/**
118 * Compare the event sequence of incomplete and successful sessions
119 * @param {*} successfulSessions
120 * @param {*} incompleteSessions
121 * @returns
122 */
123const compareLists = (successfulSessions, incompleteSessions) => {
124 let results = []
125 for(const successfulSession of successfulSessions) {
126 for(const incompleteSession of incompleteSessions) {
127 // if the event sequence is the same, let's inspect these sessions
128 // to validate that they are a match
129 if(successfulSession.enrichments.eventSequence.includes(incompleteSession.enrichments.eventSequence)){
130 processMatch(successfulSession, incompleteSession, results)
131 }
132 }
133 }
134 return results
135}
136
137const processSessions = (sessions) => {
138 // console.log(`Processing the following list:`, JSON.stringify(sessions, null, 2));
139 // enrich sessions for processing
140 const enrichedSessions = sessions.map(session => {
141 return { ...session, enrichments: enrichEvents(session.events)}
142 })
143 // separate successful and incomplete sessions
144 const successfulEvents = enrichedSessions.filter(session => { return session.enrichments.isSuccessful})
145 const incompleteEvents = enrichedSessions.filter(session => { return !session.enrichments.isSuccessful})
146
147 return compareLists(successfulEvents, incompleteEvents);
148}
149
150/**
151 * Main Entry Point
152 */
153const main = () => {
154 const results = processSessions(eventsBySession);
155 console.log(`Results:`, results);
156}
157
158main();
159
160module.exports = processSessions;
Execute o script você mesmo.

Etapa 4: Aprimore nossa consulta de pesquisa com os sinônimos recém-anexados

1[
2 {
3 '$search': {
4 'index': 'synonym-search',
5 'text': {
6 'query': 'hungarian',
7 'path': 'cuisine-type'
8 },
9 'synonyms': 'similarCuisines'
10 }
11 }
12]

Próximos passos

Aqui está, pessoal. Pegamos dados brutos gravados em nosso servidor de aplicativos e os usamos criando um feedback que incentiva o comportamento positivo do usuário.
Ao medir esse ciclo de feedback em relação aos seus KPIs, você pode criar um teste A/B simples em relação a determinados sinônimos e padrões de usuário para otimizar seu aplicativo!

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Tutorial

Usando Go para AI


Nov 07, 2024 | 19 min read
Tutorial

Otimize com o MongoDB Atlas: Performance Advisor, analisador de queries e muito mais


Jan 30, 2024 | 6 min read
Tutorial

Crie uma Cocktail API com o Beanie e o MongoDB


Oct 01, 2024 | 6 min read
Artigo

Usando o Atlas Data Federation para controlar o acesso ao seu nó de análise


Aug 28, 2024 | 9 min read
Sumário
  • Exemplo