mcpField
M
memex

memex

本地服务
github.com/ayushagrawal288记忆与知识121 周前更新

关于 memex

memex 为 AI Agent 提供持久化记忆服务,同时支持 REST 和 MCP 接口。它结合语义搜索与近期性排序,并通过 ONNX 在本地生成嵌入。

主要功能

  1. 01存储、搜索和删除记忆
  2. 02按相似度与近期性排序
  3. 03本地 ONNX 嵌入
  4. 04后台整理情景记忆
  5. 05Prometheus 指标与 Grafana 面板

适用场景

  1. 01保留 Agent 跨会话上下文
  2. 02检索近期且相关的对话信息
  3. 03通过 MCP 记忆工具连接 Agent

MCP 相关

提供 Streamable HTTP MCP 服务,支持存储、搜索、删除和计数记忆的工具。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"ayushagrawal288-memex": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"ghcr.io/ayushagrawal288/memex:v0.1.0"
]
}
}
}
启动命令docker run -i --rm ghcr.io/ayushagrawal288/memex:v0.1.0

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
ocighcr.io/ayushagrawal288/memex:v0.1.0

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

memex

GitHub release Python FastAPI MCP License: MIT ayushagrawal288/memex MCP server

memex MCP server

面向 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 流量):

端点RPSp50(ms)p95(ms)p99(ms)错误率
POST /v1/memories(写入)271602703300%
POST /v1/memories/search831102002500%
汇总1131202303000%

饱和度测试(500 个用户,最短思考时间——找出吞吐量上限):

端点RPS(平台值)p50(ms)p99(ms)错误率
POST /v1/memories(写入)283,9006,1000%
POST /v1/memories/search913,6005,8000%
汇总~1203,7005,9000%

在 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/—
Prometheushttp://localhost:9090—
Grafanahttp://localhost:3000admin / 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_ENABLEDtrue启用或停用后台任务
SUMMARIZATION_THRESHOLD100每个组合触发摘要的情景记忆数量
SUMMARIZATION_BATCH_SIZE50每次运行精简的最早 N 条记忆
SUMMARIZATION_INTERVAL_SECONDS300后台任务的唤醒间隔

后续计划

  • 记忆摘要 — 当旧情景记忆数量超过阈值时,通过后台任务精简记忆(本地抽取式算法,不调用 API)
  • Prometheus + Grafana — p50/p99 延迟仪表板、嵌入 API 调用时长、连接池饱和度
  • 兼容 MCP 的端点 — 端口 8001 上的 Streamable HTTP 服务;4 个工具(存储、搜索、删除、计数);可连接 Claude Desktop 和 Claude Code
  • HNSW 索引选项 — 为向量数量 >1M 的部署提供从 ivfflat 切换到 HNSW 的标志
  • 重要性加权检索 — 在相似度和时效性之外,将 importance 分数纳入排序公式

技术栈

层选型原因
APIFastAPI + 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,可模拟真实用户行为

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。