-
Notifications
You must be signed in to change notification settings - Fork 1
A failed Judge request keeps extraction's pages; wire the Judge into production ingest (HAL-1369) #62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A failed Judge request keeps extraction's pages; wire the Judge into production ingest (HAL-1369) #62
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package config | ||
|
|
||
| import "testing" | ||
|
|
||
| func TestJudgeKeyFromEnv(t *testing.T) { | ||
| t.Setenv("VLE_TYPESAFE_API_KEY", "") | ||
| t.Setenv("TYPESAFE_API_KEY", "bare") | ||
|
Comment on lines
+6
to
+7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Clear inherited
📍 Affects 1 file
🤖 Prompt for AI Agents |
||
| c := Default() | ||
| applyEnvOverrides(&c) | ||
| if c.LLM.Judge.TypeSafe.APIKey != "bare" { | ||
| t.Errorf("bare TYPESAFE_API_KEY not picked up: %q", c.LLM.Judge.TypeSafe.APIKey) | ||
| } | ||
| t.Setenv("VLE_TYPESAFE_API_KEY", "prefixed") | ||
| c = Default() | ||
| applyEnvOverrides(&c) | ||
| if c.LLM.Judge.TypeSafe.APIKey != "prefixed" { | ||
| t.Errorf("VLE_ prefix should win: %q", c.LLM.Judge.TypeSafe.APIKey) | ||
| } | ||
| } | ||
|
|
||
| func TestJudgeIsOffByDefault(t *testing.T) { | ||
| t.Setenv("VLE_TYPESAFE_API_KEY", "") | ||
| t.Setenv("TYPESAFE_API_KEY", "") | ||
| c := Default() | ||
| applyEnvOverrides(&c) | ||
| if c.LLM.Judge.TypeSafe.APIKey != "" { | ||
| t.Errorf("no key configured, got %q", c.LLM.Judge.TypeSafe.APIKey) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -136,6 +136,19 @@ type Usage struct { | |
| TotalTokens int | ||
| CostUSD float64 | ||
| LLMCalls int | ||
|
|
||
| // Degraded lists the Judge-path steps that could not complete and | ||
| // what Build did instead. Empty means every step ran as configured. | ||
| // A document built with a non-empty Degraded is not wrong, but it is | ||
| // not what was asked for, and the caller must be able to see that: | ||
| // VERIZON_2022_10K once ingested with no page on any leaf after a | ||
| // single failed Judge request, and reported success (HAL-1369). | ||
| Degraded []string | ||
| } | ||
|
|
||
| // degrade records a Judge-path step that fell back. | ||
| func (u *Usage) degrade(step, what string) { | ||
| u.Degraded = append(u.Degraded, step+": "+what) | ||
| } | ||
|
|
||
| // add folds the per-response usage from one LLM call into the | ||
|
|
@@ -210,10 +223,16 @@ func (b *TOCBuilder) Build(ctx context.Context, pages []PageText) ([]tree.TOCNod | |
| // verification with search, and it is what makes the extraction body | ||
| // window irrelevant to page accuracy (HAL-1367). Plain verification | ||
| // remains the fallback when resolution cannot run. | ||
| if resolved, handled := b.resolvePagesJudge(ctx, nodes, pages, tocPages, &usage); handled { | ||
| applyResolvedPages(nodes, resolved) | ||
| } else if verdicts, handled := b.verifyTitlesJudge(ctx, nodes, pages, &usage); handled { | ||
| applyJudgeVerdicts(nodes, verdicts) | ||
| // | ||
| // A failed Judge request is not "no Judge". The generative verifier | ||
| // asks about extraction's printed page numbers, which are wrong for | ||
| // every leaf past the cover of a long filing, so falling to it after | ||
| // a transport failure zeroes the tree and looks like success | ||
| // (HAL-1369). With a Judge configured, resolution is retried once | ||
| // with a fresh budget; if it still fails, extraction's pages are | ||
| // kept as they are and the degradation is recorded on Usage. | ||
| if b.Judge != nil { | ||
| b.resolvePagesOrKeep(ctx, nodes, pages, tocPages, &usage) | ||
| } else { | ||
| b.verifyTitlesConcurrent(ctx, nodes, pages, concurrency, &usage) | ||
| } | ||
|
|
@@ -229,6 +248,36 @@ func (b *TOCBuilder) Build(ctx context.Context, pages []PageText) ([]tree.TOCNod | |
| return nodes, usage, nil | ||
| } | ||
|
|
||
| // resolverAttempts is how many times Build asks the Judge to resolve | ||
| // pages before keeping extraction's. Two: the first failure is almost | ||
| // always transport, and a resolver batch is two cheap requests. | ||
| const resolverAttempts = 2 | ||
|
|
||
| // resolvePagesOrKeep runs Judge page resolution with one retry. On | ||
| // exhaustion it leaves the tree exactly as extraction produced it and | ||
| // records the fact; it never routes a Judge-path document through the | ||
| // generative verifier. | ||
| func (b *TOCBuilder) resolvePagesOrKeep(ctx context.Context, nodes []tree.TOCNode, pages []PageText, exclude []int, usage *Usage) { | ||
| var lastErr error | ||
| for attempt := 1; attempt <= resolverAttempts; attempt++ { | ||
| resolved, handled, err := b.resolvePagesJudgeErr(ctx, nodes, pages, exclude, usage) | ||
| if err == nil { | ||
|
Comment on lines
+263
to
+264
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '215,290p' pkg/ingest/toc_builder.go
sed -n '400,545p' pkg/ingest/toc_resolve.go
sed -n '545,595p' pkg/ingest/toc_resolve.go
rg -n 'resolvePagesJudgeErr|resolvePagesOrKeep|resolverAttempts|applyResolvedPages' pkg/ingestRepository: hallelx2/vectorless-engine Length of output: 10347 Retain assignments from completed resolver batches.
Return the partial assignments with the error. Apply those assignments before retaining extraction pages for unresolved leaves. Do not apply unresolved entries. Preserve cancellation behavior by stopping further attempts when 🤖 Prompt for AI Agents |
||
| if handled { | ||
| applyResolvedPages(nodes, resolved) | ||
| } | ||
| // handled=false with no error means there was nothing to | ||
| // resolve (no leaves with titles); extraction's pages stand. | ||
| return | ||
| } | ||
| lastErr = err | ||
| log.Printf("toc: judge page resolution attempt %d/%d failed: %v", attempt, resolverAttempts, err) | ||
| if ctx.Err() != nil { | ||
| break | ||
| } | ||
| } | ||
| usage.degrade("page resolution", fmt.Sprintf("kept extraction's pages after %d failed Judge attempts: %v", resolverAttempts, lastErr)) | ||
| } | ||
|
|
||
| // detectTOCPages scans the first tocCheck pages with the | ||
| // TreeWalk-style single-page detector. Returns the 1-indexed page | ||
| // numbers (in order) the LLM judged as table-of-contents pages. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Document the supported
VLS_TYPESAFE_API_KEYvariable. Both examples omit a supported Judge API-key source.config.example.yaml#L114-L114: addVLS_TYPESAFE_API_KEYto the documented environment-variable list.config.server.example.yaml#L94-L95: addVLS_TYPESAFE_API_KEYto the documented environment-variable list.📍 Affects 2 files
config.example.yaml#L114-L114(this comment)config.server.example.yaml#L94-L95🤖 Prompt for AI Agents