Files
ragflow/internal/agent/harness/answer.go
Zhichang Yu 4e78f1f440 Port Python agentic search to Go (nav service, harness, tools) (#17702)
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.
2026-08-03 11:16:16 +08:00

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 ""
}