You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug] Content between embedder limit and chat_window_max_tokens is silently stored without a vector | 内容落在嵌入上限与切分阈值之间时静默丢失向量 #2461
Summary: Fixed the silent no-vector write for memory items whose token count falls in (embedder per-item limit, chat_window_max_tokens] (issue #2461). Such an item was previously neither split (the splitter only fired above chat_window_max_tokens, hard-coded to 1024 and never passed by either api/config.py mem_reader assembly site) nor embeddable (it exceeded the embedder's limit). /product/add still returned 200, but the item was persisted with metadata.embedding=None and vector_sync != "success", and search_by_embedding filters on vector_sync == "success" — so the memory was permanently invisible to semantic search. A secondary cause: _split_large_memory_item trusted chunker output and never re-checked the token budget per chunk, and the sentence chunker (chonkie) does not split CJK punctuation (it can also raise on an incompatible installed version), in which case the except branch returned the over-budget item unchanged.
The fix adds an explicit, env-configurable chat_window_max_tokens field on BaseMemReaderConfig (MEM_READER_CHAT_WINDOW_MAX_TOKENS, default 1024 so existing deployments are unchanged), wired into all three mem_reader assembly sites via APIConfig.get_chat_window_max_tokens() with fault-tolerant parsing. The effective split budget is now min(chat_window_max_tokens, embedder per-item limit). A new _chunk_within_budget helper re-checks every chunker-produced chunk and hard-splits any that still exceeds the budget — preferring a punctuation boundary (CJK and Latin terminators) and falling back to a binary-search character window — and a chunker that raises or yields nothing now hard-splits instead of passing the over-budget item through. _concat_multi_modal_memories uses the effective budget for both the split trigger and the window accumulator, plus a guard that emits over-budget items directly rather than aggregating them into a larger window. Finally, the per-item embedding retry (and the base reader's _make_memory_item path) truncates to budget before calling the embedder, so one over-limit item can no longer leave a whole batch without vectors.
Verification: a new 18-case regression suite (tests/mem_reader/test_embed_budget_split.py) passes, covering budget derivation, punctuation-free CJK splitting within budget, boundary preference, empty/failed chunker fallback, contiguous re-indexing, the 700-token in-window case, and the mixed-batch embedding fallback. Affected suites (mem_reader, configs, api, memories, mem_cube) report 341 passed with 4 pre-existing failures confirmed on the untouched baseline (markitdown extra not installed; unrelated kv-cache API). ruff check and ruff format both clean. An end-to-end reproduction using the real SentenceChunker and tiktoken counter with a 512-token embedder limit turns the issue's 780-token Chinese paragraph into chunks of 512 + 268 tokens, all embedded, while the same script's pre-fix branch reproduces the reported embedded=[False].
Out of scope: backfilling vectors for memories already stored without one.
Base branch:main Branch:bugfix/autodev-2461-20261008063231554 Commit:c3572b0 PR:#2462 Assigned to:@syzsunshine219 Reviewers:@WeiminLee
Activity
Memtensor-AI commentedon Oct 8, 2026
🤖 AutoDev has picked up this issue and started working on it.
Task ID:
00c0f8f3534623dfWorking branch:
bugfix/autodev-2461-20261008063231554Target branch:
latest dev* branchWorkflow: opsp (analysis → coding → testing → PR)
I will post the PR link here once done. If I need more information, I will ask in the comments.
KongFUN2018 commentedon Oct 8, 2026
补充:我们已在本地 v2.0.33 上实现了这个修复,并做了判别性验证,实现细节供参考(如需完整 patch 可提供)。
修复方案(两层,缺一不可)
第 1 层——装配时把分段阈值压到嵌入上限之下(
src/memos/api/config.py,两处 mem_reader 装配的embedder之后):默认 480:本地 embedder 单条上限 512,留约 6% 余量;env 可调。
第 2 层——chunker 输出后加硬切兜底(
multi_modal_struct.py的_split_large_memory_item,chunks = self.chunker.chunk(item_text)之后、chunk_total = len(chunks)之前):_hard_split_text 实现(二分字符窗 + 标点优先硬切)
为什么两层都要:只调阈值——分句器失明时(不识别中文标点、整段穿过)仍可能产出超限单块;只做硬切——
(嵌入上限, chat_window_max_tokens]区间根本不触发_split_large_memory_item,硬切没有机会执行。判别性验证(v2.0.33 实测)
注意:直接写长文本测试不能判别修复是否生效——LLM 抽取会把长内容压缩成短记忆,走不到切块路径(修复前后都会"通过")。需要 stub 掉 chunker 构造"分句器整段穿过"的场景:
vector_sync=success、可搜索另:本机曾因此累计 22 条记忆无向量(事后对照元数据逐条重灌恢复),问题真实且全程无任何错误上报。
fix: split memory items that exceed the embedder per-item limit
Memtensor-AI commentedon Oct 8, 2026
✅ AutoDev task
00c0f8f3534623dfcompleted.Summary: Fixed the silent no-vector write for memory items whose token count falls in (embedder per-item limit, chat_window_max_tokens] (issue #2461). Such an item was previously neither split (the splitter only fired above chat_window_max_tokens, hard-coded to 1024 and never passed by either api/config.py mem_reader assembly site) nor embeddable (it exceeded the embedder's limit). /product/add still returned 200, but the item was persisted with metadata.embedding=None and vector_sync != "success", and search_by_embedding filters on vector_sync == "success" — so the memory was permanently invisible to semantic search. A secondary cause: _split_large_memory_item trusted chunker output and never re-checked the token budget per chunk, and the sentence chunker (chonkie) does not split CJK punctuation (it can also raise on an incompatible installed version), in which case the except branch returned the over-budget item unchanged.
The fix adds an explicit, env-configurable chat_window_max_tokens field on BaseMemReaderConfig (MEM_READER_CHAT_WINDOW_MAX_TOKENS, default 1024 so existing deployments are unchanged), wired into all three mem_reader assembly sites via APIConfig.get_chat_window_max_tokens() with fault-tolerant parsing. The effective split budget is now min(chat_window_max_tokens, embedder per-item limit). A new _chunk_within_budget helper re-checks every chunker-produced chunk and hard-splits any that still exceeds the budget — preferring a punctuation boundary (CJK and Latin terminators) and falling back to a binary-search character window — and a chunker that raises or yields nothing now hard-splits instead of passing the over-budget item through. _concat_multi_modal_memories uses the effective budget for both the split trigger and the window accumulator, plus a guard that emits over-budget items directly rather than aggregating them into a larger window. Finally, the per-item embedding retry (and the base reader's _make_memory_item path) truncates to budget before calling the embedder, so one over-limit item can no longer leave a whole batch without vectors.
Verification: a new 18-case regression suite (tests/mem_reader/test_embed_budget_split.py) passes, covering budget derivation, punctuation-free CJK splitting within budget, boundary preference, empty/failed chunker fallback, contiguous re-indexing, the 700-token in-window case, and the mixed-batch embedding fallback. Affected suites (mem_reader, configs, api, memories, mem_cube) report 341 passed with 4 pre-existing failures confirmed on the untouched baseline (markitdown extra not installed; unrelated kv-cache API). ruff check and ruff format both clean. An end-to-end reproduction using the real SentenceChunker and tiktoken counter with a 512-token embedder limit turns the issue's 780-token Chinese paragraph into chunks of 512 + 268 tokens, all embedded, while the same script's pre-fix branch reproduces the reported embedded=[False].
Out of scope: backfilling vectors for memories already stored without one.
Base branch:
mainBranch:
bugfix/autodev-2461-20261008063231554Commit:
c3572b0PR: #2462
Assigned to: @syzsunshine219
Reviewers: @WeiminLee
fix: keep split budget decisions on the safe token counter