// // 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 // // gen-component-parity is a small developer tool that walks the live Go // component and tool registries and emits a markdown parity matrix for // docs/component-parity.md. It supersedes the manually-curated table // previously maintained in that file. // // Usage: // // go run ./tools/gen-component-parity > docs/component-parity.generated.md // go run ./tools/gen-component-parity -format=tsv // // The tool imports the production packages (which trigger init() // registration of every component / tool factory) and reflects on each // registered entry's Name() / Inputs() / Outputs() (for components) or // Info() (for tools). It then writes a markdown table to stdout. // // Limitations: // // - The script does NOT evaluate Python parity. Every entry is marked // "🟡 parity check via docs/component-parity.md" — the human-curated // table is the source of truth for parity; this script only emits // the structural inventory. // - Component factories that require non-empty params to construct // (e.g. Retrieval, ExeSQL) are skipped with a "⏭" annotation; their // parity is captured in the hand-written matrix. // - The script does not import cmd/server_main.go to avoid pulling // in the full boot path (Redis, MySQL, etc.). package main import ( "context" "flag" "fmt" "sort" "strings" "ragflow/internal/agent/component" "ragflow/internal/agent/tool" ) func main() { format := flag.String("format", "md", "output format: md|tsv") flag.Parse() switch *format { case "md": writeMarkdown() case "tsv": writeTSV() default: fmt.Fprintf(flag.CommandLine.Output(), "unknown format %q (md|tsv)\n", *format) flag.CommandLine.Usage() } } func writeMarkdown() { fmt.Println("# Component & Tool Registry — Auto-generated inventory") fmt.Println() fmt.Println("> Generated by `tools/gen-component-parity`. Do not edit by hand;") fmt.Println("> the human-curated `docs/component-parity.md` is the source of truth for") fmt.Println("> Python parity annotations (✅/🟡/⚠️/❌). This file only emits the") fmt.Println("> structural inventory (registry name, factory, public surface).") fmt.Println() fmt.Println("## Universe A — Canvas DAG components") fmt.Println() fmt.Println("| Name | Source file | Public surface |") fmt.Println("|---|---|---|") for _, name := range component.RegisteredNames() { c, err := component.New(name, map[string]any{}) if err != nil { // Some components (ExeSQL, Retrieval, DocsGenerator, ListOperations, ...) // require non-empty params at construction time. We surface those as // "requires params" rather than "error" so the table reads as // "factory exists, params needed" — the human-curated parity doc // already documents the param surface. fmt.Printf("| %s | `internal/agent/component/*.go` | (requires non-empty params) |\n", name) continue } surface := summariseComponent(c) fmt.Printf("| %s | `internal/agent/component/*.go` | %s |\n", name, surface) } fmt.Println() fmt.Println("## Universe B — eino ReAct tools") fmt.Println() fmt.Println("| Name | Public surface |") fmt.Println("|---|---|") for _, name := range sortedToolNames() { bt, err := tool.BuildByName(name, map[string]any{}) if err != nil { fmt.Printf("| %s | (build error: %s) |\n", name, err.Error()) continue } info, infoErr := bt.Info(context.Background()) surface := "no Info()" if infoErr == nil && info != nil { surface = fmt.Sprintf("name=%q desc=%q", info.Name, truncate(info.Desc, 80)) } fmt.Printf("| %s | %s |\n", name, surface) } } func writeTSV() { fmt.Println("universe\tname\tsurface") for _, name := range component.RegisteredNames() { c, err := component.New(name, map[string]any{}) if err != nil { fmt.Printf("A\t%s\trequires_params\n", name) continue } fmt.Printf("A\t%s\t%s\n", name, summariseComponent(c)) } for _, name := range sortedToolNames() { bt, err := tool.BuildByName(name, map[string]any{}) if err != nil { fmt.Printf("B\t%s\tERROR:%s\n", name, err.Error()) continue } info, infoErr := bt.Info(context.Background()) surface := "no Info()" if infoErr == nil && info != nil { surface = info.Name } fmt.Printf("B\t%s\t%s\n", name, surface) } } func summariseComponent(c component.Component) string { parts := []string{ fmt.Sprintf("Name=%q", c.Name()), } in := c.Inputs() out := c.Outputs() if len(in) > 0 { parts = append(parts, fmt.Sprintf("inputs=%d", len(in))) } if len(out) > 0 { parts = append(parts, fmt.Sprintf("outputs=%d", len(out))) } return strings.Join(parts, " ") } // sortedToolNames mirrors the universe A's RegisteredNames for // deterministic output. The tool package doesn't expose a public // registry iteration helper; we use BuildByName's error to probe the // known list of factory entries. In practice this list is the // hand-maintained keys in tool/registry.go:registry. func sortedToolNames() []string { known := []string{ "akshare", "arxiv", "code_exec", "crawler", "deepl", "duckduckgo", "email", "execute_sql", "exesql", "github", "google", "google_scholar", "jin10", "pubmed", "qweather", "retrieval", "search_my_dataset", "search_my_dateset", "searxng", "tavily", "tushare", "wencai", "web_crawler", "wikipedia", "yahoo_finance", } sort.Strings(known) return known } func truncate(s string, n int) string { if len(s) <= n { return s } return s[:n-3] + "..." }