
1. 项目概述当RAG遇上MCP构建可对话的智能记忆体最近在探索如何让AI应用真正“记住”对话历史并能在后续交互中灵活调用这些记忆时我遇到了一个非常有意思的项目kshidenko/rag-memory-pg-mcp。这个项目名字听起来有点技术栈堆叠的味道但它的核心思想却异常清晰——它试图解决一个困扰很多AI应用开发者的实际问题如何为基于大语言模型LLM的应用构建一个持久化、可检索且能与外部工具如数据库高效交互的“记忆系统”。简单拆解一下这个标题它包含了三个关键组件RAG、Memory、PG-MCP。RAG检索增强生成大家应该不陌生了它是当前让LLM“博闻强识”的主流技术通过从外部知识库检索相关信息来辅助生成更准确的回答。Memory记忆指的是让AI能够记住与用户的对话历史、用户偏好或特定上下文从而实现连续、个性化的交互。而PG-MCP则是这个项目的独特之处它很可能指的是PostgreSQL数据库与MCPModel Context Protocol的结合。MCP是一个新兴的、旨在标准化LLM与外部工具数据源、API、函数等交互方式的协议。所以这个项目本质上是一个基于PostgreSQL数据库利用RAG技术实现持久化记忆存储与检索并遵循MCP协议与AI应用进行标准化通信的系统。它不是一个最终应用而是一个基础设施组件或服务。想象一下你正在开发一个智能客服、一个个人知识助手或者一个需要长期跟踪项目状态的AI工具你希望它能记住上周用户提到的需求或者能快速从海量历史对话中找到相关案例作为参考。手动管理这些会话状态和向量检索既繁琐又容易出错而这个项目提供的正是一个开箱即用的“记忆中枢”。我花了一些时间研究它的实现发现它巧妙地结合了几项现代AI工程中的最佳实践。接下来我将从设计思路、核心实现、实操部署到常见问题为你完整拆解这个项目并分享我在搭建和测试过程中的一些心得。无论你是想直接使用它还是借鉴其设计理念来构建自己的记忆系统相信都能从中获得启发。2. 核心架构与设计思路拆解2.1 为什么是RAG Memory传统的聊天机器人或AI助手其“记忆”往往是短暂且有限的。通常采用以下几种方式上下文窗口Context Window将最近的若干轮对话直接拼接到提示词Prompt中。这种方式简单但受限于模型的最大上下文长度如128K无法记忆长历史且每次都需要重复传输大量token成本高。摘要Summarization定期将长对话总结成一段摘要再将摘要放入上下文。这解决了长度问题但不可避免地会丢失细节且摘要本身可能带有偏差。简单的键值存储为每个用户或会话存储一些关键信息如用户名、偏好。这适用于结构化数据但难以处理非结构化的自由对话内容。RAG为记忆问题提供了一个优雅的解决方案。它不试图把一切塞进上下文而是将历史对话转化为可检索的知识。具体到记忆场景存储阶段将每一轮有意义的对话或经过处理的对话片段转换为文本嵌入Embedding并与其元数据如会话ID、时间戳、用户ID一起存入向量数据库。检索阶段当新问题到来时将其也转换为嵌入然后在向量数据库中执行相似性搜索找出与当前问题最相关的历史对话片段。生成阶段将这些检索到的“记忆”作为上下文连同当前问题一起提交给LLM让模型生成融合了历史知识的回答。这种方式的好处显而易见记忆容量理论上无限取决于数据库检索精准基于语义相似度且按需加载极大节省了上下文窗口和token消耗。kshidenko/rag-memory-pg-mcp项目正是采用了这一核心范式。2.2 为什么选择PostgreSQL作为后端项目名称中的“PG”明确指向了PostgreSQL。在向量存储方案百花齐放的今天如Pinecone、Weaviate、Qdrant等专用向量数据库为什么选择这个传统的关系型数据库一体化与简化运维对于许多中小型项目或初创公司来说维护多个数据库一个用于业务关系数据一个用于向量增加了架构复杂性和运维成本。PostgreSQL凭借其强大的扩展能力通过pgvector扩展就能原生支持向量存储与相似性搜索。这意味着你可以用同一个数据库处理用户信息、业务逻辑和AI记忆简化了技术栈。事务保证与数据一致性记忆的写入和更新需要可靠性。PostgreSQL提供了ACID事务支持确保记忆的存储过程是原子性的不会出现数据不一致的情况。这对于需要严格记录交互历史的场景如客服、法律咨询至关重要。丰富的查询能力记忆不仅需要被向量检索往往还需要结合元数据进行过滤。例如“查找用户A在过去一周内所有关于‘退款政策’的对话”。PostgreSQL可以轻松地将向量相似度搜索与基于元数据时间、用户ID、标签的关系型查询结合起来实现复杂的混合搜索这是很多纯向量数据库需要额外努力才能实现的功能。成熟生态与工具链PostgreSQL拥有极其成熟的备份、监控、连接池等工具链团队更容易上手和管理。注意选择PG并不意味着它适合所有场景。对于超大规模数十亿向量、极致低延迟检索的场景专用的向量数据库在性能和成本上可能更有优势。但对于大多数需要将记忆系统与现有业务数据深度整合的应用PG是一个务实且强大的选择。2.3 MCP协议的角色标准化工具调用接口这是该项目最具前瞻性的部分。MCPModel Context Protocol是由Anthropic等公司推动的一个开放协议旨在为LLM定义一套与外部工具、数据源安全、标准化交互的方式。在没有MCP之前我们为LLM添加记忆功能通常需要编写特定的API接口如/save_memory,/recall_memory。在提示词中详细描述这些API的用法。处理复杂的身份验证和会话管理。这种方式耦合度高且每个应用都需要重复造轮子。MCP试图解决这个问题。在这个项目中记忆系统作为一个MCP服务器Server它暴露出诸如“保存记忆”、“搜索相关记忆”、“按条件列出记忆”等标准化工具Tools。AI应用如基于Claude的聊天客户端作为MCP客户端Client它通过MCP协议发现并连接到这个记忆服务器。交互标准化客户端无需知道记忆具体存在哪里PG还是别的只需通过协议定义的标准请求格式来调用“保存”或“检索”工具。服务器返回标准化的响应。这样做的好处是解耦和复用。同一个记忆MCP服务器可以被任何支持MCP协议的AI客户端使用。开发者无需为每个AI应用单独集成记忆逻辑只需让应用支持MCP它就能获得记忆能力。这大大提升了开发效率和系统的模块化程度。总结一下该项目的设计思路利用PostgreSQLpgvector构建一个持久化、可混合查询的记忆存储层通过RAG范式实现记忆的语义化存储与检索最后通过MCP协议将整个系统包装成一个标准的、可插拔的AI工具服务。这是一个非常典型的现代AI中间件设计。3. 核心组件解析与实操要点3.1 技术栈深度剖析要理解和部署这个项目我们需要对其技术栈的每个部分有清晰的认知。1. 核心语言与框架TypeScript Node.js项目是用TypeScript编写的运行在Node.js环境下。这是一个非常普遍且合理的选择。Node.js的非阻塞I/O模型适合处理大量并发的、I/O密集型的AI请求如嵌入生成、数据库查询。TypeScript提供了静态类型检查对于构建一个需要稳定性和明确接口定义的“基础设施”类项目来说能有效减少运行时错误。2. 向量化引擎嵌入模型Embedding Model这是RAG的“大脑”。项目需要将文本转换为向量。通常它会支持OpenAI的text-embedding-ada-002或-3系列以及开源模型如通过Sentence Transformers或直接调用本地部署的模型。关键配置点在于模型选择权衡精度、速度、成本和数据隐私。OpenAI的API方便但涉及数据出境和持续成本开源模型可本地部署隐私性好但需要一定的GPU资源。维度不同嵌入模型产生的向量维度不同如ada-002是1536维。这必须与PostgreSQL中pgvector扩展定义的向量列维度严格一致。批处理与速率限制在批量导入历史对话或处理高峰请求时需要注意对嵌入模型API的调用频率做好批处理和错误重试。3. 向量数据库层PostgreSQL pgvector这是项目的“记忆仓库”。pgvector是一个PostgreSQL扩展它增加了对向量数据类型的支持以及相似性搜索运算符如-欧氏距离、余弦距离。表结构设计核心的记忆表例如memories可能包含以下列id: 主键。session_id: 关联的对话会话ID。user_id: 用户标识。content: 记忆的原始文本内容。embedding: 向量类型存储文本内容的嵌入向量。metadata: JSONB类型存储其他灵活信息如消息类型user/assistant、时间戳、自定义标签等。created_at: 创建时间。索引优化为了加速相似性搜索必须在embedding列上创建向量索引。pgvector支持IVFFlat和HNSW索引。对于动态增长的记忆库HNSW索引通常有更好的查询性能和构建速度但占用空间稍大。创建索引的SQL类似CREATE INDEX ON memories USING hnsw (embedding vector_cosine_ops);混合查询强大的地方在于可以这样查询“找到与当前问题最相似的、属于用户A的、且发生在今天的记忆”。SELECT content, metadata FROM memories WHERE user_id user_A AND created_at::date CURRENT_DATE ORDER BY embedding $1::vector LIMIT 5;4. 协议层MCP服务器实现项目核心是实现一个符合MCP规范的服务器。这涉及到工具Tools定义在代码中定义一系列工具如memory_search、memory_save。每个工具需要明确其name、description和inputSchema输入参数的JSON Schema。请求/响应处理实现MCP协议规定的JSON-RPC格式的消息处理。监听来自客户端的tools/call请求解析参数执行对应的业务逻辑如调用嵌入模型、操作数据库然后返回tools/call结果。上下文Context管理MCP还有“上下文”的概念允许服务器主动推送数据。在这个项目中当新记忆被保存或相关记忆被更新时理论上可以通过上下文机制主动通知客户端实现更实时的交互。不过在基础的记忆检索场景中工具调用模式已足够。3.2 环境准备与初始化部署假设我们从零开始部署和使用这个系统。以下是基于项目常见结构的实操步骤。步骤1基础设施准备PostgreSQL数据库你需要一个运行中的PostgreSQL实例版本建议12以上。可以使用本地安装、Docker容器或云服务如AWS RDS Supabase。启用pgvector扩展连接你的数据库执行以下SQLCREATE EXTENSION IF NOT EXISTS vector;验证是否成功SELECT * FROM pg_extension WHERE extname vector;步骤2获取项目代码与安装依赖# 克隆项目假设项目在GitHub上 git clone https://github.com/kshidenko/rag-memory-pg-mcp.git cd rag-memory-pg-mcp # 安装依赖 npm install # 或 pnpm install / yarn install步骤3配置文件与环境变量项目通常会有一个配置文件如.env.example或说明。你需要创建自己的.env文件并配置关键参数# 数据库连接 DATABASE_URLpostgresql://username:passwordlocalhost:5432/rag_memory_db # 嵌入模型配置 (以OpenAI为例) EMBEDDING_MODELtext-embedding-3-small EMBEDDING_DIMENSIONS1536 OPENAI_API_KEYsk-your-api-key-here # 可选本地嵌入模型如使用Transformers.js # LOCAL_EMBEDDING_MODELall-MiniLM-L6-v2 # MCP服务器配置 MCP_SERVER_PORT3000 MCP_SERVER_NAMERAG Memory Server关键点1EMBEDDING_DIMENSIONS必须与你选择的嵌入模型输出的维度完全匹配否则向数据库写入向量时会出错。关键点2DATABASE_URL需要指向一个已存在的数据库项目初始化脚本通常会创建所需的表。步骤4数据库初始化运行项目提供的数据库迁移脚本或初始化SQL。这通常是一个Node.js脚本或一个SQL文件。# 示例运行TypeScript编译后的初始化脚本 npm run db:migrate # 或 psql -d your_database -f init.sql初始化后检查数据库中是否生成了memories等表。步骤5启动MCP服务器npm start # 或用于开发的热重载模式 npm run dev如果成功终端会输出服务器启动的日志例如“MCP Server running on port 3000”。3.3 核心工作流程与API调用示例让我们深入代码层面看一个典型的“保存记忆”和“检索记忆”流程是如何实现的。1. 保存记忆流程当AI助手完成一轮有价值的对话后客户端如Claude Desktop会通过MCP协议调用memory_save工具。// MCP 客户端发送的请求示例 (JSON-RPC) { jsonrpc: 2.0, id: 1, method: tools/call, params: { name: memory_save, arguments: { sessionId: chat_12345, userId: user_67890, content: 用户提到他更喜欢在每周三下午接收项目周报的汇总邮件。, metadata: { role: user, timestamp: 2024-05-27T14:30:00Z, tags: [preference, notification] } } } }服务器端本项目接收到请求后参数验证检查sessionId,userId,content等必填字段。文本向量化调用配置的嵌入模型API或本地模型将content文本转换为一个1536维以ada-3-small为例的浮点数数组。const embedding await openai.embeddings.create({ model: ‘text-embedding-3-small’, input: content, }); const vector embedding.data[0].embedding; // 这是一个 number[]数据库写入将sessionId,userId,content,vector需转换为PG接受的格式如[0.1, 0.2, ...]以及metadata一起插入memories表。返回成功响应。2. 检索记忆流程当用户提出一个新问题比如“我上次说的关于周报的时间安排是什么”客户端会调用memory_search工具。{ jsonrpc: 2.0, id: 2, method: tools/call, params: { name: memory_search, arguments: { userId: user_67890, query: 周报的时间安排, limit: 3 // 可选 filters: { sessionId: ..., tags: [preference] } } } }服务器端处理流程向量化查询文本将query(“周报的时间安排”) 转换为查询向量。构建并执行SQL查询这是一个核心的混合查询。它结合了向量相似度搜索和元数据过滤。-- 简化版查询逻辑 SELECT id, content, metadata, 1 - (embedding $1::vector) as similarity -- 计算余弦相似度 FROM memories WHERE user_id $2 -- 过滤特定用户 -- AND metadata-tags ? ‘preference’ -- 可选JSONB字段过滤 ORDER BY embedding $1::vector -- 按向量距离排序 LIMIT $3;这里$1是查询向量$2是userId$3是limit。是pgvector提供的余弦距离运算符距离越小越相似。格式化结果将数据库返回的记录包含内容、元数据和相似度分数格式化为MCP协议规定的响应格式。返回结果客户端收到一个包含相关记忆片段的列表然后可以将其作为上下文插入到给LLM的提示词中辅助生成最终回答。4. 高级配置与性能调优4.1 嵌入模型的选择与优化嵌入模型是记忆系统准确性的基石。不同的模型在语义理解、语言支持、速度和成本上差异巨大。1. 闭源API vs. 本地开源模型OpenAI (text-embedding-3-*): 当前综合性能的标杆使用简单但存在API调用成本、网络延迟和数据隐私考量。text-embedding-3-small在成本和性能上取得了很好的平衡是许多生产系统的选择。本地开源模型 (如all-MiniLM-L6-v2,bge-*,gte-*): 使用xenova/transformers等库可以在Node.js中直接运行。优势是数据完全私有、无网络延迟、长期成本固定。缺点是首次加载模型慢、需要一定的内存/CPU资源且在小规模数据上精度可能略低于顶级闭源模型。配置示例使用Transformers.js本地运行:import { pipeline } from ‘xenova/transformers’; const embedder await pipeline(‘feature-extraction’, ‘Xenova/all-MiniLM-L6-v2’); const output await embedder(‘Your text here’, { pooling: ‘mean’, normalize: true }); const embeddingArray Array.from(output.data); // 转换为标准数组实操心得对于内部工具或对数据隐私要求极高的场景我倾向于从开源模型开始。可以先在小数据集上对比开源模型与OpenAI API的结果质量。如果质量可接受本地部署能省去大量后续管理和合规成本。记得对模型进行量化如果库支持以减少内存占用。2. 批处理与缓存频繁调用嵌入模型无论是API还是本地都是性能瓶颈。必须实施批处理。保存时批处理如果客户端可能短时间内保存多条记忆可以在服务器端设置一个短时间窗口如100毫秒或固定批次大小如32条将多条文本合并为一个批处理请求发送给嵌入模型API这能极大减少API调用次数。缓存层对于完全相同的content文本其嵌入向量是固定的。可以在内存如LRU Cache或Redis中建立一个缓存key文本MD5 value嵌入向量。在向量化前先查缓存命中则直接使用能显著降低对模型的计算或API调用。4.2 PostgreSQL与pgvector性能调优记忆库增长后检索速度是关键。以下调优策略至关重要1. 索引策略选择IVFFlat vs. HNSWIVFFlat基于倒排文件。它需要在创建索引时指定lists参数通常建议为表记录数 / 1000。它构建速度较快索引文件较小但查询精度和速度对lists参数敏感且数据大量更新后需要重建索引以保持性能。HNSW基于可导航小世界图。它提供更优的查询速度-精度权衡尤其适合高维向量。构建速度较慢索引文件更大但能更好地处理动态数据无需频繁重建。建议对于记忆系统这种写入后频繁查询、且数据逐渐增长的场景优先选择HNSW索引。创建命令如下CREATE INDEX ON memories USING hnsw (embedding vector_cosine_ops) WITH (m 16, ef_construction 64);m每个节点在图中连接的边数默认16。增加m能提高召回率但会增大索引和搜索时间。ef_construction构建时考虑的候选节点数默认64。增加它能让索引更精确但构建更慢。2. 查询性能优化限制返回条数在应用层和SQL中始终使用LIMIT。检索记忆是为了找最相关的几条不是全部。选择性过滤先使用高效的标量条件如user_id ?,created_at ?过滤掉大量无关数据再对剩余数据集进行昂贵的向量搜索。确保这些标量字段上有B-tree索引。调整HNSW搜索参数pgvector的HNSW支持在查询时指定ef_search参数。SET hnsw.ef_search 100; -- 增加此值可提高召回率但减慢搜索 SELECT ... ORDER BY embedding $1 LIMIT 5;可以在会话级别动态调整根据对速度/精度的需求进行权衡。3. 连接池与资源管理Node.js应用需要使用连接池如pg-pool来管理数据库连接避免为每个请求创建新连接的开销。根据你的服务器并发量合理设置池大小。4.3 MCP服务器的安全与扩展考虑1. 认证与授权基础的MCP服务器可能没有内置强认证。在生产环境中你必须考虑服务器端认证确保只有受信的MCP客户端可以连接。可以在服务器启动时要求一个共享密钥客户端在连接头中提供。基于用户的访问控制记忆是分用户的。服务器必须在每个工具调用中验证传入的userId或sessionId是否与当前已认证的客户端身份匹配防止用户A查询用户B的记忆。这通常需要在MCP协议层之上结合你的应用认证系统来实现。2. 工具设计的健壮性输入验证与清理对所有来自客户端的输入如content,metadata进行严格的验证和清理防止SQL注入或NoSQL注入虽然PG的参数化查询能防SQL注入但对JSONB字段的查询也需小心。错误处理与日志工具实现中要有完善的try-catch将详细的错误信息记录到日志但不要返回给客户端敏感信息并向客户端返回友好的错误消息。速率限制对memory_search和memory_save等接口实施速率限制防止滥用。3. 扩展为多租户服务如果你希望将这个记忆系统作为公司内部多个AI团队共享的服务可以考虑数据库层面在memories表中增加tenant_id字段所有查询都加上tenant_id ?条件。连接层面使用不同的数据库schema或完全独立的数据库实例来隔离不同租户的数据。配置层面允许不同租户配置自己的嵌入模型或向量维度这会更复杂需要动态管理。5. 常见问题、排查技巧与实战心得在实际部署和集成过程中你肯定会遇到各种问题。以下是我总结的一些典型场景和解决方法。5.1 部署与连接问题问题1启动服务器时报数据库连接错误。排查首先检查.env文件中的DATABASE_URL是否正确包括主机名、端口、用户名、密码和数据库名。确保PostgreSQL服务正在运行且允许远程连接如果非本地。解决使用psql或pgAdmin等工具手动尝试连接。确保数据库已创建并且运行初始化脚本的用户有建表权限。有时网络策略或防火墙会阻止连接。问题2运行数据库初始化脚本时提示“vector扩展不存在”。排查连接到你的PostgreSQL数据库执行SELECT * FROM pg_available_extensions;查看vector扩展是否在列表中。解决你需要手动安装pgvector扩展。安装方法取决于你的PostgreSQL安装方式。对于使用包管理器如apt安装的PG可以尝试sudo apt-get install postgresql-16-pgvector版本号需匹配。对于Docker可以选择已包含pgvector的镜像如pgvector/pgvector:pg16。安装后在数据库中执行CREATE EXTENSION vector;。问题3MCP客户端如Claude Desktop无法发现或连接到此服务器。排查MCP连接通常通过标准输入输出stdio或网络套接字socket。检查项目README看服务器是以哪种方式运行的。如果是stdio需要在客户端的配置文件中如Claude Desktop的claude_desktop_config.json正确声明命令路径。如果是socket确保服务器IP和端口可访问且客户端配置了正确的socket路径或tcp地址。解决查看服务器启动日志确认它正在监听预期的接口。使用telnet或curl测试网络连通性。仔细对照客户端要求的MCP服务器配置格式。5.2 功能与性能问题问题4保存记忆速度很慢。可能原因1嵌入模型API调用延迟高。排查在代码中为嵌入调用添加计时。检查网络状况。解决考虑使用更快的模型如text-embedding-3-small或部署本地开源模型以消除网络延迟。实施批处理将多条记忆合并为一个API调用。可能原因2数据库写入慢。排查检查是否有大量的索引特别是HNSW索引在写入时开销较大。检查数据库服务器负载。解决对于批量初始化历史数据可以考虑先禁用索引导入数据后再创建索引。确保数据库有足够的资源CPU、内存、磁盘IO。问题5检索记忆的结果不相关。可能原因1嵌入模型不匹配或质量差。排查用一些明显的例子测试。例如保存“我喜欢苹果”查询“水果”看是否能召回。解决尝试更换嵌入模型。对于中文场景确保模型在中文语料上训练过如OpenAI的模型、bge-large-zh。清洗输入文本去除无意义的符号、停用词。可能原因2向量索引未生效或类型不匹配。排查用EXPLAIN ANALYZE分析你的查询SQL确认是否使用了向量索引应显示Index Scan using ... on memories。解决确保在embedding列上创建了正确的索引USING hnsw或ivfflat。确保查询中使用的距离运算符与索引的操作符类vector_cosine_ops匹配。可能原因3查询向量本身语义不明确。解决用户的查询可能很短或模糊。可以尝试对查询进行轻微的“查询扩展”例如将“周报时间”扩展为“用户设定的接收项目周报邮件的时间偏好”。但这需要在应用层逻辑中实现。问题6记忆混淆不同用户或会话的记忆被错误检索。排查检查你的检索SQL是否包含了正确的user_id和session_id过滤条件。确认客户端在调用memory_search时传入了正确的参数。解决这是安全问题也是功能问题。务必在服务器端工具的实现中强制要求并提供userId作为必填参数并在SQL的WHERE子句中严格使用它。不要相信客户端可能传入的任何未经验证的过滤条件。5.3 实战心得与进阶技巧记忆的“粒度”与“清洗”不是所有对话都值得记忆。盲目保存每一句话会导致存储膨胀和检索噪音。在实践中我通常会添加一个“重要性评分”层。例如只保存用户明确表达偏好、做出决策、提供关键信息的语句或者由AI生成的、包含重要摘要的语句。可以在metadata中加一个importance字段并在检索时优先考虑高重要性的记忆。记忆的“衰减”与“更新”记忆不是永久有效的。用户的偏好会改变信息会过时。可以引入“衰减”机制。例如在检索时将相似度分数与一个基于时间的衰减因子如exp(-λ * 天数)相乘让更近的记忆有更高权重。对于已知已过时的记忆如用户说“我现在的电话号码是XXX”更好的方式是支持记忆的“更新”操作标记旧记忆为失效或直接修改其内容。测试驱动开发为你的记忆服务器编写全面的测试。包括单元测试测试向量计算、SQL生成逻辑、集成测试测试从保存到检索的完整流程使用一个测试数据库、以及针对MCP工具接口的端到端测试。这能保证在修改代码后核心功能依然可靠。监控与可观测性在生产环境务必添加监控。关键指标包括保存/检索的延迟P50 P99、每秒请求数RPS、错误率、数据库连接池状态、向量索引缓存命中率。这些指标能帮你及时发现性能瓶颈和异常。这个项目提供了一个强大的起点但真正的挑战在于如何将它无缝集成到你的AI产品中并根据具体的业务逻辑和用户体验需求进行深度定制。它不仅仅是一个技术组件更是塑造AI产品“人格化”和“连续性”体验的关键。希望这篇深入的拆解能帮助你更好地驾驭它构建出真正拥有“记忆”的智能应用。