Memtide
轻量级 Agent 记忆引擎 — 生产级 PostgreSQL + Qdrant、可插拔 LLM
波峰 M:字母的两个峰是两道潮,中央负空间的潮谷里藏着一块品牌绿——涨潮强化,落潮遗忘。
License note: Memtide is MIT. The Docker image ships PostgreSQL with the ParadeDB
pg_searchextension (AGPL-3.0 community edition) — it runs as a separate database process, so your application code stays MIT.
运行时固定使用 PostgreSQL + Qdrant + 真实 OpenAI 兼容端点;核心 HTTP 客户端只用 Python 标准库,PostgreSQL 驱动 psycopg 是唯一运行时依赖。
Memtide 融合了 2026 年主流记忆框架的核心设计,外加一个原创机制:
| 借鉴自 | 设计 | 在 Memtide 中 |
|---|---|---|
| Mem0 | 记忆是原子事实而非原始消息;写时 LLM 决策 ADD/UPDATE/DELETE/NOOP | engine.add() 两阶段管线 |
| Letta (MemGPT) | 常驻要点:render_context() 按重要度×留存度实时选出 core memory 块注入 system prompt | render_context() |
| Zep / Graphiti | 时间线审计:失效不删除,valid_at/invalid_at 双时标 + 全量事件日志 |
get_history() |
| LangMem / 认知科学 | 检索强化 + 遗忘曲线:访问越多记得越牢,久不访问自然淡忘 | decay.py(Ebbinghaus) |
| 预测编码(Rao & Ballard; Itti & Baldi; van Kesteren) | 预测误差门控编码:完全被先验预测到的信息不写入,越意外记得越牢 | gating.py(PredictiveGate) |
快速开始
from memtide import MemoryEngine, MemoryConfig
# 生产模式:PostgreSQL + 任意 OpenAI 兼容 LLM/embedding 端点
mem = MemoryEngine(MemoryConfig(
pg_dsn="postgresql://memtide:pw@localhost:5432/memtide",
llm_base_url="http://your-llm/v1", llm_model="glm-5.3-flash",
llm_api_key="sk-...", embedding_backend="dashscope", dashscope_api_key="sk-..."))
# 1. 写入:对话 → 原子事实 → 与旧记忆冲突消解
mem.add([{"role": "user", "content": "嗨,我叫李雷,住在杭州,喜欢喝美式咖啡"}], user_id="alice")
# → AddResult(facts=['用户的名字是李雷', '用户住在杭州', '用户喜欢喝美式咖啡'], added=[...])
# 2. 事实变化时自动 UPDATE(不是并存!),旧的值进入审计日志
mem.add("我搬到上海了", user_id="alice")
# → AddResult(updated=[...]) '用户住在杭州' 被替换为 '用户住在上海'
# 3. 混合检索:向量 + BM25 全文 + 实体三路召回,RRF 融合后按留存度+重要度重排,每路得分可解释
hits = mem.search("用户现在住在哪里?", user_id="alice")
# → [{'memory': '用户住在上海', 'score': 0.24,
# 'components': {'rrf': 0.02, 'semantic': 0.49, 'bm25': 0, 'entity': 0, 'retention': 1.0}}]
# 4. 注入 system prompt 的核心记忆块(Letta 风格)
system_prompt = "你是助理。\n" + mem.render_context(user_id="alice", query="用户的职业?")
# 5. 全量审计:ADD → UPDATE → ACCESS,每步带 prev/new 值
mem.get_history(memory_id=hits[0].memory.id)
文档
| 文档 | 内容 |
|---|---|
| docs/design.md | 设计方案:架构总览、写入/检索/门控/反思/多模态各子系统详细设计与取舍 |
| docs/usage.md | 使用说明:Python/REST API 全参考、Web UI、Docker 部署、配置表、FAQ |
| docs/development.md | 开发文档:环境搭建、测试体系、代码约定、扩展后端、UI 构建 |
| docs/predictive-coding-gate.md | 预测编码门控的数学推导与实测标定 |
接入真实 LLM / embedding / 存储后端(可选)
cfg = MemoryConfig(
# 存储:PostgreSQL(唯一后端,pg_search BM25 全文检索)
storage_backend="postgres", pg_dsn="postgresql://memtide:pw@localhost:5432/memtide",
# 向量:Qdrant ANN(urllib 直连 REST,零 SDK)
vector_backend="qdrant", qdrant_url="http://localhost:6333",
# LLM:任何 OpenAI 兼容端点
llm_backend="openai", llm_base_url="http://your-llm/v1",
llm_model="GLM-5.3-Flash", llm_api_key="sk-...",
# embedding:DashScope(qwen3.7-text-embedding),维度自动探测
embedding_backend="dashscope", dashscope_api_key="sk-...",
)
mem = MemoryEngine(cfg)
真 LLM 下抽取/消解/反思由 LLM 完成,并输出 slot(易变属性)标签驱动冲突 更新;真 embedding 下检索为真语义向量(实测中文查询可跨语言命中英文记忆, DashScope qwen3.7-text-embedding 同主题 cos≈0.56 / 跨主题 cos≈0.28)。
更换 LLM / embedding 端点
Memtide 与任何 OpenAI 兼容端点协作(DeepSeek、Qwen、vLLM、Ollama 等):
cfg = MemoryConfig(
llm_backend="openai",
llm_base_url="https://api.deepseek.com/v1", # 或本地 vLLM/Ollama
llm_model="deepseek-chat",
llm_api_key="sk-...", # 或环境变量 OPENAI_API_KEY / LLM_API_KEY
embedding_backend="dashscope", # 或 openai
)
mem = MemoryEngine(cfg)
抽取与冲突消解由 LLM 完成(提示词见 llm.py);引擎只讲真实 OpenAI 协议,
测试用本地协议服务器(tests/fake_openai.py)保持确定性。
Docker 部署
cp .env.example .env # 填入 LLM / DASHSCOPE key
docker compose up -d --build
curl localhost:8300/stats # 返回真实 LLM / embedding / PostgreSQL / Qdrant 名称
栈组成:paradedb/paradedb:pg16(PostgreSQL 16 + pg_search BM25 全文检索)、qdrant(向量 ANN 索引,
embedding 模型变更时自动重建集合)、memtide(REST 服务)。数据分别在
pgdata/qdrantdata/mndata volume 中持久化(媒体文件在 mndata:/data/media)。
真实端点验证:python3 scripts/live_check.py --docker 顺序检查 LLM 连通、
embedding 维度、真实后端全链路(抽取/门控/检索/易变属性更新/LLM 反思/审计链)、
Docker REST 冒烟。真端点集成测试 tests/test_live.py(MEMTIDE_LIVE=1 门控,
默认跳过)。
可视化管理台(Web UI)
马卡龙浅色主题(奶油底 + 开心果绿/草莓粉/蓝莓/薰衣草语义徽章),设计规范由
ui-ux-pro-max skill
约束:语义色 token、文本对比度 ≥4.5:1、8px 间距体系、可见焦点环、
prefers-reduced-motion 支持。演示数据可用 scripts/seed_demo.py 一键播种
(在容器内执行,幂等)。
REST 服务内置 React 管理台:启动后 http://localhost:8300/ 是官网,/console 是管理台(五个页面),/docs 是可在线执行的接口文档:
| 页面 | 功能 |
|---|---|
| 总览 | 记忆数/失效数/事件分布图、后端信息 |
| 记忆库 | 过滤浏览(含失效/被取代记忆)、重要度条、门控徽章、surprise 值;编辑/删除(软/硬);点「详情」打开详情抽屉 |
| ↳ 详情抽屉 | 完整字段 + 审计时间线(ADD→UPDATE→ACCESS→CONSOLIDATE 可视化)+ superseded_by 跳转 |
| 检索试玩 | query → 命中结果带得分成分条(语义/留存度/RRF/全文/实体命中),直观理解混合检索排序 |
| 核心记忆 | 渲染 render_context() 输出块预览 + 复制 |
| 操作 | 快速写入并可视化 AddResult(每条事实的编码/拦截决策 + surprise bits)、一键后台反思、重建索引、重置库 |
开发模式:cd webui && npm install && npm run dev(Vite 5173 端口,API 代理到
8300)。构建:npm run build 产物拷入 memtide/static/(Docker 镜像多阶段构建
自动完成)。MEMTIDE_STATIC_DIR 可自定义静态目录;目录不存在时服务退化为纯 API。
REST API 服务
内置 HTTP 服务(标准库 http.server 实现;psycopg 是唯一运行时依赖),一行启动:
source .env && python -m memtide serve --port 8300
所有能力都以 JSON 接口暴露,任何语言的 agent 都能接入:
# 写入(带预测编码门控 + 冲突消解,返回完整决策明细)
curl -X POST localhost:8300/memories \
-d '{"text": "我叫李雷,住在杭州", "user_id": "alice"}'
# 混合检索(返回可解释的 components 得分)
curl -X POST localhost:8300/search \
-d '{"query": "用户住在哪里", "user_id": "alice", "limit": 3}'
# 其他接口
curl localhost:8300/memories?user_id=alice # 列出
curl localhost:8300/memories/<id> # 单条
curl -X PUT localhost:8300/memories/<id> -d '{"text": "..."}' # 修正
curl -X DELETE localhost:8300/memories/<id> # 软删除(?hard=true 彻底删)
curl "localhost:8300/context?user_id=alice&query=..." # core memory 块
curl -X POST localhost:8300/consolidate -d '{"user_id": "alice"}' # 后台反思
curl localhost:8300/history # 审计日志
curl localhost:8300/stats
多模态记忆
OpenAI content-parts 格式的图片 / 音频 / 文件可以直接进管线。做法与 Mem0 一致 (业界主流):写入时把媒体归一化为文字描述(可配置的 vision/STT 端点), 描述文本走正常的抽取 → 门控 → 冲突消解管线,原始字节以 sha256 内容寻址落盘为 附件,随记忆一起返回。文字查询即可跨模态召回图片记忆:
eng.add([{"role": "user", "content": [
{"type": "text", "text": "看看这张图"},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}, # 或 https:// / 本地路径
]}], user_id="alice")
# → 抽出的原子事实带 attachments 引用(+ metadata.modality)
# → 检索 "图里有什么" 返回事实 + GET /media/{sha256} 取回原图
curl -X POST localhost:8300/memories -d '{"messages": [{"role": "user", "content": [
{"type": "image_url", "image_url": {"url": "https://example.com/photo.jpg"}}]}]}'
curl localhost:8300/media/<sha256> # 取回原始素材(不可变缓存)
vision 端点默认复用主 LLM 配置(任何 OpenAI 兼容视觉模型,如 qwen-vl-max),
可用 MEMTIDE_VISION_BASE_URL / MEMTIDE_VISION_MODEL / MEMTIDE_VISION_API_KEY
单独指定;音频转录需配置 MEMTIDE_STT_MODEL,未配置时音频仅存引用不转写;
vision 不可用时优雅降级——素材照存、描述留空,绝不丢数据。
CLI
source .env && python -m memtide add "我叫李雷,喜欢 Rust"
python -m memtide search "用户喜欢什么语言"
python -m memtide context "用户的偏好" # 渲染核心记忆块
python -m memtide history # 审计日志
python -m memtide stats
架构
写入: messages ──▶ Extractor ──▶ 原子事实(带 slot/重要度/实体)
│
PredictiveGate 预测编码门控 ◀── 记忆先验
(完全被预测到的 → 拦下;越意外 → 重要度越高)
│
对相似旧记忆做冲突消解 ◀── LLM/规则
│
ADD / UPDATE / DELETE / NOOP
│
存储: PostgreSQL ── memories + BM25(中英可查) + entities + memory_history
│
读取: query ──▶ ┌ 向量余弦(top-40) ┐
├ BM25 全文 ├─▶ RRF 融合 ─▶ + 遗忘曲线留存度
└ 实体索引匹配 ┘ + 重要度
│
检索命中 ──▶ access_count++ (间隔效应: 越用越难忘)
关键机制
- 预测编码门控:记忆库即生成式先验,新事实按预测误差 S = -log2 p̂ 分流—— S ≤ 0.5 bits 不存(完全被预测到)、中间正常整合、S ≥ 2.5 bits 加权编码 (越意外记得越牢);易变属性冲突强制写入。详见 docs/predictive-coding-gate.md。
- 易变属性槽位(slot):
name/location/role/employer/age/stack/plan属于"人会变"的事实——同一槽位出现新值时自动 UPDATE(旧值进历史),而非叠加矛盾记忆。喜好类(like)则允许多条并存,仅在极性冲突(喜欢↔不喜欢)时替换。 - 后台反思(LangMem 式):
mem.consolidate_background()定期把同主题高密度 簇蒸馏成一条概括记忆(重要度取簇内最高 +0.05,使其更常驻 core memory 块, 且半衰期 ×3 抗衰减), 原事实标invalid_at并留superseded_by链接,审计日志记 CONSOLIDATE 事件——记忆库越用越小、越用越精,且无信息丢失。 - 遗忘是软性的:留存度
retention = 0.5^(age / (half_life × min(1 + 0.4·ln(1+access_count), 4.0))),低于阈值的记忆不再浮现但保留在库中——被再次查询时即可"复忆"。 - 多租户隔离:所有读写按
user_id/agent_id/run_id作用域隔离。
项目结构
memtide/
├── memtide/
│ ├── engine.py # MemoryEngine:add/search/render_context/get_history
│ ├── llm.py # OpenAI 兼容客户端(提示词与 JSON 解析)
│ ├── embeddings.py # OpenAI / DashScope embedder(auto 按 key 选择)
│ ├── retrieval.py # 三路召回 + RRF 融合 + 可解释得分
│ ├── decay.py # Ebbinghaus 遗忘曲线 + 检索强化
│ ├── gating.py # 预测编码门控:surprise 三分流(novel/integrate/reject)
│ ├── consolidation.py # 后台反思:聚类 → 概括 → supersede 链
│ ├── storage.py # StorageBase 契约(PostgreSQL 唯一后端)
│ ├── pgstore.py # PostgreSQL 后端(psycopg3 + pg_search BM25)
│ ├── vectorstore.py # Qdrant ANN(urllib REST)
│ ├── multimodal.py # 多模态接入:媒体 parts → 文字描述 + sha256 附件
│ ├── types.py # Memory / SearchResult / AddResult 数据类
│ ├── config.py # MemoryConfig(全部阈值可调)
│ ├── server.py # REST API(stdlib http.server,含 /media 素材服务)
│ └── cli.py # 命令行入口
├── tests/test_memtide.py # 94 个测试(hermetic 回归:全功能/性能路径/多模态/迁移/运维/连接池上限/SSRF 守卫/布尔与 flag 解析)+ 14 个 live 集成测试(真实端点含真实视觉/并发/别名/衰减,MEMTIDE_LIVE=1 门控)
└── examples/
测试
python3 -m unittest tests.test_memtide # 94 tests, PG/Qdrant + 本地协议服务器,无外部网络
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file memtide-0.1.0.tar.gz.
File metadata
- Download URL: memtide-0.1.0.tar.gz
- Upload date:
- Size: 91.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
25c98140b346cc182c8379ff40b4a9d8dc51a576379488ed03844c9506324882
|
|
| MD5 |
93fc5f60c14d9769f738d4185f0e2274
|
|
| BLAKE2b-256 |
a6a28b92ecd53899772d0f5e6e69a9e7ff38a0f1903c1c235a00e561017abc0d
|
Provenance
The following attestation bundles were made for memtide-0.1.0.tar.gz:
Publisher:
publish.yml on Yang-Shihui/memtide
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
memtide-0.1.0.tar.gz -
Subject digest:
25c98140b346cc182c8379ff40b4a9d8dc51a576379488ed03844c9506324882 - Sigstore transparency entry: 2721912008
- Sigstore integration time:
-
Permalink:
Yang-Shihui/memtide@8b3353de77b3ebc407eba985100fb3e78baa10bc -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Yang-Shihui
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8b3353de77b3ebc407eba985100fb3e78baa10bc -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file memtide-0.1.0-py3-none-any.whl.
File metadata
- Download URL: memtide-0.1.0-py3-none-any.whl
- Upload date:
- Size: 68.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b2851fce3970df4e18e850352024529e2fc1a40ced87ffe6a0d62be16af39d6c
|
|
| MD5 |
3fd7073a39fbe9459dcef955bb846965
|
|
| BLAKE2b-256 |
428ee86b750cf930b9960008f936c1084b7468432a84f45ea05e97b782c6f304
|
Provenance
The following attestation bundles were made for memtide-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on Yang-Shihui/memtide
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
memtide-0.1.0-py3-none-any.whl -
Subject digest:
b2851fce3970df4e18e850352024529e2fc1a40ced87ffe6a0d62be16af39d6c - Sigstore transparency entry: 2721912337
- Sigstore integration time:
-
Permalink:
Yang-Shihui/memtide@8b3353de77b3ebc407eba985100fb3e78baa10bc -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Yang-Shihui
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8b3353de77b3ebc407eba985100fb3e78baa10bc -
Trigger Event:
workflow_dispatch
-
Statement type: