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`.
This commit is contained in:
Hz_
2026-07-06 19:15:09 +08:00
committed by GitHub
parent b2e82a42d6
commit 7e0ccee7e2
3 changed files with 139 additions and 1 deletions

View File

@@ -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{

View File

@@ -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.",

View File

@@ -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}}