mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-08 00:18:12 +08:00
## Summary
Moves the canvas-debug parser page-cap injection out of the `task`
orchestrator and into a **debug-agnostic** `pipeline` helper, so
`PipelineExecutor` keeps only the orchestration skeleton (resolving one
of the P1 review findings: the executor was overloaded with
DSL/parser-param assembly).
### Changes
- **`pipeline/parser_page_cap.go`** (new):
- `BuildParserPageCapOverride(parserConfig, dsl, docType, capPages int,
parserComponentName string, familyOf)` — injects the
`ParserConfig[cpnID][family]["pages"]` cap through the same
`override_params` channel production uses. The cap value and family
resolution are injected by the caller, so the function carries no debug
semantics and is reusable for any page-cap scenario.
- `ExtractParserCpnID(dsl, parserComponentName)` — shared Parser cpnID
discovery from (optionally enveloped) DSL.
- `UnwrapCanvasDSL(raw []byte)` — exported single source of truth for
stripping the `{"dsl": {...}}` canvas envelope.
- `pipeline` does **not** import `component` (no reverse dependency);
callers inject `component.ComponentNameParser` /
`component.ParserFileFamily`.
- **`task/pipeline_executor.go`**: removed `injectDebugPageCap` (the
`debugPageCapPages = 2` constant stays in the task package). The debug
branch now calls `pipeline.BuildParserPageCapOverride(...)`.
- **`task/pipeline_executor.go` `warnUnknownComponentParams`**: fixed a
production no-op bug — it passed the enveloped DSL straight to
`ExtractAllComponentParams`, which silently errored and disabled the
unknown-cpnID guard. It now unwraps the envelope first.
- **`task/debug_result_dsl.go`**: reuses `pipeline.UnwrapCanvasDSL`
instead of a third inline envelope-unwrap copy.
### Behavior
No external debug-preview behavior changes. The three original
invariants are preserved exactly:
1. explicit `pages` caps under `cpnID+family` are respected (not
overwritten),
2. an empty family (unknown docType) is a no-op,
3. the injected shape is `[]any{[]any{1, capPages}}` (the
`[]any`-of-`[]any` form `NormalizePDFPages` requires).
## Test plan
- New `pipeline/parser_page_cap_test.go`: `BuildParserPageCapOverride`
(inject / respect-existing / unknown-family no-op / no-Parser no-op),
`ExtractParserCpnID` (enveloped + raw), `UnwrapCanvasDSL`.
- `task/debug_test.go`: `TestInjectDebugPageCap` migrated to the new
helper; new
`TestWarnUnknownComponentParamsDetectsUnknownCPNFromEnvelope` captures
the warning via `zaptest/observer` to prove the envelope no-op bug is
fixed.
- Both `internal/ingestion/pipeline` and `internal/ingestion/task` pass
`build.sh --test` (unit tier).
## Notes
- `TOKEN_CHUNKER_HANDOFF.md` is an unrelated untracked file and was
deliberately **not** included in this PR.
110 lines
3.5 KiB
Go
110 lines
3.5 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
)
|
|
|
|
// UnwrapCanvasDSL decodes a raw pipeline DSL and strips the optional canvas
|
|
// envelope {"dsl": {...}}, returning the inner components-carrying map. A raw
|
|
// (non-enveloped) DSL is returned unchanged. It is the []byte entry point used
|
|
// by helpers that need the inner DSL (ExtractParserCpnID,
|
|
// BuildParserPageCapOverride) so envelope-handling lives in exactly one place.
|
|
func UnwrapCanvasDSL(raw []byte) (map[string]any, error) {
|
|
var top map[string]any
|
|
if err := json.Unmarshal(raw, &top); err != nil {
|
|
return nil, fmt.Errorf("UnwrapCanvasDSL: decode: %w", err)
|
|
}
|
|
if top == nil {
|
|
return nil, errNilDSL
|
|
}
|
|
if env, ok := top["dsl"].(map[string]any); ok && len(env) > 0 {
|
|
return env, nil
|
|
}
|
|
return top, nil
|
|
}
|
|
|
|
// ExtractParserCpnID returns the cpnID of the first component whose
|
|
// component_name equals parserComponentName, or "" when no such component
|
|
// exists or the DSL cannot be read. dsl may be enveloped; it is unwrapped
|
|
// first.
|
|
func ExtractParserCpnID(dsl []byte, parserComponentName string) string {
|
|
inner, err := UnwrapCanvasDSL(dsl)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
innerJSON, err := json.Marshal(inner)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
schemas, err := ExtractAllComponentParams(innerJSON)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
for _, s := range schemas {
|
|
if s.ComponentName == parserComponentName {
|
|
return s.CpnID
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// BuildParserPageCapOverride returns parserConfig with a page cap injected for
|
|
// the Parser component, keyed by its cpnID and the document's filetype family.
|
|
//
|
|
// It is debug-agnostic: capPages and familyOf are supplied by the caller, so
|
|
// the function carries no debug-specific semantics and is reusable for any
|
|
// page-cap scenario. docType is the uploaded file extension (e.g. "pdf");
|
|
// familyOf maps it to a parser setup family (callers pass
|
|
// component.ParserFileFamily); parserComponentName is the Parser component
|
|
// name (callers pass component.ComponentNameParser) — kept as a parameter so
|
|
// the pipeline package does not import component.
|
|
//
|
|
// When no Parser component is found or the family is empty, the call is a
|
|
// no-op and parserConfig is returned unchanged. An explicit "pages" cap
|
|
// already present under cpnID+family is respected and left untouched — the
|
|
// cap is only a fallback. The injected shape is []any{[]any{1, capPages}}: a
|
|
// JSON-decoded list of 1-indexed inclusive ranges, the exact form the
|
|
// deepdoc/pdf parser's NormalizePDFPages consumes via
|
|
// ParserConfig[cpnID][family]["pages"].
|
|
func BuildParserPageCapOverride(
|
|
parserConfig map[string]any,
|
|
dsl []byte,
|
|
docType string,
|
|
capPages int,
|
|
parserComponentName string,
|
|
familyOf func(string) string,
|
|
) map[string]any {
|
|
if parserConfig == nil {
|
|
parserConfig = map[string]any{}
|
|
}
|
|
parserCpnID := ExtractParserCpnID(dsl, parserComponentName)
|
|
if parserCpnID == "" {
|
|
return parserConfig
|
|
}
|
|
family := familyOf(docType)
|
|
if family == "" {
|
|
return parserConfig
|
|
}
|
|
// Respect an explicit page cap already present under cpnID + family.
|
|
if cpnEntry, ok := parserConfig[parserCpnID].(map[string]any); ok {
|
|
if famEntry, ok := cpnEntry[family].(map[string]any); ok {
|
|
if _, has := famEntry["pages"]; has {
|
|
return parserConfig
|
|
}
|
|
}
|
|
}
|
|
cpnEntry, ok := parserConfig[parserCpnID].(map[string]any)
|
|
if !ok {
|
|
cpnEntry = map[string]any{}
|
|
parserConfig[parserCpnID] = cpnEntry
|
|
}
|
|
famEntry, ok := cpnEntry[family].(map[string]any)
|
|
if !ok {
|
|
famEntry = map[string]any{}
|
|
cpnEntry[family] = famEntry
|
|
}
|
|
famEntry["pages"] = []any{[]any{1, capPages}}
|
|
return parserConfig
|
|
}
|