Skip to content

Commit dfc1c45

Browse files
authored
Merge pull request #30 from hallelx2/feat/minimal-ingest-mode
feat(ingest): minimal mode — parse→persist→ready, skip LLM enrichment + tables
2 parents 8d292cb + 0f9a2cf commit dfc1c45

10 files changed

Lines changed: 642 additions & 14 deletions

File tree

cmd/engine/main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ func run() error {
174174
LLM: llmClient,
175175
Parsers: ingest.RegistryFromTableOpts(tableOptsFromConfig(cfg.Ingest.Tables)),
176176
Logger: logger,
177+
Mode: cfg.Ingest.Mode,
177178
HyDEEnabled: cfg.Ingest.HyDE.Enabled,
178179
HyDEModel: cfg.Ingest.HyDE.Model,
179180
HyDENumQuestions: cfg.Ingest.HyDE.NumQuestions,
@@ -184,7 +185,9 @@ func run() error {
184185
SummaryAxesMaxNumbers: cfg.Ingest.SummaryAxes.MaxNumbers,
185186
GlobalLLMConcurrency: cfg.Ingest.GlobalLLMConcurrency,
186187
})
187-
if cfg.Ingest.Tables.Enabled {
188+
if cfg.Ingest.Mode == ingest.ModeMinimal {
189+
logger.Info("ingest: MINIMAL mode — parse→persist→ready; skipping summarize/HyDE/multi-axis/TOC + table extraction")
190+
} else if cfg.Ingest.Tables.Enabled {
188191
logger.Info("ingest: pdf table extraction enabled",
189192
"vertical_strategy", cfg.Ingest.Tables.VerticalStrategy,
190193
"horizontal_strategy", cfg.Ingest.Tables.HorizontalStrategy,

cmd/server/main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ func run() error {
200200
LLM: llmClient,
201201
Parsers: ingest.RegistryFromTableOpts(tableOptsFromConfig(cfg.Engine.Ingest.Tables)),
202202
Logger: logger,
203+
Mode: cfg.Engine.Ingest.Mode,
203204
HyDEEnabled: cfg.Engine.Ingest.HyDE.Enabled,
204205
HyDEModel: cfg.Engine.Ingest.HyDE.Model,
205206
HyDENumQuestions: cfg.Engine.Ingest.HyDE.NumQuestions,
@@ -214,7 +215,9 @@ func run() error {
214215
TOCCheckPages: cfg.Engine.Ingest.TOC.TOCCheckPages,
215216
GlobalLLMConcurrency: cfg.Engine.Ingest.GlobalLLMConcurrency,
216217
})
217-
if cfg.Engine.Ingest.Tables.Enabled {
218+
if cfg.Engine.Ingest.Mode == ingest.ModeMinimal {
219+
logger.Info("ingest: MINIMAL mode — parse→persist→ready; skipping summarize/HyDE/multi-axis/TOC + table extraction")
220+
} else if cfg.Engine.Ingest.Tables.Enabled {
218221
logger.Info("ingest: pdf table extraction enabled",
219222
"vertical_strategy", cfg.Engine.Ingest.Tables.VerticalStrategy,
220223
"horizontal_strategy", cfg.Engine.Ingest.Tables.HorizontalStrategy,

config.example.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,10 +294,37 @@ retrieval:
294294
model: ""
295295

296296
ingest:
297+
# Ingest mode — how much work the pipeline does before a document is
298+
# marked `ready` (queryable).
299+
#
300+
# full (default) parse -> build tree -> persist -> summarize ->
301+
# HyDE -> multi-axis summaries -> TOC build. Maximises
302+
# retrieval quality but costs ~1,000-3,000 LLM calls plus a
303+
# pdftable table-finding pass on a large filing — minutes of
304+
# wall time for a 90-page 10-K.
305+
#
306+
# minimal parse -> build tree -> persist -> ready. Skips ALL
307+
# per-section LLM enrichment (summarize, HyDE, multi-axis,
308+
# TOC build) AND the pdftable table-extraction pass, so a
309+
# document becomes queryable in ~parse-speed (seconds).
310+
# The page-based strategy (/v1/answer/pageindex) needs none
311+
# of the skipped work: it navigates a TOC synthesised from
312+
# the section tree (documents.toc_tree is left NULL) and
313+
# reads raw section/page text at query time — and that raw
314+
# page text still contains the tables' text, so dropping
315+
# table *sections* loses nothing for it. The
316+
# summary-dependent strategies (chunked-tree, agentic)
317+
# degrade to titles + raw content with no summaries.
318+
#
319+
# Override per-process with VLE_INGEST_MODE; on the deployed
320+
# vectorless-server use VLS_INGEST_MODE=minimal (no secret edit needed).
321+
mode: "full"
322+
297323
# The summarize and HyDE stages run concurrently. This caps the total
298324
# number of LLM calls in flight across both stages combined, so the
299325
# provider's per-tenant concurrency limit isn't exceeded. 0 disables
300326
# the global cap; default applied by the engine is 12.
327+
# (Ignored when mode: minimal — no LLM stages run.)
301328
global_llm_concurrency: 12
302329

303330
# HyDE candidate-question stage. For each leaf section the pipeline asks

config.server.example.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,21 @@ engine:
9999
include_sibling_breadcrumbs: true
100100

101101
ingest:
102+
# Ingest mode: full (default) | minimal.
103+
# full parse -> persist -> summarize -> HyDE -> multi-axis ->
104+
# TOC build. Maximum retrieval quality; minutes on a large
105+
# filing.
106+
# minimal parse -> persist -> ready. Skips every LLM enrichment
107+
# stage AND table extraction — queryable in seconds. The
108+
# page-based strategy (/v1/answer/pageindex) works on it
109+
# unchanged (synthesised TOC + raw page reads).
110+
# Flip the live service without a secret edit: VLS_INGEST_MODE=minimal.
111+
mode: "full"
112+
102113
# The summarize and HyDE stages run concurrently. This caps the total
103114
# number of LLM calls in flight across both stages combined.
104115
# 0 disables the global cap; default is 12.
116+
# (Ignored when mode: minimal — no LLM stages run.)
105117
global_llm_concurrency: 12
106118

107119
# HyDE candidate-question generation per leaf section. Folded into

internal/config/config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,12 @@ func applyEnvOverrides(c *Config) {
320320
if v := firstEnv("VLS_LLM_DRIVER", "VLE_LLM_DRIVER"); v != "" {
321321
c.Engine.LLM.Driver = v
322322
}
323+
// Ingest mode (full | minimal). Forwarded so the live
324+
// vectorless-server can be flipped to minimal ingest with a single
325+
// env var, no secret/config edit. VLS_-prefixed wins over VLE_.
326+
if v := firstEnv("VLS_INGEST_MODE", "VLE_INGEST_MODE"); v != "" {
327+
c.Engine.Ingest.Mode = v
328+
}
323329
// Anthropic-compatible gateway overrides (e.g. GLM/Zhipu via
324330
// https://api.z.ai/api/anthropic): base URL + model, so the
325331
// anthropic driver can run a non-Anthropic model without a secret

pkg/config/config.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,35 @@ type Config struct {
3333
// IngestConfig configures retrieval-quality boosters that run during
3434
// the ingest pipeline (between summarize and StatusReady).
3535
type IngestConfig struct {
36+
// Mode selects how much work the ingest pipeline does before a
37+
// document is marked ready.
38+
//
39+
// "full" (default) — parse → build tree → persist → summarize
40+
// → HyDE → multi-axis summaries → TOC build.
41+
// Maximises retrieval quality at the cost of
42+
// ~1,000-3,000 LLM calls + a table-extraction
43+
// pass on a large filing (minutes of wall time).
44+
//
45+
// "minimal" — parse → build tree → persist → ready.
46+
// Skips ALL per-section LLM enrichment
47+
// (summarize, HyDE, multi-axis, TOC build)
48+
// AND the pdftable table-finding pass, so a
49+
// document becomes queryable in ~parse-speed
50+
// (seconds). The page-based retrieval strategy
51+
// (/v1/answer/pageindex) needs none of the
52+
// skipped enrichment: it navigates a TOC tree
53+
// (synthesised from the section tree when
54+
// documents.toc_tree is NULL) and reads raw
55+
// section/page text at query time — and the raw
56+
// page text still contains the tables' text, so
57+
// dropping table *sections* loses nothing for
58+
// it. The summary-dependent strategies
59+
// (chunked-tree, agentic) degrade to using
60+
// titles + raw content with no summaries.
61+
//
62+
// Empty defaults to "full". Engine env override: VLE_INGEST_MODE.
63+
Mode string `yaml:"mode"`
64+
3665
HyDE HyDEConfig `yaml:"hyde"`
3766

3867
// Tables configures pdftable's table-finding pass over PDF inputs.
@@ -695,6 +724,7 @@ func Default() Config {
695724
},
696725
},
697726
Ingest: IngestConfig{
727+
Mode: "full",
698728
GlobalLLMConcurrency: 12,
699729
LLMCallTimeoutSeconds: 90,
700730
MaxSections: 400,
@@ -838,6 +868,11 @@ func applyEnvOverrides(c *Config) {
838868
if v := os.Getenv("VLE_RETRIEVAL_AGENTIC_MODEL"); v != "" {
839869
c.Retrieval.Agentic.Model = v
840870
}
871+
// Ingest mode switch (full | minimal). A single env var flips the
872+
// engine into fast/minimal ingest with no secret edit.
873+
if v := os.Getenv("VLE_INGEST_MODE"); v != "" {
874+
c.Ingest.Mode = v
875+
}
841876
// Ingest / HyDE knobs. Booleans accept the usual truthy strings —
842877
// kept narrow so a typo doesn't silently flip the flag.
843878
if v := os.Getenv("VLE_INGEST_HYDE_ENABLED"); v != "" {
@@ -1144,6 +1179,12 @@ func (c Config) Validate() error {
11441179
return fmt.Errorf("server.tls.min_version must be 1.2 or 1.3, got %q", v)
11451180
}
11461181

1182+
switch c.Ingest.Mode {
1183+
case "", "full", "minimal":
1184+
default:
1185+
return fmt.Errorf("ingest.mode must be one of full|minimal, got %q", c.Ingest.Mode)
1186+
}
1187+
11471188
if c.Ingest.HyDE.NumQuestions < 0 {
11481189
return fmt.Errorf("ingest.hyde.num_questions must be >= 0, got %d", c.Ingest.HyDE.NumQuestions)
11491190
}

pkg/config/config_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,52 @@ func TestDefaultValues(t *testing.T) {
8484
}
8585
}
8686

87+
// TestIngestModeDefault locks the default ingest mode to "full" so the
88+
// current full-enrichment behaviour is preserved unless explicitly
89+
// switched.
90+
func TestIngestModeDefault(t *testing.T) {
91+
t.Parallel()
92+
cfg := Default()
93+
if cfg.Ingest.Mode != "full" {
94+
t.Errorf("ingest.mode = %q, want full (default)", cfg.Ingest.Mode)
95+
}
96+
}
97+
98+
// TestIngestModeEnvOverride covers the VLE_INGEST_MODE override — the
99+
// single env var that flips the engine into fast/minimal ingest.
100+
func TestIngestModeEnvOverride(t *testing.T) {
101+
prev := os.Getenv("VLE_INGEST_MODE")
102+
defer os.Setenv("VLE_INGEST_MODE", prev)
103+
104+
os.Setenv("VLE_INGEST_MODE", "minimal")
105+
cfg := Default()
106+
applyEnvOverrides(&cfg)
107+
if cfg.Ingest.Mode != "minimal" {
108+
t.Errorf("VLE_INGEST_MODE=minimal not applied, got %q", cfg.Ingest.Mode)
109+
}
110+
}
111+
112+
// TestIngestModeValidate asserts Validate accepts the documented values
113+
// (and empty, which Default normalises to full) and rejects garbage.
114+
func TestIngestModeValidate(t *testing.T) {
115+
t.Parallel()
116+
for _, m := range []string{"", "full", "minimal"} {
117+
cfg := Default()
118+
cfg.Database.URL = "postgres://localhost/test"
119+
cfg.Ingest.Mode = m
120+
if err := cfg.Validate(); err != nil {
121+
t.Errorf("ingest.mode=%q should pass validation, got %v", m, err)
122+
}
123+
}
124+
125+
cfg := Default()
126+
cfg.Database.URL = "postgres://localhost/test"
127+
cfg.Ingest.Mode = "turbo"
128+
if err := cfg.Validate(); err == nil {
129+
t.Error("ingest.mode=turbo should fail validation")
130+
}
131+
}
132+
87133
func TestTOCEnvOverride(t *testing.T) {
88134
// Mutates env — restore on exit. Not parallel.
89135
keys := []string{

0 commit comments

Comments
 (0)