Skip to content

Commit

Permalink
Merge pull request #57 from weni-ai/feat/vtex-search-card
Browse files Browse the repository at this point in the history
Add support for searching for Vtex products
  • Loading branch information
Robi9 authored Dec 1, 2023
2 parents 0bd294e + c4a1697 commit 0b93d5e
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 42 deletions.
6 changes: 5 additions & 1 deletion core/handlers/msg_catalog_created_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ func TestMsgCatalogCreated(t *testing.T) {
{"product_retailer_id": "eb2305cc-bf39-43ad-a069-bbbfb6401acc"},
},
false,
"",
"",
true,
),
},
Expand All @@ -53,11 +55,13 @@ func TestMsgCatalogCreated(t *testing.T) {
{"product_retailer_id": "63157bd2-6f94-4dbb-b394-ea4eb07ce156"},
},
false,
"",
"",
true,
),
},
testdata.Bob: []flows.Action{
actions.NewSendMsgCatalog(handlers.NewActionUUID(), "No URNs", "", "", "View Products", "i want a water bottle", nil, false, false),
actions.NewSendMsgCatalog(handlers.NewActionUUID(), "No URNs", "", "", "View Products", "i want a water bottle", nil, false, "", "", false),
},
},
Msgs: handlers.ContactMsgMap{
Expand Down
16 changes: 15 additions & 1 deletion core/models/catalog_products.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ func loadCatalog(ctx context.Context, db *sqlx.DB, orgID OrgID) ([]assets.MsgCat
}
defer rows.Close()

if err == sql.ErrNoRows || !rows.Next() {
return nil, nil
}

catalog := make([]assets.MsgCatalog, 0)
for rows.Next() {
msgCatalog := &MsgCatalog{}
Expand All @@ -161,6 +165,11 @@ func loadCatalog(ctx context.Context, db *sqlx.DB, orgID OrgID) ([]assets.MsgCat
if err != nil {
return nil, err
}

if err == nil && channelUUID == assets.ChannelUUID("") {
return nil, nil
}

msgCatalog.c.ChannelUUID = channelUUID
msgCatalog.c.Type = "msg_catalog"
catalog = append(catalog, msgCatalog)
Expand Down Expand Up @@ -196,8 +205,13 @@ ORDER BY
func ChannelUUIDForChannelID(ctx context.Context, db *sqlx.DB, channelID ChannelID) (assets.ChannelUUID, error) {
var channelUUID assets.ChannelUUID
err := db.GetContext(ctx, &channelUUID, `SELECT uuid FROM channels_channel WHERE id = $1 AND is_active = TRUE`, channelID)
if err != nil {
if err != nil && err != sql.ErrNoRows {
return assets.ChannelUUID(""), errors.Wrapf(err, "no channel found with id: %d", channelID)
}

if err == sql.ErrNoRows {
return assets.ChannelUUID(""), nil
}

return channelUUID, nil
}
14 changes: 2 additions & 12 deletions core/models/catalog_products_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package models_test

import (
"context"
"fmt"
"testing"
"time"

"github.com/nyaruka/mailroom/core/models"
"github.com/nyaruka/mailroom/testsuite"
Expand All @@ -16,11 +14,6 @@ func TestCatalogProducts(t *testing.T) {
ctx, _, db, _ := testsuite.Get()
defer testsuite.Reset(testsuite.ResetDB)

// _, err := db.Exec(catalogProductDDL)
// if err != nil {
// t.Fatal(err)
// }

_, err := db.Exec(`INSERT INTO public.wpp_products_catalog
(uuid, facebook_catalog_id, "name", created_on, modified_on, is_active, channel_id, org_id)
VALUES('2be9092a-1c97-4b24-906f-f0fbe3e1e93e', '123456789', 'Catalog Dummy', now(), now(), true, $1, $2);
Expand Down Expand Up @@ -52,10 +45,7 @@ func TestChannelUUIDForChannelID(t *testing.T) {
ctx, _, db, _ := testsuite.Get()
defer testsuite.Reset(testsuite.ResetAll)

ctxp, cancelp := context.WithTimeout(ctx, time.Second*5)
defer cancelp()

ctp, err := models.ChannelUUIDForChannelID(ctxp, db, testdata.TwilioChannel.ID)
ctp, err := models.ChannelUUIDForChannelID(ctx, db, testdata.TwilioChannel.ID)
assert.NoError(t, err)
assert.Equal(t, ctp, testdata.Org2Channel.UUID)
assert.Equal(t, ctp, testdata.TwilioChannel.ID)
}
4 changes: 1 addition & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,4 @@ go 1.17

replace github.com/nyaruka/gocommon => github.com/Ilhasoft/gocommon v1.16.2-weni


replace github.com/nyaruka/goflow => github.com/weni-ai/goflow v0.3.0-goflow-0.144.3

replace github.com/nyaruka/goflow => github.com/weni-ai/goflow v0.4.0-goflow-0.144.3
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,8 @@ github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLD
github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
github.com/weni-ai/goflow v0.3.0-goflow-0.144.3 h1:I6d3rcMBCS56McFVq0eAN17Nk8sPVDf/315uCosOWtM=
github.com/weni-ai/goflow v0.3.0-goflow-0.144.3/go.mod h1:o0xaVWP9qNcauBSlcNLa79Fm2oCPV+BDpheFRa/D40c=
github.com/weni-ai/goflow v0.4.0-goflow-0.144.3 h1:CqWVO7qwOrTeZGdhuWTcYy2RvjUU9sxzMV7f25RIk6c=
github.com/weni-ai/goflow v0.4.0-goflow-0.144.3/go.mod h1:o0xaVWP9qNcauBSlcNLa79Fm2oCPV+BDpheFRa/D40c=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand Down
156 changes: 138 additions & 18 deletions services/external/weni/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"time"

"github.com/jmoiron/sqlx"
"github.com/nyaruka/gocommon/httpx"
"github.com/nyaruka/gocommon/jsonx"
"github.com/nyaruka/goflow/assets"
"github.com/nyaruka/goflow/flows"
"github.com/nyaruka/goflow/utils"
Expand Down Expand Up @@ -78,7 +81,7 @@ func (s *service) Call(session flows.Session, params assets.MsgCatalogParam, log

content := params.ProductSearch
productList, traceWeniGPT, err := GetProductListFromChatGPT(ctx, s.rtConfig, content)
callResult.TraceWeniGPT = traceWeniGPT
callResult.Traces = append(callResult.Traces, traceWeniGPT)
if err != nil {
return callResult, err
}
Expand All @@ -98,20 +101,30 @@ func (s *service) Call(session flows.Session, params assets.MsgCatalogParam, log
return callResult, err
}

productRetailerIDS := []string{}
productRetailerIDS := map[string][]string{}
productRetailerIDMap := make(map[string]struct{})
searchResult := []string{}
var trace *httpx.Trace

for _, product := range productList {
searchResult, trace, err := GetProductListFromSentenX(product, catalog.FacebookCatalogID(), searchThreshold, s.rtConfig)
callResult.TraceSentenx = trace
if params.SearchType == "default" {
searchResult, trace, err = GetProductListFromSentenX(product, catalog.FacebookCatalogID(), searchThreshold, s.rtConfig)
callResult.Traces = append(callResult.Traces, trace)
} else if params.SearchType == "vtex" {
searchResult, trace, err = GetProductListFromVtex(product, params.SearchUrl, params.ApiType)
callResult.Traces = append(callResult.Traces, trace)
if searchResult == nil {
continue
}
}
if err != nil {
return callResult, errors.Wrapf(err, "on iterate to search products on sentenx")
return callResult, errors.Wrapf(err, "on iterate to search products")
}
for _, prod := range searchResult {
productRetailerID := prod["product_retailer_id"]
productRetailerID := prod
_, exists := productRetailerIDMap[productRetailerID]
if !exists {
productRetailerIDS = append(productRetailerIDS, productRetailerID)
productRetailerIDS[product] = append(productRetailerIDS[product], productRetailerID)
productRetailerIDMap[productRetailerID] = struct{}{}
}
}
Expand Down Expand Up @@ -152,7 +165,7 @@ func GetProductListFromWeniGPT(rtConfig *runtime.Config, content string) ([]stri
return products["products"], trace, nil
}

func GetProductListFromSentenX(productSearch string, catalogID string, threshold float64, rtConfig *runtime.Config) ([]map[string]string, *httpx.Trace, error) {
func GetProductListFromSentenX(productSearch string, catalogID string, threshold float64, rtConfig *runtime.Config) ([]string, *httpx.Trace, error) {
client := sentenx.NewClient(http.DefaultClient, nil, rtConfig.SentenxBaseURL)

searchParams := sentenx.NewSearchRequest(productSearch, catalogID, threshold)
Expand All @@ -166,18 +179,12 @@ func GetProductListFromSentenX(productSearch string, catalogID string, threshold
return nil, trace, errors.New("no products found on sentenx")
}

pmap := make(map[string]struct{})
pmap := []string{}
for _, p := range searchResponse.Products {
pmap[p.ProductRetailerID] = struct{}{}
}

result := []map[string]string{}
for k := range pmap {
mapElement := map[string]string{"product_retailer_id": k}
result = append(result, mapElement)
pmap = append(pmap, p.ProductRetailerID)
}

return result, trace, nil
return pmap, trace, nil
}

func GetProductListFromChatGPT(ctx context.Context, rtConfig *runtime.Config, content string) ([]string, *httpx.Trace, error) {
Expand All @@ -196,11 +203,15 @@ func GetProductListFromChatGPT(ctx context.Context, rtConfig *runtime.Config, co
Role: chatgpt.ChatMessageRoleSystem,
Content: "Always use this pattern: {\"products\": []}",
}
prompt4 := chatgpt.ChatCompletionMessage{
Role: chatgpt.ChatMessageRoleSystem,
Content: "Ensure that no product names are repeated, and each product should be in singular form without any numbers or quantities.",
}
question := chatgpt.ChatCompletionMessage{
Role: chatgpt.ChatMessageRoleUser,
Content: content,
}
completionRequest := chatgpt.NewChatCompletionRequest([]chatgpt.ChatCompletionMessage{prompt1, prompt2, prompt3, question})
completionRequest := chatgpt.NewChatCompletionRequest([]chatgpt.ChatCompletionMessage{prompt1, prompt2, prompt3, prompt4, question})
response, trace, err := chatGPTClient.CreateChatCompletion(completionRequest)
if err != nil {
return nil, trace, errors.Wrapf(err, "error on chatgpt call for list products")
Expand All @@ -215,3 +226,112 @@ func GetProductListFromChatGPT(ctx context.Context, rtConfig *runtime.Config, co
}
return products["products"], trace, nil
}

func GetProductListFromVtex(productSearch string, searchUrl string, apiType string) ([]string, *httpx.Trace, error) {
var result []string
var trace *httpx.Trace
var err error

if apiType == "legacy" {
result, trace, err = VtexLegacySearch(searchUrl, productSearch)
if err != nil {
return nil, trace, err
}
} else if apiType == "intelligent" {
result, trace, err = VtexIntelligentSearch(searchUrl, productSearch)
if err != nil {
return nil, trace, err
}
}

return result, trace, nil
}

func VtexLegacySearch(searchUrl string, productSearch string) ([]string, *httpx.Trace, error) {
urlAfter := strings.TrimSuffix(searchUrl, "/")
url := fmt.Sprintf("%s/%s", urlAfter, productSearch)

req, err := httpx.NewRequest("GET", url, nil, nil)
if err != nil {
return nil, nil, err
}

client := &http.Client{}
trace, err := httpx.DoTrace(client, req, nil, nil, -1)
if err != nil {
return nil, trace, err
}

response := []struct {
Items []struct {
ItemId string `json:"itemId"`
} `json:"items"`
}{}

err = jsonx.Unmarshal(trace.ResponseBody, &response)
if err != nil {
return nil, trace, err
}

result := []string{}

if len(response) == 0 {
return result, trace, nil
}

for i, product := range response {
if i == 5 {
break
}
product_retailer_id := product.Items[0].ItemId
result = append(result, product_retailer_id)
}

return result, trace, nil
}

func VtexIntelligentSearch(searchUrl string, productSearch string) ([]string, *httpx.Trace, error) {
query := url.Values{}
query.Add("query", productSearch)
query.Add("locale", "pt-BR")
query.Add("hideUnavailableItems", "true")

urlAfter := strings.TrimSuffix(searchUrl, "/")

url_ := fmt.Sprintf("%s?%s", urlAfter, query.Encode())

req, err := httpx.NewRequest("GET", url_, nil, nil)
if err != nil {
return nil, nil, err
}

client := &http.Client{}
trace, err := httpx.DoTrace(client, req, nil, nil, -1)
if err != nil {
return nil, trace, err
}

response := &struct {
Products []struct {
Items []struct {
ItemId string `json:"itemId"`
} `json:"items"`
} `json:"products"`
}{}

err = jsonx.Unmarshal(trace.ResponseBody, &response)
if err != nil {
return nil, trace, err
}

result := []string{}
for i, product := range response.Products {
if i == 5 {
break
}
product_retailer_id := product.Items[0].ItemId
result = append(result, product_retailer_id)
}

return result, trace, nil
}
Loading

0 comments on commit 0b93d5e

Please sign in to comment.