From 7e0ccee7e2d3bb8dbea49b3cf13ca5cd0e59e943 Mon Sep 17 00:00:00 2001 From: Hz_ Date: Mon, 6 Jul 2026 19:15:09 +0800 Subject: [PATCH] fix(go-agent): missing input form for ExeSQL and Browser agent nodes (#16675) ## Summary - Add Go dynamic input form support for ExeSQL and Browser components. - Align their input form metadata with the Python implementation. - Add regression tests for `/components/:component_id/input-form`. --- internal/agent/component/browser.go | 13 ++ .../agent/component/universe_a_wrappers.go | 9 ++ internal/handler/agent_component_test.go | 118 +++++++++++++++++- 3 files changed, 139 insertions(+), 1 deletion(-) diff --git a/internal/agent/component/browser.go b/internal/agent/component/browser.go index 9f52c2826..c535dc6b7 100644 --- a/internal/agent/component/browser.go +++ b/internal/agent/component/browser.go @@ -334,6 +334,19 @@ func (b *BrowserComponent) Inputs() map[string]string { } } +func (b *BrowserComponent) GetInputForm() map[string]any { + return map[string]any{ + "prompts": map[string]any{ + "type": "text", + "name": "Prompts", + }, + "upload_sources": map[string]any{ + "type": "line", + "name": "Upload sources", + }, + } +} + // Outputs returns the response surface. func (b *BrowserComponent) Outputs() map[string]string { return map[string]string{ diff --git a/internal/agent/component/universe_a_wrappers.go b/internal/agent/component/universe_a_wrappers.go index e7d295bb7..3ec8f7e6a 100644 --- a/internal/agent/component/universe_a_wrappers.go +++ b/internal/agent/component/universe_a_wrappers.go @@ -517,6 +517,15 @@ func (c *exesqlComponent) Inputs() map[string]string { } } +func (c *exesqlComponent) GetInputForm() map[string]any { + return map[string]any{ + "sql": map[string]any{ + "name": "SQL", + "type": "line", + }, + } +} + func (c *exesqlComponent) Outputs() map[string]string { return map[string]string{ "columns": "Result-set column names.", diff --git a/internal/handler/agent_component_test.go b/internal/handler/agent_component_test.go index 725ac1a9d..1b0bd65ab 100644 --- a/internal/handler/agent_component_test.go +++ b/internal/handler/agent_component_test.go @@ -26,8 +26,8 @@ import ( "github.com/gin-gonic/gin" - agentruntime "ragflow/internal/agent/runtime" _ "ragflow/internal/agent/component" // registers the production factory + agentruntime "ragflow/internal/agent/runtime" "ragflow/internal/dao" "ragflow/internal/entity" ) @@ -104,6 +104,122 @@ func TestGetComponentInputForm_HappyPath(t *testing.T) { } } +func TestGetComponentInputForm_ExeSQLDynamicInputForm(t *testing.T) { + cv := &entity.UserCanvas{ + ID: "c1", + DSL: map[string]any{ + "components": map[string]any{ + "exesql:0": map[string]any{ + "obj": map[string]any{ + "component_name": "ExeSQL", + "params": map[string]any{ + "database": "demo", + "username": "root", + "host": "127.0.0.1", + "port": float64(3306), + "password": "secret", + "top_n": float64(50), + }, + }, + }, + }, + }, + } + h := &AgentHandler{loader: &fakeCanvasLoader{canvas: cv}} + + c, w := componentCtx(t, "GET", "/api/v1/agents/c1/components/exesql:0/input-form", "") + c.Params = gin.Params{ + {Key: "canvas_id", Value: "c1"}, + {Key: "component_id", Value: "exesql:0"}, + } + h.GetComponentInputForm(c) + + if w.Code != 200 { + t.Fatalf("status = %d, want 200; body=%s", w.Code, w.Body.String()) + } + var env struct { + Code int `json:"code"` + Data map[string]interface{} `json:"data"` + } + if err := json.Unmarshal(w.Body.Bytes(), &env); err != nil { + t.Fatalf("decode: %v (body=%s)", err, w.Body.String()) + } + if env.Code != 0 { + t.Fatalf("code = %d, want 0; body=%s", env.Code, w.Body.String()) + } + sql, ok := env.Data["sql"].(map[string]interface{}) + if !ok { + t.Fatalf("data.sql = %v, want a map", env.Data["sql"]) + } + if sql["name"] != "SQL" { + t.Errorf("data.sql.name = %v, want SQL", sql["name"]) + } + if sql["type"] != "line" { + t.Errorf("data.sql.type = %v, want line", sql["type"]) + } +} + +func TestGetComponentInputForm_BrowserDynamicInputForm(t *testing.T) { + cv := &entity.UserCanvas{ + ID: "c1", + DSL: map[string]any{ + "components": map[string]any{ + "Browser:FlatWolvesTry": map[string]any{ + "obj": map[string]any{ + "component_name": "Browser", + "params": map[string]any{ + "llm_id": "gpt-4o@OpenAI", + "prompts": "{sys.query}", + }, + }, + }, + }, + }, + } + h := &AgentHandler{loader: &fakeCanvasLoader{canvas: cv}} + + c, w := componentCtx(t, "GET", "/api/v1/agents/c1/components/Browser:FlatWolvesTry/input-form", "") + c.Params = gin.Params{ + {Key: "canvas_id", Value: "c1"}, + {Key: "component_id", Value: "Browser:FlatWolvesTry"}, + } + h.GetComponentInputForm(c) + + if w.Code != 200 { + t.Fatalf("status = %d, want 200; body=%s", w.Code, w.Body.String()) + } + var env struct { + Code int `json:"code"` + Data map[string]interface{} `json:"data"` + } + if err := json.Unmarshal(w.Body.Bytes(), &env); err != nil { + t.Fatalf("decode: %v (body=%s)", err, w.Body.String()) + } + if env.Code != 0 { + t.Fatalf("code = %d, want 0; body=%s", env.Code, w.Body.String()) + } + prompts, ok := env.Data["prompts"].(map[string]interface{}) + if !ok { + t.Fatalf("data.prompts = %v, want a map", env.Data["prompts"]) + } + if prompts["name"] != "Prompts" { + t.Errorf("data.prompts.name = %v, want Prompts", prompts["name"]) + } + if prompts["type"] != "text" { + t.Errorf("data.prompts.type = %v, want text", prompts["type"]) + } + uploadSources, ok := env.Data["upload_sources"].(map[string]interface{}) + if !ok { + t.Fatalf("data.upload_sources = %v, want a map", env.Data["upload_sources"]) + } + if uploadSources["name"] != "Upload sources" { + t.Errorf("data.upload_sources.name = %v, want Upload sources", uploadSources["name"]) + } + if uploadSources["type"] != "line" { + t.Errorf("data.upload_sources.type = %v, want line", uploadSources["type"]) + } +} + func TestGetComponentInputForm_CanvasNotFound(t *testing.T) { h := &AgentHandler{loader: &fakeCanvasLoader{err: dao.ErrUserCanvasNotFound}}