Rebuild ContextIQ: simple-agentic financial doc-QA (FinanceBench corpus 0.71 vs 0.19, per-doc ~0.79)#32
Merged
Merged
Conversation
Add ven-eval as editable dev dependency, scaffold evals/ dir via ven-eval CLI, replace stub provider with ContextiqProvider wired to answer_question(), and add smoke test confirming call_api shape against live Qdrant. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds evals/contextiq.yaml as a placeholder stub; debug assert _debug_dump.py was run statically (disk full blocked npx) and removed. Task 12 will populate the real test cases. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Task 14 acceptance run surfaced 6 real wiring bugs the build never hit:
- pin promptfoo 0.118.0 (0.121.13 ships a broken @posthog/core .mjs)
- anchor provider to project root (promptfoo runs it from evals/)
- map question var into the prompt (prompts: ["{{question}}"])
- grader id anthropic:messages:claude-sonnet-4-6 (0.118 rejects bare name)
- run promptfoo at -j 1 (embedded Qdrant is single-writer)
- structural asserts inline in contextiq.yaml: file:// asserts in an
external tests file get inlined by promptfoo and lose get_assert(),
so they silently returned {}. Inline expression form is robust.
run.py: pin PROMPTFOO_PYTHON to the venv, tolerate promptfoo exit 100
(test failures are signal), and always write the EvalRecord file.
Result: 4 cases run green end-to-end, 0 errors, records written.
AnthropicLLMClient hardcoded cost_usd=None, so every EvalRecord had a null cost. Add a model-tier price table (opus/sonnet/haiku, USD per 1M tokens) and compute cost from usage.input_tokens/output_tokens. Unknown models return None rather than guessing. This populates cost_usd through the ven_eval records sink so the DB foundation can track $ per eval.
Opt-in DB sink: when VEN_EVAL_DB is set (e.g. the workspace ven_master.db), run.py writes the normalized EvalRecords to SQLite in addition to JSON. Keeps contextiq decoupled from the monorepo layout while enabling the 4-layer eval path: promptfoo -> ven_eval -> ven_master.db -> dashboard.
…Answer risk_factor_synthesis eval flagged an uncited summary intro. Tighten the answer prompt: extend the cite-every-claim rule to all sections, require the Direct Answer to be citation-free high-level synthesis (no standalone facts, no vouching for the evidence), and explicitly separate document evidence from interpretation. Lifts the case 0.72 -> 0.85; evidence/interpretation-separation rubric now passes; Evidence tables fully cited.
…c to Evidence risk_factor_synthesis was failing because comprehensive multi-source answers truncated mid-section at 1000 tokens — dropping the trailing citation and the Confidence/Gaps interpretation block, which a moving target across runs. Raising the cap to 4000 lets syntheses complete (Sonnet supports 8k+ output). Also scope the citation rubric to the Evidence section (the Direct Answer is high-level synthesis, not per-claim cited). Eval now 4/4 (100%); risk_factor 0.95.
…n gate Corpus doc-QA rebuild, phase 1. Swap the answer path to OpenRouter (free Nemotron 3 Ultra) over raw httpx (not the OpenAI SDK, per CLAUDE.md), keeping AnthropicLLMClient as a Citations-API seam. Provider chosen by CONTEXTIQ_LLM_PROVIDER (default openrouter). Reuses existing [block_id, page N] chunk-id citations; adds a NOT_IN_DOCUMENT abstain sentinel. Plan doc included. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…, staged) One clean retrieve: store.hybrid_hits(query, k=40) = Qdrant hybrid dense+BM25 via search_hybrid, no rerank. Proven end-to-end: ingest sample-contract.md -> 9 sources -> Nemotron grounded answer with Confidence/Gaps; empty context -> NOT_IN_DOCUMENT abstain. Built alongside the legacy path (build-new-alongside-old): build_context still calls search_with_trace. Cut over + delete the legacy stack together once the FinanceBench eval shows hybrid wins, to avoid migrating ~8 legacy-coupled tests prematurely. Parse deferred — Docling already handles PDFs; hosted OCR is a gated lever (OpenRouter mistral-ocr markdown extraction is unreliable per field reports). Suite at baseline: 156 pass, only the 9 pre-existing enterprise-stack failures remain. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tron Flip the OpenRouter default from free Nemotron to minimax/minimax-m3 (1M ctx, ~$0.001/query, $15 credits loaded) — sharper structured output + per-claim chunk-id citations. Env-driven (CONTEXTIQ_OPENROUTER_MODEL), httpx, no code-path change. Updated config default, .env.example, test, and plan doc; scrubbed all Nemotron references. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Retry transient 429/5xx and transport errors up to 3x with linear backoff so a free/cheap-tier blip doesn't silently fall back to extractive mid-eval. Client errors (400/401/404) are not retried. Two tests cover both paths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
run_answers.py scores the corpus pipeline (hybrid_hits -> minimax-m3 grounded answer) vs gold FinanceBench answers via an LLM judge, tracking accuracy, abstain-rate, and correct-when-answered. Judge parser has a deterministic self-check. Scores only ingested docs (run_recall convention). Ships the 150-Q set. Gate: beat naive-RAG 0.19, target >=0.55. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
minimax-m3 spends ~100-200 tokens reasoning before emitting YES/NO, so the judge's max_tokens=8 truncated it to empty content -> every question scored wrong (false 0/21). Bumped to 2000. Real result on the AMD/AmEx/Boeing subset: 10/21 = 0.476 vs naive-RAG baseline 0.19. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…aseline Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
build_context now retrieves via store.hybrid_hits (Qdrant hybrid dense+BM25) — the pipeline measured at 0.476 vs 0.19 on FinanceBench — instead of the legacy search_with_trace. Adds a standalone keyword-scan fallback for degraded operation (empty/unavailable vector index: offline, cold corpus, tests); this is a safety net, not the retired multi-stage pipeline. Eval gains a --legacy flag for the head-to-head. Suite green (158 pass; only pre-existing enterprise-stack failures). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…h result Replace the SPLADE/ColBERT "enterprise" narrative and fabricated results table with what actually runs: PDF -> Docling -> Qdrant hybrid -> minimax-m3 grounded answer with citations + abstain. Real number: 0.476 vs 0.19 naive-RAG baseline on FinanceBench, reproducible via evals/financebench/run_answers.py. States the subset scope and the table-retrieval failure mode honestly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…se 4 pt1) ContextEngine.build_context is now just hybrid retrieve -> token-budget pack (151 -> 43 lines). Removes the fixture-fitted _trim_structured_code_table / _trim_asset_mapping_table (EM-### / CN-P-103 / UC-T matching) and their 3 tests. Delete UPGRADE_PLAN.md (stale SPLADE/ColBERT plan, contradicted the honest README). Records the hybrid>legacy>naive head-to-head. Suite unchanged (155 pass; only pre-existing enterprise-stack failures remain). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove ~2,860 LOC (src 7,164 -> 4,306, 44 -> 34 files) of code disconnected from the live path. Deleted: the multi-stage legacy pipeline (candidates, ranker, expansion, parent_resolver, pipeline, query, profile) and the enterprise SPLADE/ColBERT stack (intent_router, embedding_pipeline, adaptive_chunker). Rewrote store.py (490->~290) and vector_index.py (755->~290) to just the live hybrid path: index_blocks + search_hybrid + hybrid_hits + fallback. Repointed the CLI eval-retrieval command and dropped the eval --legacy flag. Deleted the tests for the removed modules; migrated the survivors to search_hybrid. Suite: 76 passed, 0 failed (the 9 pre-existing failures lived in the deleted code). ruff clean across the repo. Live CLI ask verified stages=hybrid end-to-end. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Delete benchmarks.md (fabricated 0.736/0.556 numbers), code-flow.md (maps the deleted retrieval stack), architecture.md + application-architecture.md (describe the removed SPLADE/ColBERT enterprise pipeline as current). The honest README is now the single source of truth. Kept: demo-questions.md, the 300-page blueprint, and specs/ (forward-looking roadmap, not claims about what ships today). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…> 0.8
One decompose call rewrites a concept question into underlying line items
("quick ratio" -> current assets / current liabilities / cash), retrieves each
via hybrid_hits, merges/dedups, then a table-aware LLM rerank keeps the best
blocks in budget. No multi-agent orchestration.
Measured head-to-head on the 10 hardest FinanceBench questions: plain hybrid
5/10 (0.500) -> agentic 8/10 (0.800), correct-when-answered 0.889. Flipped the
table-retrieval failures: quick ratio now computed from the retrieved balance
sheet, Xilinx-amortization driver found, geographies taxonomy matched.
Built alongside plain (run_answers --agentic) so it stays measurable. Unit test
covers decompose/merge/dedup/rerank parsing with fakes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Live corpus queries were unscoped: "AMD current assets" matched every company's balance sheet, so a cross-company corpus returned a mix and the model abstained. Add route_document (1 LLM call: pick the target filing from the manifest) — agentic now routes to the filing when the store is unscoped, then decompose/retrieve/rerank within it. Live "AMD quick ratio" now scopes to AMD only and computes ~1.57. Wire agentic as the live default (CONTEXTIQ_AGENTIC, graceful fallback to plain without a client): query.py builds the client, ContextEngine runs agentic_retrieve. run_answers gains --corpus (unscoped, routed) and --show. Eval respects CONTEXTIQ_DATA_DIR. Tests cover routing/index parsing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…per-doc ~0.79) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
…MMLongBench Resolves the divergence between the lean agentic rebuild (this branch) and main's enterprise-direction work: - Rebuild wins on retrieval: enterprise stack stays deleted; store.py/vector_index.py keep the clean hybrid rewrite; config keeps the agentic/OpenRouter settings. - KEPT main's self-contained document-tree + extractor work (tree, tree_store, summarizer, heading_hierarchy, extractors/*) — it integrates cleanly (105 tests pass). - DROPPED the MMLongBench eval (coupled to the deleted enterprise retrieval — recoverable from #22 for deliberate re-integration on the new pipeline) and dead enterprise scripts (qa/smoke/reindex). Suite: 105 passed, 0 failed. ruff clean. Live agentic answer verified end-to-end. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
…sses CI CI failed at 'uv sync' because pyproject pinned local editable path deps (ven-obs at ../ven-obs, ven-eval at ../../.worktrees/...) that don't exist on the runner. Removed the hard ven-obs dependency, [tool.uv.sources], and the ven-eval dependency group. obs.py now degrades to a no-op observability API when ven_obs isn't importable; query.py imports `api` from contextiq.obs. The span test skips (importorskip) when ven_obs is absent. Suite 105 pass; no-op path verified with ven_obs blocked. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
… eval test)
- Untrack docs/superpowers/ (already gitignored) so the "no process docs in tree"
hygiene gate passes; the plan stays local.
- test_provider_shape: importorskip("evals.provider") — the promptfoo provider
needs the local ven_eval package, unavailable in CI.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Rebuilds ContextIQ from a disconnected, over-engineered "enterprise" stack (4 retrieval pipelines, the weakest wired to the app, fabricated README numbers) into a lean, measured, simple-agentic financial document-QA system.
Results (FinanceBench, LLM-judge, 12 companies / 52 questions / 8 sectors)
Out-of-sample check on 9 unseen companies: 24/31 = 0.77. Every number is LLM-judged vs gold answers and reproducible via
evals/financebench/run_answers.py. Numbers are ranges (±1 question of LLM variance).Pipeline
route (pick filing) → decompose (concept → line items) → hybrid retrieve per sub-query → merge/dedup → table-aware rerank → minimax-m3 answer + citations + NOT_IN_DOCUMENT abstainNo multi-agent orchestration — one route + one decompose + one rerank call. Every component was measured head-to-head before adoption (decompose alone lifted the hardest questions 0.48 → ~0.79).
Also in this PR
src/7,164 → ~4,400 LOC;retrieval/12 → 4 files.Honest limitations
Validated on 12 companies (not the full FinanceBench 150); routing recovers most but not all per-document quality (corpus 0.71 vs scoped ~0.79); agentic adds ~3 model calls/question.
🤖 Generated with Claude Code