mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-15 09:28:27 +08:00
## Summary - Resolve configured ExeSQL SQL references before tool invocation. - Align ExeSQL parameters, defaults, JSON tags, and tool schema with Python. - Preserve SQL string literals and restore Canvas output fields. ## Testing - `bash build.sh --test ./internal/agent/tool/...` - `bash build.sh --test ./internal/agent/component/...` - `bash build.sh --test ./internal/agent/runtime/...` <img width="2039" height="1041" alt="image" src="https://github.com/user-attachments/assets/9f4beca7-ca28-4641-adda-0570415fcaa1" />
101 lines
2.9 KiB
Go
101 lines
2.9 KiB
Go
package component
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
einotool "github.com/cloudwego/eino/components/tool"
|
|
|
|
"ragflow/internal/agent/runtime"
|
|
)
|
|
|
|
type exesqlInvokerStub struct {
|
|
arguments string
|
|
result string
|
|
err error
|
|
}
|
|
|
|
func (s *exesqlInvokerStub) InvokableRun(_ context.Context, arguments string, _ ...einotool.Option) (string, error) {
|
|
s.arguments = arguments
|
|
return s.result, s.err
|
|
}
|
|
|
|
func TestExeSQLComponentResolvesConfiguredSQL(t *testing.T) {
|
|
stub := &exesqlInvokerStub{
|
|
result: `{"columns":["id","status"],"rows":[{"id":1,"status":"Completed"}]}`,
|
|
}
|
|
state := runtime.NewCanvasState("run", "task")
|
|
state.SetVar("Agent:SparklyMooseDivide", "content", "SELECT id FROM orders WHERE status = 'Completed'")
|
|
c := &exesqlComponent{
|
|
inner: stub,
|
|
sql: "{Agent:SparklyMooseDivide@content}",
|
|
}
|
|
|
|
out, err := c.Invoke(runtime.WithState(context.Background(), state), map[string]any{
|
|
"content": "this must not be used as SQL",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Invoke: %v", err)
|
|
}
|
|
var arguments map[string]any
|
|
if err := json.Unmarshal([]byte(stub.arguments), &arguments); err != nil {
|
|
t.Fatalf("decode tool arguments: %v", err)
|
|
}
|
|
if got := arguments["sql"]; got != "SELECT id FROM orders WHERE status = 'Completed'" {
|
|
t.Fatalf("sql argument = %#v, want resolved SQL", got)
|
|
}
|
|
if _, exists := arguments["content"]; exists {
|
|
t.Fatalf("tool arguments unexpectedly contain upstream content: %s", stub.arguments)
|
|
}
|
|
formalized, ok := out["formalized_content"].(string)
|
|
if !ok || !strings.Contains(formalized, "Completed") {
|
|
t.Fatalf("formalized_content = %#v, want rendered row", out["formalized_content"])
|
|
}
|
|
jsonResult, ok := out["json"].([]any)
|
|
if !ok || len(jsonResult) != 1 {
|
|
t.Fatalf("json = %#v, want one statement result", out["json"])
|
|
}
|
|
}
|
|
|
|
func TestNewExeSQLComponentKeepsConfiguredSQL(t *testing.T) {
|
|
component, err := newExeSQLComponent(map[string]any{
|
|
"db_type": "mysql",
|
|
"database": "demo",
|
|
"username": "root",
|
|
"host": "db.example.com",
|
|
"port": 3306,
|
|
"password": "secret",
|
|
"max_records": 100,
|
|
"sql": "{Agent:SparklyMooseDivide@content}",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("newExeSQLComponent: %v", err)
|
|
}
|
|
exeSQL, ok := component.(*exesqlComponent)
|
|
if !ok {
|
|
t.Fatalf("component type = %T, want *exesqlComponent", component)
|
|
}
|
|
if exeSQL.sql != "{Agent:SparklyMooseDivide@content}" {
|
|
t.Fatalf("configured SQL = %q", exeSQL.sql)
|
|
}
|
|
}
|
|
|
|
func TestExeSQLComponentPreservesToolErrorAsCanvasOutput(t *testing.T) {
|
|
stub := &exesqlInvokerStub{
|
|
result: `{"_ERROR":"exesql: empty sql"}`,
|
|
err: errors.New("exesql: empty sql"),
|
|
}
|
|
c := &exesqlComponent{inner: stub, sql: ""}
|
|
|
|
out, err := c.Invoke(context.Background(), map[string]any{"sql": ""})
|
|
if err != nil {
|
|
t.Fatalf("Invoke returned a hard error: %v", err)
|
|
}
|
|
if got := out["_ERROR"]; got != "exesql: empty sql" {
|
|
t.Fatalf("_ERROR = %#v, want tool error", got)
|
|
}
|
|
}
|