mirror of
https://github.com/alibaba/higress.git
synced 2026-03-14 13:50:46 +08:00
179 lines
5.1 KiB
Go
179 lines
5.1 KiB
Go
package quark
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
|
"github.com/tidwall/gjson"
|
|
|
|
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-search/engine"
|
|
)
|
|
|
|
type QuarkSearch struct {
|
|
apiKey string
|
|
timeoutMillisecond uint32
|
|
client wrapper.HttpClient
|
|
count uint32
|
|
optionArgs map[string]string
|
|
contentMode string // "summary" or "full"
|
|
}
|
|
|
|
const (
|
|
Path = "/linked-retrieval/linked-retrieval-entry/v2/linkedRetrieval/commands/genericSearch"
|
|
ContentSha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" // for empty body
|
|
Action = "GenericSearch"
|
|
Version = "2024-11-11"
|
|
SignatureAlgorithm = "ACS3-HMAC-SHA256"
|
|
SignedHeaders = "host;x-acs-action;x-acs-content-sha256;x-acs-date;x-acs-signature-nonce;x-acs-version"
|
|
)
|
|
|
|
func urlEncoding(rawStr string) string {
|
|
encodedStr := url.PathEscape(rawStr)
|
|
encodedStr = strings.ReplaceAll(encodedStr, "+", "%2B")
|
|
encodedStr = strings.ReplaceAll(encodedStr, ":", "%3A")
|
|
encodedStr = strings.ReplaceAll(encodedStr, "=", "%3D")
|
|
encodedStr = strings.ReplaceAll(encodedStr, "&", "%26")
|
|
encodedStr = strings.ReplaceAll(encodedStr, "$", "%24")
|
|
encodedStr = strings.ReplaceAll(encodedStr, "@", "%40")
|
|
// encodedStr := url.QueryEscape(rawStr)
|
|
return encodedStr
|
|
}
|
|
|
|
func getSignature(stringToSign, secret string) string {
|
|
h := hmac.New(sha256.New, []byte(secret))
|
|
h.Write([]byte(stringToSign))
|
|
hash := h.Sum(nil)
|
|
return hex.EncodeToString(hash)
|
|
}
|
|
|
|
func getCanonicalHeaders(params map[string]string) string {
|
|
paramArray := []string{}
|
|
for k, v := range params {
|
|
paramArray = append(paramArray, k+":"+v)
|
|
}
|
|
sort.Slice(paramArray, func(i, j int) bool {
|
|
return paramArray[i] <= paramArray[j]
|
|
})
|
|
return strings.Join(paramArray, "\n") + "\n"
|
|
}
|
|
|
|
func getHasedString(input string) string {
|
|
hash := sha256.Sum256([]byte(input))
|
|
hashHex := hex.EncodeToString(hash[:])
|
|
return hashHex
|
|
}
|
|
|
|
func generateHexID(length int) (string, error) {
|
|
bytes := make([]byte, length/2)
|
|
if _, err := rand.Read(bytes); err != nil {
|
|
return "", err
|
|
}
|
|
return hex.EncodeToString(bytes), nil
|
|
}
|
|
|
|
func NewQuarkSearch(config *gjson.Result) (*QuarkSearch, error) {
|
|
engine := &QuarkSearch{}
|
|
engine.apiKey = config.Get("apiKey").String()
|
|
if engine.apiKey == "" {
|
|
return nil, errors.New("apiKey not found")
|
|
}
|
|
serviceName := config.Get("serviceName").String()
|
|
if serviceName == "" {
|
|
return nil, errors.New("serviceName not found")
|
|
}
|
|
servicePort := config.Get("servicePort").Int()
|
|
if servicePort == 0 {
|
|
return nil, errors.New("servicePort not found")
|
|
}
|
|
engine.count = uint32(config.Get("count").Int())
|
|
if engine.count == 0 {
|
|
engine.count = 10
|
|
}
|
|
engine.client = wrapper.NewClusterClient(wrapper.FQDNCluster{
|
|
FQDN: serviceName,
|
|
Port: servicePort,
|
|
})
|
|
engine.timeoutMillisecond = uint32(config.Get("timeoutMillisecond").Uint())
|
|
if engine.timeoutMillisecond == 0 {
|
|
engine.timeoutMillisecond = 5000
|
|
}
|
|
engine.optionArgs = map[string]string{}
|
|
for key, value := range config.Get("optionArgs").Map() {
|
|
valStr := value.String()
|
|
if valStr != "" {
|
|
engine.optionArgs[key] = value.String()
|
|
}
|
|
}
|
|
engine.contentMode = config.Get("contentMode").String()
|
|
if engine.contentMode == "" {
|
|
engine.contentMode = "summary"
|
|
}
|
|
if engine.contentMode != "full" && engine.contentMode != "summary" {
|
|
return nil, fmt.Errorf("contentMode is not valid:%s", engine.contentMode)
|
|
}
|
|
return engine, nil
|
|
}
|
|
|
|
func (g QuarkSearch) NeedExectue(ctx engine.SearchContext) bool {
|
|
return ctx.EngineType == "" || ctx.EngineType == "internet"
|
|
}
|
|
|
|
func (g QuarkSearch) Client() wrapper.HttpClient {
|
|
return g.client
|
|
}
|
|
|
|
func (g QuarkSearch) CallArgs(ctx engine.SearchContext) engine.CallArgs {
|
|
queryUrl := fmt.Sprintf("https://cloud-iqs.aliyuncs.com/search/genericSearch?query=%s",
|
|
url.QueryEscape(strings.Join(ctx.Querys, " ")))
|
|
var extraArgs []string
|
|
for key, value := range g.optionArgs {
|
|
extraArgs = append(extraArgs, fmt.Sprintf("%s=%s", key, url.QueryEscape(value)))
|
|
}
|
|
if len(extraArgs) > 0 {
|
|
queryUrl = fmt.Sprintf("%s&%s", queryUrl, strings.Join(extraArgs, "&"))
|
|
}
|
|
return engine.CallArgs{
|
|
Method: http.MethodGet,
|
|
Url: queryUrl,
|
|
Headers: [][2]string{
|
|
{"Accept", "application/json"},
|
|
{"X-API-Key", g.apiKey},
|
|
},
|
|
TimeoutMillisecond: g.timeoutMillisecond,
|
|
}
|
|
}
|
|
|
|
func (g QuarkSearch) ParseResult(ctx engine.SearchContext, response []byte) []engine.SearchResult {
|
|
jsonObj := gjson.ParseBytes(response)
|
|
var results []engine.SearchResult
|
|
for index, item := range jsonObj.Get("pageItems").Array() {
|
|
var content string
|
|
if g.contentMode == "full" {
|
|
content = item.Get("markdownText").String()
|
|
if content == "" {
|
|
content = item.Get("mainText").String()
|
|
}
|
|
} else if g.contentMode == "summary" {
|
|
content = item.Get("snippet").String()
|
|
}
|
|
result := engine.SearchResult{
|
|
Title: item.Get("title").String(),
|
|
Link: item.Get("link").String(),
|
|
Content: content,
|
|
}
|
|
if result.Valid() && index < int(g.count) {
|
|
results = append(results, result)
|
|
}
|
|
}
|
|
return results
|
|
}
|