in plugins/wasm-go/extensions/ai-search/main.go [454:555]
func executeSearch(ctx wrapper.HttpContext, config Config, queryIndex int, body []byte, searchContexts []engine.SearchContext, log wrapper.Log) types.Action {
searchResultGroups := make([][]engine.SearchResult, len(config.engine))
var finished int
var searching int
for i := 0; i < len(config.engine); i++ {
configEngine := config.engine[i]
// Check if engine needs to execute for any of the search contexts
var needsExecute bool
for _, searchCtx := range searchContexts {
if configEngine.NeedExectue(searchCtx) {
needsExecute = true
break
}
}
if !needsExecute {
continue
}
// Process all search contexts for this engine
for _, searchCtx := range searchContexts {
if !configEngine.NeedExectue(searchCtx) {
continue
}
args := configEngine.CallArgs(searchCtx)
index := i
err := configEngine.Client().Call(args.Method, args.Url, args.Headers, args.Body,
func(statusCode int, responseHeaders http.Header, responseBody []byte) {
defer func() {
finished++
if finished == searching {
// Merge search results from all engines with deduplication
var mergedResults []engine.SearchResult
seenLinks := make(map[string]bool)
for _, results := range searchResultGroups {
for _, result := range results {
if !seenLinks[result.Link] {
seenLinks[result.Link] = true
mergedResults = append(mergedResults, result)
}
}
}
if len(mergedResults) == 0 {
log.Warnf("no search result found, searchContexts:%#v", searchContexts)
proxywasm.ResumeHttpRequest()
return
}
// Format search results for prompt template
var formattedResults []string
var formattedReferences []string
for j, result := range mergedResults {
if config.needReference {
formattedResults = append(formattedResults,
fmt.Sprintf("[webpage %d begin]\n%s\n[webpage %d end]", j+1, result.Content, j+1))
formattedReferences = append(formattedReferences,
fmt.Sprintf("[%d] [%s](%s)", j+1, result.Title, result.Link))
} else {
formattedResults = append(formattedResults,
fmt.Sprintf("[webpage begin]\n%s\n[webpage end]", result.Content))
}
}
// Prepare template variables
curDate := time.Now().In(time.FixedZone("CST", 8*3600)).Format("2006年1月2日")
searchResults := strings.Join(formattedResults, "\n")
log.Debugf("searchResults: %s", searchResults)
// Fill prompt template
prompt := strings.Replace(config.promptTemplate, "{search_results}", searchResults, 1)
prompt = strings.Replace(prompt, "{question}", searchContexts[0].Querys[0], 1)
prompt = strings.Replace(prompt, "{cur_date}", curDate, 1)
// Update request body with processed prompt
modifiedBody, err := sjson.SetBytes(body, fmt.Sprintf("messages.%d.content", queryIndex), prompt)
if err != nil {
log.Errorf("modify request message content failed, err:%v, body:%s", err, body)
} else {
log.Debugf("modifeid body:%s", modifiedBody)
proxywasm.ReplaceHttpRequestBody(modifiedBody)
if config.needReference {
ctx.SetContext("References", strings.Join(formattedReferences, "\n\n"))
}
}
proxywasm.ResumeHttpRequest()
}
}()
if statusCode != http.StatusOK {
log.Errorf("search call failed, status: %d, engine: %#v", statusCode, configEngine)
return
}
// Append results to existing slice for this engine
searchResultGroups[index] = append(searchResultGroups[index], configEngine.ParseResult(searchCtx, responseBody)...)
}, args.TimeoutMillisecond)
if err != nil {
log.Errorf("search call failed, engine: %#v", configEngine)
continue
}
searching++
}
}
if searching > 0 {
return types.ActionPause
}
return types.ActionContinue
}