Skip to content

fix(knowledge): bound chunking separators so one config can't stall processing - #6735

Merged
waleedlatif1 merged 2 commits into
stagingfrom
fix/chunking-separators-bound
Aug 15, 2026
Merged

fix(knowledge): bound chunking separators so one config can't stall processing#6735
waleedlatif1 merged 2 commits into
stagingfrom
fix/chunking-separators-bound

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Cap strategyOptions.separators at 32 entries of at most 100 characters on knowledge-base create/update. It sat next to a pattern field already capped at 500 chars, but had no bound of its own.
  • RecursiveChunker splits the whole document once per separator and restarts the list for every oversized fragment, so a saved config with thousands of non-matching separators burned seconds of synchronous CPU on every later document upload. The processing Promise.race timeout and the chunk-count cap can't interrupt a synchronous split loop. On a 21.3 MB document: 632 ms at 100 separators, 6.1 s at 1000, 36.8 s at 5000 — now ~300 ms at every count.
  • Keep the stored/read shape unbounded so a config written before this change still lists instead of failing response validation.
  • Clamp in the chunker too, so an already-saved oversized list can't reach the split loop. An over-long separator is dropped rather than truncated — a truncated separator matches where the configured one never did, which would silently re-cut the document. A list left empty falls back to the recipe.
  • Walk non-matching separators iteratively instead of recursively, so stack depth no longer tracks the separator count.
  • Validate in the create-base modal so the limit surfaces inline instead of as a 400.

Type of Change

  • Bug fix

Testing

  • New unit tests for the separator bounds and the contract; each verified to fail against the unfixed code.
  • Diffed the rewritten split against the previous implementation over 4000 randomized configs (custom separators, all three recipes, varying chunk size and overlap) — byte-identical output, so the loop rewrite changes nothing for in-bound configs.
  • bun run check:audits (27 audits), bun run type-check, and 750 tests across lib/chunkers, lib/api/contracts/knowledge, and lib/knowledge pass.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

…rocessing

`chunkingStrategyOptionsSchema.separators` accepted an arbitrary-length array
of arbitrary-length strings, next to a `pattern` field already capped at 500
chars. `RecursiveChunker` splits the whole document once per separator and
walks the list from the top for every oversized fragment, so a persisted
config with thousands of non-matching separators cost seconds of synchronous
CPU on every later document upload — work neither the processing `Promise.race`
timeout nor the after-the-fact chunk-count cap can interrupt. Measured on a
21.3 MB document: 632 ms at 100 separators, 6.1 s at 1000, 36.8 s at 5000.

- Bound `separators` to 32 entries of at most 100 characters on the write path.
  The largest built-in recipe (markdown) uses 16, so hand-tuned lists still fit.
- Keep the stored/read shape tolerant, so a config written before the bound
  still lists instead of failing response validation.
- Clamp in `RecursiveChunker` too, with a warning, so an already-persisted
  oversized list cannot reach the split loop. An over-long separator is dropped
  rather than truncated: a truncated separator matches where the configured one
  never did, silently re-cutting the document, while dropping it behaves like a
  separator that finds no match. A list left empty falls back to the recipe.
- Walk non-matching separators iteratively instead of recursing, so stack depth
  no longer tracks the separator count. Verified behavior-preserving against the
  previous implementation over 4000 randomized configs — byte-identical output.
- Validate in the create-base modal so the limit surfaces inline.

After the fix the same 21.3 MB document costs ~300 ms at every separator count.
@vercel

vercel Bot commented Aug 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Aug 15, 2026 6:59pm

Request Review

@cursor

cursor Bot commented Aug 15, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes document-processing CPU bounds and API validation for chunking config; legacy stored configs are tolerated but clamped at runtime, so behavior for oversized lists may differ from pre-fix expectations without failing reads.

Overview
Caps recursive chunking custom separators at 32 entries and 100 characters per entry, because RecursiveChunker scans the full document once per separator synchronously—large lists could stall document processing for tens of seconds per upload.

API: New write-time Zod limits on strategyOptions.separators; a separate stored/read schema stays unbounded so legacy oversized configs still validate on list/get. Chunker: Clamps separator lists at construction (drops over-long separators instead of truncating) and walks non-matching separators in a loop instead of deep recursion so stack depth does not grow with separator count.

UI: Create knowledge base modal validates the comma-separated separator field inline (shared parseSeparators helper) with field errors instead of a generic 400.

Tests cover contracts, chunker clamp behavior, and backward-compatible reads.

Reviewed by Cursor Bugbot for commit 6f94d58. Configure here.

@greptile-apps

greptile-apps Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR bounds custom recursive-chunking separators at API, UI, and runtime boundaries while preserving compatibility with legacy stored configurations.

  • Limits new configurations to 32 separators of at most 100 characters each.
  • Clamps legacy configurations before document processing and falls back to recipe defaults when no usable separator remains.
  • Replaces recursive traversal of non-matching separators with an iterative loop.
  • Adds contract, runtime-boundary, fallback, and compatibility tests.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains, and the previously reported import-path issue is fixed at the current head.

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/knowledge/components/create-base-modal/create-base-modal.tsx Adds strategy-aware separator validation, shared parsing, and inline field errors.
apps/sim/lib/api/contracts/knowledge/base.ts Separates bounded write validation from permissive legacy-config response validation.
apps/sim/lib/chunkers/constants.ts Defines shared limits for separator count and per-separator length.
apps/sim/lib/chunkers/recursive-chunker.ts Enforces runtime bounds and iteratively skips non-matching separators while retaining recipe fallback.
apps/sim/lib/api/contracts/knowledge/base.test.ts Covers write rejection at both bounds and backward-compatible reads of legacy configurations.
apps/sim/lib/chunkers/recursive-chunker.test.ts Covers list clamping, oversized-item removal, fallback behavior, and uses the required absolute imports.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  UI[Create-base form] -->|Validate separator bounds| API[Knowledge-base write contract]
  API -->|Persist bounded config| DB[(Knowledge-base config)]
  Legacy[(Legacy unbounded config)] --> Read[Permissive read contract]
  DB --> Read
  Read --> Clamp[RecursiveChunker runtime clamp]
  Clamp --> Iterate[Iterative separator traversal]
  Iterate --> Chunks[Document chunks]
  Clamp -->|No usable separators| Recipe[Built-in recipe fallback]
  Recipe --> Chunks
Loading

Reviews (2): Last reviewed commit: "fix(knowledge): gate separator validatio..." | Re-trigger Greptile

Comment thread apps/sim/lib/chunkers/recursive-chunker.test.ts Outdated
- The separator refines ran for every strategy, but the field only renders for
  `recursive` and only that strategy submits it, so a value left behind by a
  strategy switch could block submit with no visible field to clear. Gated the
  same way the regex-pattern refine already is.
- Use absolute imports in the chunker test, per the repo convention.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor 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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 6f94d58. Configure here.

@waleedlatif1
waleedlatif1 merged commit f0fd48c into staging Aug 15, 2026
24 checks passed
@waleedlatif1
waleedlatif1 deleted the fix/chunking-separators-bound branch August 15, 2026 19:06
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.

1 participant