# babel-memory
**首个专门解决 AI 记忆系统多语言盲区的独立工具库。**
*22 个受支持语言代码。零必需依赖。BM25 + RAG 的即插即用预处理层。*
> *与 Babel.js 无关。以巴别塔命名——打破 AI Agent 记忆的语言壁垒。*
[](https://www.npmjs.com/package/babel-memory)
[](https://www.npmjs.com/package/babel-memory)
[](LICENSE)
[](https://github.com/AliceLJY/babel-memory)
[](https://github.com/AliceLJY/babel-memory)
[](https://github.com/AliceLJY/babel-memory)
[English](README.md) | **简体中文**
---
## 为什么需要这个库
依赖空格切词的检索管线,在不以空格分隔词语的语言上经常损失召回。综合 8 篇学术论文(MMTEB、XRAG、MIT 2025)的研究结果,可以看到一条更广泛的 **5 层语义损失级联**:
| 层级 | 出了什么问题 | 影响 |
|------|-------------|------|
| Token 估算 | `string.length / 4` 对中文低估 **4-8 倍** | 上下文溢出 |
| BM25 分词 | 按空格切分中文 = **0 个匹配** | 混合检索退化为纯向量搜索 |
| LLM 提取 | 英文 KG/摘要 prompt 处理中文 | 事实准确性**下降 24%** |
| 跨语言检索 | 查询与文档语言不匹配 | 召回率**下降 56%**(XRAG 基准) |
| 自动评估 | LLM-as-Judge 高估非英文质量 | 问题被**系统性漏报** |
**babel-memory 修复了第 2-4 层。** 相同的简洁 API,零必需依赖,按需安装语言包。
## 修复前 vs 修复后
```
修复前:
存储: "机器学习在自然语言处理中的应用"
BM25 搜索("机器学习") → [] (零结果)
KG 提取 → 英文 prompt 处理中文实体,质量降级
修复后:
存储: "机器学习在自然语言处理中的应用"
→ fts_text: "机器 学习 机器学习 自然 语言 处理 自然语言 应用"
BM25 搜索("机器学习") → [命中!]
KG 提取 → 中文 prompt + 中文 few-shot 示例,提取质量大幅提升
```
```
修复前(欧洲语言):
存储: "Maschinelles Lernen verbessert die Verarbeitung"
BM25 搜索("Verarbeitung") → [命中]
BM25 搜索("verarbeitungen") → [] (词形不同,零结果)
修复后 + snowball-stemmers:
存储: "Maschinelles Lernen verbessert die Verarbeitung"
→ fts_text: "maschinell lern verbess verarbeit"
BM25 搜索("verarbeitungen") → stem("verarbeitungen") = "verarbeit" → [命中!]
```
## 模块化安装
```bash
# 仅安装核心(零依赖——纯 TypeScript)
npm install babel-memory
# 按需添加语言包:
npm install jieba-wasm # 中文(最高质量)
npm install @sglkc/kuromoji # 日文(最高质量)
npm install wordcut # 泰文
npm install snowball-stemmers # 本库当前可用的 16 个映射算法
npm install tinyld # 拉丁字母语言自动检测(de/fr/es/...)
```
**用什么装什么。** 核心包零依赖——语言包在运行时懒加载。而且得益于内置的 **Intl.Segmenter 层**(Node 16+/Bun/Deno/浏览器自带的 ICU 词典),零依赖安装就已经能获得中文、日文、泰文的**词级分词**——安装语言包则进一步提升质量。绝不会崩溃。
## 快速上手
```typescript
import { detectLanguage, initTokenizer, tokenizeForFts, getKgPrompt } from "babel-memory";
// 1. 启动时初始化一次(加载已安装的语言包)
await initTokenizer();
// 2. 检测语言(零依赖,纯 Unicode 分析)
detectLanguage("这个项目的架构设计非常优秀"); // "zh"
detectLanguage("東京タワーはとても高いです"); // "ja"(不会误判为中文——先检测到假名)
detectLanguage("이 프로젝트는 매우 훌륭합니다"); // "ko"
detectLanguage("สวัสดีครับ"); // "th"
detectLanguage("مرحبا بالعالم"); // "ar"
detectLanguage("Машинное обучение"); // "ru"
// 3. BM25 预分词(核心修复)
tokenizeForFts("机器学习很有趣", "zh");
// → "机器 学习 很 有趣" (jieba 词级分词)
tokenizeForFts("東京タワー", "ja");
// → "東京 タワー" (kuromoji 词级分词)
tokenizeForFts("Maschinelles Lernen", "de");
// → "maschinell lern" (Snowball 词干提取)
// 4. ⚠️ 关键:查询侧必须做同样的分词处理。
// 分词只有在索引侧和查询侧一致时才能匹配:
const ftsQuery = tokenizeForFts(userQuery, detectLanguage(userQuery));
// 存储侧: "机器学习很有趣" → "机器 学习 很 有趣" (已索引)
// 查询侧: "机器学习" → "机器 学习" (命中!)
// 忘记这一步 = 拿整串 "机器学习" 去匹配分词后的文本 = 零结果。
// 这是 FTS 预分词方案最经典的坑。
// 5. 获取语言匹配的 LLM prompt
const { system, userTemplate } = getKgPrompt("zh"); // → 中文 prompt
getKgPrompt("ja"); // → 日语原生 prompt
getKgPrompt("ko"); // → 英文 prompt(用无关第三语言写指令会伤害质量)
// 所有变体中谓词都保持英文(规范化 key)
// 6. 进阶工具
import { detectLanguageDetailed, detectLanguageExtended, getLoadedTokenizers } from "babel-memory";
detectLanguageDetailed("我在用 TypeScript 给 RecallNest 写 tokenizer hook");
// → { language: "en", scripts: { cjk: 0.13, latin: 0.83, ... }, isMixed: true }
// AI 对话里中英混排是常态——isMixed 告诉你何时发生。
// 需要分词的文字系统(CJK/泰文)从约 2% 占比起就标记混排:
// 哪怕一小段中文岛不分词也是搜不到的。
// (无论如何,嵌入的中文片段都会被自动分词,见下文。)
detectLanguageExtended("Das ist ein guter Tag");
// → 装了 tinyld 时返回 "de";未装时返回 "en"(优雅降级)
tokenizeForFts("机器学习的应用", "zh", { removeStopwords: true });
// → "机器 学习 应用" ("的"被移除;opt-in,默认关闭)
getLoadedTokenizers(); // → ["jieba", "kuromoji", "tinyld"] — 验证部署配置
// 按需加载(kuromoji 单独就要 ~1-2s + ~17MB 内存):
await initTokenizer({ languages: ["zh", "de"] });
```
## 工作原理
核心思路:**在 FTS 索引之前,对无空格分隔的文本做预分词,对屈折语言做词干提取。**
```
标准 FTS 流程(中文不工作):
"知识图谱提取" → 按空格切分 → ["知识图谱提取"] → 1 个巨大 token → 搜不到
babel-memory 流程(修复):
"知识图谱提取" → jieba 分词 → "知识 图谱 知识图谱 提取" → 按空格切分 → 4 个 token → 命中!
```
这个方案兼容**任何**基于空格的 FTS 引擎:Tantivy(LanceDB)、SQLite FTS5、Elasticsearch、Meilisearch。无需修改引擎本身。
### 小型确定性 smoke benchmark
`bun bench/recall-benchmark.ts` — SQLite FTS5,20 条中文文档,12 个查询:
| 档位 | 召回率 | 零命中查询数 |
|------|--------|-------------|
| 原始文本(多数记忆系统的现状) | **0.0%** | 12/12 |
| 零依赖档(Intl.Segmenter + bigram) | **100%** | 0/12 |
| 完整档(jieba) | **100%** | 0/12 |
在这组固定的 20 篇中文文档 / 12 个查询 fixture 上,原始文本没有返回结果,两档预处理都召回了预期文档。它用于防回归,不代表普遍检索质量。
### 混排文本(AI 对话的真实形态)
真实的 Agent 对话大量中英混排:*"I fixed 机器学习模型 using TensorFlow"*。按字符比例检测会依占比落到 `zh` 或 `en`——现在两条路都能正确处理混排:`zh` 路径的分词器会原样保留英文 token,`en` 路径会探测内嵌的中文/谚文/泰文片段并把每段路由给对应分词器。v2.1 之前,被判成 `en` 的文本里的中文岛会原样穿过、无法搜索。需要显式的混排信号时用 `detectLanguageDetailed()`。
### 检测顺序很重要
日文使用汉字(CJK 字符)。简单的 CJK 检测会把日文误判为中文。babel-memory 优先检测**语言独有的文字系统**:
1. 检测到平假名/片假名?→ 日文(日文独有)
2. 检测到谚文?→ 韩文(韩文独有)
3. 泰文字符?→ 泰文
4. 阿拉伯文字?→ 阿拉伯语
5. 天城文?→ 印地语
6. 西里尔字母?→ 俄语
7. CJK 表意文字但无日韩标记?→ 中文
8. 默认 → 英文
## 优雅降级
babel-memory **绝不会**因缺少可选包而崩溃。每种语言都有**三档**降级链——v2.1 起中间档(运行时内置的 ICU `Intl.Segmenter`)让零依赖安装也有词级质量:
| 语言 | 第一档:已装语言包 | 第二档:零依赖(内置 ICU) | 第三档:兜底 |
|------|-------------------|---------------------------|-------------|
| 中文 | jieba 搜索模式分词 | ICU 词级分词 + CJK bigram | 字符切分 |
| 日文 | kuromoji 分词 | ICU 词级分词 + CJK bigram | 字符切分 |
| 泰文 | wordcut 分词 | ICU 词级分词 | 原样返回 |
| 欧洲语言(德、法、西...) | Snowball 词干提取 | 原样返回 | 原样返回 |
| 韩文 | — | 音节级切分(刻意设计,见下) | 同左 |
| 阿拉伯语、俄语 | Snowball 词干提取 | 原样返回 | 原样返回 |
| 印地语 | — | 原样返回(天城文带空格) | 同左 |
| 英文 | — | 原样返回 | 同左 |
> **为什么要 bigram?** ICU 会把复合词整词输出("東京タワー"是一个 token),部分匹配查询"タワー"就失配了。长度 ≥ 3 的 CJK token 在索引侧和查询侧都做 bigram 扩展——与 lunr-languages 的做法一致。
>
> **韩文为什么是音节级?** 韩语是黏着语:"프로젝트는" = "프로젝트" + 主题助词。词级 token 会让查询"프로젝트"完全失配;音节切分让 BM25 部分匹配保持稳定。
每个缺失的包只会输出一次警告日志,让你知道安装什么可以获得更好的质量。应用程序始终正常运行。
## API 参考
| 函数 | 输入 | 输出 | 说明 |
|------|------|------|------|
| `detectLanguage(text)` | `string` | `Language` | 基于 Unicode 字符比例分析。检测 zh、ja、ko、th、ar、hi、ru、en,零依赖。 |
| `detectLanguageDetailed(text)` | `string` | `LanguageDetail` | 同上 + 各文字系统占比和混排标志 `isMixed`。 |
| `detectLanguageExtended(text)` | `string` | `string` | 装了 `tinyld` 时把拉丁字母文本细分为 de/fr/es/...;未装时等同 `detectLanguage`。 |
| `initTokenizer(opts?)` | `{ languages?: string[] }` | `Promise` | 并行加载分词器。传 `languages` 可按需加载。幂等,加载失败不影响整体。 |
| `tokenizeForFts(text, lang, opts?)` | `string, string, { removeStopwords? }` | `string` | 先 NFKC 归一化,再 BM25 预分词。**索引侧和查询侧都要调用**。 |
| `getLoadedTokenizers()` | — | `string[]` | 诊断:当前实际加载了哪些可选包。 |
| `segmentWithIntl(text, locale)` | `string, string` | `string \| null` | 原始 ICU 词级分词构件。不可用时返回 `null`。 |
| `intlWithBigrams(text, locale)` | `string, string` | `string \| null` | ICU 分词 + CJK bigram 扩展(zh/ja 降级路径用的就是它)。 |
| `getKgPrompt(lang)` | `string` | `{ system, userTemplate }` | KG 三元组提取 prompt。zh → 中文,ja → 日语,其余 → 英文。 |
| `getSessionPrompt(lang)` | `string` | `{ system, dimensionLabels }` | 会话总结 prompt,同样的语言路由,9 个结构化维度。 |
**类型:** `Language = "zh" | "ja" | "ko" | "th" | "ar" | "hi" | "ru" | "en"`
`tokenizeForFts` 也接受任何 Snowball 语言代码(如 `"de"`、`"fr"`、`"es"`)作为字符串参数。
> **关于 script 检测的局限**:`detectLanguage` 无法区分共用拉丁字母的语言——德语、法语、西班牙语都会返回 `"en"`。这正是 `detectLanguageExtended` + 可选包 `tinyld` 存在的意义:装上它,拉丁字母语言就能自动识别,补全"自动检测 → Snowball 词干提取"的闭环。繁体中文说明:ICU 和 jieba 都能处理繁体文本,但 jieba 词典以简体为主;简繁混合语料建议先用外部工具(OpenCC)归一化再索引。
## 支持的语言
### 自动检测(通过 `detectLanguage`)
| 代码 | 语言 | 文字系统 | FTS 分词策略 | 所需包 |
|------|------|---------|-------------|--------|
| `zh` | 中文 | CJK 表意文字 | jieba 搜索模式词级分词 | `jieba-wasm` |
| `ja` | 日文 | 平假名 + 片假名 + CJK | kuromoji 词级分词 | `@sglkc/kuromoji` |
| `ko` | 韩文 | 谚文 + CJK | 字符级切分 | (内置) |
| `th` | 泰文 | 泰文字符 | wordcut 分词 | `wordcut` |
| `ar` | 阿拉伯语 | 阿拉伯文字 | Snowball 词干提取 | `snowball-stemmers` |
| `hi` | 印地语 | 天城文 | 原样返回 | (无) |
| `ru` | 俄语 | 西里尔字母 | Snowball 词干提取 | `snowball-stemmers` |
| `en` | 英文 | 拉丁字母 | 原样返回 | (无) |
### Snowball 词干提取语言(将语言代码传给 `tokenizeForFts`)
| 代码 | 语言 | 代码 | 语言 |
|------|------|------|------|
| `de` | 德语 | `nl` | 荷兰语 |
| `fr` | 法语 | `sv` | 瑞典语 |
| `es` | 西班牙语 | `no` | 挪威语 |
| `pt` | 葡萄牙语 | `da` | 丹麦语 |
| `it` | 意大利语 | `fi` | 芬兰语 |
| `hu` | 匈牙利语 | `tr` | 土耳其语 |
| `ro` | 罗马尼亚语 | `cs` | 捷克语 |
总计:**22 个不同的受支持语言代码**:8 个自动检测代码,加 14 个额外显式 Snowball 代码;阿拉伯语和俄语已经包含在前 8 个中。
装上可选包 `tinyld` 后,`detectLanguageExtended()` 可以把本库支持的拉丁字母语言路由到对应 Snowball 算法,调用方无需预先传代码。
## 适用场景
- **AI 记忆系统开发者** — 在 LanceDB、ChromaDB 或任何向量+BM25 混合存储上构建
- **RAG 管线开发者** — 用户说非英语但 BM25 搜不到东西
- **MCP Server 作者** — 记忆工具需要多语言支持
- **所有人** — 如果你注意到 AI Agent 会"忘记"非英文对话
## 范围与集成
| 能力 | babel-memory 提供什么 |
|---|---|
| CJK/泰语分词 | 可选 jieba / kuromoji / wordcut,并有 ICU 零依赖 fallback |
| 混排文本 | 在拉丁字母主导文本中识别并路由其他文字系统片段 |
| 词干提取 | 当前可用 16 个 Snowball 映射,其中 14 个是 8 个自动检测代码之外的额外代码 |
| 语言检测 | 8 个基于文字系统的代码,可用 tinyld 细化本库支持的拉丁字母语言 |
| 语言匹配 prompt | 英文、中文、日文 KG / session prompt |
| FTS 集成 | 输出空格分隔文本,可接 Tantivy、SQLite FTS5、Elasticsearch、Meilisearch 等引擎 |
| 必需依赖 | 0 |
babel-memory **不是**记忆系统——它是一个预处理层,让任何记忆系统都能正确处理多语言内容。
## 谁在使用
- [RecallNest](https://github.com/AliceLJY/recallnest) — MCP 原生共享记忆,服务 Claude Code、Codex 和 Gemini CLI
## 研究参考
本库的设计基于以下研究成果:
- MMTEB: 大规模多语言文本嵌入基准 (arXiv 2502.13595)
- XRAG: 跨语言检索增强生成 (arXiv 2505.10089)
- MIT: 分词如何改变 LLM 中的语义 (Computational Linguistics, 2025)
## 许可证
MIT