mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-04 23:00:30 +08:00
Port Python rag/advanced_rag agentic search to Go: ES-backed dataset-nav service, agentic-search harness, and agent tools. Includes agentic-search port plan and self-review docs.
125 lines
3.8 KiB
Go
125 lines
3.8 KiB
Go
//
|
|
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
package harness
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/cloudwego/eino/schema"
|
|
|
|
"gorm.io/gorm"
|
|
"ragflow/internal/agent/chat"
|
|
)
|
|
|
|
// FINAL_ANSWER_SYSTEM mirrors Python report_prompt.FINAL_ANSWER_SYSTEM.
|
|
const finalAnswerSystem = `You are a smart agent. Answer the user's question using ONLY the evidence provided below. Do not invent facts: if the evidence cannot support a claim, say so plainly instead of guessing.
|
|
|
|
# Citation rules
|
|
{cite_rules}
|
|
|
|
# Language
|
|
Answer in the SAME language as the question. Translate retrieved evidence into that language as part of composing the answer; only verbatim quoted snippets may stay in their source language.
|
|
|
|
# Fallback
|
|
If the evidence does not answer the question, reply with a clear statement that you don't have enough information based on the available sources (in the user's language).
|
|
`
|
|
|
|
const partialAnswerPreamble = "Note: the following answer is based on partial information and may be incomplete."
|
|
|
|
const defaultCiteRules = "Cite passages with their source documents where available."
|
|
|
|
const (
|
|
abstainMessage = "I cannot answer this question based on the available information."
|
|
emptyResultMessage = "I don't have enough information based on the available sources."
|
|
)
|
|
|
|
// AnswerResult mirrors the formalize_answer node output.
|
|
type AnswerResult struct {
|
|
FinalAnswer string
|
|
Abstained bool
|
|
Empty bool
|
|
}
|
|
|
|
// FormalizeAnswer generates the final answer from the gathered kbinfos. Mirrors
|
|
// Python's formalize_answer node: abstain/empty short-circuits, otherwise builds
|
|
// system+user and calls the chat invoker.
|
|
func FormalizeAnswer(ctx context.Context, db *gorm.DB, question string, kb *Kbinfos, partial, abstain, empty bool) AnswerResult {
|
|
if abstain {
|
|
return AnswerResult{FinalAnswer: abstainMessage, Abstained: true}
|
|
}
|
|
if empty || kb == nil || len(kb.Chunks) == 0 {
|
|
return AnswerResult{FinalAnswer: emptyResultMessage, Empty: true}
|
|
}
|
|
|
|
var b strings.Builder
|
|
b.WriteString("Question:\n" + question + "\n")
|
|
if partial {
|
|
b.WriteString(partialAnswerPreamble + "\n")
|
|
}
|
|
b.WriteString("\nEvidence:\n")
|
|
for i, c := range kb.Chunks {
|
|
text := chunkText(c)
|
|
doc := chunkDoc(c)
|
|
if text == "" {
|
|
continue
|
|
}
|
|
b.WriteString(fmt.Sprintf("[%d] %s", i+1, text))
|
|
if doc != "" {
|
|
b.WriteString(fmt.Sprintf(" (source: %s)", doc))
|
|
}
|
|
b.WriteString("\n")
|
|
}
|
|
|
|
system := strings.ReplaceAll(finalAnswerSystem, "{cite_rules}", defaultCiteRules)
|
|
inv := chat.GetDefaultInvoker()
|
|
if inv == nil {
|
|
return AnswerResult{FinalAnswer: "I'm sorry, the chat invoker is not configured."}
|
|
}
|
|
resp, err := inv.Invoke(ctx, db, chat.Request{
|
|
Messages: []schema.Message{
|
|
{Role: schema.System, Content: system},
|
|
{Role: schema.User, Content: b.String()},
|
|
},
|
|
})
|
|
if err != nil {
|
|
return AnswerResult{FinalAnswer: "I'm sorry, I encountered an error while composing the answer."}
|
|
}
|
|
return AnswerResult{FinalAnswer: resp.Content}
|
|
}
|
|
|
|
func chunkText(c map[string]interface{}) string {
|
|
if t, ok := c["content_with_weight"].(string); ok && t != "" {
|
|
return t
|
|
}
|
|
if t, ok := c["content"].(string); ok {
|
|
return t
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func chunkDoc(c map[string]interface{}) string {
|
|
if d, ok := c["docnm_kwd"].(string); ok {
|
|
return d
|
|
}
|
|
if d, ok := c["doc_id"].(string); ok {
|
|
return d
|
|
}
|
|
return ""
|
|
}
|