feat(ingestion): agentic per-document strategy selection - #275
Conversation
Introduce the agent-selectable option sets (chunkers/extractors/resolvers), per-strategy tunable PARAM_SPECS with safe clamping, the frozen IngestionPlan dataclass, and default_plan/parse_plan for reading a planner's decision. This is the validation core the planners and builders build on. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add build_chunker/build_extractor/build_resolver and build_ingestion_strategies, which turn a validated IngestionPlan into concrete chunker/extractor/resolver instances (applying clamped params). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add HeuristicIngestionPlanner (zero-cost, feature-based) and LLMIngestionPlanner (one small guided LLM call), plus the model guide and the document sampler. Both return an IngestionPlan and fall back to safe defaults on any error, mirroring retrieval's PathRouter shape. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Re-export IngestionPlan, LLMIngestionPlanner, HeuristicIngestionPlanner and build_ingestion_strategies from the ingestion package and the top-level graphrag_sdk namespace. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add auto= and planner= to GraphRAG.ingest so the planner fills in any chunker/extractor/resolver the caller did not pass explicitly. Explicit strategies always win; planner errors degrade to the existing per-strategy defaults, so behavior is never silently lost. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add tests for IngestionPlan validation, parse_plan (json/fenced/key-value/ partial/garbage) and the tunable-param clamping rules. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add tests for HeuristicIngestionPlanner, LLMIngestionPlanner, the strategy builders, and GraphRAG.ingest(auto=...) merge semantics (explicit strategies win, planner failures fall back to defaults). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR introduces an agentic ingestion strategy planner module with heuristic and LLM-based implementations for selecting chunker, extractor, and resolver strategies. It wires this into ChangesAgentic Ingestion Strategy Planner
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant GraphRAG as GraphRAG.ingest()
participant PlanFn as _plan_ingestion_strategies()
participant Planner as LLMIngestionPlanner
participant LLM
participant Builder as build_ingestion_strategies()
Caller->>GraphRAG: ingest(source, auto=True)
GraphRAG->>PlanFn: request missing strategies
PlanFn->>Planner: plan(text, source, ctx)
Planner->>LLM: ainvoke(prompt with _GUIDE + sample)
LLM-->>Planner: response text
Planner->>Planner: parse_plan(response)
alt parse succeeds
Planner-->>PlanFn: IngestionPlan
else parse fails or error
Planner-->>PlanFn: default_plan()
end
PlanFn->>Builder: build_ingestion_strategies(plan, llm, embedder, entity_types)
Builder-->>PlanFn: (chunker, extractor, resolver)
PlanFn-->>GraphRAG: strategies (explicit values preserved)
GraphRAG->>GraphRAG: run ingestion pipeline
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
graphrag_sdk/tests/test_ingestion_planner.py (1)
358-361: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAvoid asserting on a private attribute.
ext.entity_extractor._thresholdreaches into a protected member. If the internal implementation renames it, the test breaks without a public-contract change. Consider asserting via a public accessor/property if one exists, or exposing one for testability.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@graphrag_sdk/tests/test_ingestion_planner.py` around lines 358 - 361, The test is asserting against the protected _threshold member on the object returned by build_extractor, which ties the test to internal implementation details. Update test_build_extractor_applies_threshold to verify the threshold through a public API on the extractor if one exists, or add a public accessor/property on the extractor type used by build_extractor so the test can assert behavior without touching private state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@graphrag_sdk/src/graphrag_sdk/api/main.py`:
- Around line 1576-1590: The auto-ingestion planner in `main.py` is only
receiving `source` when `text` is `None`, so it cannot plan from the actual
loaded file content. Update the `auto` path in the ingestion method that calls
`_plan_ingestion_strategies` to load or sample the file content before planning,
and pass that content to the planner alongside `source` so it can inspect
document structure instead of relying on the path alone.
- Around line 1313-1314: The sync wrapper is missing the new `auto` and
`planner` keywords that were added to the async ingest API, so `ingest_sync()`
currently drops them and raises `TypeError` for callers using the new feature.
Update `ingest_sync()` in `main.py` to accept `auto` and `planner` alongside the
existing ingest parameters, and forward both values through to the underlying
async ingest call used by `ingest_sync()` so the sync and async signatures stay
aligned.
- Around line 1795-1813: The fallback handling in the ingestion planner path is
incomplete: only active.plan() is protected, so invalid planner results or
failures inside build_ingestion_strategies still escape instead of using
defaults. Update the planner/fallback logic in the ingestion strategy method
around active.plan() and build_ingestion_strategies so any None/invalid plan or
construction exception is caught and returns the default chunker, extractor, and
resolver just like the existing planner failure branch.
---
Nitpick comments:
In `@graphrag_sdk/tests/test_ingestion_planner.py`:
- Around line 358-361: The test is asserting against the protected _threshold
member on the object returned by build_extractor, which ties the test to
internal implementation details. Update test_build_extractor_applies_threshold
to verify the threshold through a public API on the extractor if one exists, or
add a public accessor/property on the extractor type used by build_extractor so
the test can assert behavior without touching private state.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: cffcdfab-f699-41a6-b0bb-52dec86d5310
📒 Files selected for processing (5)
graphrag_sdk/src/graphrag_sdk/__init__.pygraphrag_sdk/src/graphrag_sdk/api/main.pygraphrag_sdk/src/graphrag_sdk/ingestion/__init__.pygraphrag_sdk/src/graphrag_sdk/ingestion/ingestion_planner.pygraphrag_sdk/tests/test_ingestion_planner.py
Add an 'Agentic Strategy Selection' section to docs/ingestion.md covering auto=True usage, planner options, param clamping, safety guarantees, and the LLM vs heuristic planners; add ingestion_planner.py to the file reference. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… error Address github-code-quality 'empty except': replace the bare pass in parse_plan with a debug log. Behavior is unchanged — malformed JSON still falls through to key:value scraping — but the intent is now explicit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Address review feedback on the ingestion planner wiring: - ingest_sync now forwards auto= / planner= (previously dropped, so ingest_sync(..., auto=True) raised TypeError). - Plan from loaded file content: in file mode the raw text isn't loaded yet, so _plan_ingestion_strategies now best-effort loads a content sample via the loader instead of planning from the file path alone. - Harden the fallback: a None/invalid plan or a build_ingestion_strategies failure now degrades to defaults inside the guarded path, instead of propagating, matching the documented 'never silently broken' contract. Adds tests for the None-plan fallback and the file-mode content sample. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ault clamp_params only checked hard_threshold <= soft_threshold when both keys were present. A planner emitting just hard_threshold=0.6 (vs the resolver's default soft=0.80) made LLMVerifiedResolution's constructor raise, and the whole plan was silently discarded. Validate a lone key against the other's constructor default and drop it if the pair would invert. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds opt-in, per-document “agentic” ingestion strategy selection to GraphRAG.ingest(...) via a new ingestion planner module (LLM-backed or heuristic), with safe defaults and parameter clamping.
Changes:
- Introduces
IngestionPlan, param clamping (PARAM_SPECS), plan parsing, and strategy builders (build_*) in a newingestion_planner.py. - Wires
auto=/planner=intoGraphRAG.ingest()+ batch/sync variants and exposes planner APIs at package top-level. - Adds end-to-end unit tests covering plan parsing, clamping, builders, planners, and ingest merge behavior; updates ingestion docs.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| graphrag_sdk/tests/test_ingestion_planner.py | New test suite for planner parsing/clamping/builders and ingest(auto=...) merge behavior. |
| graphrag_sdk/src/graphrag_sdk/ingestion/ingestion_planner.py | New ingestion-planning module (plans, planners, parsing, clamping, and builders). |
| graphrag_sdk/src/graphrag_sdk/ingestion/init.py | Re-exports planner-related symbols from graphrag_sdk.ingestion. |
| graphrag_sdk/src/graphrag_sdk/api/main.py | Adds auto/planner parameters and implements _plan_ingestion_strategies integration. |
| graphrag_sdk/src/graphrag_sdk/init.py | Top-level exports for planner APIs. |
| docs/ingestion.md | Documents auto=True usage and planner options. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Review — agentic ingestion strategy selectionNice work overall — the safety-first design is solid: strategy ids validated, params clamped with the cross-field guards ( Issues found, by size: Big
Medium
Small
Also noting the PR is stacked on #274's branch, so merge order matters. Verdict: approve with minor changes — #1 is the one I'd want addressed (or consciously accepted) before merge; the rest is polish. |
_plan_ingestion_strategies() already fully loads the source document (file mode) to build the planner's content sample, but the loaded DocumentOutput was discarded — only .text was kept. IngestionPipeline.run() then loaded the same source a second time, doubling I/O/CPU cost for every auto=True ingest() call on a file (worst for expensive loaders like PDFs). - IngestionPipeline.run() gains a preloaded_document parameter: when given, it skips both the text= and loader-call branches and reuses the already loaded DocumentOutput (including .elements, which StructuralChunking depends on), applying the same document_info merge logic as the existing loader branch. - _plan_ingestion_strategies() now returns the loaded DocumentOutput as a 4th tuple element instead of dropping it. - _ingest_single() threads it through to pipeline.run() when in file mode, eliminating the second load. - Also types planner as IngestionPlanner (a Protocol) instead of Any across ingest()/ingest_batch()/update(), and rewords a docstring line that referenced a nonexistent retrieval PathRouter file. - Adds an end-to-end test exercising the real ingest(auto=True) wiring (not just _plan_ingestion_strategies in isolation), asserting the loader is invoked exactly once. Updates existing planner-merge tests for the new 4-tuple return shape.
Documents the contract a custom planner= argument must satisfy (a single async plan(text, *, source=None, ctx=None) -> IngestionPlan | None method) as a runtime_checkable typing.Protocol, matching what HeuristicIngestionPlanner and LLMIngestionPlanner already implement. Exported from graphrag_sdk.ingestion and the top-level graphrag_sdk package alongside the existing planner exports.
Comments/docstrings/docs referenced graphrag_sdk/retrieval/strategies/ path_router.py, which doesn't exist in this repo — a broken pointer for anyone following it. Rewords the ingestion_planner.py module header comment, LLMIngestionPlanner's docstring, and the auto=True section of docs/ingestion.md to describe the routing behavior generically instead of naming a nonexistent file.
…tion risk - _GUIDE (the planner prompt text) omitted the semantic resolver's force_summary_threshold/max_summary_tokens even though PARAM_SPECS already allows them for that strategy — the LLM planner had no way to know it could tune them. Added to match PARAM_SPECS. - Added a comment on build_chunker's clamp_params() call noting the re-clamp is intentional/idempotent (params may come from an already-clamped IngestionPlan or a hand-assembled dict), to prevent a future 'cleanup' from removing it. - docs/ingestion.md: noted the prompt-injection blast radius is bounded — a malicious document can only steer the planner toward one of the known-safe strategy ids with clamped parameters, never an arbitrary or unsafe choice.
What
Adds agentic ingestion strategy selection to
GraphRAG.ingest(...), mirroring the retrieval-sidePathRouter. Whenauto=True, a planner inspects a sample of each document and picks the chunker, entity-extraction backend, and resolver per document — filling in only the strategies the caller did not pass explicitly.Design
chunker/extractor/resolveralways win.sentence/gliner/exact), so behavior is never silently lost.LLMIngestionPlanner(default) — one small guided LLM call.HeuristicIngestionPlanner— zero-cost, feature-based.PARAM_SPECS), so the model can never produce an unsafe config.Usage
Commits
IngestionPlanschema + param clampingauto=/planner=wiring inGraphRAG.ingestingest(auto=)mergeTests
38 passed—tests/test_ingestion_planner.py. Ruff clean on all changed files.Summary by CodeRabbit
New Features
Bug Fixes