mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-06-29 23:41:12 +08:00
Ports the agent canvas subsystem from Python to Go.
## What's included
### Canvas Engine (Phase 0/1)
- State engine, scheduler, variable resolver, Redis checkpoint store,
cancel protocol
- **209 tests** across canvas / component / io packages
### 22 Components (P0–P4)
| Tier | Components |
|---|---|
| P0 T1+T2+T3 | LLM, Agent, ExitLoop, Switch, Categorize, Begin,
Message, Invoke |
| P1 T3 | VariableAggregator, VariableAssigner, StringTransform,
ListOperations, DataOperations |
| P2 T3 | Iteration, IterationItem, Loop, LoopItem |
| P3 T3 | UserFillUp, Fillup |
| P4 T5 | Browser, ExcelProcessor, DocsGenerator |
### DSL v2 Schema (Phase 2.5)
- Typed v2 in-memory model with v1-to-v2 auto-detect converter
- v1 legacy field stripping per plan §2.11.7
### HTTP Endpoints & Bug Fixes (Plans PR1–PR3)
- **DELETE SQL bug fix**: gorm v2 `Where("id = ?", id).Delete(...)`
pattern
- **CreateAgent validation**: title/DSL required, duplicate check, 103
envelope
- **13 new endpoints**: templates, prompts, tags, sessions CRUD,
chat/completions (SSE + non-stream stubs), rerun, test_db_connection,
logs, webhook/logs
- **756 Go unit tests** (745 → 756, +18)
- **17 → 0 Python integration test failures** (test_agents.py +
test_session_management/)
### Tools
21 eino tools: HTTPHelper, search tools, financial/data tools, mandatory
stubs
### Infrastructure
OTel observability, NATS message queue, DeepDoc gRPC client, SSRF
guards, IDOR mitigation
140 lines
4.8 KiB
Go
140 lines
4.8 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 — Fillup component (T3, plan §2.11.3 row 3).
|
|
//
|
|
// Fillup is the lighter sibling of UserFillUp: it does NOT render a
|
|
// `tips` template. It only passes the form's input map through to its
|
|
// outputs (file inputs are still stubbed because FileService integration
|
|
// is Phase 5 per plan §2.11.10). The Python codebase has no separate
|
|
// Fillup class — per plan §2.11.3 row 3, this component is the Go
|
|
// port's normalized, tips-less variant of UserFillUp so the DSL can
|
|
// spawn it without paying for the unused template path.
|
|
package component
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"ragflow/internal/agent/runtime"
|
|
)
|
|
|
|
const componentNameFillup = "Fillup"
|
|
|
|
// fillupParam is the per-instance configuration for Fillup. It is
|
|
// strictly a subset of userFillUpParam — `enable_tips` and `tips` are
|
|
// intentionally absent because Fillup never renders tips.
|
|
type fillupParam struct {
|
|
LayoutRecognize string `json:"layout_recognize"`
|
|
}
|
|
|
|
// Update copies a fresh params map into the receiver. Layout_recognize
|
|
// is the only field; unknown keys are silently ignored to keep the
|
|
// Update contract forgiving (mirrors the existing P0/P1 components).
|
|
func (p *fillupParam) Update(conf map[string]any) error {
|
|
if conf == nil {
|
|
conf = map[string]any{}
|
|
}
|
|
if v, ok := stringFrom(conf, "layout_recognize"); ok {
|
|
p.LayoutRecognize = v
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Check performs parameter validation. Fillup has no required fields.
|
|
func (p *fillupParam) Check() error { return nil }
|
|
|
|
// AsDict returns the param as a plain map for serialization / debug.
|
|
func (p *fillupParam) AsDict() map[string]any {
|
|
return map[string]any{
|
|
"layout_recognize": p.LayoutRecognize,
|
|
}
|
|
}
|
|
|
|
// FillupComponent is the canvas tips-less form-filling node.
|
|
type FillupComponent struct {
|
|
name string
|
|
param fillupParam
|
|
}
|
|
|
|
// NewFillupComponent builds a FillupComponent from a DSL params map.
|
|
func NewFillupComponent(p fillupParam) *FillupComponent {
|
|
return &FillupComponent{name: componentNameFillup, param: p}
|
|
}
|
|
|
|
// Name returns the registered component name.
|
|
func (f *FillupComponent) Name() string { return f.name }
|
|
|
|
// Invoke emits one output per form field, with file-typed fields
|
|
// stubbed as "<file:key>". No "tips" key is added — that is the
|
|
// defining difference from UserFillUp.
|
|
func (f *FillupComponent) Invoke(ctx context.Context, inputs map[string]any) (map[string]any, error) {
|
|
// State is required by the engine contract; we don't read from it
|
|
// here, but we still extract it to fail loudly if the engine forgot
|
|
// to wire it (consistent with UserFillUp's behavior).
|
|
if _, _, err := runtime.GetStateFromContext[*runtime.CanvasState](ctx); err != nil {
|
|
return nil, fmt.Errorf("Fillup: %w", err)
|
|
}
|
|
|
|
fields, _ := formFields(inputs)
|
|
out := make(map[string]any, len(fields))
|
|
for k, v := range fields {
|
|
out[k] = resolveFieldValue(k, v)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// Stream is the synchronous facade over Invoke: a single payload, then
|
|
// close. Mirrors the pattern used by UserFillUp and the P0 components.
|
|
func (f *FillupComponent) Stream(ctx context.Context, inputs map[string]any) (<-chan map[string]any, error) {
|
|
out, err := f.Invoke(ctx, inputs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ch := make(chan map[string]any, 1)
|
|
ch <- out
|
|
close(ch)
|
|
return ch, nil
|
|
}
|
|
|
|
// Inputs returns parameter metadata. There is no "tips" surface — the
|
|
// DSL editor should not show one for Fillup nodes.
|
|
func (f *FillupComponent) Inputs() map[string]string {
|
|
return map[string]string{
|
|
"inputs": "Map of form-field name → {value, type, optional?}.",
|
|
"layout_recognize": "Layout recognizer hint used for file inputs (deferred to Phase 5).",
|
|
}
|
|
}
|
|
|
|
// Outputs returns one entry per form field. The "*" wildcard mirrors
|
|
// the UserFillUp contract; no "tips" key is ever emitted.
|
|
func (f *FillupComponent) Outputs() map[string]string {
|
|
return map[string]string{
|
|
"*": "One output per form-field key in inputs (file inputs are stubbed as \"<file:key>\").",
|
|
}
|
|
}
|
|
|
|
// init registers Fillup with the orchestrator-owned registry.
|
|
func init() {
|
|
Register(componentNameFillup, func(params map[string]any) (Component, error) {
|
|
var p fillupParam
|
|
if err := p.Update(params); err != nil {
|
|
return nil, err
|
|
}
|
|
return NewFillupComponent(p), nil
|
|
})
|
|
}
|