fix(go-agent): align agent debug input form with Python (#16654)

## Summary

- derive Go Agent debug input forms from prompt variable references
instead of Agent meta fields
- seed `sys.*` debug params into `CanvasState.Sys` so single-component
debug resolves prompt variables like Python
- restore Agent test-run parity for form rendering and debug execution

## Tests

- `go test ./internal/agent/component -run
'TestAgent_(GetInputForm_UsesPromptReferences|GetInputForm_DeduplicatesPromptReferences|Meta_DefaultsToEmpty|Reset_NoTools)$'`
- `go test ./internal/handler -run
'Test(DebugComponent_SeedsSysInputsIntoCanvasState|DebugComponent_HappyPath_Begin|GetComponentInputForm_HappyPath)$'`

AFTER:
<img width="669" height="456" alt="image"
src="https://github.com/user-attachments/assets/4fd86559-aafc-4027-91ae-6e666137ee1b"
/>
This commit is contained in:
Hz_
2026-07-06 13:29:10 +08:00
committed by GitHub
parent c9f064d5fd
commit c4166a91e0
4 changed files with 203 additions and 31 deletions

View File

@@ -32,6 +32,7 @@ import (
"context"
"errors"
"fmt"
"strings"
"github.com/gin-gonic/gin"
@@ -211,6 +212,11 @@ func (h *AgentHandler) DebugComponent(c *gin.Context) {
// decorator for the debug endpoint.
debugState := canvas.NewCanvasState("debug-"+componentID, "debug-task")
debugState.Sys["tenant_id"] = user.ID
for key, value := range inputs {
if strings.HasPrefix(key, "sys.") && key != "sys.tenant_id" {
debugState.Sys[strings.TrimPrefix(key, "sys.")] = value
}
}
invokeCtx := runtime.WithState(c.Request.Context(), debugState)
outputs, err := comp.Invoke(invokeCtx, inputs)

View File

@@ -17,6 +17,7 @@
package handler
import (
"context"
"encoding/json"
"errors"
"net/http/httptest"
@@ -25,6 +26,7 @@ import (
"github.com/gin-gonic/gin"
agentruntime "ragflow/internal/agent/runtime"
_ "ragflow/internal/agent/component" // registers the production factory
"ragflow/internal/dao"
"ragflow/internal/entity"
@@ -210,6 +212,115 @@ func TestDebugComponent_HappyPath_Begin(t *testing.T) {
}
}
type sysEchoComponent struct{}
func (s *sysEchoComponent) Invoke(ctx context.Context, _ map[string]any) (map[string]any, error) {
state, _, err := agentruntime.GetStateFromContext[*agentruntime.CanvasState](ctx)
if err != nil {
return nil, err
}
return map[string]any{
"query": state.Sys["query"],
"tenant_id": state.Sys["tenant_id"],
}, nil
}
func TestDebugComponent_SeedsSysInputsIntoCanvasState(t *testing.T) {
origFactory := agentruntime.DefaultFactory()
agentruntime.SetDefaultFactory(func(name string, _ map[string]any) (agentruntime.Component, error) {
if name != "Probe" {
return nil, errors.New("unexpected component name: " + name)
}
return &sysEchoComponent{}, nil
})
t.Cleanup(func() { agentruntime.SetDefaultFactory(origFactory) })
cv := &entity.UserCanvas{
ID: "c1",
DSL: map[string]any{
"components": map[string]any{
"probe": map[string]any{
"obj": map[string]any{
"component_name": "Probe",
"params": map[string]any{},
},
},
},
},
}
h := &AgentHandler{loader: &fakeCanvasLoader{canvas: cv}}
body := `{"params":{"sys.query":{"value":"hello sys"}}}`
c, w := componentCtx(t, "POST", "/api/v1/agents/c1/components/probe/debug", body)
c.Params = gin.Params{
{Key: "canvas_id", Value: "c1"},
{Key: "component_id", Value: "probe"},
}
h.DebugComponent(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.Data["query"] != "hello sys" {
t.Fatalf("data.query = %v, want 'hello sys'", env.Data["query"])
}
}
func TestDebugComponent_RejectsSysTenantIDOverride(t *testing.T) {
origFactory := agentruntime.DefaultFactory()
agentruntime.SetDefaultFactory(func(name string, _ map[string]any) (agentruntime.Component, error) {
if name != "Probe" {
return nil, errors.New("unexpected component name: " + name)
}
return &sysEchoComponent{}, nil
})
t.Cleanup(func() { agentruntime.SetDefaultFactory(origFactory) })
cv := &entity.UserCanvas{
ID: "c1",
DSL: map[string]any{
"components": map[string]any{
"probe": map[string]any{
"obj": map[string]any{
"component_name": "Probe",
"params": map[string]any{},
},
},
},
},
}
h := &AgentHandler{loader: &fakeCanvasLoader{canvas: cv}}
body := `{"params":{"sys.query":{"value":"hello sys"},"sys.tenant_id":{"value":"attacker-tenant"}}}`
c, w := componentCtx(t, "POST", "/api/v1/agents/c1/components/probe/debug", body)
c.Params = gin.Params{
{Key: "canvas_id", Value: "c1"},
{Key: "component_id", Value: "probe"},
}
h.DebugComponent(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.Data["tenant_id"] != "u-1" {
t.Fatalf("data.tenant_id = %v, want u-1", env.Data["tenant_id"])
}
}
func TestDebugComponent_InvalidParams_MissingField(t *testing.T) {
cv := beginCanvas()
h := &AgentHandler{loader: &fakeCanvasLoader{canvas: cv}}