内容
[](https://github.com/ayushagrawal288/memex/releases)
[](https://www.python.org)
[
[](https://modelcontextprotocol.io)
[](LICENSE)
[
[](https://glama.ai/mcp/ayushagrawal288/memex)
一款面向AI智能体的生产级持久化内存服务。默认情况下,智能体会忘记会话之间的所有内容——而memex可以解决这个问题。它使用具有时间衰减的语义搜索来存储、对话内存,以便智能体能够获取相关且最近的内存,而不仅仅是最语义相似的内存。
```
POST /v1/memories → 存储一个内存,嵌入它,持久化到Postgres
POST /v1/memories/search → 检索按相似度和时间衰减排序的top-k个内存
DELETE /v1/memories/{id} → 忘记一个特定的内存
GET /v1/memories/count → 该智能体/用户有多少个内存
GET /health → 存活性和数据库连接检查
GET /metrics → Prometheus指标
```
---
## 架构
```
调用者(智能体/应用程序)
│
▼ FastAPI(异步)
│
┌────┴────┐
│ │
嵌入 asyncpg连接池(最小=5,最大=20)
(fastembed │
ONNX, ▼
本地) 16
pgvector扩展
ivfflat索引(余弦)
```
**写入路径:** 内容 → fastembed ONNX推理(本地,约12毫秒CPU,`BAAI/bge-small-en-v1.5`)→ 插入带有384维向量的内存 → 返回内存ID。
**读取路径:** 查询 → 嵌入 → pgvector(top_k × 3个候选)→ 在Python中重新排序,应用时间衰减 → 返回个结果及其分数。
---
## 设计决策
### 1. 在语义搜索基础上实现时间衰减
纯向量相似性返回最语义相似的内存,而不是最有。90天前的一个相似度为0.95的事实往往比昨天的相似度为0.80的用。
评分公式:
```
score = α × cosine_similarity + (1 − α) × exp(−λ × age_days)
```
其中`λ = ln(2) / half_life_days`(30天,因此30天后的内存权重为50%)。
`α`可按请求配置(默认0.7导向的智能体使用更高的α(语义占主导地位)。对话式使用更低的α(时间衰减更重要)。
### 2. 获取3倍候选,在重新排序
pgvector查询返回按纯相似的`top_k × 3`个候选。Python使用衰减公式重新排序并切片到`top。这防止了时间衰减饥饿高相似度的旧内存——它们仍然在候选池中。
在10倍规模(>1M内存/智能体)时推送到Postgres函数中,使用`pg_proc`消除Python重新排序的往返。
### 3. asyncpg + 显式连接池大小优于SQLAlchemy异步在每个查询开销。热检索路径——嵌入、查询、重新排序——需要紧凑。asyncpg提供对连接池最小/最大值的直接控制(与调优HikariCP类似)。pgvector查询需要原始SQL以支持`<=>`运算符。
连接池默认值:`min=5, max=20`。为单个调整大小。可以通过`DB_MAX_POOL_SIZE`。
### 4Postgres中限制Redis中
sert实现滑动窗口计数依赖项。并发请求(事务up在10倍规模分布式:使用Redis ` + EXPIRE`操作,无锁竞争。
### 5. ivff,而不是HNSW
`ivfflat`具有较低成本和内存占用——在小到中等规模1M向量)下是合适的权衡。`lists=100`适用于约行。在10倍:切换到H(`m=16_construction=64以获得更好的召回内存和构建时间---
## 本地运行
**前提条件:** Compose。不需要——整个堆栈都在。
```bash
https://github.comagrawal288
cd memex compose up
```
http://localhost:8000`处可用。文档在`http://localhost0/docs`处可用。
---
## API### 存储一个内存
```bash
curl -X:8001/memories \
-Content-Type:" \
-{
"agent_idmy-agent",
"user_id": "user",
"content": "用户更喜欢简洁的回复,不喜欢冗。",
"memory_type": "semantic",
"importance": 1. }'
```
``` "id3fa85f64-5717-4562fc-2c963f66afa6",
"agent_id": "",
"user_iduser-123",
"content": "用户更喜欢简洁的回复,不长的解释。",
"importance": 2,
"memory_type": "semantic",
": "2026-05-26T:00Z",
"score": null
}
```
### 搜索内存
```bash
curl -X://localhost:8000/v1/memories/search \
-H "Content-Type: application/json" \
d '{
"agent_id": "my-agent",
"user_id": "user-123",
"query": "这个用户喜欢如何沟通",
"top_k": 5,
"alpha": 0.7
}'
```
```json
{
"results": [
{
"id": "3fa64-...",
": "用户更喜欢的回复冗长的解释。",
"memory_type": "semantic",
"": "2026-05-26T30:00Z "score": 01
}
"query":用户喜欢如何沟通",
"total": }
```
### 内存类型
| 类型 | 用|---|---|
|odic` |事件,过去的| `semantic` | 事实,偏好,一般| `procedural流程,如何指导 负载测试结果
在MacBook M Desktop、单个Postgres实例上运行:
locust -f scripts/load_test.py --://localhost:8000 \
headless 50 -r t 60s**现实负载** (50个用户,100毫秒思考时间——模拟实际智能体流量): 端点 | RPS | p50(毫秒) | p95(毫秒) | p99(毫秒)率 |
|---|---|---|---|| POST /v1/memories(写入) | 27 | 160 | 270 | 330 | 0% |
| POST /v1/memories/search | 83 | 110 | 200 | 250 | 0% |
聚合 | ** 120 | 230 | 300 |%** |
**饱和 (500个用户思考时间——找到吞吐量上限):
点 | RPS(平台期) | p50(毫秒) | p99) | 错误率 |
---|---|---|
| POST /v1/mem写入) | 283,900 |100 | **0 |
| POST //memories/search91 | 3,600 | 5, **0%** |
| 聚合 |120** | 3,700 | 5 | **0%** |
> 在MacBook M系列、Docker Desktop(4个CPU)、4个uv工作进程、16个线程/工作进程上运行。<br>
> 嵌入:本地ONNX(`BAAI/bge-small-en-v1.5`)——零外部API调用,零成本。
**为什么上限是~120 RPS:** <br>
每次写入和每次搜索都需要一次ONNX推理(~10–15)。使用4个Docker CPU:`4 cores / 12 ms ≈ 333 embeddings/s`理论最大值。在销、数据库异步调度~120 RPS实际值。
**更高量的路径:**
| 方法 | 预期增 复杂度 |
|---|---|---|
| 嵌入缓存键=文本的SHA256) | 2–3倍(重复智能体查询的40–60%命中率) | 低 |
| 水平扩展均衡器后面的N个副本) | N倍线性 |
| GPU推理(切换ONNX运行时→ 10–50倍 | 中 |
|3 API(卸载到Anthropic的推理集群) | 可扩展到数千RPS,受API限制 | 代码更改少 |
---
## 项目结构memex/
├── app/
│ ├── main.py # REST API — FastAPI,生命周期,路由器注册
│ ├── mcp_server.py # MCP服务器工作进程FastAPI,端口8001 ├── core/
│ │ └── config 所有设置,从加载
│ /
│ │ └── pool.py #连接池,迁移
│ ├── models/
│ │ └── schemas.py # Pydantic请求模型
│ ├── services/
│ ├── embeddings.py # fastembed ONNX推理(本地,零API调用)
│ │ ├── local_summarizer.py提取式 Jaccard重复数据删除+TF评分 │ ├── memory.py # 核心搜索/评分逻辑
│ │ ├── # Prometheus指标定义
│ │ ├── summarizer.py # 后台摘要作业
│ │ └── rate_limit.py # 滑动限制器
│ └── api/routes/
│ ├── memories.py # 内存端点
│ ├── health.py # 健康检查+可用性
│ └── mcp_tools.py # MCP工具定义(存储,搜索,删除,计数)
├── scripts/
│ └── load_test.py # Locust负载├── docker-compose.yml
├── Dockerfile
└── requirements.txt
```
---
## 可观测性
`docker compose up`启动Prometheus和Grafana服务:
| 服务 | URL | 凭证 |
|---|---|---|
| REST API文档 | http://localhost:8000/docs | — |
| MCP服务器://localhost:800cp/ | — |
| Prometheus | http://localhost:9090 | — |
| Grafana |localhost:3000 | admin / admin |
Grafana仪表盘会自动配置。面板:
- **速率 + 延迟p50/p99** —`prometheus-fastapi-instrumentator`
-API延迟p50/p99** — 按`embed` / `embed_batch`)分方图
- **每秒内存操作数、搜索、删除吞吐量
- **数据库连接池利用率** — 活动连接连接(更新间隔:15秒)
摘要器活动** — 每小时压缩的内存结果
- **每分钟嵌入错误数**操作和错误类型
自定义指标在`etrics.py`中,并在`/metrics`处FastAPI仪器指标一起公开。
---
## MCP端点
memex将自己暴露为[MCP://modelcontextprotocol.io)服务器,因此任何M智能体(Claude Desktop,Claude自定义智能体)都可以在不进行自定义HTTP情況下存储和检索内存。
**传输:式HTTP(MCP 2024-11)。端口8001上的单工作进程 — 会话状态在进程内,因此单独的服务避免了粘性会话复杂度,同时保持REST API的多工作吐量。
**工具:**
| 工具 | 描述 |
|---|---|
| `store_memory` | 嵌入+持久化内存(类型,重要性可配置) |
| `search_memories` | 语义+时间衰减排序检索,alpha可配置 |
| `delete_memory` | 通过UUID忘记特定内存 |
| `count_memories` | 智能体/用户对有多少内存 |
### 从Claude Desktop连接
添加到`~/.config/claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"memex": {
"type": "streamable-http",
": "http://localhost1/mcp }
}
}
```
### 从Claude Code连接
```bash
claude mcp add --transport http memex http://localhost:8001/mcp/
```
### 设计:为什么使用单独的服务
MCP流式HTTP传输是有状态的 — `initialize`、`tools/list`和`tools/call`必须全部到达**同一个服务器。REST API运行icorn,带有轮询路由不同的MCP到不同的工作破坏会话状态。
在端口8001上用的单工作进程服务,避免了粘性会话基础设施(nginx ``,Redis),同时保持REST多工作进程。
---
## 内存摘要可配置的间隔每5分钟一次后台异步任务运行任何`(agent user_id)`对,其中情景内存计数超过,将最批压缩成单个`内存,然后删除。**完全本地LLM API调用**摘要方式 纯Python提取句子通过J相似度(≥ 7阈进行重复数据删除,通过词频(TF评分,并按原始前N个1毫秒每次摘要于标准库零依赖。
**限情景内存:景内存是时间过期性的对话事件。语义和过程事实和技能 — 默默地压缩精度;它们衰减老并发安全性:** 使用`pg_tryory_xact_lock键名为`hash_id|user。锁仅在期间持有嵌入调用期间环境变量调整:
| 变量 | | 描述 |
|---|---|---|
| `SUM_ENABLED` | `true` | 切换| `SUM_THRESHOLD` | ` | 触发每情景计数 |
SUMMARIZATION_BATCH_SIZE` | `50` | 每次运行要压缩的最旧内存数量 |
| `SUMMARIZATION_INTERVAL_SECONDS` | `300作业唤醒频率 |
---
## 下一步
- [x] **内存摘要** — 后台作业,压缩旧情景内存(本地提取算法,无API调用),当计数超过阈值
- [x] **Prometheus + Grafana** — p50/p99盘,嵌入API调用持续时间,连接池饱和度
- [x] **MCP兼容端点** — 流式HTTP服务器,端口8001;4种工具(存储,搜索,删除,计数);与Claude Desktop和Claude Code连接
- [ ] **HNSW索引选项** — 标志,切换为HNSW,适用于>1M向量的部署
- [ ] **重要性加权检索** — 检索公式中考虑`importance`分数
---
## 技术栈
| 层 | 选择 | 原因 |
|---|---|---|
| API | FastAPI + uvic 异步优先,优秀的Open |
| fastembed ONNXBAAI/bge-small-en-v1`) |,无API调用,约12毫秒CPU推理,384维 |
| 数据库 | PostgreSQL 16vector | 关系型+向量存储在,无需额外基础设施 |
| 向量索引 | ivfflat | 在此规模下构建成本低于HNSW |
| | asyncpg控制,无ORM开| 摘要器 | 纯Python提取 | Jaccard重复+TF评分,零ML依赖,约 |
| 重试 | tenacity | 基于超时重试 |
| 指标 | Prometheus + prometapi-instrumentator | 标准可观测| 负载测试 | Locust | Python现实用户模拟 |