mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-16 20:57:21 +08:00
## Summary
This PR consolidates Eino-backed Agent tools behind the shared
ToolBackedComponent implementation and aligns their Canvas
configuration,
runtime inputs, output conversion, validation, and registration.
## What changed
- Migrated these Canvas components to the unified tool-backed path:
- Tavily Search and Extract
- Execute SQL
- Google
- Yahoo Finance
- Email
- DuckDuckGo
- Wikipedia
- Google Scholar
- ArXiv
- PubMed
- BGPT
- GitHub
- WenCai
- SearXNG
- Keenable Search
- Removed superseded component wrappers and their duplicate tests.
- Added dedicated registry builders for node-level configuration and
validation.
- Kept model-emitted runtime inputs separate from Canvas node
configuration.
- Moved Email defaults, template resolution, recipient parsing, SMTP
execution, and output conversion into the owning tool.
- Added complete ToolComponent specifications for Canvas inputs,
outputs, and input forms.
- Preserved raw upstream fields where downstream workflows may depend on
them.
- Added workflow registration coverage for all migrated component names.
- Kept HTTP Request, Docs Generator, and Browser as standalone
components because they are not Eino-backed tools.
## Testing
Passed:
bash build.sh --test ./internal/agent/tool/...
bash build.sh --test ./internal/agent/component/...
bash build.sh --test ./internal/agent/runtime/...
<img width="2057" height="1111" alt="image"
src="https://github.com/user-attachments/assets/c728d7a3-9d15-4c5c-b0eb-6b77ad0e41ac"
/>
<img width="2057" height="1111" alt="image"
src="https://github.com/user-attachments/assets/ef13119a-ef92-4b43-9061-eee9511bf492"
/>
<img width="2057" height="1111" alt="image"
src="https://github.com/user-attachments/assets/f50f2c30-eab0-463c-b0b3-0a02523219f1"
/>
132 lines
4.3 KiB
Go
132 lines
4.3 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"
|
|
"strings"
|
|
|
|
"ragflow/internal/agent/runtime"
|
|
agenttool "ragflow/internal/agent/tool"
|
|
)
|
|
|
|
// 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, 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))
|
|
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 (c *ToolBackedComponent) Stream(_ context.Context, _ 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: "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),
|
|
)
|
|
}
|
|
}
|