mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-14 04:36:52 +08:00
### Summary Add Querit Contents as a built-in page content tool for RAGFlow Agents and Canvas workflows. Querit Contents crawls one or more known URLs and returns their page content and optional metadata. It complements the existing Querit Search tool: Search discovers relevant pages, while Contents retrieves pages already selected by an Agent or workflow. This integration supports two usage modes: - A standalone `QueritContents` node in Canvas workflows. - An embedded content tool available to RAGFlow Agents.
155 lines
4.9 KiB
Go
155 lines
4.9 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 component
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"ragflow/internal/agent/runtime"
|
|
agenttool "ragflow/internal/agent/tool"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// ToolBackedComponent is the single Canvas adapter for tools that implement
|
|
// agenttool.ToolComponent.
|
|
type ToolBackedComponent struct {
|
|
name string
|
|
tool agenttool.ToolComponent
|
|
spec agenttool.ComponentSpec
|
|
}
|
|
|
|
func newToolComponentFactory(componentName, toolName string) Factory {
|
|
return func(params map[string]any) (Component, error) {
|
|
base, err := agenttool.BuildByName(toolName, params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
componentTool, ok := base.(agenttool.ToolComponent)
|
|
if !ok {
|
|
return nil, fmt.Errorf("%s: tool %q does not implement ToolComponent", componentName, toolName)
|
|
}
|
|
return &ToolBackedComponent{
|
|
name: componentName,
|
|
tool: componentTool,
|
|
spec: componentTool.ComponentSpec(),
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
func (c *ToolBackedComponent) Name() string { return c.name }
|
|
|
|
func (c *ToolBackedComponent) Inputs() map[string]string { return c.spec.Inputs }
|
|
|
|
func (c *ToolBackedComponent) Outputs() map[string]string { return c.spec.Outputs }
|
|
|
|
func (c *ToolBackedComponent) GetInputForm() map[string]any { return c.spec.InputForm }
|
|
|
|
func (c *ToolBackedComponent) Invoke(ctx context.Context, db *gorm.DB, inputs map[string]any) (map[string]any, error) {
|
|
argsJSON, err := json.Marshal(inputs)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("canvas: %s: encode inputs: %w", c.name, err)
|
|
}
|
|
|
|
raw, invokeErr := c.tool.InvokableRun(ctx, string(argsJSON))
|
|
var decoded map[string]any
|
|
if c.spec.PreserveJSONNumbers {
|
|
decoded = parseToolEnvelopeLossless(raw)
|
|
} else {
|
|
decoded = parseToolEnvelope(raw)
|
|
}
|
|
if rawValue, invalid := decoded["_raw"]; invalid {
|
|
if invokeErr != nil {
|
|
return nil, fmt.Errorf("canvas: %s: %w", c.name, invokeErr)
|
|
}
|
|
return nil, fmt.Errorf("canvas: %s: invalid tool result: %v", c.name, rawValue)
|
|
}
|
|
if existing, _ := decoded["_ERROR"].(string); strings.TrimSpace(existing) != "" {
|
|
outputs := c.tool.BuildComponentOutputs(decoded)
|
|
if outputs == nil {
|
|
outputs = make(map[string]any, 1)
|
|
}
|
|
outputs["_ERROR"] = existing
|
|
return outputs, nil
|
|
}
|
|
if invokeErr != nil {
|
|
return nil, fmt.Errorf("canvas: %s: %w", c.name, invokeErr)
|
|
}
|
|
|
|
if builder, ok := c.tool.(agenttool.ReferenceBuilder); ok {
|
|
chunks, docAggs := builder.BuildReferences(ctx, decoded)
|
|
if state, _, stateErr := runtime.GetStateFromContext[*runtime.CanvasState](ctx); stateErr == nil && state != nil {
|
|
state.SetRetrievalReferences(chunks, docAggs)
|
|
}
|
|
}
|
|
return c.tool.BuildComponentOutputs(decoded), nil
|
|
}
|
|
|
|
func parseToolEnvelopeLossless(jsonStr string) map[string]any {
|
|
var out map[string]any
|
|
decoder := json.NewDecoder(strings.NewReader(jsonStr))
|
|
decoder.UseNumber()
|
|
if err := decoder.Decode(&out); err != nil {
|
|
return map[string]any{"_raw": jsonStr}
|
|
}
|
|
if err := decoder.Decode(&struct{}{}); err != io.EOF {
|
|
return map[string]any{"_raw": jsonStr}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (c *ToolBackedComponent) Stream(_ context.Context, _ *gorm.DB, _ map[string]any) (<-chan map[string]any, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
var toolComponentRegistrations = []struct {
|
|
componentName string
|
|
toolName string
|
|
}{
|
|
{componentName: "GitHub", toolName: "github"},
|
|
{componentName: "BGPT", toolName: "bgpt"},
|
|
{componentName: "ArXiv", toolName: "arxiv"},
|
|
{componentName: "DuckDuckGo", toolName: "duckduckgo"},
|
|
{componentName: "Email", toolName: "email"},
|
|
{componentName: "ExeSQL", toolName: "execute_sql"},
|
|
{componentName: "Google", toolName: "google"},
|
|
{componentName: "GoogleScholar", toolName: "google_scholar"},
|
|
{componentName: "KeenableSearch", toolName: "keenable"},
|
|
{componentName: "PubMed", toolName: "pubmed"},
|
|
{componentName: "QueritContents", toolName: "querit_contents"},
|
|
{componentName: "QueritSearch", toolName: "querit_search"},
|
|
{componentName: "SearXNG", toolName: "searxng"},
|
|
{componentName: "TavilySearch", toolName: "tavily"},
|
|
{componentName: "TavilyExtract", toolName: "tavily_extract"},
|
|
{componentName: "WenCai", toolName: "wencai"},
|
|
{componentName: "Wikipedia", toolName: "wikipedia"},
|
|
{componentName: "YahooFinance", toolName: "yahoo_finance"},
|
|
}
|
|
|
|
func init() {
|
|
for _, registration := range toolComponentRegistrations {
|
|
Register(
|
|
registration.componentName,
|
|
newToolComponentFactory(registration.componentName, registration.toolName),
|
|
)
|
|
}
|
|
}
|