Skip to content

feat(nodes): add text Chunker pipeline node - #897

Merged
dsapandora merged 19 commits into
rocketride-org:developfrom
nihalnihalani:feat/chunker-node-clean
Sep 8, 2026
Merged

dsapandora merged 19 commits into
rocketride-org:developfrom
nihalnihalani:feat/chunker-node-clean

Conversation

@nihalnihalani

@nihalnihalani nihalnihalani commented May 15, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Adds a chunker preprocessor node (nodes/src/nodes/chunker/) that splits documents on the documents lane into one document per chunk, with three strategies: recursive character (separator hierarchy \n\n, \n, . , , then hard split), sentence boundary (never cuts mid-sentence; a single sentence longer than chunk_size is emitted whole, documented in the node README), and token-based (tiktoken, lazy-imported so the other two strategies pull in no dependencies).
  • chunk_overlap is reserved inside chunk_size, so an emitted chunk never exceeds chunk_size and the configured overlap is still honored on full-size chunks — including the hard-split path. Each chunk carries chunkId, parentId, chunk_index, start_char, end_char and total_chunks; start_char/end_char stay truthful under overlap (test_overlap_honored_on_full_size_chunks asserts text == source[start_char:end_char] for every overlapped chunk). Source metadata is copied per chunk, never shared.
  • Ships the co-located chunker.svg icon and README.md (with the ROCKETRIDE:GENERATED:PARAMS markers), a services.json with classType: ["preprocessor"], capabilities: ["experimental"] and a live test block, plus a 644-line unit suite at nodes/test/chunker/test_all.py. All four Python files carry the MIT header.
  • Catalog row in docs/README-nodes.md and a CHANGELOG.md [Unreleased] / ### Added entry.

How this differs from the existing preprocessor_langchain: that node hard-codes chunk_overlap = 0 for every splitter it constructs (nodes/src/nodes/preprocessor_langchain/langchain.py:168, passed at :180) and emits no per-chunk character offsets. chunker makes overlap configurable and records start_char/end_char for every chunk.

Type

Feature (new node). No changes to existing nodes, the test harness, or CI.

Testing

  • Tests added or updated — nodes/test/chunker/test_all.py, 48 tests (47 test functions, one parametrized over overlap in [0, 1]): per-strategy behavior, size/overlap invariants (test_overlap_never_exceeds_chunk_size, test_overlap_stays_within_chunk_size_with_real_separators, test_chunk_never_exceeds_chunk_size_when_overlap_below_separator, test_overlap_honored_on_full_size_chunks), an O(n²) decode-regression guard on the token strategy, edge cases (empty / whitespace-only / single character / no separator), metadata correctness, and IGlobal/IInstance lifecycle.
  • Tested locally — partially. The 40 pure-strategy tests (TestRecursiveCharacterChunker, TestSentenceChunker, TestTokenChunker) pass on CPython 3.12.12 (40 passed, 8 deselected) — note the chunker package __init__ imports IGlobal, which imports rocketlib, so outside the engine build chunker.chunker_strategies has to be pre-imported before collection. The remaining 8 (TestIGlobalLifecycle, TestIInstanceWriteDocuments) import rocketlib and ai.common.schema and fail locally with ModuleNotFoundError: No module named 'rocketlib' by design — there are no pytest.skip / importorskip fallbacks; they run under the engine build interpreter in CI. ruff check, ruff format --check (7 files) and node nodes/scripts/gen-credentials.mjs --check (exit 0, no catalog entry required) were run against this head.
  • ./builder test passes — in CI, not locally (no engine build in this checkout). Run 33893136431 on the current head b627bed8 ran the full matrix — Ruff, gitleaks, Build on Ubuntu 22.04 / Windows Server 2022 / macOS (ARM64) with the builder test --verbose --sequential step, Shell API contract — and CI OK completed green.

Checklist

  • Commit messages follow conventional commits
  • No secrets or credentials included — gitleaks detect --config .gitleaks.toml --log-opts upstream/develop..HEAD scans 16 commits, no leaks; gen-credentials.mjs --check finds no credential-shaped field (chunker.token is a grouping definition, not a field).
  • Wiki updated (if applicable) — n/a; documentation is the co-located nodes/src/nodes/chunker/README.md plus the docs/README-nodes.md catalog row.
  • Breaking changes documented (if applicable) — n/a; purely additive.

Sync with develop (2026-09-04)

