Skip to content

feat(ingestion): agentic per-document strategy selection - #275

Open
Naseem77 wants to merge 16 commits into
feat/agentic-retrieval-skills-mcpfrom
feat/agentic-ingestion-planner
Open

feat(ingestion): agentic per-document strategy selection#275
Naseem77 wants to merge 16 commits into
feat/agentic-retrieval-skills-mcpfrom
feat/agentic-ingestion-planner

Conversation

@Naseem77

@Naseem77 Naseem77 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

What

Adds agentic ingestion strategy selection to GraphRAG.ingest(...), mirroring the retrieval-side PathRouter. When auto=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

  • Explicit chunker / extractor / resolver always win.
  • On an empty/invalid plan or any planner error, it falls back to today's defaults (sentence / gliner / exact), so behavior is never silently lost.
  • Two planners:
    • LLMIngestionPlanner (default) — one small guided LLM call.
    • HeuristicIngestionPlanner — zero-cost, feature-based.
  • The planner may also tune parameters inside each strategy; all values are coerced and clamped to safe ranges (PARAM_SPECS), so the model can never produce an unsafe config.

Usage

# LLM planner picks strategies per document
await rag.ingest(source="doc.md", auto=True)

# zero-cost heuristic instead
from graphrag_sdk import HeuristicIngestionPlanner
await rag.ingest(source="doc.md", auto=True, planner=HeuristicIngestionPlanner())

# explicit strategy still wins; planner fills the rest
await rag.ingest(source="doc.md", auto=True, resolver=ExactMatchResolution())

Commits

  1. IngestionPlan schema + param clamping
  2. Strategy builders
  3. Heuristic + LLM planners
  4. Package exports
  5. auto=/planner= wiring in GraphRAG.ingest
  6. Tests: schema, parsing, tunable params
  7. Tests: planners, builders, ingest(auto=) merge

Tests

38 passedtests/test_ingestion_planner.py. Ruff clean on all changed files.

Summary by CodeRabbit

  • New Features

    • Added automatic, per-document ingestion strategy selection during ingestion.
    • Exposed new ingestion-planning options at the top-level SDK for easier access.
    • Added support for choosing chunking, extraction, and resolution strategies based on document content or planner output.
  • Bug Fixes

    • Improved fallback behavior so ingestion continues with default strategies when planning cannot be completed.
    • Added validation and safe parameter clamping for supported ingestion strategy settings.

Naseem77 and others added 7 commits July 2, 2026 09:47
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>
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: abec88b8-077a-4a85-83eb-0e8196ae3c34

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This 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 GraphRAG.ingest() via new auto/planner parameters, adds public exports, and includes tests.

Changes

Agentic Ingestion Strategy Planner

Layer / File(s) Summary
Plan data model, parsing, and clamping
graphrag_sdk/src/graphrag_sdk/ingestion/ingestion_planner.py
Defines chunker/extractor/resolver option sets and defaults, PARAM_SPECS/clamp_params() for range-safe parameter coercion, the IngestionPlan dataclass with validation, default_plan()/parse_plan() for JSON/key-value parsing, and the LLM prompt guide.
Heuristic and LLM planners
graphrag_sdk/src/graphrag_sdk/ingestion/ingestion_planner.py
Implements HeuristicIngestionPlanner (feature-based structural/sentence chunker choice) and LLMIngestionPlanner (prompt-driven plan generation with budget enforcement and fallback to defaults).
Strategy builder factories
graphrag_sdk/src/graphrag_sdk/ingestion/ingestion_planner.py
Adds build_chunker, build_extractor, build_resolver, and build_ingestion_strategies to instantiate concrete pipeline components from an IngestionPlan.
GraphRAG.ingest() integration
graphrag_sdk/src/graphrag_sdk/api/main.py
Adds auto/planner keyword-only parameters across ingest() overloads, forwards them through _ingest_batch/_ingest_single, and adds _plan_ingestion_strategies() to select strategies with error fallback.
Public exports
graphrag_sdk/src/graphrag_sdk/__init__.py, graphrag_sdk/src/graphrag_sdk/ingestion/__init__.py
Re-exports the new planner classes and build_ingestion_strategies from package facades.
Tests
graphrag_sdk/tests/test_ingestion_planner.py
Adds tests for plan defaults, parsing, heuristic/LLM planning, builder factories, _plan_ingestion_strategies merge behavior, and parameter clamping.

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
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: agentic per-document ingestion strategy selection.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/agentic-ingestion-planner

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Naseem77 Naseem77 changed the title feat(ingestion): agentic per-document strategy selection (auto=True) feat(ingestion): agentic per-document strategy selection Jul 2, 2026
Comment thread graphrag_sdk/src/graphrag_sdk/ingestion/ingestion_planner.py Dismissed
@Naseem77
Naseem77 changed the base branch from main to feat/agentic-retrieval-skills-mcp July 2, 2026 06:50

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
graphrag_sdk/tests/test_ingestion_planner.py (1)

