关于 memex
memex 为 AI Agent 提供持久化记忆服务,同时支持 REST 和 MCP 接口。它结合语义搜索与近期性排序,并通过 ONNX 在本地生成嵌入。
主要功能
- 01存储、搜索和删除记忆
- 02按相似度与近期性排序
- 03本地 ONNX 嵌入
- 04后台整理情景记忆
- 05Prometheus 指标与 Grafana 面板
适用场景
- 01保留 Agent 跨会话上下文
- 02检索近期且相关的对话信息
- 03通过 MCP 记忆工具连接 Agent
MCP 相关
提供 Streamable HTTP MCP 服务,支持存储、搜索、删除和计数记忆的工具。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
docker run -i --rm ghcr.io/ayushagrawal288/memex:v0.1.0本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
ghcr.io/ayushagrawal288/memex:v0.1.0Streamable HTTP工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
memex
面向 AI Agent 的生产级持久化记忆服务。默认情况下,Agent 在会话之间会忘记所有内容——memex 解决了这一问题。它使用带有时间衰减的语义搜索来存储、检索和排序对话记忆,让 Agent 能找到相关且近期的内容,而不只是语义上最接近的内容。
POST /v1/memories → store a memory, embed it, persist to Postgres
POST /v1/memories/search → retrieve top-k memories ranked by similarity + recency
DELETE /v1/memories/{id} → forget a specific memory
GET /v1/memories/count → how many memories does this agent/user have
GET /health → liveness + DB connectivity check
GET /metrics → Prometheus metrics
架构
caller (agent / app)
│
▼
FastAPI (async)
│
┌────┴────┐
│ │
embeddings asyncpg pool (min=5, max=20)
(fastembed │
ONNX, ▼
local) PostgreSQL 16
pgvector extension
ivfflat index (cosine)
写入路径: 内容 → fastembed ONNX 推理(本地,CPU 上约 12 ms,BAAI/bge-small-en-v1.5)→ 插入带有 384 维向量的数据 → 返回记忆 ID。
读取路径: 查询 → 嵌入 → pgvector 余弦搜索(top_k × 3 个候选项)→ 在 Python 中使用时间衰减重新排序 → 返回带分数的 top_k 个结果。
设计决策
1. 在语义搜索基础上加入时间衰减
纯向量相似度会返回语义最相似的记忆,而非最有用的记忆。90 天前的一条事实若相似度匹配为 0.95,通常不如昨天的一条 0.80 匹配有用。
分数公式:
score = α × cosine_similarity + (1 − α) × exp(−λ × age_days)
其中 λ = ln(2) / half_life_days(默认值:30 天,因此一条 30 天前的记忆具有 50% 的时间权重)。
α 可按请求配置(默认值为 0.7)。任务导向型 Agent 使用较高的 α(语义占主导)。对话型 Agent 使用较低的 α(时效性更重要)。
2. 获取 3 倍候选项,再用 Python 重新排序
pgvector 查询会返回按纯相似度排序的 top_k × 3 个候选项。Python 使用衰减公式重新排序,并截取至 top_k 个结果。这样可避免时间衰减导致高相似度的旧记忆被排除——它们仍会保留在候选池中。
当规模扩大 10 倍(每个 Agent 超过 100 万条记忆)时:使用 pg_proc 将评分逻辑推入 Postgres 函数,以消除 Python 重新排序的往返开销。
3. 选择 asyncpg 和显式连接池大小,而非 SQLAlchemy async
SQLAlchemy 会为每次查询增加 ORM 开销。热检索路径——嵌入、查询、重新排序——需要尽可能精简。asyncpg 可直接控制连接池的最小值和最大值(与在 Java 中调优 HikariCP 的思路相同)。无论如何,pgvector 查询都需要针对 <=> 运算符使用原始 SQL。
连接池默认值:min=5, max=20。适用于单实例部署。可通过 DB_MAX_POOL_SIZE 环境变量覆盖。
4. 在 Postgres 中而非 Redis 中进行速率限制
通过 upsert 实现滑动窗口计数器。少一个依赖项。并发请求下结果正确(事务性 upsert)。当规模扩大 10 倍并采用分布式部署时:改用 Redis INCR + EXPIRE——原子操作,不存在锁竞争。
5. 使用 ivfflat 索引,而非 HNSW
ivfflat 的构建成本和内存占用更低——在中小规模(少于 100 万个向量)下,这是更合适的权衡。lists=100 在约 100 万行以内表现良好。规模扩大 10 倍时:切换到 HNSW(m=16, ef_construction=64),以更高的内存和构建时间为代价提升召回率。
本地运行
前置条件: Docker 和 Docker Compose。无需 API 密钥——整个技术栈都在本地运行。
git clone https://github.com/ayushagrawal288/memex
cd memex
docker compose up
API 已在 http://localhost:8000 上运行。交互式文档位于 http://localhost:8000/docs。
API 参考
存储记忆
curl -X POST http://localhost:8000/v1/memories \
-H "Content-Type: application/json" \
-d '{
"agent_id": "my-agent",
"user_id": "user-123",
"content": "User prefers concise responses and dislikes verbose explanations.",
"memory_type": "semantic",
"importance": 1.2
}'
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"agent_id": "my-agent",
"user_id": "user-123",
"content": "User prefers concise responses and dislikes verbose explanations.",
"importance": 1.2,
"memory_type": "semantic",
"created_at": "2026-05-26T10:30:00Z",
"score": null
}
搜索记忆
curl -X POST http://localhost:8000/v1/memories/search \
-H "Content-Type: application/json" \
-d '{
"agent_id": "my-agent",
"user_id": "user-123",
"query": "how does this user like to communicate",
"top_k": 5,
"alpha": 0.7
}'
{
"results": [
{
"id": "3fa85f64-...",
"content": "User prefers concise responses and dislikes verbose explanations.",
"memory_type": "semantic",
"created_at": "2026-05-26T10:30:00Z",
"score": 0.8921
}
],
"query": "how does this user like to communicate",
"total": 1
}
记忆类型
| 类型 | 用途 |
|---|---|
episodic | 特定事件、过往对话 |
semantic | 事实、偏好、常识 |
procedural | 工作流、操作指南 |
负载测试结果
在 MacBook M 系列、Docker Desktop、单个 Postgres 实例上运行:
locust -f scripts/load_test.py --host=http://localhost:8000 \
--headless -u 50 -r 10 -t 60s
真实负载(50 个用户,思考时间为 100–300 ms——模拟实际 Agent 流量):
| 端点 | RPS | p50(ms) | p95(ms) | p99(ms) | 错误率 |
|---|---|---|---|---|---|
| POST /v1/memories(写入) | 27 | 160 | 270 | 330 | 0% |
| POST /v1/memories/search | 83 | 110 | 200 | 250 | 0% |
| 汇总 | 113 | 120 | 230 | 300 | 0% |
饱和度测试(500 个用户,最短思考时间——找出吞吐量上限):
| 端点 | RPS(平台值) | p50(ms) | p99(ms) | 错误率 |
|---|---|---|---|---|
| POST /v1/memories(写入) | 28 | 3,900 | 6,100 | 0% |
| POST /v1/memories/search | 91 | 3,600 | 5,800 | 0% |
| 汇总 | ~120 | 3,700 | 5,900 | 0% |
在 MacBook M 系列、Docker Desktop(4 个 CPU)、4 个 uvicorn 工作进程、每个工作进程 16 个线程上运行。
嵌入:本地 ONNX(BAAI/bge-small-en-v1.5)——不调用外部 API,零成本。
为什么上限约为 120 RPS:
每次写入和每次搜索都需要一次 ONNX 推理(CPU 上约 10–15 ms)。使用 4 个 Docker CPU:理论最大值为 4 cores / 12 ms ≈ 333 embeddings/s。计入 Python 开销、数据库查询和 asyncio 调度后,实际约为 120 RPS。
提高吞吐量的途径:
| 方法 | 预期提升 | 复杂度 |
|---|---|---|
| 嵌入缓存(Redis,键 = 文本的 SHA256) | 2–3 倍(重复 Agent 查询的命中率为 40–60%) | 低 |
| 水平扩展(在负载均衡器后部署 N 个副本) | 线性提升 N 倍 | 中 |
| GPU 推理(将 ONNX runtime 换成 CUDA) | 10–50 倍 | 中 |
| Voyage-3 API(卸载到 Anthropic 的推理集群) | 可扩展至数千 RPS,受 API 配额限制 | 代码改动较少 |
项目结构
memex/
├── app/
│ ├── main.py # REST API — FastAPI, lifespan, router registration
│ ├── mcp_server.py # MCP server — single-worker FastAPI on port 8001
│ ├── core/
│ │ └── config.py # All settings, loaded from env
│ ├── db/
│ │ └── pool.py # asyncpg pool, migrations
│ ├── models/
│ │ └── schemas.py # Pydantic request/response models
│ ├── services/
│ │ ├── embeddings.py # fastembed ONNX inference (local, zero API calls)
│ │ ├── local_summarizer.py # Extractive summariser — Jaccard dedup + TF scoring
│ │ ├── memory.py # Core write/search/scoring logic
│ │ ├── metrics.py # Prometheus metric definitions
│ │ ├── summarizer.py # Background summarisation job
│ │ └── rate_limit.py # Sliding window rate limiter
│ └── api/routes/
│ ├── memories.py # Memory endpoints
│ ├── health.py # Health + readiness
│ └── mcp_tools.py # MCP tool definitions (store, search, delete, count)
├── scripts/
│ └── load_test.py # Locust load test
├── docker-compose.yml
├── Dockerfile
└── requirements.txt
可观测性
docker compose up 会在 API 旁启动 Prometheus 和 Grafana:
| 服务 | URL | 凭据 |
|---|---|---|
| REST API 文档 | http://localhost:8000/docs | — |
| MCP 服务 | http://localhost:8001/mcp/ | — |
| Prometheus | http://localhost:9090 | — |
| Grafana | http://localhost:3000 | admin / admin |
Grafana 仪表板会自动配置。面板:
- HTTP 请求速率 + p50/p99 延迟 — 来自
prometheus-fastapi-instrumentator - 嵌入 API 延迟 p50/p99 — 按操作(
embed/embed_batch)统计的单次尝试直方图 - 记忆操作/秒 — 创建、搜索、删除吞吐量
- 数据库连接池利用率 — 活跃连接与空闲连接(更新间隔:15 s)
- 摘要器活动 — 每小时精简的记忆数量、运行结果
- 嵌入错误/分钟 — 按操作和错误类型统计
自定义指标位于 app/services/metrics.py,与标准 FastAPI instrumentator 指标一同在 /metrics 上公开。
MCP 端点
memex 将自身作为 MCP 服务公开,使任何支持 MCP 的 Agent(Claude Desktop、Claude Code、自定义 Agent)无需自定义 HTTP 集成即可存储和检索记忆。
**传输方式:**可流式传输的 HTTP(MCP 2024-11-05 规范)。在端口 8001 上运行单工作进程——会话状态保存在进程内,因此单独的服务可避免粘性会话的复杂性,同时保留 REST API 的多工作进程吞吐量。
工具:
| 工具 | 说明 |
|---|---|
store_memory | 嵌入并持久化记忆(可配置类型和重要性) |
search_memories | 按语义和近期程度排序检索,可配置 alpha |
delete_memory | 按 UUID 忘记特定记忆 |
count_memories | 统计一对 Agent/用户拥有的记忆数量 |
从 Claude Desktop 连接
添加到 ~/.config/claude/claude_desktop_config.json:
{
"mcpServers": {
"memex": {
"type": "streamable-http",
"url": "http://localhost:8001/mcp/"
}
}
}
从 Claude Code 连接
claude mcp add --transport http memex http://localhost:8001/mcp/
设计:为何采用独立服务
MCP Streamable HTTP 传输具有会话状态——initialize、tools/list 和 tools/call 必须全部到达同一个服务进程。REST API 运行 4 个 uvicorn 工作进程,并采用轮询路由;将不同 MCP 请求路由到不同工作进程会破坏会话状态。
在端口 8001 上运行专用的单工作进程 MCP 服务,可避免配置粘性会话基础设施(nginx ip_hash、Redis 会话存储),同时让 REST API 保持完整的多工作进程能力。
记忆摘要
此功能以可配置的间隔作为 asyncio 后台任务运行(默认:每 5 分钟一次)。它会查找情景记忆数量超过阈值的所有 (agent_id, user_id) 组合,将其中最早的一批精简为一条 semantic 记忆,然后删除原始记忆。完全本地运行——不会调用 LLM API。
摘要方式: 采用纯 Python 抽取式算法。根据 Jaccard 相似度(阈值 ≥ 0.7)对句子去重,根据词频(TF)评分,然后按原始顺序返回排名最高的前 N 个句子。每次摘要约需 1 ms,除标准库外无其他依赖。
为何只处理情景记忆: 情景记忆是对话事件,会随着时间自然过时。语义记忆和程序性记忆编码的是事实与技能——静默地精简它们可能导致精度损失;它们会通过时效衰减自然过期。
并发安全: 使用以 hashtext(agent_id|user_id) 为键的 pg_try_advisory_xact_lock。此锁仅在数据库写入事务期间持有,不会在调用嵌入模型时持有。
通过环境变量调整:
| 变量 | 默认值 | 说明 |
|---|---|---|
SUMMARIZATION_ENABLED | true | 启用或停用后台任务 |
SUMMARIZATION_THRESHOLD | 100 | 每个组合触发摘要的情景记忆数量 |
SUMMARIZATION_BATCH_SIZE | 50 | 每次运行精简的最早 N 条记忆 |
SUMMARIZATION_INTERVAL_SECONDS | 300 | 后台任务的唤醒间隔 |
后续计划
- 记忆摘要 — 当旧情景记忆数量超过阈值时,通过后台任务精简记忆(本地抽取式算法,不调用 API)
- Prometheus + Grafana — p50/p99 延迟仪表板、嵌入 API 调用时长、连接池饱和度
- 兼容 MCP 的端点 — 端口 8001 上的 Streamable HTTP 服务;4 个工具(存储、搜索、删除、计数);可连接 Claude Desktop 和 Claude Code
- HNSW 索引选项 — 为向量数量 >1M 的部署提供从 ivfflat 切换到 HNSW 的标志
- 重要性加权检索 — 在相似度和时效性之外,将
importance分数纳入排序公式
技术栈
| 层 | 选型 | 原因 |
|---|---|---|
| API | FastAPI + uvicorn | 异步优先、速度快,OpenAPI 生成能力出色 |
| 嵌入模型 | fastembed ONNX (BAAI/bge-small-en-v1.5) | 本地运行、不调用 API、CPU 推理约 12 ms、384 维 |
| 数据库 | PostgreSQL 16 + pgvector | 在一个系统中同时支持关系型数据和向量,无需额外基础设施 |
| 向量索引 | ivfflat | 在当前规模下,构建成本低于 HNSW |
| 连接池 | asyncpg | 可直接控制,无 ORM 开销 |
| 摘要器 | 纯 Python 抽取式 | Jaccard 去重 + TF 评分,无 ML 依赖,约 1 ms |
| 重试 | tenacity | 针对瞬时错误采用基于抖动的退避 |
| 指标 | Prometheus + prometheus-fastapi-instrumentator | 标准可观测性方案 |
| 负载测试 | Locust | 原生支持 Python,可模拟真实用户行为 |
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。