mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-05 07:10:29 +08:00
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.
This commit is contained in:
@@ -18,12 +18,12 @@ import (
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cloudwego/eino/schema"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"ragflow/internal/agent/chat"
|
||||
"ragflow/internal/agent/component/prompts"
|
||||
"ragflow/internal/agent/runtime"
|
||||
"ragflow/internal/common"
|
||||
@@ -130,84 +130,68 @@ type LLMOutput struct {
|
||||
Tokens int
|
||||
}
|
||||
|
||||
// ChatInvoker is the abstraction the LLM component uses to talk to a
|
||||
// chat model. The default implementation lives in this file; tests can
|
||||
// override the package-level defaultChatInvoker to inject a stub.
|
||||
type ChatInvoker interface {
|
||||
Invoke(ctx context.Context, db *gorm.DB, req ChatInvokeRequest) (*ChatInvokeResponse, error)
|
||||
}
|
||||
// ChatInvoker is an alias for the shared chat.Invoker seam. The production
|
||||
// eino-based implementation lives in this file; the package-level singleton is
|
||||
// owned by internal/agent/chat so agent tools and the harness can also call the
|
||||
// LLM without an import cycle.
|
||||
type ChatInvoker = chat.Invoker
|
||||
|
||||
// ChatInvokeRequest is the minimal surface the LLM component needs to
|
||||
// dispatch a chat call. Driver / APIKey / ModelName are kept here so the
|
||||
// invoker can wire the right provider without the component caring.
|
||||
type ChatInvokeRequest struct {
|
||||
Driver string
|
||||
ModelName string
|
||||
APIKey string
|
||||
BaseURL string
|
||||
Messages []schema.Message
|
||||
Temperature *float64
|
||||
TopP *float64
|
||||
PresencePenalty *float64
|
||||
FrequencyPenalty *float64
|
||||
MaxTokens *int
|
||||
// Thinking mirrors the agent-level `thinking` setting
|
||||
// ("enabled" | "disabled" | ""). The default invoker is
|
||||
// responsible for translating this into the provider-specific
|
||||
// request body (e.g. Qwen `enable_thinking`, Kimi/GLM
|
||||
// `thinking.type`). Empty string means "use provider default"
|
||||
// and the invoker should leave the provider's reasoning mode
|
||||
// untouched.
|
||||
Thinking string
|
||||
}
|
||||
// ChatInvokeRequest is an alias for chat.Request.
|
||||
type ChatInvokeRequest = chat.Request
|
||||
|
||||
// ChatInvokeResponse mirrors what the LLM component writes to its outputs.
|
||||
type ChatInvokeResponse struct {
|
||||
Content string
|
||||
Thinking string
|
||||
Model string
|
||||
Stopped bool
|
||||
Tokens int
|
||||
}
|
||||
// ChatInvokeResponse is an alias for chat.Response.
|
||||
type ChatInvokeResponse = chat.Response
|
||||
|
||||
// defaultChatInvokerMu guards defaultChatInvoker swaps during tests.
|
||||
var defaultChatInvokerMu sync.RWMutex
|
||||
|
||||
// defaultChatInvoker is the production ChatInvoker. Replaced in tests.
|
||||
var defaultChatInvoker ChatInvoker = &einoChatInvoker{}
|
||||
|
||||
// SetDefaultChatInvoker swaps the package-level ChatInvoker (test helper).
|
||||
// Pass nil to restore the default. Concurrent-safe.
|
||||
// SetDefaultChatInvoker delegates to the shared chat package singleton (test
|
||||
// helper). Pass nil to restore the "not configured" state. The production
|
||||
// einoChatInvoker is registered at boot in cmd/server_main.go.
|
||||
func SetDefaultChatInvoker(inv ChatInvoker) {
|
||||
defaultChatInvokerMu.Lock()
|
||||
defer defaultChatInvokerMu.Unlock()
|
||||
defaultChatInvoker = inv
|
||||
}
|
||||
|
||||
// getDefaultChatInvoker returns the current default ChatInvoker.
|
||||
func getDefaultChatInvoker() ChatInvoker {
|
||||
defaultChatInvokerMu.RLock()
|
||||
defer defaultChatInvokerMu.RUnlock()
|
||||
if defaultChatInvoker == nil {
|
||||
return &einoChatInvoker{}
|
||||
if inv == nil {
|
||||
chat.SetDefaultInvoker(nil)
|
||||
return
|
||||
}
|
||||
return defaultChatInvoker
|
||||
chat.SetDefaultInvoker(inv)
|
||||
}
|
||||
|
||||
// GetDefaultChatInvokerForTest exposes the current package-level invoker so
|
||||
// GetDefaultChatInvokerForTest exposes the current shared chat invoker so
|
||||
// cross-package tests can swap it and restore it safely.
|
||||
func GetDefaultChatInvokerForTest() ChatInvoker {
|
||||
return getDefaultChatInvoker()
|
||||
return chat.GetDefaultInvoker()
|
||||
}
|
||||
|
||||
// getDefaultChatInvoker returns the shared chat invoker, falling back to the
|
||||
// production eino invoker when none has been installed.
|
||||
func getDefaultChatInvoker() ChatInvoker {
|
||||
if inv := chat.GetDefaultInvoker(); inv != nil {
|
||||
return inv
|
||||
}
|
||||
return &einoChatInvoker{}
|
||||
}
|
||||
|
||||
// InstallDefaultChatInvoker registers the production eino-based invoker as the
|
||||
// shared chat default. Called at server bootstrap so harness/agentic-search LLM
|
||||
// calls work in production; without it, chat.GetDefaultInvoker() stays nil and
|
||||
// harness falls back gracefully.
|
||||
func InstallDefaultChatInvoker() {
|
||||
chat.SetDefaultInvoker(&einoChatInvoker{})
|
||||
}
|
||||
|
||||
// einoChatInvoker is the production ChatInvoker — it constructs a fresh
|
||||
// models.EinoChatModel per call from the request and dispatches.
|
||||
// models.EinoChatModel per call from the request and dispatches. It is NOT
|
||||
// registered as the shared chat default at init (so chat.GetDefaultInvoker()
|
||||
// stays nil until bootstrap); cmd registers it via SetDefaultChatInvoker.
|
||||
type einoChatInvoker struct{}
|
||||
|
||||
// Invoke satisfies ChatInvoker.
|
||||
func (e *einoChatInvoker) Invoke(ctx context.Context, db *gorm.DB, req ChatInvokeRequest) (*ChatInvokeResponse, error) {
|
||||
if req.ModelName == "" {
|
||||
return nil, fmt.Errorf("component: LLM: model_id is required")
|
||||
// Harness/agentic-search nodes may omit the model; fall back to the
|
||||
// bootstrap-registered tenant default so those calls work in production.
|
||||
if def := chat.GetDefaultModelName(); def != "" {
|
||||
req.ModelName = def
|
||||
} else {
|
||||
return nil, fmt.Errorf("component: LLM: model_id is required and no default model is configured")
|
||||
}
|
||||
}
|
||||
driver := req.Driver
|
||||
modelName := req.ModelName
|
||||
|
||||
Reference in New Issue
Block a user