Doramagic 项目包 · 项目说明书
memory-mesh 项目
生成时间:2026-05-15 00:17:37 UTC
MemoryMesh 简介
MemoryMesh 是一个模块化的本地知识检索与记忆管理框架,专为 AI 智能体(Agent)设计。它通过统一的多源连接器体系、层级化记忆管理、增量索引管道和灵活的 API 接口,使 AI 能够持久化访问和检索用户的个人知识库,同时保持所有数据的本地化存储。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
系统架构
MemoryMesh 采用分层架构,从底向上依次为存储层、索引层、检索层、连接器层和服务层。各层职责清晰,通过明确定义的接口进行通信。
graph TD
subgraph 服务层
A[REST API]
B[MCP 工具]
C[Web Dashboard]
D[Health Server]
end
subgraph 连接器层
E[Roam Connector]
F[Logseq Connector]
G[Notion Parser]
H[Obsidian Parser]
I[Confluence Connector]
end
subgraph 索引与检索层
J[FileIndexer]
K[SearchEngine]
L[QueryExpander]
M[Reranker]
end
subgraph 记忆管理层
N[TieredMemoryManager]
O[EpisodicMemory]
P[EntityExtractor]
end
subgraph 存储层
Q[MetadataStore SQLite]
R[EmbeddingProvider]
S[VectorStore]
end
A --> N
B --> N
C --> Q
D --> Q
E --> J
F --> J
G --> J
H --> J
I --> J
J --> Q
J --> R
R --> S
K --> S
K --> L
L --> M
N --> Q
O --> Q
P --> Q资料来源:src/memorymesh/server/dashboard.py:1-50
核心模块
连接器体系
MemoryMesh 通过统一的连接器接口对接多种知识管理工具,实现文档的自动采集与解析。
| 连接器 | 数据源 | 解析格式 | 关键特性 |
|---|---|---|---|
| RoamConnector | Roam Research JSON 导出 | 递归块扁平化 | WikiLink 提取、日期过滤 |
| LogseqConnector | Logseq 图谱目录 | Markdown | 块引用转换、属性提取 |
| NotionParser | Notion HTML 导出 | SAX 解析 | UUID 提取、数据库属性 |
| ObsidianParser | Obsidian 库 | Markdown | YAML frontmatter、backlink |
| ConfluenceConnector | Confluence Cloud REST API | HTML | 空间过滤、偏移分页 |
资料来源:src/memorymesh/connectors/roam_connector.py:1-30
#### Roam Research 连接器
RoamConnector 解析 Roam Research 的 JSON 导出文件,将嵌套的块树结构扁平化为纯文本。它会依次应用正则模式去除 Roam 特有语法:
{{[[...]]}}模板引用[[...]]页面引用#[[...]]/#tag标签语法
_ROAM_PATTERNS: list[re.Pattern[str]] = [
re.compile(r"\{\{(\[\[.*?\]\])?\}\}"),
re.compile(r"\[\[([^\]]+)\]\]"),
re.compile(r"#\[\[([^\]]+)\]\]"),
]
资料来源:src/memorymesh/connectors/roam_connector.py:30-35
#### Logseq 连接器
LogseqConnector 读取图谱目录下的 pages/ 和 journals/ 子目录,处理以下 Logseq 特有语法:
| 语法模式 | 转换结果 |
|---|---|
((uuid)) 块引用 | [block] |
{{embed [[Page]]}} 嵌入 | [embed: Page] |
{{query ...}} 查询块 | 完全移除 |
key:: value 属性行 | 提取至元数据 |
_RE_BLOCK_REF = re.compile(r"\(\([0-9a-f-]{36}\)\)")
_RE_EMBED = re.compile(r"\{\{embed\s+\[\[([^\]]+)\]\]\}\}")
_RE_QUERY = re.compile(r"\{\{query[^}]*\}\}")
资料来源:src/memorymesh/connectors/logseq_connector.py:35-40
#### Notion 解析器
NotionParser 基于标准库 html.parser 实现 SAX 风格的解析,无需外部依赖。它从导出的 HTML 文件中提取:
<title>标签内容<h1>可见页面标题data-type属性(数据库属性类型)- 可见正文文本(去除标记)
文件名中嵌入的 UUID 用于唯一标识页面:Page Title a1b2c3...html
资料来源:src/memorymesh/parsing/notion_parser.py:1-40
#### Obsidian 解析器
ObsidianParser 扩展标准 Markdown 解析,增加以下能力:
- YAML Frontmatter 提取:解析
---分隔符之间的元数据 - WikiLink Backlink 提取:通过
[[link]]和[[link|alias]]模式 - 标签解析:支持
tags:字段和内联#tag语法
_FRONTMATTER_RE = re.compile(
r"^---\r?\n(.*?)\r?\n(?:---|\.\.\.)(?:\r?\n|$)",
re.DOTALL,
)
_WIKILINK_RE = re.compile(r"(?<!!)\[\[([^\[\]|#]+?)(?:\|[^\[\]]*?)?\]\]")
资料来源:src/memorymesh/parsing/obsidian_parser.py:15-25
#### Confluence 连接器
ConfluenceConnector 通过 REST API 与 Confluence Cloud 交互,支持:
- 偏移分页:通过
start/limit参数遍历所有页面 - 空间过滤:通过
space_keys参数限定特定空间 - HTML 净化:将
storage格式页面体转换为纯文本 - HTTP Basic 认证:使用
email:api_token格式
connector = ConfluenceConnector(ConfluenceConfig(
base_url="https://myorg.atlassian.net",
email="[email protected]",
api_token=SecretStr("my-token"),
space_keys=["ENG", "DOCS"],
days_past=90,
))
资料来源:src/memorymesh/connectors/confluence_connector.py:1-50
文档分块策略
MemoryMesh 实现了多种分块器(Chunker),将解析后的文档拆分为可索引的语义单元。
| 分块器 | 适用场景 | 特性 |
|---|---|---|
| RecursiveChunker | 通用文本 | 递归字符级分割,支持 overlap |
| MarkdownChunker | Markdown 文档 | 基于 ATX 标题分割 |
| CodeChunker | 源代码文件 | 基于 AST 节点分割 |
graph LR
A[ParsedDocument] --> B{Markdown?}
B -->|是| C[MarkdownChunker]
B -->|否| D{Code?}
D -->|是| E[CodeChunker]
D -->|否| F[RecursiveChunker]
C --> G[Chunk 列表]
E --> G
F --> G资料来源:src/memorymesh/chunking/markdown.py:1-20
#### Markdown 分块器
MarkdownChunker 按 ATX 标题(#, ##, …)分割文档,每个分块包含一个章节:标题行及其下方正文。超过 max_chunk_size 的章节由递归分块器进一步分割。
heading_path 元数据字段记录完整的祖先标题栈,便于搜索命中时精确定位文档位置。
资料来源:src/memorymesh/chunking/markdown.py:15-40
#### 代码分块器
CodeChunker 利用 tree-sitter 解析 AST,提取函数、类等节点。每个节点生成一个分块,附带 function_name、class_name 和 language 元数据。未识别的顶级代码作为残差分块处理。
chunks.append(
Chunk(
path=doc.path,
chunk_index=idx,
text=sub.text,
metadata=ChunkMetadata(
language=lang_name,
function_name=func_name,
class_name=class_name,
),
)
)
资料来源:src/memorymesh/chunking/code.py:50-70
层级化记忆管理
MemoryMesh 的 TieredMemoryManager 将记忆划分为三个温度层级,实现类似于人类记忆的遗忘曲线管理。
| 层级 | 描述 | 存储位置 |
|---|---|---|
| Hot | 最近访问或手动固定的内容 | RAM 缓存 |
| Warm | 正常索引的内容(默认) | 向量数据库 |
| Cold | 罕见访问的内容 | 压缩存储,衰减评分 |
class MemoryTier(StrEnum):
hot = "hot"
warm = "warm"
cold = "cold"
资料来源:src/memorymesh/core/models/config.py:40-50
#### 记忆分层工作流
graph TD
A[新索引内容] --> B{Hot 层}
B --> C[频繁访问?]
C -->|是| D[保持在 Hot]
C -->|否| E[移至 Warm]
E --> F{遗忘策略}
F --> G{访问频率低}
G -->|是| H[移至 Cold]
G -->|否| I[保持在 Warm]
H --> J[评分衰减]
J --> K{超过阈值?}
K -->|是| L[遗忘 Chunk]资料来源:src/memorymesh/server/rest_api.py:1-30
实体提取
当配置 memory.entity_extraction.enabled: true 且 Ollama 运行中时,FileIndexer 会在每个文件索引后自动提取实体并持久化。
实体记录包含:
| 字段 | 类型 | 说明 |
|---|---|---|
| name | string | 实体名称 |
| entity_type | string | 实体类型(如人物、地点、组织) |
| mention_count | int | 提及次数 |
资料来源:src/memorymesh/server/dashboard.py:80-100
情景记忆
EpisodicMemory 记录索引过程中的关键事件,用于时间线可视化和审计。
事件记录包含:
| 字段 | 类型 | 说明 |
|---|---|---|
| timestamp | float | Unix 时间戳 |
| event_type | string | 事件类型 |
| source | string | 来源标识 |
| chunk_ids | list[str] | 关联的分块 ID 列表 |
events = ctx.metadata_store.list_episodic_events(since=since_ts, limit=100)
资料来源:src/memorymesh/server/dashboard.py:60-75
数据模型
Chunk 模型
Chunk 是 MemoryMesh 的核心数据单元,代表文档的一个可索引片段。
class Chunk(BaseModel):
path: Path # 文档路径
chunk_index: int # 分块序号
text: str # 分块文本内容
start_char: int # 起始字符偏移
end_char: int # 结束字符偏移
file_type: str = "" # 文件类型扩展名
mtime: float = 0.0 # 文件修改时间
source_root: str = "" # 源根目录路径
metadata: ChunkMetadata # 分块元数据
@property
def id(self) -> str:
return f"{self.path}:{self.chunk_index}"
资料来源:src/memorymesh/core/models/chunk.py:30-50
ChunkMetadata 模型
class ChunkMetadata(ConfigDict):
heading_path: str = "" # 完整标题路径(Markdown)
language: str = "" # 编程语言(代码块)
function_name: str = "" # 函数名(代码块)
class_name: str = "" # 类名(代码块)
backlinks: list[str] = [] # 反向链接列表
chunk_type: str = "" # 分块类型标识
资料来源:src/memorymesh/core/models/chunk.py:15-25
FileRecord 模型
FileRecord 记录每个被索引文件的状态信息。
| 字段 | 类型 | 说明 |
|---|---|---|
| path | string | 绝对路径(主键) |
| source_name | string | 所属源名称 |
| sha256 | string | 内容 SHA-256 哈希 |
| mtime | float | 文件修改时间 |
| size_bytes | int | 文件大小 |
| file_type | string | 标准化扩展名 |
| n_chunks | int | 生成的分块数量 |
| status | string | 状态(indexed/parse_error/unsupported/deleted/pending_reindex) |
| error_message | string | 最后一次失败描述 |
| indexed_at | float | 最后索引时间戳 |
| embedding_model_id | string | 使用的嵌入模型 ID |
资料来源:src/memorymesh/storage/file_repository.py:15-35
服务端组件
REST API
MemoryMesh 提供基于 FastAPI 的 RESTful API,支持以下核心操作:
| 端点 | 方法 | 说明 | 权限要求 |
|---|---|---|---|
/api/search | GET | 搜索记忆内容 | read |
/api/sources | GET | 列出所有数据源 | read |
/api/chunks | GET | 获取分块详情 | read |
/api/memory/promote | POST | 提升分块至热层 | write |
/api/memory/forget | POST | 遗忘分块 | delete |
#### 遗忘操作
遗忘操作将分块降至冷层,使其相关性评分随时间衰减,而非立即删除。
Request body: {"chunk_id": "<path>:<chunk_index>"}
@app.post("/api/memory/forget")
async def forget_chunk(body: dict) -> dict:
app_ctx.metadata_store.forget_chunk(chunk_id)
return {"status": "forgotten", "chunk_id": chunk_id}
资料来源:src/memorymesh/server/rest_api.py:20-40
Web Dashboard
Dashboard 是基于 FastAPI + HTMX 的单页应用,运行于 :8767 端口。
| 页面 | 路由 | 功能 |
|---|---|---|
| 数据源 | / | 列出所有配置的源目录及文件统计 |
| 搜索 | /search | 全文搜索界面,支持稀疏/密集模式切换 |
| 记忆层级 | /tiers | 热/温/冷分块分布统计 |
| 实体 | /entities | 提取的实体列表及类型 |
| 时间线 | /timeline | 情景记忆事件时间轴 |
| 知识图谱 | /graph-ui | 可视化节点关系图 |
@app.get("/tiers", response_class=HTMLResponse)
async def tiers_page() -> str:
counts: dict[str, int] = {}
for tier in MemoryTier:
entries = ctx.metadata_store.list_chunks_by_tier(tier, limit=None)
counts[tier.value] = len(entries)
# ...
资料来源:src/memorymesh/server/dashboard.py:100-130
Health Server
Health Server 提供系统健康检查端点,运行于 :8766 端口,在守护进程启动时自动启用,退出时干净关闭。
资料来源:CHANGELOG.md:20-25
搜索架构
MemoryMesh 的搜索管道采用多阶段检索与重排序架构。
graph LR
A[用户查询] --> B[QueryExpander]
B --> C[词汇变体生成]
C --> D[并行检索]
D --> E[密集检索]
D --> F[稀疏检索]
E --> G[FuseResults]
F --> G
G --> H[SearchReranker]
H --> I[Top-K 结果]| 组件 | 说明 | 配置项 |
|---|---|---|
| QueryExpander | 生成查询词汇变体 | n_lexical_variants |
| SearchReranker | 重排序候选结果 | top_k_before_rerank |
| SentenceTransformers | 密集向量检索 | model_id |
搜索配置
| 参数 | 默认值 | 说明 |
|---|---|---|
top_k_before_rerank | 35 | 重排序前保留的候选数量 |
n_lexical_variants | 1 | 查询扩展词汇变体数量 |
资料来源:CHANGELOG.md:30-40
配置体系
MemoryMesh 通过 YAML 配置文件管理所有设置。
核心配置结构
资料来源:- name: "my-vault"
path: "~/Obsidian/vault"
recursive: true
extensions: [".md", ".py"]
memory:
tiers:
enabled: true
forgetting:
enabled: true
entity_extraction:
enabled: true
episodic:
enabled: true
embeddings:
provider: "sentence_transformers"
model_id: "all-MiniLM-L6-v2"
search:
reranker:
top_k_before_rerank: 35
query_expansion:
n_lexical_variants: 1
资料来源:src/memorymesh/core/models/config.py:50-80
代理权限
class AgentPermission(StrEnum):
read = "read"
read_index = "read+index"
read_index_delete = "read+index+delete"
admin = "admin"
资料来源:src/memorymesh/core/models/config.py:55-65
索引流程
sequenceDiagram
participant C as Connector
participant P as Parser
participant CK as Chunker
participant I as FileIndexer
participant M as MetadataStore
participant E as EmbeddingProvider
C->>P: fetch_documents()
P->>P: parse(path)
P-->>I: ParsedDocument
I->>CK: chunk(doc)
CK-->>I: List[Chunk]
I->>E: embed(chunks)
E-->>I: List[ChunkWithEmbedding]
I->>M: upsert_file()
I->>M: upsert_chunks()
I->>M: add_episodic_event()索引管道关键步骤
- 连接器采集:从各数据源获取原始文档
- 解析转换:将原始格式转换为
ParsedDocument - 分块处理:按策略分割为语义单元
- 向量化:通过 EmbeddingProvider 生成向量表示
- 持久化:写入 SQLite 元数据存储和向量数据库
- 事件记录:写入情景记忆事件
版本演进
MemoryMesh 采用语义化版本控制,主要版本功能如下:
| 版本 | 日期 | 核心特性 |
|---|---|---|
| 0.5.0 | 2026-05-03 | MVP 基线:SQLite、CLI、句子嵌入 |
| 0.6.0 | 2026-05-10 | 层级记忆、Dashboard、授权体系、实体提取、CLIP 图像索引 |
0.6.0 新增功能
- TieredMemoryManager:层级化记忆管理,替代原有的简单存储
- DashboardServer:FastAPI + HTMX Web 界面
- Auth Guard:MCP 工具统一授权检查
- Entity Extraction:Ollama 驱动的实体识别
- Health Server:健康检查端点
资料来源:CHANGELOG.md:1-50
技术栈
| 层级 | 技术选型 |
|---|---|
| 核心框架 | Python 3.11+ |
| Web 框架 | FastAPI + HTMX |
| 数据库 | SQLite3 |
| 向量检索 | Sentence-Transformers |
| CLI | Click |
| 配置 | Pydantic + YAML |
| 日志 | Loguru |
| 代码解析 | tree-sitter |
资料来源:src/memorymesh/server/dashboard.py:1-20
快速入门
安装依赖
uv add memorymesh
uv add 'fastapi[standard]' # 可选:启用 Dashboard
配置数据源
# config.yaml
资料来源:- name: "obsidian-vault"
path: "~/Obsidian/my-vault"
type: "obsidian"
memory:
tiers:
enabled: true
entity_extraction:
enabled: true
启动服务
memorymesh start # 启动守护进程
memorymesh index # 执行索引任务
访问 Dashboard
服务启动后访问 http://localhost:8767 查看:
- 所有数据源的索引状态
- 全文搜索界面
- 记忆层级分布
- 提取的实体列表
- 情景记忆时间线
资料来源:[src/memorymesh/server/dashboard.py:1-50]()
系统架构
MemoryMesh 是一个本地优先的个人知识图谱与记忆管理系统,旨在将分散在各类笔记工具(Obsidian、Logseq、Roam、Notion 等)中的内容统一索引、检索与关联。本页面详细说明其核心系统架构、各模块职责及数据流转路径。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
整体架构概览
MemoryMesh 采用分层架构,核心组件包括数据源连接层、解析与分块层、存储与索引层、检索与推理层,以及服务与接口层。各层职责清晰,依赖关系如下:
graph TD
subgraph 数据源层
OBS[Obsidian Vault]
LOG[Logseq Graph]
ROAM[Roam Research]
NOTION[Notion Export]
CONFL[Confluence]
EMAIL[Email .mbox]
CAL[Calendar .ics]
end
subgraph 连接器层
OBS_C[ObsidianConnector]
LOG_C[LogseqConnector]
ROAM_C[RoamConnector]
NOTION_C[NotionParser]
CONFL_C[ConfluenceConnector]
end
subgraph 解析层
MARKDOWN[MarkdownParser]
OBS_P[ObsidianParser]
HTML_P[NotionHTMLParser]
end
subgraph 分块层
MD_CHUNK[MarkdownChunker]
REC_CHUNK[RecursiveChunker]
end
subgraph 存储层
SQLite[(SQLite 元数据库)]
QDRANT[(Qdrant 向量库)]
CHROMS[Chroma 向量库]
end
subgraph 索引层
IDX[FileIndexer]
EMB[EmbeddingProvider]
end
subgraph 服务层
API[FastAPI MCP Server]
DASH[Dashboard Server]
HEALTH[Health Server]
MCP[MCP Tools]
end
OBS --> OBS_C
LOG --> LOG_C
ROAM --> ROAM_C
NOTION --> NOTION_C
CONFL --> CONFL_C
OBS_C --> OBS_P
LOG_C --> MARKDOWN
ROAM_C --> MARKDOWN
NOTION_C --> HTML_P
OBS_P --> MD_CHUNK
HTML_P --> MD_CHUNK
MARKDOWN --> REC_CHUNK
MD_CHUNK --> IDX
REC_CHUNK --> IDX
IDX --> EMB
EMB --> QDRANT
EMB --> CHROMS
IDX --> SQLite
SQLite --> API
QDRANT --> API
CHROMS --> API
API --> MCP
API --> DASH
API --> HEALTH资料来源:ARCHITECTURE.md
核心组件
1. 连接器层(Connectors)
连接器负责从各类外部数据源批量读取文档,统一输出为 ParsedDocument 对象。各连接器支持的平台如下:
| 连接器 | 配置类 | 数据格式 | 特性 |
|---|---|---|---|
ObsidianConnector | ObsidianSourceConfig | Markdown | 解析 YAML frontmatter、[[wikilinks]] |
LogseqConnector | LogseqConfig | Markdown | 处理 ((uuid)) 块引用、{{embed}} 嵌入、key:: value 属性 |
RoamConnector | RoamConfig | JSON | 递归展平嵌套块、清除 {{[[...]]}} 模板语法 |
NotionParser | NotionSourceConfig | HTML 导出 | 使用标准库 html.parser 解析 |
ConfluenceConnector | ConfluenceConfig | REST API | HTTP Basic 认证、分页拉取 |
EmailParser | EmailSourceConfig | .mbox | 偏好 text/plain,回退到 stripped HTML |
CalendarParser | CalendarSourceConfig | .ics | 提取 VEVENT 摘要、时间、地点 |
资料来源:src/memorymesh/connectors/logseq_connector.py:1-80
2. 解析层(Parsers)
解析器将原始文件内容转换为结构化的 ParsedDocument,提取纯文本与元数据。
#### Obsidian 解析器
ObsidianParser 专门处理 Obsidian 格式的 Markdown 文件:
- YAML frontmatter 提取:匹配
---分隔符之间的内容,解析tags、aliases、created、modified字段 - Wikilink 提取:正则
_WIKILINK_RE = r"(?<!!)\[\[([^\[\]|#]+?)(?:\|[^\[\]]*?)?\]\]"捕获双向链接目标 - 过滤嵌入图片:
![[img]]格式被排除在 backlinks 之外
_WIKILINK_RE = re.compile(r"(?<!!)\[\[([^\[\]|#]+?)(?:\|[^\[\]]*?)?\]\]")
资料来源:src/memorymesh/parsing/obsidian_parser.py:20-30
#### Logseq 解析器
LogseqConnector 内置解析逻辑,处理 Logseq 特有语法:
| 语法模式 | 正则 | 转换结果 |
|---|---|---|
| 块引用 | \(\([0-9a-f-]{36}\)\) | [block] |
| 嵌入 | \{\{embed \[\[([^\]]+)\]\]\}\} | [embed: Page] |
| 查询块 | \{\{query[^}]*\}\} | 整块移除 |
| 属性行 | ^([\w-]+)::\s*(.+)$ | 存入 metadata |
资料来源:src/memorymesh/connectors/logseq_connector.py:40-50
3. 分块层(Chunking)
分块策略决定了向量检索的粒度。MemoryMesh 支持以下分块器:
| 分块器 | 策略 | 配置参数 |
|---|---|---|
RecursiveChunker | 递归字符拆分 | chunk_size=800, chunk_overlap=50 |
MarkdownChunker | 按 ATX 标题(#, ## 等)切分 | max_chunk_size=800 |
MarkdownChunker 首先按标题分割文档,超出 max_chunk_size 的节交给 RecursiveChunker 进一步拆分,并保留 heading_path 元数据用于精确定位。
资料来源:src/memorymesh/chunking/markdown.py:1-50
4. 存储层(Storage)
#### 元数据存储(SQLite)
FileRepository 负责管理 SQLite 数据库中的以下表:
files:文件记录,包含路径、SHA-256、修改时间、块数、索引状态等sources:数据源配置index_state:索引状态追踪
文件记录使用 ON CONFLICT(path) DO UPDATE 实现 upsert 语义,保证幂等性:
INSERT INTO files
(path, source_name, sha256, mtime, size_bytes, file_type,
n_chunks, status, error_message, indexed_at, embedding_model_id)
VALUES (...)
ON CONFLICT(path) DO UPDATE SET
source_name = excluded.source_name,
sha256 = excluded.sha256,
...
资料来源:src/memorymesh/storage/file_repository.py:15-35
#### 向量存储
MemoryMesh 支持多种向量数据库作为后端:
| 向量库 | 配置键 | 说明 |
|---|---|---|
| Qdrant | vector_store.type: qdrant | 高性能向量检索 |
| Chroma | vector_store.type: chroma | 轻量级本地向量库 |
向量由 EmbeddingProvider 生成,支持 Ollama 本地模型推理。
5. 索引层(Indexer)
FileIndexer 是索引流程的核心编排器,其主要职责:
graph LR
A[文件路径] --> B{文件类型判断}
B -->|Markdown| C[MarkdownChunker]
B -->|其他| D[TextParser]
C --> E[RecursiveChunker]
D --> E
E --> F[EmbeddingProvider]
F --> G[向量写入 Qdrant/Chroma]
E --> H[Chunk 列表]
H --> I[FileRepository upsert]索引过程中:
- 根据文件扩展名选择对应 Parser
- Parser 输出
ParsedDocument(含纯文本与元数据) - Chunker 将文档切分为
Chunk列表 EmbeddingProvider为每个 chunk 生成向量- 向量写入向量库,元数据写入 SQLite
资料来源:src/memorymesh/indexer/file_indexer.py
6. 服务层(Server)
#### MCP Server
MemoryMesh 通过 FastMCP 暴露 MCP(Model Context Protocol)工具,提供以下能力:
| MCP 工具 | 功能描述 |
|---|---|
search_memory | 混合检索:向量相似度 + 关键词 |
ask_memory | RAG 问答:检索 → 上下文组装 → Ollama 生成 |
pin_memory | 将 chunk 钉住防止遗忘衰减 |
forget_memory | 抑制或冷却指定 chunk |
get_memory_tier | 查询 chunk 所在记忆层级 |
所有工具均通过 auth_guard.py 中的 check_access() 进行权限校验。
资料来源:src/memorymesh/server/tools/forget_memory.py
#### Dashboard Server
基于 FastAPI + HTMX 的 Web 界面,运行在 :8767 端口:
| 路由 | 功能 |
|---|---|
/ | 数据源概览 |
/search | 检索界面 |
/tiers | 记忆层级分布统计 |
/entities | 提取的实体列表 |
/timeline | 情景记忆时间线 |
/graph-ui | 知识图谱可视化 |
资料来源:src/memorymesh/server/dashboard.py
#### Health Server
轻量级健康检查端点,运行在 :8766 端口,用于进程监控与外部探测。
关键数据模型
ParsedDocument
class ParsedDocument(BaseModel):
path: Path
text: str
file_type: str
encoding: str = "utf-8"
metadata: dict[str, object] = Field(default_factory=dict)
Chunk
class Chunk(BaseModel):
path: Path
chunk_index: int
text: str
start_char: int
end_char: int
file_type: str = ""
metadata: ChunkMetadata = Field(default_factory=ChunkMetadata)
资料来源:src/memorymesh/core/models/chunk.py
配置架构
MemoryMesh 使用 YAML 配置文件管理所有组件行为,主要配置节点:
资料来源:# 数据源列表
- type: obsidian
path: ~/Vault
- type: logseq
vault_path: ~/Logseq/my-graph
vector_store:
type: qdrant # 或 chroma
path: ~/.memorymesh/qdrant
memory:
tiers:
enabled: true
strategy: importance_based
forgetting:
enabled: true
decay_rate: 0.95
episodic:
enabled: true
entity_extraction:
enabled: true
ollama_url: http://localhost:11434
auth:
enabled: false # 默认关闭
记忆层级系统
MemoryMesh 引入了基于重要性的记忆层级管理:
graph TD
HOT[热记忆 Hot] --> WARM[温记忆 Warm]
WARM --> COLD[冷记忆 Cold]
COLD --> ARCHIVE[归档 Archive]
style HOT fill:#ff6b6b
style WARM fill:#feca57
style COLD fill:#54a0ff
style ARCHIVE fill:#576574| 层级 | 用途 | 衰减策略 |
|---|---|---|
| Hot | 高频访问、钉住内容 | 不衰减 |
| Warm | 最近访问 | 轻量衰减 |
| Cold | 长期未访问 | 快速衰减 |
| Archive | 极低相关性 | 可选自动清理 |
TieredMemoryManager 根据访问频率与用户显式操作动态调整 chunk 所在层级。
认证与权限
auth_guard.py 提供统一的权限检查接口:
check_access(ctx: AppContext, action: str, source: str | None) -> bool
支持的权限操作:
read_source:读取指定数据源write_source:修改指定数据源admin:管理功能
当 auth.enabled: false(默认值)时,所有权限检查自动放行。
工作流程总览
sequenceDiagram
participant CLI as CLI (start)
participant CTX as AppContext
participant CONN as Connectors
participant IDX as FileIndexer
participant EMB as EmbeddingProvider
participant VDB as Vector Store
participant SQL as SQLite
CLI->>CTX: 初始化配置
CTX->>CONN: 创建连接器实例
loop 每次索引周期
CONN->>CONN: fetch_documents()
CONN-->>IDX: ParsedDocument stream
loop 每个文档
IDX->>IDX: parse() → ParsedDocument
IDX->>IDX: chunk() → Chunk list
IDX->>EMB: embed(chunks)
EMB-->>IDX: ChunkWithEmbedding list
IDX->>VDB: upsert(embeddings)
IDX->>SQL: upsert(FileRecord)
end
end
CLI->>CTX: 启动 MCP Server
CTX->>CTX: 注册 MCP 工具
Note over CTX: Dashboard :8767, Health :8766扩展机制
MemoryMesh 设计支持以下扩展点:
- 自定义 Parser:继承
Parser基类,注册到source.type映射 - 自定义 ChunkStrategy:实现
Chunker接口 - 自定义 VectorStore:实现
VectorStore接口,适配新的向量数据库 - 自定义 Connector:实现
Connector接口,从新平台拉取数据
技术栈总结
| 层级 | 技术选型 |
|---|---|
| 核心框架 | Python 3.11+, Pydantic |
| Web 框架 | FastAPI, Starlette |
| 界面 | HTMX, 原生 HTML/CSS |
| 向量库 | Qdrant / Chroma |
| 元数据 | SQLite |
| 本地推理 | Ollama |
| 协议 | MCP (FastMCP) |
| 日志 | Loguru |
| 配置 | YAML + Pydantic ConfigDict |
所有数据默认存储在 ~/.memorymesh/ 目录下,保持完全的本地化与隐私保护。
资料来源:# 数据源列表
索引管道
索引管道是 MemoryMesh 的核心子系统,负责将各种来源的文档转换为可搜索的向量和全文索引数据。本页面详细介绍索引管道的架构设计、数据流、核心组件及其交互方式。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
架构概述
索引管道采用模块化设计,主要包含以下处理阶段:
graph TD
A[文档源] --> B[解析器 Registry]
B --> C[解析器 Parser]
C --> D[ParsedDocument]
D --> E[分块器 Chunker]
E --> F[Chunk 列表]
F --> G[嵌入向量生成]
G --> H[向量存储]
G --> I[BM25 索引]
H --> J[元数据存储]
I --> J管道支持多种文档格式,包括本地 Markdown、Obsidian 笔记、Notion 导出、Roam Research、Logseq、Confluence 以及 Email 和日历等。资料来源:src/memorymesh/parsing/obsidian_parser.py、src/memorymesh/parsing/notion_parser.py、src/memorymesh/connectors/logseq_connector.py、src/memorymesh/connectors/roam_connector.py、src/memorymesh/connectors/confluence_connector.py
核心处理流程
1. 文档解析阶段
解析器注册表 (ParserRegistry) 根据文件类型和来源配置选择合适的解析器。每种解析器负责将特定格式的文档转换为统一的 ParsedDocument 数据结构。
支持的解析器类型:
| 解析器 | 文件类型 | 特殊功能 |
|---|---|---|
| MarkdownParser | .md, .mdx, .markdown | 基础解析 |
| ObsidianParser | .md | YAML frontmatter 提取、wikilink 解析 |
| NotionParser | .html, .htm | Notion 导出格式处理 |
| LogseqConnector | .md | 属性行解析、block 引用处理 |
| RoamConnector | JSON | 递归 block 扁平化 |
| ConfluenceConnector | API | HTML 内容提取 |
资料来源:src/memorymesh/parsing/registry.py
ParsedDocument 模型包含以下关键字段:
path: Path
text: str
file_type: str
encoding: str
metadata: dict[str, object] # 包含 frontmatter、tags、aliases 等
资料来源:src/memorymesh/core/models/chunk.py
2. 文本分块阶段
分块器将长文档拆分为适合嵌入的段落。每个 Chunk 对象包含文本内容、字符位置信息和元数据。
| 分块器 | 适用场景 | 分块策略 |
|---|---|---|
| MarkdownChunker | Markdown 文档 | 按 ATX 标题层级切分 |
| CodeChunker | 源代码文件 | 按函数/类定义节点切分 |
| RecursiveChunker | 通用文本 | 递归字符级切分 |
MarkdownChunker 特别维护 heading_path 元数据字段,记录文档中的完整标题路径,便于精确定位搜索结果。资料来源:src/memorymesh/chunking/markdown.py
CodeChunker 使用 AST 解析提取函数名、类名和语言类型,为代码搜索提供语义级支持。资料来源:src/memorymesh/chunking/code.py
3. 向量嵌入阶段
嵌入向量生成器将文本块转换为高维向量表示,用于语义相似度搜索。嵌入提供者通过注册表统一管理,支持配置不同的嵌入模型。
sequenceDiagram
participant C as Chunk
participant E as EmbeddingProvider
participant V as VectorStore
participant M as MetadataStore
C->>E: 批量生成嵌入向量
E-->>C: embedding: list[float]
C->>V: 存储向量 + 元数据
C->>M: 创建 FileRecord资料来源:src/memorymesh/indexer/file_indexer.py
4. 混合索引阶段
MemoryMesh 采用混合搜索策略,同时维护向量索引和 BM25 全文索引。
| 索引类型 | 用途 | 存储位置 |
|---|---|---|
| 向量索引 | 语义相似度搜索 | VectorStore |
| BM25 索引 | 关键词精确匹配 | BM25Index |
BM25 索引器构建倒排索引,支持按词频和文档频率计算相关性评分。资料来源:src/memorymesh/storage/bm25_index.py
向量存储使用配置的向量数据库,支持多种后端实现。资料来源:src/memorymesh/storage/vector_store.py
FileIndexer 核心组件
FileIndexer 是索引管道的核心编排器,协调各子系统的协作。
主要方法
| 方法 | 职责 |
|---|---|
index_file(path) | 单文件索引入口 |
index_directory(root, ...) | 目录批量索引 |
process_document(doc, path, source_name) | 文档处理流水线 |
资料来源:src/memorymesh/indexer/file_indexer.py
索引结果记录
索引完成后,系统创建 FileRecord 记录文件状态:
FileRecord(
path=str(path),
source_name=source_name,
sha256=current_hash, # 文件内容哈希
mtime=stat.st_mtime, # 修改时间
size_bytes=stat.st_size, # 文件大小
file_type=ext, # 文件类型
n_chunks=len(chunks), # 分块数量
status="indexed", # 索引状态
indexed_at=time.time(), # 索引时间戳
embedding_model_id=model_id # 嵌入模型标识
)
资料来源:src/memorymesh/indexer/file_indexer.py、src/memorymesh/core/models/chunk.py
增量索引机制
系统通过 SHA-256 哈希和 mtime 比对检测文件变化,仅对变更文件重新索引:
current_hash = hashlib.sha256(content).hexdigest()
if current_hash != existing_file.sha256:
# 重新索引
资料来源:src/memorymesh/indexer/file_indexer.py
监视器组件
FileWatcher 组件监听文件系统变化,触发增量索引。
graph LR
A[文件系统事件] --> B{事件类型}
B -->|创建/修改| C[触发索引]
B -->|删除| D[标记文件已删除]
B -->|重命名| E[更新文件路径]资料来源:src/memorymesh/indexer/watcher.py
| 功能 | 说明 |
|---|---|
| 增量更新 | 仅索引变更文件 |
| 软删除处理 | 标记而非立即删除 |
| 防抖机制 | 避免频繁触发 |
配置选项
IndexingConfig 主要字段
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
chunk_size | int | 800 | 分块目标字符数 |
chunk_overlap | int | 50 | 分块重叠字符数 |
generate_summaries | bool | False | 是否生成摘要块 |
extract_entities | bool | True | 是否提取实体 |
EmbeddingConfig 主要字段
| 字段 | 类型 | 说明 |
|---|---|---|
provider | str | 嵌入提供者名称 |
model_id | str | 模型标识符 |
batch_size | int | 批处理大小 |
dimensions | int | 向量维度 |
资料来源:src/memorymesh/embeddings/registry.py
错误处理
索引管道内置完善的错误处理机制:
| 错误类型 | 处理策略 | 状态记录 |
|---|---|---|
| 解析错误 | 返回错误文档,记录到 metadata | parse_error |
| 编码错误 | 使用 fallback 编码或跳过 | parse_error |
| 嵌入失败 | 使用 fallback 提供者 | index_error |
| 存储错误 | 回滚事务,记录错误信息 | index_error |
FileRecord 的 status 字段可能值:indexed、parse_error、unsupported、deleted、pending_reindex。资料来源:src/memorymesh/core/models/chunk.py
数据流总结
Source File → Parser → ParsedDocument → Chunker → Chunks
↓
┌─────────────────┴─────────────────┐
↓ ↓
EmbeddingProvider BM25Indexer
↓ ↓
VectorStore BM25Index
└─────────────────┬─────────────────┘
↓
MetadataStore
(FileRecord)
索引管道通过模块化设计实现了灵活的文档处理能力,支持多种数据源的统一索引,并提供向量和全文双重搜索能力,满足不同场景的检索需求。
资料来源:[src/memorymesh/parsing/registry.py]()
文件解析器
文件解析器(Parser)是 MemoryMesh 索引管道中的第一环,负责将各种来源的原始文件转换为统一的 ParsedDocument 数据结构。每个解析器专注于特定的文件格式或数据源,提取文本内容和元数据,为后续的分块(Chunking)和向量化(Embedding)做准备。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
文件解析器(Parser)是 MemoryMesh 索引管道中的第一环,负责将各种来源的原始文件转换为统一的 ParsedDocument 数据结构。每个解析器专注于特定的文件格式或数据源,提取文本内容和元数据,为后续的分块(Chunking)和向量化(Embedding)做准备。
解析器采用策略模式,通过文件扩展名和源类型自动选择合适的解析器实现。系统内置了针对 Markdown、Obsidian、Notion、PDF、DOCX、Email 等格式的专业解析器,同时也支持通过扩展机制接入新的数据源。
资料来源:src/memorymesh/parsing/base.py
架构设计
解析器基类
所有解析器都继承自 Parser 基类,定义统一接口:
class Parser(ABC):
@property
@abstractmethod
def supported_extensions(self) -> frozenset[str]:
"""返回解析器支持的文件扩展名集合"""
...
@abstractmethod
def parse(self, path: Path) -> ParsedDocument:
"""解析指定路径的文件,返回 ParsedDocument"""
...
资料来源:src/memorymesh/parsing/base.py
解析流程
graph TD
A[原始文件] --> B{文件类型检测}
B -->|Markdown .md| C[MarkdownParser]
B -->|Obsidian .md| D[ObsidianParser]
B -->|Notion .html| E[NotionParser]
B -->|PDF .pdf| F[PDFParser]
B -->|DOCX .docx| G[DOCXParser]
B -->|Email .eml/.msg| H[EmailParser]
C --> I[ParsedDocument]
D --> I
E --> I
F --> I
G --> I
H --> IParsedDocument 数据模型
解析结果封装在 ParsedDocument 模型中:
| 字段 | 类型 | 说明 |
|---|---|---|
path | Path | 文件绝对路径 |
text | str | 提取的纯文本内容 |
file_type | str | 规范化文件类型标识 |
encoding | str | 源文件编码 |
metadata | dict | 格式特定的元数据 |
资料来源:src/memorymesh/core/models/__init__.py
内置解析器
Markdown 解析器
文件路径: src/memorymesh/parsing/markdown.py
MarkdownParser 是标准 Markdown 文件的解析器,使用与文本解析器相同的编码容错读取机制。它将解析结果标记为 file_type=".md",使后续分块层能够选择标题感知的分割器。
class MarkdownParser(Parser):
@property
def supported_extensions(self) -> frozenset[str]:
return frozenset({".md", ".mdx", ".markdown"})
def parse(self, path: Path) -> ParsedDocument:
text, encoding, error = _read_with_fallback(path)
meta: dict[str, object] = {}
if error:
meta["error"] = error
return ParsedDocument(
path=path,
text=text,
file_type=".md",
encoding=encoding,
metadata=meta,
)
资料来源:src/memorymesh/parsing/markdown.py
Obsidian 解析器
文件路径: src/memorymesh/parsing/obsidian_parser.py
ObsidianParser 继承自 MarkdownParser,增加了对 Obsidian 特有语法的支持:
| 功能 | 说明 | |
|---|---|---|
| YAML 前置元数据 | 提取 --- 分隔符之间的内容 | |
| Wikilink 反向链接 | 识别 [[链接]] 和 `[[链接\ | 别名]]` 模式 |
| 嵌入图片过滤 | 排除 ![[图片]] 等嵌入媒体 | |
| 标签提取 | 解析 tags: 字段和内联 #标签 语法 |
# YAML 前置元数据正则
_FRONTMATTER_RE = re.compile(
r"^---\r?\n(.*?)\r?\n(?:---|\.\.\.)(?:\r?\n|$)",
re.DOTALL,
)
# Wikilink 提取正则(排除 ![[ 嵌入]])
_WIKILINK_RE = re.compile(r"(?<!!)\[\[([^\[\]|#]+?)(?:\|[^\[\]]*?)?\]\]")
提取的元数据字段:
meta["frontmatter"] = fm_data # YAML 字段字典
meta["tags"] = tags # 标签列表
meta["aliases"] = aliases # 别名列表
meta["created"] = created # 创建时间
meta["modified"] = modified # 修改时间
meta["backlinks"] = wikilink_targets # Wikilink 目标列表
资料来源:src/memorymesh/parsing/obsidian_parser.py
Notion 解析器
文件路径: src/memorymesh/parsing/notion_parser.py
NotionParser 专门处理 Notion HTML 导出格式,仅依赖标准库 html.parser,无需外部依赖。
class _NotionHTMLParser(HTMLParser):
"""SAX 风格解析器,收集纯文本和元数据"""
_SKIP_TAGS: frozenset[str] = frozenset(
{"style", "script", "head", "meta", "link"}
)
解析能力:
- 提取
<title>标签内容 - 提取
<h1>可见页面标题 - 收集
data-type属性值(数据库属性类型) - 剥离所有 HTML 标签获取纯文本
- 从文件名提取 Notion 页面 UUID
_UUID_FILENAME_RE = re.compile(
r"([0-9a-f]{8}(?:[0-9a-f]{4}){3}[0-9a-f]{12})(?:\s|\.|$)",
re.IGNORECASE,
)
Notion 解析器返回的元数据包含:
| 字段 | 说明 |
|---|---|
notion_id | 页面 UUID |
database_name | 所属数据库名称 |
title | 页面标题 |
db_properties | 数据库属性列表 |
资料来源:src/memorymesh/parsing/notion_parser.py
支持的文件格式
| 解析器 | 扩展名 | 源类型 |
|---|---|---|
MarkdownParser | .md, .mdx, .markdown | 通用 Markdown |
ObsidianParser | .md | obsidian |
NotionParser | .html, .htm | notion |
PDFParser | .pdf | 通用 |
DOCXParser | .docx | 通用 |
EmailParser | .eml, .msg | 通用 |
解析模式
编码容错读取
解析器使用 _read_with_fallback 函数处理各种文件编码:
def _read_with_fallback(path: Path) -> tuple[str, str, str | None]:
"""尝试 UTF-8 读取,失败时回退到 latin-1"""
该函数按顺序尝试:
- UTF-8 编码
- 回退到
latin-1编码 - 返回错误信息而非抛出异常
资料来源:src/memorymesh/parsing/text.py
错误处理
所有解析器遵循统一的错误处理策略:
if error:
meta["error"] = error
return ParsedDocument(
path=path,
text="",
file_type=file_type,
encoding=encoding,
metadata=meta,
)
解析失败时返回空的 text 内容,但保留 error 字段供诊断使用。
与连接器的协作
解析器不仅处理本地文件,还被连接器(Connector)模块间接调用:
graph LR
A[连接器] -->|fetch_documents| B[ParsedDocument]
B --> C[索引器]
C --> D[分块]
D --> E[向量化]
E --> F[向量数据库]连接器处理远程数据获取和格式转换,然后使用解析器或解析后的格式输出 ParsedDocument:
- RoamConnector - 解析 Roam Research JSON 导出
- LogseqConnector - 处理 Logseq Markdown 文件
- NotionConnector - 调用 Notion API 获取页面
- ConfluenceConnector - 从 Confluence REST API 获取页面
资料来源:src/memorymesh/connectors/roam_connector.py 资料来源:src/memorymesh/connectors/logseq_connector.py 资料来源:src/memorymesh/connectors/notion_connector.py 资料来源:src/memorymesh/connectors/confluence_connector.py
使用示例
直接使用解析器
from pathlib import Path
from memorymesh.parsing.obsidian_parser import ObsidianParser
parser = ObsidianParser()
doc = parser.parse(Path("/vault/my-note.md"))
print(f"文本长度: {len(doc.text)}")
print(f"标签: {doc.metadata.get('tags')}")
print(f"反向链接: {doc.metadata.get('backlinks')}")
配置源类型自动选择
当 source.type 配置为 obsidian 时,系统自动注册 ObsidianParser 处理 .md 文件;通用 Markdown 源则使用标准 MarkdownParser。
扩展新的解析器
实现自定义解析器需继承 Parser 基类:
from memorymesh.parsing.base import Parser
from memorymesh.core.models import ParsedDocument
class CustomParser(Parser):
@property
def supported_extensions(self) -> frozenset[str]:
return frozenset({".custom"})
def parse(self, path: Path) -> ParsedDocument:
# 实现解析逻辑
return ParsedDocument(
path=path,
text=processed_text,
file_type=".custom",
metadata={"source": "custom"},
)
总结
文件解析器模块是 MemoryMesh 多格式支持的核心,通过统一的 Parser 接口和 ParsedDocument 数据模型,实现了:
- 格式解耦 - 解析逻辑与索引逻辑分离
- 自动选择 - 根据扩展名和源类型自动路由
- 元数据丰富 - 提取格式特定信息供高级检索使用
- 容错设计 - 编码错误和解析失败不影响整体流程
资料来源:[src/memorymesh/parsing/base.py]()
文本分块策略
文本分块策略是 MemoryMesh 项目中用于将大型文档拆分为可管理单元的核心机制。在 RAG(检索增强生成)系统架构中,分块策略直接影响检索质量和生成效果。MemoryMesh 实现了多种专业化的分块器,每种分块器针对特定内容类型进行优化,确保拆分后的文本块(Chunk)既保持语义完整性,又便于后续的向量检索。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
文本分块策略是 MemoryMesh 项目中用于将大型文档拆分为可管理单元的核心机制。在 RAG(检索增强生成)系统架构中,分块策略直接影响检索质量和生成效果。MemoryMesh 实现了多种专业化的分块器,每种分块器针对特定内容类型进行优化,确保拆分后的文本块(Chunk)既保持语义完整性,又便于后续的向量检索。
系统通过统一的 Chunk 数据模型和抽象基类 Chunker 接口,支持灵活的扩展机制。文档解析完成后,相应的分块器会根据文件类型自动选择并应用,实现无缝的文档处理流程。
资料来源:src/memorymesh/chunking/base.py:1-30
架构设计
核心抽象
MemoryMesh 的分块系统采用策略模式设计,核心组件包括:
graph TD
A[ParsedDocument] --> B[Chunker Registry]
B --> C{Markdown?}
B --> D{Code?}
B --> E{Default?}
C --> F[MarkdownChunker]
D --> G[CodeChunker]
E --> H[RecursiveChunker]
F --> I[Chunk List]
G --> I
H --> IChunker 抽象基类定义了统一的分块接口,所有具体分块器都必须实现 chunk(doc: ParsedDocument) -> list[Chunk] 方法。这种设计允许在运行时根据文件类型动态选择最合适的分块策略。
资料来源:src/memorymesh/chunking/base.py:12-18
分块器类型对比
| 分块器 | 适用场景 | 分割依据 | 依赖项 |
|---|---|---|---|
| MarkdownChunker | Markdown 文档 | ATX 标题层级 | 无 |
| CodeChunker | 源代码文件 | AST 语法树节点 | tree-sitter-languages |
| RecursiveChunker | 通用文本 | 字符数/段落/句子 | 无 |
资料来源:src/memorymesh/chunking/registry.py:44-73
Markdown 分块器
工作原理
MarkdownChunker 基于 ATX 标题语法(#、##、### 等)对文档进行结构化拆分。每个分块包含一个标题及其下方内容,直到遇到同级或更高级别标题为止。
graph LR
A["# 文档标题"] --> B["## 第一章节<br>章节内容..."]
B --> C["## 第二章节<br>章节内容..."]
B --> D["### 2.1 子章节<br>子内容..."]
D --> C系统通过正则表达式 ^(#{1,6})\s+(.*) 匹配所有标题行,并维护一个 heading_stack(标题栈)来追踪当前章节的层级关系。
资料来源:src/memorymesh/chunking/markdown.py:1-50
标题路径元数据
每个 Markdown 分块都包含 heading_path 元数据字段,记录从根标题到当前标题的完整路径。例如,对于 ### 2.1 子章节,其 heading_path 为 ["文档标题", "第一章节", "子章节"]。这一特性使搜索命中可以精确定位到文档中的具体位置。
资料来源:src/memorymesh/chunking/markdown.py:80-85
溢出拆分机制
当单个章节内容超过 max_chunk_size(默认 800 字符)时,MarkdownChunker 会调用 RecursiveChunker 进行递归拆分,确保最终分块大小符合配置要求。
资料来源:src/memorymesh/chunking/markdown.py:24-27
代码分块器
AST 感知分割
CodeChunker 使用 tree-sitter 库解析源代码的抽象语法树(AST),在顶级函数和类定义处进行分割。这种分割方式保持了代码结构完整性,便于检索时代码片段的语义理解。
graph TD
A[源代码文件] --> B[tree-sitter 解析]
B --> C[提取顶级节点]
C --> D{节点类型?}
D -->|function_definition| E[函数分块]
D -->|class_definition| F[类分块]
D -->|其他| G[残留代码处理]
E --> H[Chunk List]
F --> H
G --> H资料来源:src/memorymesh/chunking/code.py:1-60
支持的编程语言
| 扩展名 | 语言标识 | 扩展名 | 语言标识 |
|---|---|---|---|
| .py | python | .go | go |
| .js / .jsx | javascript | .rs | rust |
| .ts / .tsx | typescript / tsx | .rb | ruby |
| .java | java | .php | php |
| .c / .h | c | .swift | swift |
| .cpp / .hpp | cpp | .kt | kotlin |
| .cs | c_sharp |
资料来源:src/memorymesh/chunking/code.py:16-36
分割节点类型
代码分块器在以下 AST 节点类型处进行分割:
function_definitionfunction_declarationmethod_definitionclass_definitionclass_declaration
资料来源:src/memorymesh/chunking/code.py:38-45
元数据标记
每个代码分块包含丰富的元数据:
language:编程语言名称function_name:函数名(如适用)class_name:所属类名(如适用)
这些元数据使搜索结果可以精确定位到具体的代码符号。
资料来源:src/memorymesh/chunking/code.py:95-100
回退机制
当发生以下情况时,CodeChunker 自动回退到 RecursiveChunker:
- tree-sitter-languages 未安装
- 不支持当前文件扩展名
- tree-sitter 运行时错误
资料来源:src/memorymesh/chunking/code.py:48-55
递归分块器
层级拆分策略
RecursiveChunker 采用多层级递归策略,将文本从大到小逐步拆分,直到满足大小约束。
graph TD
A[完整文本] --> B{是否超过 chunk_size?}
B -->|否| Z[直接返回]
B -->|是| C[按段落分割]
C --> D{段落是否超过限制?}
D -->|是| E[按句子分割]
E --> F{句子是否超过限制?}
F -->|是| G[按字符数硬截断]
G --> H[合并重叠分块]
D -->|否| H
F -->|否| H
H --> I[Chunk List]资料来源:src/memorymesh/chunking/recursive.py:1-80
分割层级
分块器按以下顺序尝试拆分:
- 段落分割:按双换行符
\n\n分割 - 句子分割:按
.、!、?等标点分割 - 字符截断:按
chunk_size硬截断
每层拆分后,检查子块是否仍超过大小限制,如是则继续下一层拆分。
资料来源:src/memorymesh/chunking/recursive.py:30-60
重叠机制
为保持上下文连续性,相邻分块之间存在 chunk_overlap(默认 50 字符)的重叠区域。重叠通过向前回溯实现,确保重要内容不会因分割边界而被切断。
资料来源:src/memorymesh/chunking/recursive.py:62-70
分块器注册表
文件类型选择逻辑
ChunkerRegistry 负责根据文件扩展名选择合适的分块器:
graph TD
A[输入: extension] --> B{是否为 Markdown 扩展名?}
B -->|是| C[返回 MarkdownChunker]
B -->|否| D{是否为代码扩展名?}
D -->|是| E[返回 CodeChunker]
D -->|否| F[返回 RecursiveChunker]Markdown 扩展名集合:.md、.mdx、.markdown
代码扩展名集合:.py、.js、.jsx、.ts、.tsx、.java、.c、.cpp、.h、.hpp、.cs、.go、.rs、.rb、.php、.swift、.kt
资料来源:src/memorymesh/chunking/registry.py:18-28
获取分块器接口
def get_chunker(
extension: str,
chunking_config: ChunkingConfig | None = None
) -> Chunker
当 chunking_config 为 None 时,使用默认配置参数初始化各分块器。
资料来源:src/memorymesh/chunking/registry.py:30-73
配置参数
分块配置模型
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
max_chunk_size | int | 800 | Markdown 分块器:单个章节最大字符数 |
chunk_size | int | - | RecursiveChunker:目标分块大小 |
chunk_overlap | int | 50 | RecursiveChunker:相邻分块重叠字符数 |
资料来源:src/memorymesh/chunking/markdown.py:48-50
配置使用示例
from memorymesh.chunking.registry import get_chunker
from memorymesh.core.models import ChunkingConfig, MarkdownChunkingConfig
config = ChunkingConfig(
markdown=MarkdownChunkingConfig(max_chunk_size=1000),
code=CodeChunkingConfig(max_chunk_size=500),
default=RecursiveChunkingConfig(chunk_size=300, chunk_overlap=30)
)
chunker = get_chunker(".py", chunking_config=config)
数据模型
Chunk 数据结构
class Chunk(BaseModel):
path: Path # 源文件绝对路径
chunk_index: int # 文件内分块序号
text: str # 分块文本内容
start_char: int # 在源文本中的起始字符偏移
end_char: int # 在源文本中的结束字符偏移
file_type: str # 文件类型扩展名
mtime: float # 文件修改时间戳
source_root: str # 来源配置名称
metadata: ChunkMetadata # 结构化元数据
资料来源:src/memorymesh/core/models/chunk.py:35-55
ChunkMetadata 元数据
class ChunkMetadata(BaseModel):
heading_path: list[str] = [] # Markdown 标题路径
language: str = "" # 代码语言标识
function_name: str = "" # 函数名
class_name: str = "" # 类名
资料来源:src/memorymesh/chunking/markdown.py:82-85 和 src/memorymesh/chunking/code.py:95-100
Chunk ID 标识符
每个 Chunk 具有稳定的唯一标识符,格式为 <path>:<chunk_index>,便于跨系统引用和去重。
资料来源:src/memorymesh/core/models/chunk.py:57-59
最佳实践
调整分块大小
| 使用场景 | 推荐配置 |
|---|---|
| 细粒度检索(如代码搜索) | max_chunk_size=300-500 |
| 文档问答 | max_chunk_size=800-1000 |
| 长文档摘要 | max_chunk_size=1500+ |
选择分块策略
- 结构化文档(Markdown):优先使用
MarkdownChunker保留文档结构 - 源代码:使用
CodeChunker保持函数/类完整性 - 非结构化文本:使用
RecursiveChunker按语义层级拆分
资料来源:[src/memorymesh/chunking/base.py:1-30]()
搜索引擎
MemoryMesh 的搜索引擎是一个混合检索系统,支持密集检索(dense retrieval)和稀疏检索(sparse retrieval)两种方式,并提供可选的重排序(reranking)和查询扩展(query expansion)功能。该引擎是整个知识库问答系统的核心,负责从向量数据库中快速定位相关文本块。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
1. 概述
MemoryMesh 的搜索引擎是一个混合检索系统,支持密集检索(dense retrieval)和稀疏检索(sparse retrieval)两种方式,并提供可选的重排序(reranking)和查询扩展(query expansion)功能。该引擎是整个知识库问答系统的核心,负责从向量数据库中快速定位相关文本块。
搜索引擎的核心职责包括:
- 接收用户查询文本
- 通过查询扩展生成语义变体
- 并行执行多种检索策略
- 使用排名融合(rank fusion)整合多路检索结果
- 可选地通过 LLM 进行结果重排序
- 返回排序后的搜索命中列表
资料来源:CHANGELOG.md
2. 架构设计
2.1 整体架构图
graph TD
A[用户查询] --> B[查询扩展 QueryExpander]
B --> C[生成多个查询变体]
C --> D[并行检索 Pipeline]
D --> E[密集检索 Dense Retriever]
D --> F[稀疏检索 Sparse Retriever]
E --> G[排名融合 RankFusion]
F --> G
G --> H[可选: 重排序 Reranker]
H --> I[SearchHit 结果列表]
J[Ollama LLM] -.-> H
K[向量数据库] -.-> E
L[元数据存储] -.-> F2.2 核心组件
| 组件 | 文件路径 | 职责 |
|---|---|---|
| SearchEngine | search/engine.py | 主编排引擎,协调整个检索流程 |
| QueryExpander | search/query_expander.py | 查询扩展,生成语义变体 |
| RankFusion | search/rank_fusion.py | 多路结果融合 |
| OllamaClient | llm/ollama_client.py | 本地 LLM 调用接口 |
| AskMemory | server/tools/ask_memory.py | MCP 工具封装 |
资料来源:CHANGELOG.md
3. 检索策略
3.1 并行变体检索
SearchEngine 使用 ThreadPoolExecutor 实现并行检索。针对扩展后的多个查询变体,系统会并发地向向量数据库发起检索请求,最后将所有结果汇总。这一设计显著提升了长查询的检索效率。
# 伪代码展示并行检索逻辑
def _retrieve_single(self, variant_query: str) -> list[SearchHit]:
"""执行单个查询变体的检索"""
def _fuse_all(self, all_hits: list[list[SearchHit]]) -> list[SearchHit]:
"""融合多路检索结果"""
资料来源:CHANGELOG.md
3.2 密集检索与稀疏检索
系统支持两种互补的检索模式:
- 密集检索(Dense Retrieval):使用文本嵌入模型将查询和文档都编码为向量,在向量空间中进行相似度搜索,适合语义匹配。
- 稀疏检索(Sparse Retrieval):基于关键词的传统 BM25 或类似算法,适合精确术语匹配。
两种检索结果通过排名融合算法整合,兼顾语义理解和精确匹配。
3.3 摘要块过滤
系统支持在索引阶段生成摘要块(chunk_type="summary"),这些摘要块存储在向量数据库中用于提升抽象语义召回率。但在搜索结果返回时,摘要块会被自动过滤掉,不会直接暴露给用户。
资料来源:CHANGELOG.md
4. 查询扩展
QueryExpander 负责将用户原始查询扩展为多个语义变体,以提升检索的召回率。
4.1 配置参数
| 参数 | 说明 | 默认值 |
|---|---|---|
n_lexical_variants | 生成的词汇变体数量 | 1 |
expansion_model | 使用的扩展模型 | - |
在最新版本中,n_lexical_variants 的默认值从 2 降低到 1,以减少查询噪声。
资料来源:CHANGELOG.md
4.2 扩展流程
graph LR
A[原始查询] --> B[语义分析]
B --> C[生成变体1]
B --> D[生成变体2]
C --> E[合并为查询列表]
D --> E
E --> F[并行检索]5. 排名融合
当多路检索返回结果后,RankFusion 模块负责将不同检索策略的结果合并为统一的排序列表。
5.1 融合策略
融合算法会考虑每个命中结果在不同检索通道中的排名位置,给予排名靠前的结果更高的融合分数。最终返回一个综合排序后的 SearchHit 列表。
6. 重排序(Reranking)
6.1 简介
可选的重排序功能使用 LLM 对初筛结果进行二次排序,以提升结果相关性。
6.2 配置参数
| 参数 | 说明 | 默认值 |
|---|---|---|
top_k_before_rerank | 重排序前保留的候选结果数 | 35 |
model | 使用的重排序模型 | - |
该参数默认值从 20 提高到 35,以防止长描述性查询中的相关文件在重排序前被错误排除。
资料来源:CHANGELOG.md
7. 搜索工具(Ask Memory MCP)
Ask Memory 是一个 MCP(Model Context Protocol)工具,封装了搜索引擎用于 AI 对话助手的问答场景。
7.1 工作流程
graph TD
A[用户问题] --> B[混合检索]
B --> C[上下文组装]
C --> D[Ollama Generate]
D --> E[返回答案 + 引用来源]
F[Ollama 不可用] -.-> G[返回来源列表 + 安装提示]
G --> E7.2 返回格式
class AskMemoryResponse:
answer: str | None # LLM 生成的回答
资料来源:list[SearchHit] # 搜索命中文档
model: str # 使用的模型名称
ollama_available: bool # Ollama 是否可用
hint: str | None # 仅在不可用时返回
7.3 降级策略
当 Ollama 本地 LLM 服务不可用时,系统会优雅降级:返回原始搜索结果列表,并在响应中包含安装提示(hint),确保不会因 LLM 不可用而完全失败。
资料来源:ARCHITECTURE.md
8. Web 界面
8.1 搜索页面
MemoryMesh 提供基于 FastAPI + HTMX 的 Web 搜索界面,支持用户直接输入查询词进行搜索。搜索结果以 HTML 形式渲染展示。
8.2 路由定义
| 路由 | 方法 | 功能 |
|---|---|---|
/search | GET/POST | 搜索页面,支持 mode 参数(dense/sparse/hybrid) |
<!-- 搜索表单示例 -->
<form method="get" action="/search">
<input name="q" placeholder="输入搜索内容...">
<select name="mode">
<option value="hybrid" selected>Hybrid</option>
<option value="dense">Dense</option>
<option value="sparse">Sparse</option>
</select>
<button type="submit">Search</button>
</form>
资料来源:src/memorymesh/server/dashboard.py
9. 配置示例
在 config.yaml 中配置搜索相关参数:
search:
embedding_model: sentence-transformers/all-MiniLM-L6-v2
dense:
top_k: 10
sparse:
top_k: 10
reranker:
enabled: true
top_k_before_rerank: 35
query_expansion:
enabled: true
n_lexical_variants: 1
10. 依赖关系
| 依赖项 | 用途 |
|---|---|
sentence-transformers | 文本嵌入模型 |
faiss 或 chromadb | 向量数据库 |
Ollama | 本地 LLM 推理 |
fastapi | Web 服务框架 |
httpx | HTTP 客户端 |
11. 相关文档
资料来源:[CHANGELOG.md]()
结果重排序
结果重排序(Result Reranking)是 MemoryMesh 搜索系统中提升检索精度的核心机制。该模块在完成初步检索后,通过交叉编码器(Cross-Encoder)对候选结果进行深度语义相关性评分,从而修正纯向量检索可能产生的排序偏差。
继续阅读本节完整说明和来源证据。
工作原理
MemoryMesh 采用典型的两阶段检索架构:密集检索(Dense Retrieval) → 结果重排序(Reranking)。
graph TD
A[用户查询] --> B[密集检索阶段]
B --> C[召回 Top-N 候选结果<br/>top_k_before_rerank]
C --> D[重排序阶段]
D --> E[交叉编码器评分]
E --> F[按相关性重新排序]
F --> G[返回 Top-K 最终结果<br/>top_k]
C -.->|候选集过大<br/>影响精度| D第一阶段使用密集向量检索快速从大规模语料中召回候选文档;第二阶段通过语义更精准的交叉编码器对候选集进行细粒度排序,最终输出高相关性的结果列表。
资料来源:ARCHITECTURE.md:1-50
配置参数
重排序功能通过 SearchRerankerConfig 进行配置,主要参数如下:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled | bool | True | 是否启用重排序 |
model | str | "cross-encoder" | 交叉编码器模型标识 |
top_k_before_rerank | int | 35 | 重排序前保留的候选结果数量 |
top_k | int | 5 | 最终返回的结果数量 |
配置变更历史:在 v1.2.0 版本中,top_k_before_rerank的默认值从20提升至35。此调整旨在防止长描述性查询中相关文档在重排序前被过早过滤,从而提升召回率。
资料来源:src/memorymesh/core/models/config.py:1-100 资料来源:CHANGELOG.md:1-50
交叉编码器模型
重排序阶段使用的交叉编码器接收查询与文档对(query-document pair),输出单一相关性分数。相比于双编码器(Bi-Encoder)分别编码查询和文档的方式,交叉编码器能够在注意力机制中直接建模两者的交互关系,从而捕获更精细的语义关联。
graph LR
subgraph 交叉编码器
Q[Query Token序列] -->|拼接| M[联合输入]
D[Document Token序列] -->|拼接| M
M --> AT[Attention层]
AT --> FC[全连接层]
FC --> S[相关性分数: 0-1]
end模型输出范围为 0 到 1 的浮点数,其中:
资料来源:src/memorymesh/search/reranker.py:1-80
搜索流程集成
重排序模块嵌入在 SearchEngine 的完整检索管线中:
graph TD
A[search_query] --> B[QueryExpansion]
B --> C[并行向量检索<br/>ThreadPoolExecutor]
C --> D[融合多路检索结果]
D --> E{top_k_before_rerank<br/>≥ top_k?}
E -->|是| F[交叉编码器重排序]
F --> G[返回Top-K结果]
E -->|否| G并行优化:SearchEngine已使用ThreadPoolExecutor实现多路查询变体的并行检索,提升整体响应速度。
资料来源:src/memorymesh/search/engine.py:1-100 资料来源:CHANGELOG.md:30-40
与其他搜索组件的关系
| 组件 | 作用阶段 | 输入 | 输出 |
|---|---|---|---|
| QueryExpansion | 预处理 | 用户原始查询 | 扩展查询变体列表 |
| EmbeddingProvider | 密集检索 | 查询文本 | 向量表示 |
| VectorStore | 检索 | 查询向量 | Top-N 候选文档 |
| Reranker | 重排序 | 候选文档 + 查询 | 重排序后的文档列表 |
| SearchReranker | 最终封装 | 重排序结果 | SearchHit 列表 |
资料来源:src/memorymesh/search/models.py:1-50
使用示例
from memorymesh.core.models.config import SearchConfig, SearchRerankerConfig
from memorymesh.search.engine import SearchEngine
# 配置重排序参数
reranker_cfg = SearchRerankerConfig(
enabled=True,
top_k_before_rerank=35,
top_k=5,
)
search_cfg = SearchConfig(
top_k=5,
reranker=reranker_cfg,
)
# 初始化搜索引擎
engine = SearchEngine(config=search_cfg, ...)
# 执行搜索(自动触发重排序)
results = engine.search("项目架构设计原则")
性能考量
- 候选集大小权衡:
top_k_before_rerank过小可能导致遗漏相关文档,过大则增加交叉编码器的计算开销。默认值35在大多数场景下取得较好平衡。
- 模型选择:交叉编码器模型直接影响重排序质量与推理速度。生产环境可根据硬件条件选择轻量级或高精度模型。
- 异步支持:重排序阶段可与密集检索并行执行,通过合理的调度策略进一步降低端到端延迟。
配置示例
search:
top_k: 5
reranker:
enabled: true
model: "cross-encoder/ms-marco-MiniLM-L-6-v2"
top_k_before_rerank: 35
该配置启用重排序功能,使用 ms-marco-MiniLM-L-6-v2 交叉编码器模型,在重排序前保留 35 个候选结果,最终返回 5 条最相关文档。
资料来源:[ARCHITECTURE.md:1-50]()
数据连接器
数据连接器(Connector)是 MemoryMesh 系统用于从外部数据源提取文档的核心模块。每个连接器实现特定平台的协议解析与数据拉取,将来自不同知识管理工具的原始数据转换为统一的 ParsedDocument 数据模型。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
数据连接器(Connector)是 MemoryMesh 系统用于从外部数据源提取文档的核心模块。每个连接器实现特定平台的协议解析与数据拉取,将来自不同知识管理工具的原始数据转换为统一的 ParsedDocument 数据模型。
连接器的设计遵循以下原则:
- 统一接口 - 所有连接器实现
fetch_documents()方法,返回Iterator[ParsedDocument] - 配置驱动 - 每个连接器配套一个 Pydantic 配置类,通过
config.yaml声明式配置 - 懒加载注册 - 连接器通过
CONNECTOR_REGISTRY字典按需注册,最小化导入开销 - 解析器协同 - 部分连接器(如 Notion、Obsidian)依赖配套的解析器完成文件级别的数据转换
资料来源:src/memorymesh/connectors/registry.py:1-20
架构设计
组件关系图
graph TD
A[config.yaml] --> B[ConnectorConfig]
B --> C[Connector]
C --> D{fetch_documents}
D --> E[Iterator[ParsedDocument]]
E --> F[FileIndexer]
F --> G[Chunker]
G --> H[EmbeddingProvider]
H --> I[VectorStore]
J[CONNECTOR_REGISTRY] --> C
K[HTTP Auth] --> C连接器类型矩阵
| 类型标识 | 连接器类 | 配置类 | 数据源 | 认证方式 |
|---|---|---|---|---|
confluence | ConfluenceConnector | ConfluenceConfig | Confluence Cloud REST API | HTTP Basic |
roam | RoamConnector | RoamConfig | JSON 导出文件 | 无(本地文件) |
logseq | LogseqConnector | LogseqConfig | Graph 目录 | 无(本地文件) |
notion | NotionConnector | NotionConfig | Notion API | OAuth/API Token |
jira | JiraConnector | JiraConfig | Jira REST API | HTTP Basic |
资料来源:src/memorymesh/connectors/registry.py:25-50
连接器注册机制
注册表结构
CONNECTOR_REGISTRY 是一个字典,将连接器类型字符串映射到 (ConfigClass, ConnectorClass) 元组:
CONNECTOR_REGISTRY: dict[str, tuple[Any, Any]] = {}
注册在 _register() 函数中通过延迟导入完成,避免在模块初始化时导入所有依赖:
def _register() -> None:
"""Populate CONNECTOR_REGISTRY with all known connectors."""
from memorymesh.connectors.airtable_connector import (
AirtableConfig,
AirtableConnector,
)
# ... 其他连接器导入
资料来源:src/memorymesh/connectors/registry.py:15-40
使用方式
CLI 命令通过注册表动态实例化连接器:
cfg_cls, conn_cls = CONNECTOR_REGISTRY["jira"]
config_obj = cfg_cls(**raw_config_dict)
connector = conn_cls(config_obj)
for doc in connector.fetch_documents():
indexer.index_parsed_document(doc)
Confluence 连接器
功能特性
- 偏移分页 - 通过
start/limit参数迭代获取所有页面 - 空间过滤 - 可选限制特定空间密钥(space keys)
- HTML 清洗 - 将
storage格式的页面体转换为纯文本 - 日期过滤 - 跳过
days_past时间窗口外的页面
资料来源:src/memorymesh/connectors/confluence_connector.py:1-30
配置参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
base_url | str | 是 | Confluence Cloud 实例 URL |
email | SecretStr | 是 | Atlassian 账户邮箱 |
api_token | SecretStr | 是 | API Token(从 id.atlassian.com 生成) |
space_keys | list[str] | 否 | 限制的空间密钥列表 |
days_past | int | 否 | 保留最近 N 天内的页面(默认 90) |
认证实现
HTTP Basic 认证使用 email:api_token 组合的 base64 编码:
from memorymesh.connectors._auth import basic_header
资料来源:src/memorymesh/connectors/confluence_connector.py:40-45
Roam Research 连接器
功能特性
- 递归块扁平化 - 将嵌套块树展平为纯文本字符串
- Roam 语法清洗 - 移除
{{[[...]]}}模板引用、[[...]]页面引用、#[[...]]和#tag标签语法 - 日期过滤 - 跳过
create-time或edit-time超出days_past窗口的页面 - 空页面跳过 - 跳过无块内容的页面
资料来源:src/memorymesh/connectors/roam_connector.py:1-35
导出格式
Roam 导出为单个 JSON 文件,包含页面对象数组,每个对象具有:
title- 字符串children- 嵌套块对象列表(块可递归包含children)
语法模式处理
按顺序应用多个正则表达式清洗 Roam 特定语法:
_ROAM_PATTERNS: list[re.Pattern[str]] = [
re.compile(r"\{\{(\[\[.*?\]\])"), # 模板引用
re.compile(r"\[\[.*?\]\]"), # 页面引用
re.compile(r"#\[\[.*?\]\]"), # 标签页面引用
re.compile(r"#\w+"), # 标签
]
资料来源:src/memorymesh/connectors/roam_connector.py:55-60
Logseq 连接器
功能特性
- 多目录扫描 - 扫描
pages/和journals/子目录 - 语法转换 - 处理 Logseq 特定语法
- 属性提取 - 从
key:: value属性行提取元数据 - 维基链接追踪 - 提取并去重的
[[wikilinks]]存储在元数据中 - 日记标记 - 期刊文件标记
is_journal=True
资料来源:src/memorymesh/connectors/logseq_connector.py:1-40
Logseq 语法处理对照表
| 原始语法 | 处理方式 | 示例 |
|---|---|---|
((uuid)) | 转换为 [block] | ((abc123...)) → [block] |
{{embed [[Page]]}} | 转换为 [embed: Page] | {{embed [[Note]]}} → [embed: Note] |
{{query ...}} | 完全移除 | {{query [[tag]]}} → 移除 |
key:: value | 提取到元数据,从正文移除 | tags:: #work → metadata["tags"] |
[[wikilinks]] | 保留原样 | [[Page A]] → [[Page A]] |
- 块符号 | 保留为纯文本 | - content → content |
资料来源:src/memorymesh/connectors/logseq_connector.py:45-70
文档处理流程
graph LR
A[Markdown 文件] --> B[_process_file]
B --> C{文件存在?}
C -->|否| D[跳过]
C -->|是| E[读取文本]
E --> F[正则替换]
F --> G[属性提取]
G --> H[Wikilinks 提取]
H --> I[ParsedDocument]资料来源:src/memorymesh/connectors/logseq_connector.py:90-120
配套解析器
部分数据源需要专门的解析器处理文件格式,而非通过连接器直接拉取。
Notion HTML 解析器
Notion 导出为单个 .html 文件,解析器从文件名提取 UUID、从 <h1> 或 <title> 提取标题:
# UUID 模式:Page Title a1b2c3...html
_UUID_FILENAME_RE = re.compile(
r"([0-9a-f]{8}(?:[0-9a-f]{4}){3}[0-9a-f]{12})(?:\s|\.|$)",
re.IGNORECASE,
)
提取的元数据字段:
notion_id- 页面 UUIDdatabase_name- 父目录名db_properties- 数据库属性列表
资料来源:src/memorymesh/parsing/notion_parser.py:25-45
Obsidian Markdown 解析器
Obsidian 解析器扩展基础 Markdown 解析:
| 功能 | 说明 | |
|---|---|---|
| YAML Frontmatter | 提取 tags、aliases、created、modified 字段 | |
| Wikilink 提取 | 匹配 [[link]] 和 `[[link\ | alias]]` 模式 |
| 内嵌过滤 | 排除 ![[img]] 图片引用 | |
| 标签提取 | 从 frontmatter 和行内 #tag 提取标签 |
支持的 frontmatter 字段映射:
| Frontmatter 字段 | Metadata 键 |
|---|---|
tags | tags |
aliases | aliases |
created | created |
modified | modified |
资料来源:src/memorymesh/parsing/obsidian_parser.py:25-55
Frontmatter 解析正则
_FRONTMATTER_RE = re.compile(
r"^---\r?\n(.*?)\r?\n(?:---|\.\.\.)(?:\r?\n|$)",
re.DOTALL,
)
_WIKILINK_RE = re.compile(r"(?<!!)\[\[([^\[\]|#]+?)(?:\|[^\[\]]*?)?\]\]")
HTTP 辅助模块
认证模块 (`_auth.py`)
提供基础认证头生成:
from memorymesh.connectors._auth import basic_header
支持标准 HTTP Basic 认证,用于 Confluence、Jira 等需要用户名/令牌的服务。
HTTP 模块 (`_http.py`)
提供统一的 API 请求方法:
from memorymesh.connectors._http import api_get
主要功能:
- 自动添加认证头
- 错误处理与日志记录
- JSON 响应解析
通用数据模型
ParsedDocument 结构
所有连接器返回统一的 ParsedDocument 对象:
| 字段 | 类型 | 说明 |
|---|---|---|
path | Path | 文件路径 |
text | str | 提取的纯文本内容 |
file_type | str | 规范化扩展名(如 .md、.html) |
encoding | str | 文本编码 |
metadata | dict | 源特定元数据 |
配置基类模式
每个连接器遵循统一配置模式:
from pydantic import BaseModel
class ConnectorConfig(BaseModel):
export_path: Path | None = None # 本地文件模式
days_past: int = 365 # 日期过滤窗口
使用示例
配置声明
在 config.yaml 中声明数据源:
资料来源:- name: confluence-wiki
type: confluence
config:
base_url: "https://myorg.atlassian.net"
email: "[email protected]"
api_token: "${CONFLUENCE_TOKEN}"
space_keys: ["ENG", "DOCS"]
days_past: 90
代码使用
from memorymesh.connectors.registry import CONNECTOR_REGISTRY
# 通过注册表实例化
cfg_cls, conn_cls = CONNECTOR_REGISTRY["confluence"]
config = cfg_cls(**source_config["config"])
connector = conn_cls(config)
# 迭代文档
for doc in connector.fetch_documents():
indexer.index_parsed_document(doc)
扩展新连接器
添加新连接器的步骤:
- 创建连接器文件 - 在
src/memorymesh/connectors/下创建xxx_connector.py - 定义配置类 - 创建
XxxConfig(BaseModel)配置模型 - 实现连接器类 - 创建
XxxConnector实现fetch_documents()方法 - 注册连接器 - 在
registry.py的_register()函数中添加导入语句
graph TD
A[创建 xxx_connector.py] --> B[定义 XxxConfig]
B --> C[实现 XxxConnector]
C --> D[实现 fetch_documents]
D --> E[更新 registry.py]注意事项
- 认证安全 - API Token 等敏感信息建议使用环境变量注入(
${VAR_NAME}语法) - 文件锁定 - 本地文件模式(如 Roam、Logseq)直接读取文件,注意并发写入
- 分页处理 - API 类连接器需妥善处理分页,避免漏掉数据
- 日期时区 - 日期比较使用
datetime模块,配置UTC时区处理
资料来源:[src/memorymesh/connectors/registry.py:1-20]()
MCP 工具参考
MemoryMesh 通过 FastMCP 框架提供一组 MCP(Model Context Protocol)工具,使 LLM 客户端能够访问用户的本地知识库。所有工具均注册在 FastMCP 实例上,支持搜索、检索、索引管理、内存分层和知识图谱查询等核心功能。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
工具概览
MemoryMesh MCP 工具分为以下几大类:
| 类别 | 工具 | 功能描述 |
|---|---|---|
| 搜索检索 | search_memory | 混合检索(向量 + BM25) |
ask_memory | RAG 问答(基于 Ollama) | |
search_by_date | 按时间范围检索 | |
related_documents | 查找相关文档 | |
| 文档管理 | list_sources | 列出配置的索引源 |
get_document | 获取完整文档内容 | |
summarize_source | 生成文档摘要 | |
| 索引控制 | index_now | 触发立即重建索引 |
sync_source | 同步数据源 | |
| 内存分层 | pin_memory | 将分块固定到热层 |
unpin_memory | 解除固定 | |
forget_memory | 抑制或冷置分块 | |
| 知识图谱 | graph_memory | 共现图查询 |
get_entity | 获取实体信息 | |
| 时间线 | query_timeline | 查询活动历史 |
资料来源:src/memorymesh/server/app.py:36-52
工具注册机制
所有 MCP 工具通过统一的注册函数 register(mcp, ctx) 挂载到 FastMCP 实例:
def create_mcp(ctx: AppContext) -> FastMCP:
mcp: FastMCP = FastMCP(
name="memorymesh",
instructions=(
"MemoryMesh gives you read access to the user's personal knowledge "
"base — local files indexed from configured source directories. "
"Use search_memory to find relevant passages, list_sources to "
"explore what has been indexed, get_document to read a full file, "
"index_now to trigger an immediate re-index, and ask_memory to "
"get an LLM-generated answer from your indexed content..."
),
)
# 注册所有工具
search_memory.register(mcp, ctx)
list_sources.register(mcp, ctx)
get_document.register(mcp, ctx)
index_now.register(mcp, ctx)
ask_memory.register(mcp, ctx)
# ... 其他工具
工具注册采用依赖注入模式,ctx(AppContext)包含所有运行时依赖:搜索引擎、元数据存储、审计日志等。
搜索检索类工具
search_memory
混合检索工具,结合密集向量检索(embedding)和稀疏 BM25 检索。
函数签名:
@mcp.tool()
async def search_memory(
query: Annotated[str, "要搜索的自然语言查询"],
top_k: Annotated[int, "返回的最大结果数"] = 5,
mode: Annotated[
Literal["hybrid", "dense", "sparse"],
"hybrid: 密集+稀疏混合; dense: 仅向量; sparse: 仅BM25"
] = "hybrid",
source_filter: Annotated[
list[str] | None,
"限制搜索的源名称列表"
] = None,
modality: Annotated[
Literal["all", "text", "image"],
"搜索的模态类型"
] = "all",
) -> SearchResponse
返回类型(SearchResponse):
| 字段 | 类型 | 说明 | ||
|---|---|---|---|---|
hits | list[SearchHit] | 排序后的搜索结果列表 | ||
mode | `"hybrid" \ | "dense" \ | "sparse"` | 使用的检索模式 |
duration_ms | float | 查询总延迟(毫秒) |
资料来源:src/memorymesh/core/models/search.py:1-30
ask_memory
RAG 问答工具,基于 Ollama 本地 LLM 生成答案。
核心机制:
用户问题 → search_memory → 构建上下文 → Ollama generate → 答案 + 引用源
函数签名:
@mcp.tool()
async def ask_memory(
question: str,
top_k: int = 5,
model: str | None = None,
) -> AskMemoryResponse
上下文构建参数:
_MAX_CONTEXT_CHARS_PER_HIT = 800:每个来源片段最多包含 800 字符
系统提示词模板:
_RAG_SYSTEM_PREAMBLE = (
"You are a helpful assistant answering questions about the user's personal "
"knowledge base. Use only the provided source passages to answer. "
"If the answer is not in the sources, say so clearly. "
"Cite the source file paths when relevant."
)
返回类型(AskMemoryResponse):
| 字段 | 类型 | 说明 | |
|---|---|---|---|
answer | `str \ | None` | LLM 生成的答案 |
sources | list[SearchHit] | 检索到的来源片段 | |
model | str | 使用的 Ollama 模型 | |
ollama_available | bool | Ollama 是否可用 | |
hint | `str \ | None` | 仅在 Ollama 不可用时返回安装提示 |
容错设计:当 Ollama 不可用时,ask_memory 仍返回检索结果和安装提示,绝不静默失败。
资料来源:src/memorymesh/server/tools/ask_memory.py:1-50
search_by_date
按时间范围检索文档变更历史。
典型用途: 查找特定时间段内修改过的文件内容。
related_documents
基于共现关系查找相关文档,输入一个 chunk_id,返回与其在相同上下文中出现的其他文档。
文档管理类工具
list_sources
列出所有配置的索引源及其统计信息。
返回类型(SourcesReport):
class SourcesReport(BaseModel):
资料来源:list[SourceStats]
class SourceStats(BaseModel):
name: str # 源名称
path: str # 源路径
recursive: bool # 是否递归
n_files_indexed: int # 已索引文件数
n_files_pending: int # 待处理文件数
n_files_errored: int # 错误文件数
last_scan_at: float | None # 上次扫描时间
total_chunks: int # 总分块数
disk_size_bytes: int # 磁盘占用
get_document
获取完整文档内容。
函数签名:
@mcp.tool()
async def get_document(
path: Annotated[str, "文档的绝对路径"],
max_bytes: Annotated[int, "最大读取字节数"] = 1_000_000,
) -> DocumentResponse
返回类型(DocumentResponse):
| 字段 | 类型 | 说明 |
|---|---|---|
path | str | 文档绝对路径 |
content | str | 文件内容(可能截断) |
file_type | str | 标准化文件扩展名 |
size_bytes | int | 原始文件大小 |
mtime | float | 修改时间戳 |
truncated | bool | 是否被截断 |
异常处理:
DocumentNotFoundError:路径不在任何配置的源中DocumentTooLargeError:文件超过max_bytes,返回开头 + 摘要
资料来源:src/memorymesh/core/models/search.py:45-70
summarize_source
对指定源目录生成摘要信息,支持深度分析和要点提取。
索引控制类工具
index_now
强制重新索引指定路径。
@mcp.tool()
async def index_now(
path: str,
recursive: bool = True,
) -> IndexResponse
约束条件:
- 路径必须在某个配置的源目录内
- 返回处理统计:文件数、分块数、耗时
sync_source
同步数据源,扫描新增、修改和删除的文件,更新索引。
内存分层类工具
MemoryMesh 支持内存分层(Memory Tiers),将分块分为 hot、warm、cold 层级,实现访问频率感知的存储优化。
内存层级说明
| 层级 | 说明 | 自动行为 |
|---|---|---|
| hot | 热点数据,高频访问 | 手动固定或自动提升 |
| warm | 温数据 | 定期维护时根据访问频率调整 |
| cold | 冷数据 | 逐步降级,评分衰减 |
pin_memory / unpin_memory
将分块固定到热层或解除固定。
@mcp.tool()
def pin_memory(
chunk_id: Annotated[str, "分块标识符,格式为 '<path>:<chunk_index>'"],
) -> dict:
"""固定分块到热层"""
return {"chunk_id": chunk_id, "tier": "hot", "pinned": True}
@mcp.tool()
def unpin_memory(
chunk_id: Annotated[str, "分块标识符,格式为 '<path>:<chunk_index>'"],
) -> dict:
"""解除固定,允许正常层级调整"""
return {"chunk_id": chunk_id, "pinned": False}
解除固定后,分块在下次维护运行前保留在热层,之后根据访问历史进行升降级。
资料来源:src/memorymesh/server/tools/pin_memory.py:1-60
forget_memory
抑制或冷置分块,实现内容的渐进式遗忘。
@mcp.tool()
def forget_memory(
chunk_id: Annotated[
str,
"分块标识符,格式为 '<absolute_path>:<chunk_index>'",
],
mode: Annotated[
Literal["suppress", "cold"],
"'suppress': 立即隐藏; 'cold': 降级到冷层,评分逐渐衰减"
] = "suppress",
) -> dict
模式对比:
| 模式 | 行为 | 可逆性 |
|---|---|---|
suppress | 加入抑制列表,立即从搜索结果中隐藏 | 不可逆(需直接数据库访问) |
cold | 降级到冷层,评分逐渐衰减 | 可通过 pin_memory 恢复 |
资料来源:src/memorymesh/server/tools/forget_memory.py:1-50
知识图谱类工具
graph_memory
共现图查询工具,基于实体共现关系构建知识图谱。
典型用途:
- 发现概念间的关联
- 探索特定实体类型的分布
- 设定最小提及阈值过滤噪声
服务端缓存: 图数据在服务器端缓存 60 秒,减少重复计算。
get_entity
查询特定实体的详细信息,包括属性、关系和上下文。
时间线类工具
query_timeline
查询活动历史记录,包括索引操作、查询历史和变更事件。
审计日志字段:
| 字段 | 说明 |
|---|---|
ts | 时间戳 |
tool | 调用的工具名称 |
query_hash | 查询的 SHA256 哈希(截断至 16 字符) |
n_results | 返回结果数 |
latency_ms | 延迟(毫秒) |
client_id_if_known | 客户端标识(已知时) |
隐私保护:审计日志不包含文档内容、chunk 内容或明文查询。
工具调用流程
sequenceDiagram
participant Client as LLM 客户端
participant MCP as FastMCP 服务器
participant Engine as 搜索引擎
participant DB as 元数据存储
participant Ollama as Ollama LLM
Client->>MCP: search_memory(query, top_k)
MCP->>Engine: 混合检索
Engine->>DB: 查询向量 + BM25
DB-->>Engine: SearchHit 列表
Engine-->>MCP: SearchResponse
MCP-->>Client: hits + duration_ms
Client->>MCP: ask_memory(question)
MCP->>MCP: search_memory(question, top_k)
MCP->>Ollama: generate(RAG_prompt)
Ollama-->>MCP: answer
MCP-->>Client: AskMemoryResponse
Note over Client,DB: 写操作(index_now、pin_memory 等)
Client->>MCP: index_now(path)
MCP->>DB: 验证路径有效性
MCP->>Engine: 重新索引
Engine->>DB: 更新元数据
DB-->>MCP: IndexResponse
MCP-->>Client: 处理统计访问控制与审计
认证守卫
工具注册时通过 check_access(ctx, operation) 检查访问权限:
from memorymesh.server.auth_guard import check_access
if (err := check_access(ctx, "index")) is not None:
return err
审计日志
每次工具调用都会记录到 ~/.memorymesh/audit.jsonl:
{"ts": 1703001234.567, "tool": "search_memory", "query_hash": "a1b2c3d4e5f6g7h8", "n_results": 5, "latency_ms": 42.3}
配置与扩展
配置来源优先级
_DEFAULT_SEARCH_PATHS = [
Path.cwd() / "config.yaml", # 1. 当前工作目录
Path.home() / ".memorymesh" / "config.yaml", # 2. 用户配置目录
]
必需配置示例
资料来源:- name: documents
path: ~/Documents
recursive: true
extensions: [.txt, .md, .pdf]
embeddings:
model: "sentence-transformers/all-MiniLM-L6-v2"
ollama:
enabled: true
model: "llama2"
错误处理规范
| 错误类型 | 返回方式 | 说明 |
|---|---|---|
| 配置错误 | 抛出 ConfigError | YAML 解析或验证失败 |
| 文档未找到 | 返回 DocumentNotFoundError | 路径不在任何源中 |
| 文档过大 | 返回部分内容 + DocumentTooLargeError | 包含开头和摘要 |
| Ollama 不可用 | 返回 hint | ask_memory 的容错机制 |
| 访问被拒 | 返回错误字典 | check_access 拦截 |
工具元数据速查
| 工具名称 | 返回类型 | 异步 | LLM 依赖 |
|---|---|---|---|
search_memory | SearchResponse | ✅ | ❌ |
ask_memory | AskMemoryResponse | ✅ | Ollama |
list_sources | SourcesReport | ❌ | ❌ |
get_document | DocumentResponse | ✅ | ❌ |
index_now | IndexResponse | ✅ | ❌ |
pin_memory | dict | ❌ | ❌ |
unpin_memory | dict | ❌ | ❌ |
forget_memory | dict | ❌ | ❌ |
graph_memory | dict | ❌ | ❌ |
get_entity | dict | ❌ | ❌ |
query_timeline | dict | ❌ | ❌ |
search_by_date | SearchResponse | ❌ | ❌ |
related_documents | list[SearchHit] | ❌ | ❌ |
summarize_source | dict | ❌ | ❌ |
sync_source | dict | ❌ | ❌ |
资料来源:[src/memorymesh/server/app.py:36-52]()
失败模式与踩坑日记
保留 Doramagic 在发现、验证和编译中沉淀的项目专属风险,不把社区讨论只当作装饰信息。
假设不成立时,用户拿不到承诺的能力。
新项目、停更项目和活跃项目会被混在一起,推荐信任度下降。
下游已经要求复核,不能在页面中弱化。
风险会影响是否适合普通用户安装。
Pitfall Log / 踩坑日志
项目:kilhubprojects/memory-mesh
摘要:发现 6 个潜在踩坑项,其中 0 个为 high/blocking;最高优先级:能力坑 - 能力判断依赖假设。
1. 能力坑 · 能力判断依赖假设
- 严重度:medium
- 证据强度:source_linked
- 发现:README/documentation is current enough for a first validation pass.
- 对用户的影响:假设不成立时,用户拿不到承诺的能力。
- 建议检查:将假设转成下游验证清单。
- 防护动作:假设必须转成验证项;没有验证结果前不能写成事实。
- 证据:capability.assumptions | github_repo:1238654023 | https://github.com/kilhubprojects/memory-mesh | README/documentation is current enough for a first validation pass.
2. 维护坑 · 维护活跃度未知
- 严重度:medium
- 证据强度:source_linked
- 发现:未记录 last_activity_observed。
- 对用户的影响:新项目、停更项目和活跃项目会被混在一起,推荐信任度下降。
- 建议检查:补 GitHub 最近 commit、release、issue/PR 响应信号。
- 防护动作:维护活跃度未知时,推荐强度不能标为高信任。
- 证据:evidence.maintainer_signals | github_repo:1238654023 | https://github.com/kilhubprojects/memory-mesh | last_activity_observed missing
3. 安全/权限坑 · 下游验证发现风险项
- 严重度:medium
- 证据强度:source_linked
- 发现:no_demo
- 对用户的影响:下游已经要求复核,不能在页面中弱化。
- 建议检查:进入安全/权限治理复核队列。
- 防护动作:下游风险存在时必须保持 review/recommendation 降级。
- 证据:downstream_validation.risk_items | github_repo:1238654023 | https://github.com/kilhubprojects/memory-mesh | no_demo; severity=medium
4. 安全/权限坑 · 存在评分风险
- 严重度:medium
- 证据强度:source_linked
- 发现:no_demo
- 对用户的影响:风险会影响是否适合普通用户安装。
- 建议检查:把风险写入边界卡,并确认是否需要人工复核。
- 防护动作:评分风险必须进入边界卡,不能只作为内部分数。
- 证据:risks.scoring_risks | github_repo:1238654023 | https://github.com/kilhubprojects/memory-mesh | no_demo; severity=medium
5. 维护坑 · issue/PR 响应质量未知
- 严重度:low
- 证据强度:source_linked
- 发现:issue_or_pr_quality=unknown。
- 对用户的影响:用户无法判断遇到问题后是否有人维护。
- 建议检查:抽样最近 issue/PR,判断是否长期无人处理。
- 防护动作:issue/PR 响应未知时,必须提示维护风险。
- 证据:evidence.maintainer_signals | github_repo:1238654023 | https://github.com/kilhubprojects/memory-mesh | issue_or_pr_quality=unknown
6. 维护坑 · 发布节奏不明确
- 严重度:low
- 证据强度:source_linked
- 发现:release_recency=unknown。
- 对用户的影响:安装命令和文档可能落后于代码,用户踩坑概率升高。
- 建议检查:确认最近 release/tag 和 README 安装命令是否一致。
- 防护动作:发布节奏未知或过期时,安装说明必须标注可能漂移。
- 证据:evidence.maintainer_signals | github_repo:1238654023 | https://github.com/kilhubprojects/memory-mesh | release_recency=unknown
来源:Doramagic 发现、验证与编译记录