mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-14 00:48:26 +08:00
## Summary - Merge upstream main and retain PubMed component support. - Preserve newly registered tool components and update registry verification. ## Tests - `bash build.sh --test ./internal/agent/component/...` - `bash build.sh --test ./internal/agent/tool/...` <img width="1817" height="972" alt="image" src="https://github.com/user-attachments/assets/9fcb9448-9e26-41b9-940c-a9bfde9835e9" /> --------- Co-authored-by: Jin Hai <haijin.chn@gmail.com>
151 lines
4.7 KiB
Go
151 lines
4.7 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"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
einotool "github.com/cloudwego/eino/components/tool"
|
|
)
|
|
|
|
type fakePubMedInvoker struct {
|
|
args map[string]any
|
|
err error
|
|
out string
|
|
}
|
|
|
|
func (f *fakePubMedInvoker) InvokableRun(_ context.Context, argsJSON string, _ ...einotool.Option) (string, error) {
|
|
if err := json.Unmarshal([]byte(argsJSON), &f.args); err != nil {
|
|
return "", err
|
|
}
|
|
if f.out != "" || f.err != nil {
|
|
return f.out, f.err
|
|
}
|
|
return `{"results":[{"title":"Deep learning for retrieval augmented generation","url":"https://pubmed.ncbi.nlm.nih.gov/12345678","content":"Title: Deep learning for retrieval augmented generation\nAuthors: Furqan Khan, Jane Smith\nJournal: Nature Machine Intelligence\nVolume: 10\nIssue: 2\nPages: 101-110\nDOI: 10.1000/example.doi\nAbstract: A short abstract."}]}`, nil
|
|
}
|
|
|
|
func TestPubMed_RegisteredFactory(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c, err := New("PubMed", map[string]any{
|
|
"top_n": 8,
|
|
"email": "node@example.com",
|
|
"outputs": map[string]any{"formalized_content": map[string]any{}},
|
|
"setups": map[string]any{"query": "configured query"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("New(PubMed) errored: %v", err)
|
|
}
|
|
if got := c.Name(); got != "PubMed" {
|
|
t.Fatalf("Name() = %q, want PubMed", got)
|
|
}
|
|
formGetter, ok := c.(interface{ GetInputForm() map[string]any })
|
|
if !ok {
|
|
t.Fatal("PubMed component does not expose GetInputForm")
|
|
}
|
|
form := formGetter.GetInputForm()
|
|
if len(form) != 1 {
|
|
t.Fatalf("GetInputForm size = %d, want 1", len(form))
|
|
}
|
|
query, ok := form["query"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("GetInputForm()[query] has type %T, want map", form["query"])
|
|
}
|
|
if query["type"] != "line" {
|
|
t.Fatalf("GetInputForm()[query][type] = %v, want line", query["type"])
|
|
}
|
|
if _, ok := c.Outputs()["formalized_content"]; !ok {
|
|
t.Fatal("Outputs() missing formalized_content")
|
|
}
|
|
if _, ok := c.Outputs()["json"]; !ok {
|
|
t.Fatal("Outputs() missing json")
|
|
}
|
|
}
|
|
|
|
func TestPubMed_InvokeOnlyPassesQuery(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
fake := &fakePubMedInvoker{}
|
|
c := newPubMedComponentWithInvoker(fake)
|
|
out, err := c.Invoke(context.Background(), map[string]any{
|
|
"query": " retrieval augmented generation ",
|
|
"top_n": float64(8),
|
|
"email": "ignored@example.com",
|
|
"unused": true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Invoke errored: %v", err)
|
|
}
|
|
if got := fake.args["query"]; got != "retrieval augmented generation" {
|
|
t.Fatalf("query arg = %v, want trimmed query", got)
|
|
}
|
|
if len(fake.args) != 1 {
|
|
t.Fatalf("runtime args = %#v, want only query", fake.args)
|
|
}
|
|
formalized, _ := out["formalized_content"].(string)
|
|
for _, want := range []string{"ID: 0", "Title: Deep learning for retrieval augmented generation", "URL: https://pubmed.ncbi.nlm.nih.gov/12345678", "Content:", "Abstract: A short abstract."} {
|
|
if !strings.Contains(formalized, want) {
|
|
t.Fatalf("formalized_content missing %q: %s", want, formalized)
|
|
}
|
|
}
|
|
results, ok := out["json"].([]any)
|
|
if !ok || len(results) != 1 {
|
|
t.Fatalf("json output = %#v, want one result", out["json"])
|
|
}
|
|
}
|
|
|
|
func TestPubMed_InvokeEmptyQueryReturnsEmptyPayload(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := newPubMedComponentWithInvoker(&fakePubMedInvoker{})
|
|
out, err := c.Invoke(context.Background(), map[string]any{"query": " "})
|
|
if err != nil {
|
|
t.Fatalf("Invoke errored: %v", err)
|
|
}
|
|
if got := out["formalized_content"]; got != "" {
|
|
t.Fatalf("formalized_content = %v, want empty string", got)
|
|
}
|
|
results, ok := out["json"].([]any)
|
|
if !ok || len(results) != 0 {
|
|
t.Fatalf("json output = %#v, want empty []any", out["json"])
|
|
}
|
|
}
|
|
|
|
func TestPubMed_InvokeSurfacesToolErrorEnvelope(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
fake := &fakePubMedInvoker{
|
|
out: `{"results":[],"_ERROR":"upstream down"}`,
|
|
err: errors.New("boom"),
|
|
}
|
|
c := newPubMedComponentWithInvoker(fake)
|
|
out, err := c.Invoke(context.Background(), map[string]any{"query": "pubmed"})
|
|
if err != nil {
|
|
t.Fatalf("Invoke errored: %v", err)
|
|
}
|
|
if got := out["_ERROR"]; got != "upstream down" {
|
|
t.Fatalf("_ERROR = %v, want upstream down", got)
|
|
}
|
|
if got := out["formalized_content"]; got != "" {
|
|
t.Fatalf("formalized_content = %v, want empty string", got)
|
|
}
|
|
}
|