Rebased onto upstream/develop @ 65a2e133a10ed762640ce71a63ef13e8abf6dfc0; the branch is 16 linear commits on top of it (head b627bed8). The branch previously carried two merge-from-develop commits, which the rebase removes (CONTRIBUTING.md:109-116 asks for rebases). Two further commits were dropped:

  • 09d1c466 chore(format): apply ruff format to llm_vision_* files — a duplicate of the closed PR chore(format): apply ruff format to llm_vision_* files #900. develop already has that formatting via 07f78f3e (2026-05-15). This PR now touches no llm_vision_* file at all.
  • 93c165ce test: front-load nodes/src/nodes on sys.path in conftest — dropped; nodes/test/conftest.py is untouched by this PR. The image_cleanup collection error it was aimed at is fixed on develop: the real cause was nodes/test/cloud_tts/test_cloud_tts.py doing sys.path.insert(0, <nodes/test root>) at module import time (:25 before 39bc5090), which shadowed nodes/src/nodes on any xdist worker that collected cloud_tts first — no conftest-level insert could override that. 39bc5090 (feat(nodes): add tool_pipedrive CRM tool node #1687) replaced that insert with a package-relative from ..test_contracts import parse_service_json, added test_test_root_is_not_on_sys_path, and gave nodes/test/image_cleanup/test_provenance.py its own front-of-path insert (:29-32).

Also dropped: the nodes/test/local_text_output/test_instance.py hunk. develop already has the strong sys.path insert this PR was proposing (:14-19, from #1687), so develop's version of that file is kept verbatim.

Ported on top: "documentation": "https://docs.rocketride.org" in services.json (141 of develop's services.json files carry it, right after "icon"); the test-module docstring's stale suite names milvus/pinecone updated to store_milvus/store_pinecone after the ddf94dd6 rename; the docs/README-nodes.md catalog row and the CHANGELOG.md entry. The catalog header's "121 node directories → 151 services" count is left untouched — it is already stale on develop (137 node directories), so bumping it here would be unrelated churn.

git diff --name-only 65a2e133..HEAD is exactly 12 files: the eight under nodes/src/nodes/chunker/, the two under nodes/test/chunker/, docs/README-nodes.md and CHANGELOG.md (1573 insertions, 0 deletions).

Commits on the branch (git log --oneline upstream/develop..HEAD):

SHA Subject
b627bed8 chore(chunker): add the MIT header to the node package init
fee9359b docs(nodes): add chunker to the node catalog and changelog (#1643)
f6e12ca2 chore(chunker): add documentation link and refresh stale suite names
75e198af test(chunker): revert import rework, keep the size-cap fix + regression tests
6d9ce2d4 fix(nodes): chunker — enforce size cap for overlap<separator, drop global sys.path mutation
15122ec6 fix(nodes): address chunker review — icon, README, overlap bug, classType (#1643)
d1a32690 fix(nodes): stop test dirs from shadowing node packages on sys.path
3ee567c1 test(chunker): remove skip fallbacks but keep required sys.path
2272fdde test(chunker): drop vanilla-pytest fallbacks; rely on build interpreter
61f7e2c1 test(chunker): assert max decode input <= chunk_size in token guard
1ee305ba test(chunker): relocate suite to nodes/test/chunker/test_all.py
3259e7cc fix(chunker): preserve separators, cap overlap, and safe tiktoken decode
53059e6f refactor(chunker): drop dead pending-docs buffer and always preventDefault
679f57d4 fix(chunker): raise on unknown strategy instead of silently defaulting
c8f6c067 chore(chunker): mark node experimental and tighten field types
d9e4852b feat(nodes): add text Chunker pipeline node

CI checks that ran against this PR for the first time (all postdate the previous head of 2026-07-21) — every one of them passed in run 33893136431 on b627bed8:

Check Added
sys.modules core-stub leak guard in nodes:test a438c23c (#1642) 07-22 / 39bc5090 (#1687) 07-31
Postgres pgvector + Apache AGE containers with RR_REQUIRE_DB_TESTS=1 in the Build job 8db7a42d (#1713) 08-03
Credentials-catalog node nodes/scripts/gen-credentials.mjs --check inside Shell API contract 210c9c7f (#1880) 08-19
ai:test config-key catalogue sweep (packages/ai/tests/ai/common/test_config_key_warnings.py) 75a33fe6 (#2166) 09-02

Known external dependency: open PR #2139 (docs revamp) removes docs/README-nodes.md and adds a docs-schemas gate. If it merges first, the catalog row here goes away with the file and the node README may need re-shaping to the new schema; nothing else in this PR depends on it.

Linked Issue

Closes #1643


History: this is the chunker portion of the closed PR #499, resubmitted on a clean branch after #499 was closed for scope (three nodes plus unrelated churn). rerank_cohere and search_hybrid are separate PRs.

🤖 AI-assisted: prepared with Claude Code; all changes reviewed by the author.

@github-actions github-actions Bot added the module:nodes Python pipeline nodes label May 15, 2026
@coderabbitai

coderabbitai Bot commented May 15, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a Text Chunker node: three chunking strategies (recursive, sentence, token), IGlobal lifecycle for strategy initialization, per-instance chunking and emission with chunk metadata, service configuration and requirements, and comprehensive tests.

Changes

Text Chunker Node Implementation

Layer / File(s) Summary
Chunking strategy algorithms
nodes/src/nodes/chunker/chunker_strategies.py
Defines ChunkingStrategy and three implementations: RecursiveCharacterChunker, SentenceChunker, and TokenChunker. Each splits text, applies overlap, and emits chunks with chunk_index, start_char, and end_char metadata.
Global lifecycle and strategy initialization
nodes/src/nodes/chunker/IGlobal.py
IGlobal reads/validates chunker config, probes token dependency when token is selected, instantiates the chosen chunker in beginGlobal(), and clears it in endGlobal().
Instance document processing
nodes/src/nodes/chunker/IInstance.py
IInstance coerces inputs to Doc, skips empty content, calls IGlobal.strategy.chunk(), copies documents/metadata per chunk, annotates chunk-specific metadata, batches emitted chunks, and always prevents default forwarding.
Module configuration and dependencies
nodes/src/nodes/chunker/__init__.py, nodes/src/nodes/chunker/requirements.txt, nodes/src/nodes/chunker/services.json
Exports IGlobal and IInstance; adds tiktoken>=0.7.0,<1.0.0; registers the node, preconfigures recursive/sentence/token profiles and exposes conditional schema for strategy parameters plus a smoke test.
Comprehensive test suite
nodes/test/chunker/test_all.py
Tests chunking algorithms (splitting, overlap, offsets, validation), token-encoder stubs/fallbacks, IGlobal lifecycle parsing/selection, and IInstance writeDocuments behavior including metadata isolation and preventDefault semantics.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant IGlobal
  participant IInstance
  participant ChunkingStrategy
  Client->>IGlobal: validateConfig()
  Note over IGlobal: probe token deps if selected
  Client->>IGlobal: beginGlobal()
  IGlobal->>ChunkingStrategy: new RecursiveCharacterChunker/SentenceChunker/TokenChunker
  ChunkingStrategy-->>IGlobal: strategy instance
  Client->>IInstance: open(entry)
  IInstance->>IInstance: chunkId = 0
  loop process documents
    Client->>IInstance: writeDocuments(documents)
    IInstance->>IInstance: coerce to Doc schema
    IInstance->>ChunkingStrategy: chunk(text)
    ChunkingStrategy-->>IInstance: list of chunks with metadata
    IInstance->>IInstance: copy Doc per chunk, annotate metadata
    IInstance->>Client: instance.writeDocuments(chunks)
  end
  Client->>IGlobal: endGlobal()
  IGlobal->>IGlobal: strategy = None
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • jmaionchi
  • Rod-Christensen
  • stepmikhaylov

Poem

🐰 I nibble text into tidy bits,
By token, sentence, or recursive splits,
Threads hum softly as chunks take flight,
Metadata glowing in moonlit byte,
Hooray — a rabbit's small code delight! 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 92.68% which is sufficient. The required threshold is 80.00%.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a text chunker pipeline node.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 5

🤖 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 `@nodes/src/nodes/chunker/chunker_strategies.py`:
- Around line 129-145: The chunking logic in chunk() prepends overlap to raw
chunks produced by _split_text(), causing final chunk_text to exceed chunk_size
(e.g., raw_chunks from _split_text() are already <= chunk_size but adding
chunk_overlap makes them ~chunk_size+chunk_overlap); fix by ensuring final
chunk_text is truncated to chunk_size after prefixing overlap (or adjust overlap
to not exceed remaining space): locate chunk() where raw_chunks, overlap_text,
and chunk_text are composed and enforce that len(chunk_text) <= self.chunk_size
(trim the prefix or suffix as needed) and recompute start_char/end_char
accordingly so indexes match the trimmed chunk.
- Around line 82-110: The split routine (_split_text) currently drops the
separator when candidate overflows; fix it so boundary separators are preserved
by attaching the separator to one of the emitted chunks: when you decide to
finalize and append current (in the overflow branch where you do
chunks.append(current)), append the separator to that chunk first (e.g., append
current + separator), or alternatively, when starting the next chunk keep the
separator by prefixing part with separator before recursing/assigning it to
current; ensure this change is applied in the overflow branch that handles
len(candidate) > self.chunk_size and the case where len(part) <=
self.chunk_size, and keep use of variables parts, separator, current, candidate,
chunks, self.chunk_size and remaining_separators intact so start_char/end_char
align with the original text.

In `@nodes/src/nodes/chunker/IGlobal.py`:
- Around line 79-96: The branch logic currently silently falls back to
RecursiveCharacterChunker for any unknown strategy_name; change it so
'recursive' is an explicit elif and raise a clear exception (e.g., ValueError)
for any unrecognized strategy_name. Update the conditional that sets
self.strategy (the block that checks strategy_name and instantiates
TokenChunker, SentenceChunker, and RecursiveCharacterChunker) to add an explicit
elif strategy_name == 'recursive' path and an else that raises an error
referencing the invalid strategy_name so typos or stale configs fail fast.

In `@nodes/src/nodes/chunker/IInstance.py`:
- Around line 164-167: The current branch only calls self.preventDefault() when
chunked_docs is truthy, so if no chunks are produced the original batch is not
suppressed; update the method in IInstance (the block using chunked_docs and
self.instance.writeDocuments) to always call self.preventDefault() after
handling the incoming batch — keep the writeDocuments call conditional on
chunked_docs but move or add a call to self.preventDefault() so it executes
unconditionally (ensuring the chunker always consumes the original batch even
when zero chunks are emitted).

In `@test/nodes/test_chunker.py`:
- Around line 395-400: The test currently only asserts a bound on
call_counts['decode'] but can miss implementations that still decode large
prefix slices; modify the test around the decode call tracking (the mock that
increments call_counts['decode']) to also record the input token lengths (e.g.,
push len(tokens) into a list like decode_lengths), then add an assertion after
running the chunker that no decode call was invoked with a large-prefix length
(for example assert max(decode_lengths) <= max_chunk_len or assert all(l <=
threshold for l in decode_lengths)), and keep the existing linear call-count
check; reference the existing names call_counts, decode (mock), chunks, and
tokens when adding the length tracking and the new assertion.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: aad80c4d-79fa-4ca7-b14f-d2b9b077b9e6

📥 Commits

Reviewing files that changed from the base of the PR and between 5362aad and dae8bf1.

📒 Files selected for processing (7)
  • nodes/src/nodes/chunker/IGlobal.py
  • nodes/src/nodes/chunker/IInstance.py
  • nodes/src/nodes/chunker/__init__.py
  • nodes/src/nodes/chunker/chunker_strategies.py
  • nodes/src/nodes/chunker/requirements.txt
  • nodes/src/nodes/chunker/services.json
  • test/nodes/test_chunker.py

Comment thread nodes/src/nodes/chunker/chunker_strategies.py Outdated
Comment thread nodes/src/nodes/chunker/chunker_strategies.py Outdated
Comment thread nodes/src/nodes/chunker/IGlobal.py Outdated
Comment thread nodes/src/nodes/chunker/IInstance.py Outdated
Comment thread test/nodes/test_chunker.py Outdated
@nihalnihalani
nihalnihalani force-pushed the feat/chunker-node-clean branch from dae8bf1 to 502fad2 Compare May 15, 2026 20:46

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🤖 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 `@nodes/src/nodes/chunker/chunker_strategies.py`:
- Around line 337-341: The fallback decode currently calls
encoder.decode(chunk_tokens, errors='replace') which tiktoken.decode does not
accept and will raise TypeError; update the exception handling in the chunking
logic that assigns chunk_text so the fallback does not pass an unsupported
errors kwarg—either call encoder.decode(chunk_tokens) without errors, or iterate
chunk_tokens and decode each token safely (e.g., using
encoder.decode_single_token_bytes or per-token decode with try/except) and join
the results, or log the decode failure and return an empty string; adjust the
code around encoder.decode and chunk_tokens (and any variable named chunk_text)
accordingly.

In `@nodes/src/nodes/chunker/IInstance.py`:
- Around line 94-98: The DocMetadata construction in IInstance (calling
DocMetadata(**metadata_data)) can raise TypeError if metadata_data contains
unknown keys; update the code in the method where metadata_data is built to
either (a) filter metadata_data to only the fields accepted by DocMetadata
before calling DocMetadata(**metadata_data) using DocMetadata.__annotations__ or
a known field list, or (b) call DocMetadata.model_validate(metadata_data) /
DocMetadata.parse_obj (whichever validation API is available) to tolerate extra
fields; ensure you still set defaults for 'objectId' and 'chunkId' on the
filtered/validated data and return the same tuple structure.

In `@nodes/src/nodes/chunker/services.json`:
- Around line 54-65: The schema entries "chunker.chunk_size" and
"chunker.chunk_overlap" currently use "type": "number" which allows fractional
and negative values; change "chunker.chunk_size" to "type": "integer" with
"minimum": 1 and change "chunker.chunk_overlap" to "type": "integer" with
"minimum": 0 to ensure whole non-negative counts. Also add a runtime/config
validation (or a JSON Schema cross-field rule if your validator supports it) to
enforce chunker.chunk_overlap < chunker.chunk_size so overlap cannot equal or
exceed the chunk size.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 33af72ec-c33b-4f85-a558-f5695936d2e1

📥 Commits

Reviewing files that changed from the base of the PR and between dae8bf1 and 502fad2.

📒 Files selected for processing (7)
  • nodes/src/nodes/chunker/IGlobal.py
  • nodes/src/nodes/chunker/IInstance.py
  • nodes/src/nodes/chunker/__init__.py
  • nodes/src/nodes/chunker/chunker_strategies.py
  • nodes/src/nodes/chunker/requirements.txt
  • nodes/src/nodes/chunker/services.json
  • test/nodes/test_chunker.py

Comment thread nodes/src/nodes/chunker/chunker_strategies.py Outdated
Comment thread nodes/src/nodes/chunker/IInstance.py Outdated
Comment thread nodes/src/nodes/chunker/services.json
@nihalnihalani

Copy link
Copy Markdown
Collaborator Author

Heads-up on CI: the Ruff check is currently red on this PR because three pre-existing files on develop (nodes/src/nodes/llm_vision_gemini/IGlobal.py, nodes/src/nodes/llm_vision_gemini/gemini_vision.py, nodes/src/nodes/llm_vision_openai/openai_vision.py) fail ruff format --check. The format drift surfaced after PR #723's cleanup merged on 2026-05-04 and currently affects every open PR that branches from develop — develop's own CI run on sha 5362aad1 is in conclusion: failure for the same reason.

I've opened #900 (chore(format): apply ruff format to llm_vision_* files) as a focused 3-file mechanical ruff format pass to clear this. Once #900 merges, I'll rebase this branch onto fresh develop and Ruff will go green.

This PR's code-level CI is otherwise fully green: Build / Ubuntu, Build / Windows, Build / macOS, and gitleaks all pass.

@nihalnihalani
nihalnihalani force-pushed the feat/chunker-node-clean branch from 502fad2 to 09d1c46 Compare May 15, 2026 22:08

@coderabbitai coderabbitai Bot 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.

♻️ Duplicate comments (6)
nodes/src/nodes/chunker/IGlobal.py (1)

79-96: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail fast on unknown strategy values.

The current else silently defaults to recursive. A typo in config can run the wrong algorithm without surfacing an error.

💡 Suggested fix
             if strategy_name == 'token':
                 self.strategy = TokenChunker(
                     chunk_size=chunk_size,
                     chunk_overlap=chunk_overlap,
                     encoding_name=encoding_name,
                 )
             elif strategy_name == 'sentence':
                 self.strategy = SentenceChunker(
                     chunk_size=chunk_size,
                     chunk_overlap=chunk_overlap,
                 )
-            else:
-                # Default to recursive character chunker
+            elif strategy_name == 'recursive':
                 self.strategy = RecursiveCharacterChunker(
                     chunk_size=chunk_size,
                     chunk_overlap=chunk_overlap,
                 )
+            else:
+                raise ValueError(f'Unknown chunking strategy: {strategy_name}')
🤖 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 `@nodes/src/nodes/chunker/IGlobal.py` around lines 79 - 96, The strategy
selection currently falls through to RecursiveCharacterChunker for any
unrecognized strategy_name; instead, validate strategy_name when building the
strategy (the block that sets self.strategy) and raise a clear exception if it
is not one of the supported values ('token' or 'sentence'); keep TokenChunker
and SentenceChunker branches as-is and remove the silent defaulting to
RecursiveCharacterChunker (or make RecursiveCharacterChunker explicit only for a
supported value), referencing the variables/class names strategy_name,
self.strategy, TokenChunker, SentenceChunker, and RecursiveCharacterChunker so
the caller gets an immediate error on typos or unsupported strategies.
nodes/src/nodes/chunker/chunker_strategies.py (3)

129-145: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Overlap prefix can violate configured chunk_size.

raw chunks are already bounded, then overlap is prepended unconditionally, so emitted chunks can exceed self.chunk_size.

💡 Suggested fix (cap overlap to remaining budget)
         for i, raw in enumerate(raw_chunks):
-            # Compute overlap prefix from the previous chunk
-            if i > 0 and self.chunk_overlap > 0:
-                prev = raw_chunks[i - 1]
-                overlap_text = prev[-self.chunk_overlap :]
-                chunk_text = overlap_text + raw
-            else:
-                chunk_text = raw
+            overlap_len = 0
+            if i > 0 and self.chunk_overlap > 0:
+                prev = raw_chunks[i - 1]
+                overlap_len = min(self.chunk_overlap, max(0, self.chunk_size - len(raw)))
+                overlap_text = prev[-overlap_len:] if overlap_len > 0 else ''
+                chunk_text = overlap_text + raw
+            else:
+                chunk_text = raw
@@
-            if i > 0 and self.chunk_overlap > 0:
-                overlap_start = max(0, start_char - self.chunk_overlap)
+            if overlap_len > 0:
+                overlap_start = max(0, start_char - overlap_len)
                 start_char = overlap_start
🤖 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 `@nodes/src/nodes/chunker/chunker_strategies.py` around lines 129 - 145, The
code unconditionally prepends full overlap_text to raw, which can make
chunk_text exceed self.chunk_size; change the logic in the chunk assembly (the
block using raw_chunks, i, raw, chunk_text and self.chunk_overlap) to compute
overlap_len = min(self.chunk_overlap, max(0, self.chunk_size - len(raw))) and
then take overlap_text = prev[-overlap_len:] (only if overlap_len > 0) before
forming chunk_text = overlap_text + raw; also update the overlap_start
adjustment (used to compute start_char) to subtract overlap_len rather than
self.chunk_overlap so start_char/end_char remain consistent with the actual
prepended bytes.

97-107: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve boundary separators in overflow splits.

When a candidate overflows, the separator between current and part is dropped. That loses source characters and makes chunk span metadata non-faithful.

🤖 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 `@nodes/src/nodes/chunker/chunker_strategies.py` around lines 97 - 107, The
overflow branch in _split_text drops the separator between current and part,
losing characters and breaking chunk spans; when len(part) > self.chunk_size and
you recurse (sub_chunks = self._split_text(part, remaining_separators)),
preserve the boundary separator by including it in the recursive input or by
prefixing the first sub_chunk with that separator before extending chunks.
Update _split_text to determine the separator string that was used between
current and part (from the separators sequence) and either prepend it to part
when calling _split_text(part, remaining_separators) or insert it at the front
of the first element in sub_chunks, then extend chunks and reset current = ''.
Ensure this uses the existing symbols current, part, remaining_separators,
chunks, and self.chunk_size so span metadata remains faithful.

337-341: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fallback decode path calls unsupported tiktoken API.

Line 340 and Line 371 pass errors='replace' to encoder.decode(...); that can raise TypeError in the exception path and fail chunking entirely.

💡 Suggested fix (byte-safe fallback)
             try:
                 chunk_text = encoder.decode(chunk_tokens)
             except Exception:
-                chunk_text = encoder.decode(chunk_tokens, errors='replace')
+                chunk_bytes = b''.join(encoder.decode_single_token_bytes(tok) for tok in chunk_tokens)
+                chunk_text = chunk_bytes.decode('utf-8', errors='replace')
@@
                 try:
                     step_text = encoder.decode(step_tokens)
                 except Exception:
-                    step_text = encoder.decode(step_tokens, errors='replace')
+                    step_bytes = b''.join(encoder.decode_single_token_bytes(tok) for tok in step_tokens)
+                    step_text = step_bytes.decode('utf-8', errors='replace')
Does Python tiktoken Encoding.decode accept an `errors` keyword argument?

Also applies to: 369-372

🤖 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 `@nodes/src/nodes/chunker/chunker_strategies.py` around lines 337 - 341, The
fallback path should not call encoder.decode(..., errors='replace') because
tiktoken's Encoding.decode doesn't accept an errors kwarg; instead, catch the
original exception and build chunk_text by decoding tokens individually (call
encoder.decode([token]) inside a loop), substituting a replacement character
(e.g. '\uFFFD') for any token that raises when decoded, then join those parts;
apply this change for the occurrences that set chunk_text (the encoder.decode
fallback around chunk_text and the similar block at lines 369-372) so both use
the per-token safe decode strategy rather than relying on an unsupported errors
parameter.
nodes/src/nodes/chunker/IInstance.py (1)

164-167: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Always consume the original batch after chunking attempt.

preventDefault() is currently conditional. If no chunks are emitted, original documents can continue unchunked.

💡 Suggested fix
         if chunked_docs:
             debug(f'Chunker emitting {len(chunked_docs)} chunks')
             self.instance.writeDocuments(chunked_docs)
-            return self.preventDefault()
+        return self.preventDefault()
🤖 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 `@nodes/src/nodes/chunker/IInstance.py` around lines 164 - 167, The current
logic only calls self.preventDefault() when chunked_docs is truthy, allowing the
original documents to continue unchunked; fix by always consuming the original
batch after attempting to chunk: keep the
self.instance.writeDocuments(chunked_docs) call inside the if chunked_docs
block, then unconditionally call and return self.preventDefault() after that
block (so chunking still writes when present, but the original batch is always
prevented/consumed). Reference: chunked_docs, self.instance.writeDocuments, and
self.preventDefault().
nodes/src/nodes/chunker/services.json (1)

54-65: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use integer schema types for chunk counts.

chunk_size/chunk_overlap are count fields; "number" allows fractional values that get silently coerced later.

💡 Suggested schema tightening
 		"chunker.chunk_size": {
-			"type": "number",
+			"type": "integer",
 			"title": "Chunk size",
 			"description": "Maximum size of each chunk (characters for recursive/sentence, tokens for token strategy)",
-			"default": 1000
+			"default": 1000,
+			"minimum": 1
 		},
 		"chunker.chunk_overlap": {
-			"type": "number",
+			"type": "integer",
 			"title": "Chunk overlap",
 			"description": "Number of characters or tokens to overlap between consecutive chunks",
-			"default": 200
+			"default": 200,
+			"minimum": 0
 		},
🤖 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 `@nodes/src/nodes/chunker/services.json` around lines 54 - 65, The schema uses
"number" for chunker.chunk_size and chunker.chunk_overlap which permits
fractional values; change their JSON Schema "type" to "integer" (and optionally
add "minimum": 0) so these count fields only accept whole non-negative integers;
update the entries named "chunker.chunk_size" and "chunker.chunk_overlap" in
services.json accordingly.
🤖 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.

Duplicate comments:
In `@nodes/src/nodes/chunker/chunker_strategies.py`:
- Around line 129-145: The code unconditionally prepends full overlap_text to
raw, which can make chunk_text exceed self.chunk_size; change the logic in the
chunk assembly (the block using raw_chunks, i, raw, chunk_text and
self.chunk_overlap) to compute overlap_len = min(self.chunk_overlap, max(0,
self.chunk_size - len(raw))) and then take overlap_text = prev[-overlap_len:]
(only if overlap_len > 0) before forming chunk_text = overlap_text + raw; also
update the overlap_start adjustment (used to compute start_char) to subtract
overlap_len rather than self.chunk_overlap so start_char/end_char remain
consistent with the actual prepended bytes.
- Around line 97-107: The overflow branch in _split_text drops the separator
between current and part, losing characters and breaking chunk spans; when
len(part) > self.chunk_size and you recurse (sub_chunks = self._split_text(part,
remaining_separators)), preserve the boundary separator by including it in the
recursive input or by prefixing the first sub_chunk with that separator before
extending chunks. Update _split_text to determine the separator string that was
used between current and part (from the separators sequence) and either prepend
it to part when calling _split_text(part, remaining_separators) or insert it at
the front of the first element in sub_chunks, then extend chunks and reset
current = ''. Ensure this uses the existing symbols current, part,
remaining_separators, chunks, and self.chunk_size so span metadata remains
faithful.
- Around line 337-341: The fallback path should not call encoder.decode(...,
errors='replace') because tiktoken's Encoding.decode doesn't accept an errors
kwarg; instead, catch the original exception and build chunk_text by decoding
tokens individually (call encoder.decode([token]) inside a loop), substituting a
replacement character (e.g. '\uFFFD') for any token that raises when decoded,
then join those parts; apply this change for the occurrences that set chunk_text
(the encoder.decode fallback around chunk_text and the similar block at lines
369-372) so both use the per-token safe decode strategy rather than relying on
an unsupported errors parameter.

In `@nodes/src/nodes/chunker/IGlobal.py`:
- Around line 79-96: The strategy selection currently falls through to
RecursiveCharacterChunker for any unrecognized strategy_name; instead, validate
strategy_name when building the strategy (the block that sets self.strategy) and
raise a clear exception if it is not one of the supported values ('token' or
'sentence'); keep TokenChunker and SentenceChunker branches as-is and remove the
silent defaulting to RecursiveCharacterChunker (or make
RecursiveCharacterChunker explicit only for a supported value), referencing the
variables/class names strategy_name, self.strategy, TokenChunker,
SentenceChunker, and RecursiveCharacterChunker so the caller gets an immediate
error on typos or unsupported strategies.

In `@nodes/src/nodes/chunker/IInstance.py`:
- Around line 164-167: The current logic only calls self.preventDefault() when
chunked_docs is truthy, allowing the original documents to continue unchunked;
fix by always consuming the original batch after attempting to chunk: keep the
self.instance.writeDocuments(chunked_docs) call inside the if chunked_docs
block, then unconditionally call and return self.preventDefault() after that
block (so chunking still writes when present, but the original batch is always
prevented/consumed). Reference: chunked_docs, self.instance.writeDocuments, and
self.preventDefault().

In `@nodes/src/nodes/chunker/services.json`:
- Around line 54-65: The schema uses "number" for chunker.chunk_size and
chunker.chunk_overlap which permits fractional values; change their JSON Schema
"type" to "integer" (and optionally add "minimum": 0) so these count fields only
accept whole non-negative integers; update the entries named
"chunker.chunk_size" and "chunker.chunk_overlap" in services.json accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f645ca2c-4075-4927-a77c-c82f0093b952

📥 Commits

Reviewing files that changed from the base of the PR and between 502fad2 and 09d1c46.

📒 Files selected for processing (10)
  • nodes/src/nodes/chunker/IGlobal.py
  • nodes/src/nodes/chunker/IInstance.py
  • nodes/src/nodes/chunker/__init__.py
  • nodes/src/nodes/chunker/chunker_strategies.py
  • nodes/src/nodes/chunker/requirements.txt
  • nodes/src/nodes/chunker/services.json
  • nodes/src/nodes/llm_vision_gemini/IGlobal.py
  • nodes/src/nodes/llm_vision_gemini/gemini_vision.py
  • nodes/src/nodes/llm_vision_openai/openai_vision.py
  • test/nodes/test_chunker.py

nihalnihalani added a commit to nihalnihalani/rocketride-server that referenced this pull request May 15, 2026
Those nodes are tracked in PRs rocketride-org#897 and rocketride-org#899 separately. They were
accidentally included on this branch via an older merge (commit 905ab76)
and were causing a dynamic-test failure unrelated to the Helm chart.
Also restored upstream versions of asset files that were drifted by the
same merge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nihalnihalani added a commit to nihalnihalani/rocketride-server that referenced this pull request May 15, 2026
Prior run failed on `from rocketlib import normalize_tool_input` —
the symbol IS exported from rocketlib/__init__.py line 63/141 on this
branch (identical to develop). Same import passes on companion PRs
rocketride-org#897/rocketride-org#898/rocketride-org#899/rocketride-org#900 with the same merge of develop, so this is a
stale build-cache flake.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@nihalnihalani

nihalnihalani commented May 16, 2026 •

Copy link
Copy Markdown
Collaborator Author

Could you please review the new commit?

@stepmikhaylov stepmikhaylov left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Change requests

  • Missing "experimental" capability — nodes/src/nodes/chunker/services.json:5 has "capabilities": []. New nodes must include "experimental" (cf. agent_rocketride, tool_exa_search).

  • Test file in wrong directory — test/nodes/test_chunker.py does not follow the convention. Tests must be placed under nodes/test/chunker/.

  • Mocks for built-in modules rocketlib, ai.*, and depends — test/nodes/test_chunker.py:434–521 (TestChunkerLifecycle._install_stubs) installs sys.modules stubs for rocketlib, ai, ai.common, ai.common.config, ai.common.schema, and depends. These are provided by the build interpreter and must not be mocked.

  • File-path-based imports bypass interpreter path configuration — test/nodes/test_chunker.py:21–37 uses Path(__file__).resolve().parent.parent.parent / ... and importlib.util.spec_from_file_location() to locate modules by filesystem path. Import by package name instead; the build interpreter configures paths correctly.

  • Dead code: _pending_docs buffer — nodes/src/nodes/chunker/IInstance.py: writeDocuments() emits synchronously and never populates _pending_docs, making _flush_documents(), the open() initializer, and closing() unreachable.

@stepmikhaylov

Copy link
Copy Markdown
Collaborator

Hi @nihalnihalani

Thank you for the contribution! The chunker node is a solid addition — the strategy implementations are well-structured and the core logic is clean. Please address the change requests below before we can merge.

@nihalnihalani

nihalnihalani commented May 19, 2026 •

Copy link
Copy Markdown
Collaborator Author

Update — addressed @stepmikhaylov + CodeRabbit feedback (pushed bc93386)

stepmikhaylov structural items:

  • ✅ services.json capabilities: ["experimental"]
  • ✅ Test relocated: test/nodes/test_chunker.py → nodes/test/chunker/test_all.py
  • ✅ Dropped sys.modules stubs for rocketlib/ai.*/depends; tests now use real package imports gated by pytest.importorskip('chunker') (matches nodes/test/local_text_output/test_instance.py pattern)
  • ✅ Replaced spec_from_file_location with package imports (from chunker.chunker_strategies import ...)
  • ✅ Removed dead _pending_docs buffer + _flush_documents() + closing()
  • ⚠️ Kept open() — it resets self.chunkId = 0 per Entry, which is observable in emitted metadata; only stripped its _pending_docs = [] line. Happy to drop entirely if you'd prefer.

CodeRabbit correctness fixes:

  • ✅ Preserve boundary separator in _split_text overflow so start_char/end_char stay faithful
  • ✅ Cap overlap to remaining budget in chunk() (min(chunk_overlap, max(0, chunk_size - len(raw))))
  • ✅ Explicit elif 'recursive' + ValueError on unknown strategy
  • ✅ Always preventDefault() (moved out of if chunked_docs: block)
  • ✅ Removed unsupported errors='replace' from tiktoken decode(); added _safe_decode per-token fallback via decode_single_token_bytes
  • ✅ services.json chunk_size/chunk_overlap typed integer with bounds; cross-field overlap < size enforced at runtime in IGlobal.beginGlobal
  • ⚠️ Pushback on CodeRabbit's DocMetadata(**metadata_data) TypeError claim — DocMetadata uses model_config = ConfigDict(extra='allow'), so unknown fields don't raise. Still switched to model_validate defensively to avoid a positional-arg collision risk.

ruff check + ruff format clean. Devil's-advocate review of the diff against scope ran. Re-requesting @stepmikhaylov, @jmaionchi, @Rod-Christensen.

nihalnihalani added a commit to nihalnihalani/rocketride-server that referenced this pull request May 19, 2026
- Set capabilities to ["experimental"] consistent with sibling new nodes
  (PRs rocketride-org#897/rocketride-org#898).
- Add a "test" block per docs/README-node-testing.md so the dynamic test
  runner exercises the node through the real server. The "balanced"
  profile re-ranks three pre-scored documents and asserts the top result
  mentions "machine learning".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@nihalnihalani

Copy link
Copy Markdown
Collaborator Author

Following up: the stepmikhaylov structural items and CodeRabbit correctness items have all been addressed in bc93386. Specifically: tests relocated to nodes/test/chunker/test_all.py, capabilities marked experimental, dead code removed, separator preservation in recursive split, overlap capped below chunk_size, safe tiktoken decode fallback without unsupported kwargs, and services.json chunk sizing fields tightened to integer typing with minimums. Two items kept as-is with reasoning: open() retained because chunkId resets per Entry rather than per document, and DocMetadata(**metadata_data) verified safe since DocMetadata uses extra='allow'. CI is green across Ubuntu, macOS ARM64, and Windows. @stepmikhaylov @Rod-Christensen @jmaionchi could you take another look when you get a chance?

stepmikhaylov pushed a commit that referenced this pull request May 19, 2026
* fix(deps): update pnpm-lock.yaml for lucide-react dependency

* chore: optimize SVG images via ImgBot (2.95% reduction)

Automated image optimization by ImgBot. 85 SVG files optimized with total size reduction from 462.69kb to 449.05kb (2.95%).

* feat(nodes): add text chunker and hybrid search nodes

Three chunking strategies (recursive, sentence, token) with configurable size/overlap. Hybrid search node with BM25 + vector RRF fusion. All CodeRabbit review feedback addressed including critical writeAnswers contract fix, score propagation, RRF validation, and typing modernization.

* feat(engine): add Kubernetes Helm chart for production deployment

- Add Helm chart with engine deployment, service, HPA, ingress, configmap, secret
- Add optional PostgreSQL+pgvector StatefulSet with PVC persistence
- Add optional ChromaDB deployment
- Configurable resource limits, replicas, probes, TLS, service accounts
- Helm test for /ping connectivity verification
- Comprehensive values.yaml with production-ready defaults

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(engine): address code review feedback on Helm chart PR

- Fix postgres probes to use Helm template values instead of shell expansion
- Remove Milvus section (use official subchart instead), clean up NOTES.txt
- Change default postgres password to 'changeme'
- Add secret checksum annotation for automatic pod restart on secret changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(helm): address CodeRabbit review feedback

- Wrap pg_isready probe commands in sh -c for proper env var expansion
- Guard HPA metrics list to avoid empty array when both targets unset
- Add email field to Chart.yaml maintainers entry

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(helm): address reviewer feedback on Helm chart PR #510

- Add fail guard in HPA when autoscaling enabled without metrics
- Add checksum/postgres-secret annotation to both engine deployment and
  postgres StatefulSet for automatic rollout on credential changes
- Add WARNING comments for default Postgres credentials in values.yaml
- Fix NOTES.txt NodePort address lookup to filter ExternalIP first
- Add securityContext and resource limits to test pod
- Document Chroma PVC lifecycle (intentional data persistence)
- Add GPU resource/nodeSelector/tolerations documentation in values.yaml
- Document HPA limitation for GPU inference workloads
- Clarify chart is for community/self-hosted deployments
- Add Helm templates to .prettierignore (Go template syntax)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(helm): show autoscaling info in NOTES.txt and harden container security

- NOTES.txt now shows HPA min/max replicas when autoscaling is enabled
  instead of the static replicaCount which is misleading
- Set readOnlyRootFilesystem: true in default securityContext
- Add /tmp emptyDir volume mount to deployment template so the engine
  has a writable scratch directory with read-only root filesystem

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(helm): remove bundled Postgres and ChromaDB, add external service examples

This chart should not bundle stateful services that are better managed
externally. Users should provision PostgreSQL and ChromaDB via managed
cloud services or dedicated Helm charts.

- Delete postgres-statefulset.yaml and chroma-deployment.yaml templates
- Remove postgres/chroma sections from values.yaml
- Remove postgres env vars and checksum annotation from deployment.yaml
- Remove postgres secret helper from _helpers.tpl
- Add deploy/helm/examples/ with external-postgres.yaml, external-chroma.yaml
- Add examples/README.md documenting how to use managed services

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(helm): add architecture docs and GPU limitation warnings

- Create deploy/helm/ARCHITECTURE.md with community vs SaaS comparison
- Add WARNING comment in hpa.yaml about CPU/memory HPA being unsuitable
  for GPU inference workloads
- Create deploy/helm/examples/keda-gpu-scaling.yaml with KEDA ScaledObject
  example for GPU-aware autoscaling
- Add ARCHITECTURE.md reference in Chart.yaml

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(helm): add GPU support with nodeSelector and tolerations

- Add engine.gpu config section (enabled, count, nodeSelector, tolerations)
- Conditionally add nvidia.com/gpu resource limit in deployment template
- Merge GPU nodeSelector and tolerations with base engine values
- Create deploy/helm/examples/gpu-values.yaml with working GPU example

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(helm): add secret validation and update appVersion to 3.1.2

- Add rocketride.validateSecrets template that fails if neither
  engine.existingSecret nor engine.secrets is configured
- Invoke validation at the top of the deployment template
- Update Chart.yaml appVersion from 3.1.1 to 3.1.2 to match package.json
- Verify health endpoint: /ping on port 5565 is correct (confirmed via
  Dockerfile.engine EXPOSE and AI web server route registration)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(helm): address post-review findings on Kubernetes Helm chart

- Lower engine.replicaCount default 2 → 1 for single-node/lab users;
  update autoscaling.minReplicas to match; add HA tuning note in comment
- Add operator-facing README.md covering install/upgrade/uninstall,
  existingSecret pattern, examples pointer, and HA tuning guide
- Add helm-lint + kubeconform CI jobs to ci.yml, path-filtered to
  deploy/helm/** via dorny/paths-filter (matches existing ci.yml style)

Exec audit: confirmed no exec.command blocks with $(VAR) syntax
exist anywhere in deploy/helm/rocketride/templates/ — clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add helm-lint to ci-ok gatekeeper needs

helm-lint and helm-changes were added to ci.yml but omitted from the
ci-ok aggregator job, meaning helm failures would not block PR merges.
Added both to needs[] and updated the echo statement to include their
results in the CI OK diagnostic output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(helm): support external secret rollout bump

* style(tests): ruff format chunker and hybrid search tests

* chore(helm): remove out-of-scope chunker/search_hybrid scope creep

Those nodes are tracked in PRs #897 and #899 separately. They were
accidentally included on this branch via an older merge (commit 905ab76)
and were causing a dynamic-test failure unrelated to the Helm chart.
Also restored upstream versions of asset files that were drifted by the
same merge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: imgbot[bot] <31301654+imgbot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@stepmikhaylov

Copy link
Copy Markdown
Collaborator

Change Requests

Tests assume the build interpreter — no fallbacks for vanilla pytest
The build interpreter is mandatory; rocketlib, ai.*, and depends are always present. Tests must not guard against their absence or skip when they are missing.

  1. sys.path manipulation via Path(__file__) — nodes/test/chunker/test_all.py:22–24
    Remove the manual sys.path prefix; the build interpreter configures paths correctly.

  2. pytest.importorskip hides missing-interpreter failures — test_all.py:29
    Replace with a direct import chunker; a real ModuleNotFoundError should surface as a collection error, not a silent skip.

  3. pytest.skip in _import_node_classes() hides failures — test_all.py:377–378
    Remove the try/except skip; let ModuleNotFoundError propagate so broken imports are visible.

  4. pytest.skip in _import_schema() hides failures — test_all.py:385–386
    Same as above; remove the guard and let the error surface.

@stepmikhaylov

Copy link
Copy Markdown
Collaborator

@nihalnihalani, Thank you for addressing the previous change requests — please address the ones posted above.

@nihalnihalani

Copy link
Copy Markdown
Collaborator Author

Change requests addressed — requesting re-review. @stepmikhaylov @jmaionchi @Rod-Christensen @kwit75

  • services.json carries "capabilities": ["experimental"]
  • Suite at nodes/test/chunker/test_all.py (legacy test/nodes/test_chunker.py removed)
  • No sys.modules stubs for built-in rocketlib/ai.*/depends — real imports guarded by importorskip
  • Imports by package name (no spec_from_file_location)
  • Dead _pending_docs/_flush_documents/closing removed; writeDocuments emits synchronously
  • Also strengthened the O(n²) decode guard per CodeRabbit (assert max decode input <= chunk_size)

ruff clean; node tests run under the build interpreter in CI.

@nihalnihalani

Copy link
Copy Markdown
Collaborator Author

@stepmikhaylov — addressed the build-interpreter test points; Build is green again across Ubuntu/Windows/macOS.

  • pytest.importorskip('chunker') → direct import: done. A missing build-interpreter module now surfaces as a collection error, not a skip.
  • pytest.skip in _import_node_classes(): removed — the imports are direct so failures propagate.
  • pytest.skip in _import_schema(): removed, same.
  • sys.path prefix (nodes/src/nodes): pushing back on removing this one. Dropping it fails collection under the build interpreter too — CI errored with ModuleNotFoundError: No module named 'chunker' at test_all.py:21 (run). The node source isn't on the interpreter's import path by default, so every existing node suite prepends it (local_text_output/test_instance.py, milvus/, pinecone/, tool_git/, tool_mcp_client/, weaviate/, …). I kept that single line; with the skip fallbacks removed, a missing rocketlib/ai/depends still errors out as you asked. If there's a different path mechanism you'd prefer, point me at a node test that imports without the prefix and I'll match it.

nihalnihalani added a commit to nihalnihalani/rocketride-server that referenced this pull request May 21, 2026
The build interpreter provides rocketlib, ai.* and depends (plus the native
engLib) at runtime, and CI installs tiktoken/json5. Mirror the validated
chunker pattern (PR rocketride-org#897): keep only the nodes/src/nodes sys.path insert and
import the context_optimizer package by name. Remove the engLib/depends
stubs, the rocketlib/ai/client-python sys.path additions, the stale
rocketlib/ai sys.modules purge, and the test-local tiktoken/json5 stub
injection. No skips or fallbacks: outside the build interpreter, imports
error at collection by design.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nihalnihalani added a commit to nihalnihalani/rocketride-server that referenced this pull request May 21, 2026
Align the search_hybrid node tests with the maintainer's "build interpreter
is mandatory" philosophy (mirrors PR rocketride-org#897):

- Put nodes/src/nodes on sys.path and import the engine, IInstance and IGlobal
  by PACKAGE NAME (search_hybrid.hybrid_search / .IInstance / .IGlobal) instead
  of importlib.util.spec_from_file_location. File-path-based imports bypass
  interpreter path configuration.
- Import the real schema types directly: from ai.common.schema import
  Answer, Doc, Question, QuestionText. QuestionText replaces the duck-typed
  _SubQuestion stand-in.
- Remove the _HAS_ROCKETLIB / _HAS_AI_SCHEMA find_spec gates, the skipif on
  TestIGlobalAlphaClamp, and the in-fixture / in-test pytest.skip fallbacks.
  rocketlib, ai.* and depends are provided by the build interpreter and must
  not be stubbed; collection/fixture error when absent is the intended
  behavior.
- Keep the rank_bm25 third-party compute-lib mock (nodes/test/mocks/rank_bm25)
  and its module-scoped install/teardown; it is the only stub, on par with the
  openai / elasticsearch mocks.
- Preserve the writeAnswers single-Answer contract (ans.answer) and the
  keyword QuestionText(text=...) construction.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nihalnihalani added a commit to nihalnihalani/rocketride-server that referenced this pull request Jul 7, 2026
Sync PR rocketride-org#897 branch with develop (232 commits behind). No conflicts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
nihalnihalani and others added 15 commits September 4, 2026 03:33
Adds a `chunker` pipeline node that splits long documents into smaller
chunks suitable for embedding and retrieval. Multiple chunking strategies
are bundled: recursive character splitting, sentence-aware splitting, and
token-based splitting.

Components:
- IGlobal — standard lifecycle with depends() for SDK auto-install.
- IInstance — receives documents, applies the configured strategy, writes
  chunked documents to the documents output lane.
- chunker_strategies — pluggable strategy implementations.
- services.json — strategy profiles with configurable chunk_size and overlap.

Resubmission of the chunker portion of closed PR rocketride-org#499 per maintainer
feedback: one PR per node, clean branch from develop, no unrelated
file changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Set capabilities to ["experimental"] (required for new nodes) and switch
chunk_size/chunk_overlap from "number" to "integer" with minimum bounds.
Cross-field validation (overlap < size) remains enforced at runtime in
IGlobal.beginGlobal and in each strategy constructor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add an explicit elif branch for 'recursive' and raise ValueError when the
configured strategy_name is none of recursive/sentence/token. Previously a
typo (e.g. 'recurisve') would silently fall through to recursive, masking
bad config in production.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…fault

writeDocuments() emits chunks synchronously and never populated the
_pending_docs buffer, so _flush_documents()/closing()/close() were dead
helpers that risked re-emitting if anything ever did populate the buffer.
Remove them; keep open() solely to reset chunkId per object.

Also always call preventDefault() after handling a batch: previously, if
every input document produced zero chunks (empty/whitespace), the
originals leaked downstream.

Switch DocMetadata construction to model_validate() when available; this
avoids colliding with DocMetadata.__init__'s positional ``pInstance`` arg
and matches the pattern already used for Doc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three correctness fixes in chunker_strategies:

1. RecursiveCharacterChunker._split_text: when a separator-driven emit
   happens, append the separator to the emitted chunk instead of dropping
   it. Without this, every overflow silently lost separator characters,
   causing chunk reconstruction to diverge from the source text.

2. RecursiveCharacterChunker.chunk: cap the prepended overlap to the
   remaining budget (chunk_size - len(raw)) so an already-full chunk plus
   overlap can never exceed chunk_size. Use the *applied* overlap when
   adjusting start_char so offsets stay truthful.

3. TokenChunker: tiktoken's Encoding.decode does not accept an ``errors=``
   kwarg (passing one raises TypeError). Replace the bogus fallback with
   _safe_decode, which falls back to decode_single_token_bytes per token
   and uses errors='replace' at the bytes -> str boundary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Match the per-node test convention used by agent_rocketride,
tool_exa_search and local_text_output: a ``test_all.py`` under
``nodes/test/<node>/`` rather than the legacy ``test/nodes/test_*.py``
path.

Drop the sys.modules stubs for rocketlib/ai.common.schema/depends
(maintainer policy: do not mock built-in interpreter modules) and replace
the ``spec_from_file_location`` path imports with normal package imports
(``from chunker.chunker_strategies import ...``) after putting
``nodes/src/nodes`` on sys.path.

The package import is guarded by ``pytest.importorskip('chunker')`` so a
vanilla local pytest run (without the build interpreter that provides
rocketlib) skips cleanly instead of erroring at collection.

Coverage retained: every assertion from the original suite plus three new
regression tests for the fixes in this PR — separator preservation on
overflow, overlap capping under chunk_size, preventDefault on empty
batches, ValueError on unknown strategy_name, and _safe_decode fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Strengthen the O(n^2) decode regression guard: track decode input token
lengths in the mock encoder and assert max(decoded_lengths) <= chunk_size,
in addition to the existing linear call-count assertion. A growing-prefix
decode regression can keep the call count linear while inflating individual
decode inputs, which the count-only check would miss.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per maintainer review: the build interpreter is mandatory and configures
import paths. Remove the sys.path manipulation, pytest.importorskip, and the
try/except pytest.skip guards in _import_node_classes()/_import_schema() so a
missing module surfaces as a collection error instead of a silent skip.
Removing pytest.importorskip and the try/except pytest.skip guards (per review)
so missing build-interpreter modules surface as collection errors instead of
silent skips. Restored the nodes/src/nodes sys.path prefix: without it the node
package is not importable even under the build interpreter (CI: ModuleNotFound
'chunker'), matching every other node suite (local_text_output, milvus, ...).
The chunker suite's addition perturbs pytest's collection order, which
exposed a latent fragility in the shared "prepend nodes/src/nodes" import
pattern: `nodes/test/<node>/` is itself importable as `<node>` (it has an
`__init__.py`), and a plain `if str(NODES_SRC) not in sys.path` guard does
not guarantee the node source is searched *before* the test directory. When
another node suite had already appended `nodes/src/nodes` behind the test
dir, `import local_text_output` resolved to `nodes/test/local_text_output`
(which has no `IInstance`), failing collection with
`ModuleNotFoundError: No module named 'local_text_output.IInstance'` on
Ubuntu and macOS.

Force `nodes/src/nodes` to the front of `sys.path` in both the chunker and
local_text_output suites so the real node package always wins.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…Type (rocketride-org#1643)

Review response (joshuadarron, CHANGES_REQUESTED):
- Add co-located chunker.svg icon (services.json referenced a missing
  asset — broken node-picker render).
- Add co-located README.md with the generated schema/dependencies/source
  block, config fields, and lanes (co-location rule; 106/120 nodes ship
  one).
- Fix overlap silently dropping to 0 on full-size chunks: split now
  reserves overlap headroom (_split_size = chunk_size - overlap) and
  reads the overlap from the original text preceding each raw chunk, so
  overlap is honored on the common prose/hard-split path. Cap the applied
  overlap by min(chunk_overlap, raw_start, chunk_size - len(raw)) so a
  trailing separator on a full raw chunk can never push an emitted chunk
  past chunk_size — the two regression tests (overlap honored on a
  full-size chunk; no chunk exceeds chunk_size with real '. ' separators,
  input found by fuzzing) fail on the pre-fix code and pass after.
- classType 'transform' -> 'preprocessor' (the established category for
  text splitters; no other node uses 'transform').

Closes rocketride-org#1643

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…obal sys.path mutation

Follow-up to review response, from a fresh-eyes audit of my own fix:

- Real invariant bug: the previous cap only trimmed the overlap, so a raw
  chunk emitted as current+separator could still exceed chunk_size when
  chunk_overlap was smaller than the trailing separator (e.g. default
  '\n\n' with overlap 0 or 1 — both schema-allowed). Cap the emitted body
  at chunk_size (dropping only excess trailing separator chars, never
  content); current always fits _split_size so no content is lost. 0
  invariant violations across 200k fuzz trials with random multi-char
  separators; full overlap still honored on true full-size chunks.
  Regression test added for overlap 0/1 with default separators.
- Test import: load chunker_strategies.py directly (pure stdlib) instead
  of importing the chunker package and permanently prepending src/nodes to
  sys.path at module import; the IGlobal/IInstance tests scope that
  insertion to a restored context manager. Removes a global import-state
  side effect that could perturb how sibling node suites resolve
  same-named test-vs-source packages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…on tests

My previous commit's direct-file load of chunker_strategies.py made the
module-level strategy classes distinct objects from the ones the chunker
package instantiates, so the IGlobal lifecycle tests'
isinstance(strategy, SentenceChunker) failed. Restore the working package
import (as on 908d3c6, whose chunker tests passed) and keep the three
overlap invariant tests. The chunker_strategies.py emit_len size-cap fix
is unchanged.

Note: the separate image_cleanup collection failure is a pre-existing
fragility in upstream test/image_cleanup/test_provenance.py (it assumes
'import image_cleanup' resolves to the source node; the same-named test
package can shadow it under xdist), surfaced by adding a node test — not
a chunker defect. A hardening fix for that test is proposed separately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two small alignments with today's develop:

- services.json gained the `documentation` key that 141 of develop's
  services.json files already carry (bare docs root, placed right after
  `icon`). Without it the node's Documentation menu entry has no target.
- The test module docstring still referred to the `milvus` and `pinecone`
  suites by their pre-ddf94dd6 names; they are `store_milvus` and
  `store_pinecone` on develop. Comment-only change, no behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e-org#1643)

Every node PR merged since July adds a catalog row and a changelog entry;
this PR was written before that became the norm.

- docs/README-nodes.md: new Document Processing row for `chunker`
  (documents -> documents). The header counts on lines 13-14 are already
  stale against develop, so they are deliberately left alone rather than
  bumped by one on a number that is wrong either way.
- CHANGELOG.md: [Unreleased] / ### Added line, alphabetical among the
  existing **nodes** entries.

Note: open PR rocketride-org#2139 removes docs/README-nodes.md as part of the docs
revamp. If that lands first this row goes away with the file; nothing
else in this PR depends on it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@nihalnihalani
nihalnihalani force-pushed the feat/chunker-node-clean branch from 93c165c to fee9359 Compare September 4, 2026 07:23
@nihalnihalani

nihalnihalani commented Sep 4, 2026 •

Copy link
Copy Markdown
Collaborator Author

@stepmikhaylov @joshuadarron — this PR has been rebased onto today's develop (65a2e133) and the body has been rewritten to the PR template. Re-review requested.

What changed since your reviews (current head b627bed8 — the rebase plus the MIT license header on chunker/__init__.py):

  1. The nodes/test/conftest.py change is gone (dropped commit 93c165ce). It was never the right fix: the red image_cleanup legs came from nodes/test/cloud_tts/test_cloud_tts.py:25 doing sys.path.insert(0, <nodes/test root>) at module import time, which shadowed nodes/src/nodes on whichever xdist worker collected cloud_tts first — a conftest-level insert runs earlier and cannot override it. That was fixed upstream in feat(nodes): add tool_pipedrive CRM tool node #1687 (39bc5090): cloud_tts now imports package-relative, test_test_root_is_not_on_sys_path was added, and image_cleanup/test_provenance.py:29-32 got its own front-of-path insert. This PR no longer touches the shared harness.
  2. nodes/test/local_text_output/test_instance.py is no longer modified — develop already carries the strong sys.path insert this PR was proposing (:14-19, also from feat(nodes): add tool_pipedrive CRM tool node #1687), so develop's file is kept verbatim.
  3. The duplicate llm_vision_* ruff-format commit is dropped (09d1c466); develop has that formatting via 07f78f3e. The PR touches no llm_vision_* file.
  4. Added the docs/README-nodes.md catalog row, a CHANGELOG.md [Unreleased] entry, and "documentation": "https://docs.rocketride.org" in services.json (the value 141 of develop's services.json files carry). The test-module docstring's stale milvus/pinecone suite names are now store_milvus/store_pinecone after ddf94dd6.

The July review items remain addressed: co-located chunker.svg and README.md, classType: ["preprocessor"], overlap honored on full-size chunks (test_overlap_honored_on_full_size_chunks, plus three size-cap invariant tests), and the single-long-sentence behavior documented in the node README. One item I never replied to explicitly — the minor "start_char drifts under overlap when separators sit between chunks": fixed with the overlap rewrite; test_overlap_honored_on_full_size_chunks asserts chunk['text'] == source[start_char:end_char] for every overlapped chunk, and test_start_char_correctness_with_overlap covers the token strategy.

On the overlap question with preprocessor_langchain (@joshuadarron): that node hard-codes chunk_overlap = 0 for every splitter it builds — nodes/src/nodes/preprocessor_langchain/langchain.py:168, passed through at :180 — and emits no per-chunk character offsets. chunker makes overlap configurable, reserves it inside chunk_size so chunks never overrun the budget, and records start_char/end_char per chunk. Happy to rename the directory to preprocessor_chunker if you would rather it sat under that prefix; that would move protocol, path, prefix, the README, the catalog row and the test directory.

Scope after the rebase is nodes/src/nodes/chunker/**, nodes/test/chunker/**, docs/README-nodes.md and CHANGELOG.md — nothing else (12 files).

CI: green on the current head b627bed8 — run 33893136431: Ruff, gitleaks, Build on Ubuntu 22.04 / Windows Server 2022 / macOS (ARM64) including the builder test step, Shell API contract, and CI OK.


AI-assisted (Claude Code), reviewed by the author.

CONTRIBUTING.md requires the MIT header block on Python sources, and every
other file in this node (IGlobal.py, IInstance.py, chunker_strategies.py)
already carries it — only the package __init__.py was missing it. Copied
verbatim from the canonical form used by 94 of develop's node __init__.py
files (e.g. nodes/src/nodes/__init__.py); byte-identical to the block on
this node's own IGlobal.py.

nodes/test/chunker/__init__.py is deliberately left empty: only 23 of
develop's 83 node test-package __init__.py files carry a header, and 44 of
them are empty, so the majority convention is no header there.

Comment-only change: no behaviour, no imports, no exports touched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@nihalnihalani

Copy link
Copy Markdown
Collaborator Author

@joshuadarron on your minor start_char point (L449-451): the overlap rewrite in 15122ec6 fixed the drift, and TestRecursiveCharacterChunker::test_overlap_honored_on_full_size_chunks now asserts chunk['text'] == text[start_char:end_char] for every overlapped chunk (nodes/test/chunker/test_all.py:163-165), with TestTokenChunker::test_start_char_correctness_with_overlap covering the token path.


AI-assisted (Claude Code), reviewed by the author.

@dsapandora dsapandora left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I went looking for the usual chunker defects and did not find them: search_start advances past each raw chunk so repeated text cannot mislocate offsets, the cap at chunk_size drops only surplus separator characters and says so, and the overlap clamp holds at chunk_overlap of 0 and 1. The logic is sound.

The request is about scope, left inline.

Comment thread nodes/src/nodes/chunker/chunker_strategies.py Outdated
nihalnihalani and others added 3 commits September 7, 2026 22:00
…langchain

The preprocessor_langchain node already exposes LangChain's
RecursiveCharacterTextSplitter through its `default` and `recursive` profiles
(nodes/src/nodes/preprocessor_langchain/services.json, langchain.py:186-204),
so shipping a second recursive implementation gave the product two ways to do
the same thing and left pipeline authors to guess which node to reach for.

Narrow this node to the two strategies the engine does not already have:

- sentence: stdlib-regex sentence grouping, no third-party dependency and no
  language-model download (unlike the NLTK/Spacy profiles of General Text).
- token: real tiktoken BPE counts, versus the byte-length approximation
  (`bytes/3`) that General Text labels "Estimated tokens" (langchain.py:50-59).

`sentence` becomes the default profile. A stale `strategy: recursive` config
now raises at startup naming preprocessor_langchain rather than silently
falling back, so existing pipelines fail loudly with a pointer.

Docs record why the split exists and what each node is for.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A sentence is indivisible in this strategy, so chunk_size is a grouping target
rather than a hard cap. With 'sentence' now the default profile, input that
carries no sentence-ending punctuation (log lines, CSV rows, minified text,
OCR dumps) has no boundary to group on and comes back as a single oversized
chunk -- previously the recursive default would have hard-split it.

Document the case in the README, route those inputs to the token strategy
(which caps unconditionally) or the General Text node, and pin both halves of
the behavior in a test so the trade-off stays explicit.

No behavior change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@nihalnihalani

nihalnihalani commented Sep 7, 2026 •

Copy link
Copy Markdown
Collaborator Author

Ready for another look — the latest review's ask is done: the recursive strategy is removed in favor of preprocessor_langchain, narrowing the node to sentence + token (−421/+121 over the round). The inline thread has the details and is resolved. The branch carries a merge of current develop (847ccfe5), has no conflicts, and CI is green on 152758ab (17 passed, 4 skipped). Requesting re-review.

@dsapandora dsapandora left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verified the removal rather than taking it on trust: RecursiveCharacterChunker is gone from chunker_strategies.py, the tests no longer reference it, and services.json now offers sentence and token only.

The part I did not ask for and that makes this land cleanly: strategy: recursive still raises at startup, but with a message naming preprocessor_langchain and its profiles instead of a bare "unknown strategy". An existing .pipe configured against the old shape gets a pointer rather than a dead end. The README carries the same table.

What is left is what the engine genuinely lacked: sentence-boundary splitting with no third-party dependency, and token splitting on the real tiktoken BPE rather than a byte estimate.

@dsapandora

Copy link
Copy Markdown
Collaborator

@stepmikhaylov @joshuadarron — this is still blocked on your July and May reviews. I went through both against the current branch and every point looks addressed, so a re-review from either of you would unblock it.

What I checked, in the code rather than the summary:

@stepmikhaylov's five: capabilities is now ["experimental"]; the suite moved to nodes/test/chunker/; no sys.modules stubs remain for rocketlib, ai.* or depends; no spec_from_file_location imports; the dead _pending_docs buffer and its unreachable _flush_documents() / closing() are gone.

@joshuadarron's: chunker.svg ships, README.md is co-located, and classType is ["preprocessor"] instead of the non-standard "transform".

Your other two resolved together. You asked whether this overlapped preprocessor_langchain, and it did — the recursive strategy has since been dropped in favour of that node, which also removes the full-size-chunk overlap defect you found, since it lived in that class. What is left is sentence and token, neither of which the engine has today. A .pipe still configured with strategy: recursive raises at startup with a pointer to the General Text node rather than an unknown-strategy error.

CI is green, no unresolved threads, branch current with develop. I have approved; yours are the two that still hold it.

@dsapandora
dsapandora self-requested a review September 8, 2026 13:55
@dsapandora
dsapandora dismissed stale reviews from stepmikhaylov and joshuadarron September 8, 2026 14:50

All five verified applied on the current branch: capabilities is ["experimental"], the suite moved to nodes/test/chunker/, no sys.modules stubs remain for rocketlib/ai/depends, no spec_from_file_location imports, and the dead _pending_docs buffer with its unreachable _flush_documents/closing is gone. Dismissing so this can land; reopen if I have read any of them wrong.

@dsapandora
dsapandora merged commit 2d5b276 into rocketride-org:develop Sep 8, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation module:nodes Python pipeline nodes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(nodes): add text chunker node (recursive / sentence / token strategies, configurable overlap)

4 participants