// // 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 — Begin component (T3, plan §2.11.3 row 1). // // Begin is the DSL entry node. It injects the request's `inputs` into the // shared *CanvasState.Sys namespace and passes the input map through to its // downstream unchanged. File-input handling (FileService.get_files) is // deferred to a later phase per plan §2.7 / Phase 0 note — Phase 2 P0 // handles only the `query` and `user_id` keys. package component import ( "context" "fmt" "maps" "ragflow/internal/agent/runtime" ) // mapsCopy is a thin alias for the stdlib maps.Copy to keep the call // sites uniform with the rest of the package (which uses the same name // in switch.go and message.go). func mapsCopy(dst, src map[string]any) { maps.Copy(dst, src) } const componentNameBegin = "Begin" // BeginComponent is the canvas entry node. The exported fields are // populated by the factory (registered via init) from the DSL params map. // ParamBase surface is intentionally omitted for P0 — Begin is trivial // and needs no validation beyond what the State writes perform. type BeginComponent struct { name string } // NewBeginComponent constructs a Begin component. It accepts the DSL params // map but does not retain it (Begin has no per-instance configuration). func NewBeginComponent(_ map[string]any) (Component, error) { return &BeginComponent{name: componentNameBegin}, nil } // Name returns the registered component name. Used by the registry and // the eino node-name injection in BuildWorkflow. func (b *BeginComponent) Name() string { return b.name } // Invoke writes inputs["query"] and (when present) inputs["user_id"] into // the shared *CanvasState.Sys namespace, then returns the input map as // outputs unchanged. The input map is shallow-copied to avoid aliasing // surprises across concurrent goroutines that share an inputs map. func (b *BeginComponent) Invoke(ctx context.Context, inputs map[string]any) (map[string]any, error) { state, _, err := runtime.GetStateFromContext[*runtime.CanvasState](ctx) if err != nil { return nil, fmt.Errorf("Begin: %w", err) } if state == nil { return nil, fmt.Errorf("Begin: nil canvas state") } // Query: required to drive downstream components. query, _ := inputs["query"].(string) state.Sys["query"] = query // Optional user_id — present in interactive chat flows, absent in // background jobs. Always a string when set; cast failure silently // drops the value (mirrors Python's getattr fallback). if uid, ok := inputs["user_id"].(string); ok && uid != "" { state.Sys["user_id"] = uid } // Passthrough: a shallow copy keeps the caller's map un-aliased. out := make(map[string]any, len(inputs)) mapsCopy(out, inputs) return out, nil } // Stream is a synchronous facade over Invoke for P0. SSE streaming of // Begin output is not meaningful (Begin has no I/O), so the channel // receives a single payload and closes — same shape as Invoke's return. func (b *BeginComponent) Stream(ctx context.Context, inputs map[string]any) (<-chan map[string]any, error) { out, err := b.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. Descriptions are short; the doc // strings live on the struct / method above. func (b *BeginComponent) Inputs() map[string]string { return map[string]string{ "query": "User query string (the chat input).", "user_id": "Optional user/tenant identifier.", "inputs": "Optional free-form inputs map; passthrough only.", } } // Outputs returns the same keys as Inputs (Begin is a passthrough). func (b *BeginComponent) Outputs() map[string]string { return map[string]string{ "query": "Query string (passthrough).", "user_id": "User id, if provided (passthrough).", "inputs": "Raw inputs map (passthrough).", } } func init() { Register(componentNameBegin, NewBeginComponent) }