358-361: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid asserting on a private attribute.

ext.entity_extractor._threshold reaches 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

📥 Commits

Reviewing files that changed from the base of the PR and between 0ab92ba and 42313f1.

📒 Files selected for processing (5)
  • graphrag_sdk/src/graphrag_sdk/__init__.py
  • graphrag_sdk/src/graphrag_sdk/api/main.py
  • graphrag_sdk/src/graphrag_sdk/ingestion/__init__.py
  • graphrag_sdk/src/graphrag_sdk/ingestion/ingestion_planner.py
  • graphrag_sdk/tests/test_ingestion_planner.py

Comment thread graphrag_sdk/src/graphrag_sdk/api/main.py Outdated
Comment thread graphrag_sdk/src/graphrag_sdk/api/main.py
Comment thread graphrag_sdk/src/graphrag_sdk/api/main.py Outdated
Naseem77 and others added 5 commits July 2, 2026 10:07
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>
@Naseem77 Naseem77 mentioned this pull request Jul 5, 2026
11 tasks
@Naseem77
Naseem77 requested a review from Copilot July 5, 2026 18:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 new ingestion_planner.py.
  • Wires auto= / planner= into GraphRAG.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.

Comment thread graphrag_sdk/src/graphrag_sdk/ingestion/ingestion_planner.py Outdated
Comment thread graphrag_sdk/src/graphrag_sdk/ingestion/ingestion_planner.py Outdated
Comment thread graphrag_sdk/src/graphrag_sdk/api/main.py
Comment thread docs/ingestion.md Outdated
Comment thread graphrag_sdk/src/graphrag_sdk/api/main.py
@galshubeli

Copy link
Copy Markdown
Collaborator

Review — agentic ingestion strategy selection

Nice work overall — the safety-first design is solid: strategy ids validated, params clamped with the cross-field guards (chunk_overlap < chunk_size, hard > soft) matching the real constructor constraints, and LatencyBudgetExceededError correctly re-raised while everything else degrades to defaults. Mirrors the retrieval PathRouter contract cleanly.

Issues found, by size:

Big

  1. Double document load_plan_ingestion_strategies fully loads the file to get a content sample, then IngestionPipeline loads it again. With auto=True that's two full parses per document (expensive for PDFs, multiplied in batch mode), and the sample load also runs for HeuristicIngestionPlanner, which mostly needs the path. Suggest caching the loaded document for the pipeline, or at least skipping the sample load when the planner doesn't need content and documenting the extra cost.

Medium

  1. No end-to-end test of ingest(auto=True) — tests drive _plan_ingestion_strategies via a stub, but nothing exercises the real ingest() wiring (planner runs after loader resolution + ontology init). One integration test with a mocked LLM would close this.

Small

  1. planner: Any | None in the public API — a small Protocol (async def plan(...) -> IngestionPlan | None) would document the contract and give type-checker support.
  2. parse_plan partial-JSON gap — if the JSON contains any one of the three keys, the key:value fallback scrape is skipped for the others. Degrades safely; just noting.
  3. Double clamping (in IngestionPlan.__post_init__ and again in build_*) — harmless/idempotent, but a one-line comment would prevent a future "cleanup".
  4. _GUIDE omits semantic's force_summary_threshold/max_summary_tokens even though PARAM_SPECS allows them — either add to the guide or drop from the spec.
  5. Prompt injection via document content can steer the planner; blast radius is well-bounded (valid strategies, clamped params — worst case a pricier config), worth one sentence in the docs' safety-guarantees list.
  6. Docs link to blob/main/ingestion_planner.py, which only exists once this and feat: agentic retrieval - graph-walk, skills, MCP server, and MultiPath path router #274 land on main — fine at merge time.

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.

Naseem77 added 4 commits July 13, 2026 16:51
_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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants