Knowledge sources accept chunk settings that silently store nothing or drop characters
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 58.8k
- Forks
- 8.5k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 109
Description
Description
Every knowledge source accepts chunk_size / chunk_overlap combinations that cannot produce a valid chunking window. _chunk_text() then either stores zero documents or silently drops characters from the text it embeds, and neither case reports an error to the caller.
The chunking step is derived from the pair, and neither field is validated against the other (or against zero):
# lib/crewai/src/crewai/knowledge/source/base_knowledge_source.py:19
chunk_size: int = 4000
chunk_overlap: int = 200
# lib/crewai/src/crewai/knowledge/source/base_knowledge_source.py:43
def _chunk_text(self, text: str) -> list[str]:
"""Utility method to split text into chunks."""
return [
text[i : i + self.chunk_size]
for i in range(0, len(text), self.chunk_size - self.chunk_overlap)
]
chunk_size - chunk_overlap is the range() step, so it is the only thing deciding what reaches storage. Three shapes are reachable through the public constructor:
chunk_size |
chunk_overlap |
step | what happens |
|---|---|---|---|
| 100 | 200 | -100 |
range(0, len(text), -100) is empty → 0 chunks → storage.save([]) |
| 100 | 100 | 0 |
add() raises ValueError: range() arg 3 must not be zero |
| 10 | -1 |
11 |
window advances by more than chunk_size → characters between chunks are never stored |
The negative-overlap row is the one that worries me most: the run reports success, the knowledge base is written, and part of every document is simply unretrievable. The overlap > size row is the more likely typo (chunk_overlap=4000, chunk_size=2000), and it produces an empty knowledge base that still looks like a successful ingest.
Steps to Reproduce
No storage backend and no LLM needed — a stub storage object records what add() hands over.
from unittest.mock import MagicMock
from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource
ALPHABET = "0123456789ABCDEFGHIJ"
def case(chunk_size: int, chunk_overlap: int, text: str = ALPHABET) -> None:
source = StringKnowledgeSource(
content=text, chunk_size=chunk_size, chunk_overlap=chunk_overlap
)
storage = MagicMock()
source.storage = storage
try:
source.add()
except Exception as exc: # noqa: BLE001
print(f"({chunk_size}, {chunk_overlap}) add() raised {type(exc).__name__}: {exc}")
return
saved = storage.save.call_args[0][0]
lost = "".join(sorted(set(text) - set("".join(saved))))
print(f"({chunk_size}, {chunk_overlap}) saved {len(saved)} chunk(s) {saved!r} chars_lost={lost!r}")
case(100, 200, "x" * 5000) # 5000 characters of content, nothing stored
case(100, 100)
case(10, 11)
case(10, -1)
case(0, 0)
case(-5, 0)
Actual behavior (measured, main @ 3831e8b, Python 3.13 / Windows)
(100, 200) saved 0 chunk(s) [] chars_lost='x'
(100, 100) add() raised ValueError: range() arg 3 must not be zero
(10, 11) saved 0 chunk(s) [] chars_lost='0123456789ABCDEFGHIJ'
(10, -1) saved 2 chunk(s) ['0123456789', 'BCDEFGHIJ'] chars_lost='A'
(0, 0) add() raised ValueError: range() arg 3 must not be zero
(-5, 0) saved 0 chunk(s) [] chars_lost='0123456789ABCDEFGHIJ'
All six constructions above succeed. The first, third and sixth store less than what was given, with no signal at all.
Note the (10, -1) output: '0123456789' + 'BCDEFGHIJ' — the character at index 10 is in neither chunk, so it never enters the embedding input.
Expected behavior
A source that cannot chunk its content should fail where the mistake is made — at construction, with a message naming both numbers — instead of writing an empty or partial knowledge base, or raising a raw range() complaint from deep inside add().
Cause
BaseKnowledgeSource declares chunk_size / chunk_overlap as plain int fields, so pydantic only checks the type. The relation that actually matters (0 <= chunk_overlap < chunk_size, chunk_size > 0) is implicit in the range() step at :47 and is never asserted.
_chunk_text() is also duplicated verbatim in the six subclasses that carry their own copy (csv_knowledge_source.py:50, excel_knowledge_source.py:180, json_knowledge_source.py:62, pdf_knowledge_source.py:62, string_knowledge_source.py:40, text_file_knowledge_source.py:42), so a per-subclass fix would have to be repeated. All eight sources inherit the field declarations from BaseKnowledgeSource, which is where a single guard belongs.
Possible solution
Validate the pair on the model, where both fields are visible, so the check no longer depends on when add() happens to run:
@model_validator(mode="after")
def _validate_chunk_settings(self) -> Self:
"""_chunk_text steps by chunk_size - chunk_overlap: zero makes range() raise, negative makes it yield no chunk."""
if self.chunk_size <= 0:
raise ValueError("chunk_size must be a positive integer")
if self.chunk_overlap < 0:
raise ValueError("chunk_overlap must be a non-negative integer")
if self.chunk_overlap >= self.chunk_size:
raise ValueError(
f"chunk_overlap ({self.chunk_overlap}) must be smaller than "
f"chunk_size ({self.chunk_size})"
)
return self
This is a narrowing, not a behavior change: every pair that produced correct chunks before still constructs, and only pairs that could not chunk at all are now rejected up front. The chunk_overlap >= chunk_size branch subsumes the step == 0 and step < 0 cases; the chunk_size <= 0 and chunk_overlap < 0 branches exist so the message names the bad field instead of blaming the other one.
Deduplicating the seven _chunk_text() copies looks like a separate, mechanical cleanup — I left it out to keep this change reviewable.
Environment
- crewAI:
1.15.22(main@3831e8b) - Python 3.13, Windows 11 (nothing OS specific)
- Verified with
lib/crewai/tests/knowledge/: 6 new failing-before / passing-after cases plus controls; the only failures in that directory are two pre-existingtest_docling_*ImportErrors (doclingnot installed locally) that reproduce identically onmain.
Authored with an AI coding assistant. .github/CONTRIBUTING.md requires the llm-generated label for agent-authored contributions; external contributors cannot apply labels, so maintainers please add it.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with lib/crewai/src/crewai/knowledge/source/base_knowledge_source.py and inspect how its Pydantic fields are validated and how _chunk_text() is used by the source subclasses. Run the knowledge tests under lib/crewai/tests/knowledge/, including the six new cases described in the issue. Done means invalid chunk settings fail at construction with clear validation errors while valid settings and existing controls continue to pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend, testing-qa
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- Half a day
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 90/100