news 2026/10/20 2:35:24

AI智能体外部记忆系统:向量检索与第二大脑设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI智能体外部记忆系统:向量检索与第二大脑设计

这次我们来看一个被反复讨论但很容易做偏的问题:AI 智能体为什么总是记不住东西,以及怎么给它补一个可用的“第二大脑”。很多人把第二大脑理解为 Obsidian 或 Notion 里的一份精读笔记,但对于 AI 智能体来说,第二大脑必须是一套可读、可写、可检索的外部记忆系统。简单说,智能体应该能把新信息写进去,在需要时把旧信息查出来,再基于查到的内容完成当前任务。围绕这个概念有不少专题讨论,Vanishing Gradients 也专门聊过这个方向,核心结论并不复杂:Agent 的聪明程度,取决于它能在多大范围、多快、多准确地访问自己的过去。

实现层面,这套系统通常由五个模块组成:文档存储、文本切分、向量化、检索服务,以及智能体工具调用链路。别急着上复杂平台,先用最小闭环验证:写入一条记忆、检索一条记忆、让 Agent 在回答中引用这条记忆。能跑通这三步,后面的知识图谱、定时同步、权限审计才有意义。如果只写不查,第二大脑会变成垃圾桶;如果只查不写,智能体仍然是金鱼记忆。

本文会覆盖核心能力、适用边界、系统设计、环境准备、代码实现、接口接入、批量任务、性能观察和常见问题排查,适合正在做 AI 智能体、个人知识库、RAG 应用和研究助手的开发者。文末会给出一个可以直接复制的最小参考实现和检查清单,先把闭环跑通,再谈规模化。

1. 第二大脑系统核心能力速览

所谓给 AI 智能体构建第二大脑,本质是给模型加一层外部记忆。它和普通知识库的区别在于:知识库只提供“读”,第二大脑还要求“写回”。智能体不仅要在需要时把信息查出来,还要能在对话过程中把值得记住的内容写进长期存储。

能力项说明
核心目标为 AI 智能体提供持久、可检索、可写回的外部记忆
典型记忆类型用户偏好、项目状态、事实笔记、工作日志、决策记录
存储层Markdown、SQLite、PostgreSQL、向量数据库等,按数据规模选择
检索层关键词、向量相似度、重排序,或直接交给现有知识库工具
接入方式CLI 脚本、HTTP API、智能体 Function Calling、MCP 工具
批量任务目录导入、增量索引、定时刷新、失败重试
硬件要求不做强依赖;纯文本和向量检索可在普通 CPU 上运行,Embedding / LLM 按实际服务配置
显存占用取决于所选 Embedding 模型和 LLM 服务,需要按实际环境实测,没有统一数值
更适合的场景个人知识库、项目上下文、客服助手、研究助手、长周期任务执行

这套方案的典型使用方式是:把当前任务需要用到的知识写进记忆库,Agent 在回答问题前先调用query_memory检索相关片段,再把片段拼进提示词上下文。整个过程对用户透明,但对 Agent 来说,上下文不再是单次会话,而是一个可积累、可更新的外部事实层。

2. 适用场景与使用边界

这个方案最值得做的场景有三类:

  1. 个人知识管理:笔记、文章、会议记录散落在不同文件里,AI 智能体无法直接使用。第二大脑把这些非结构化文本统一成可检索片段,Agent 能回答“我之前是怎么处理这个问题的”。
  2. 项目长期上下文:一个项目持续数月,每次新会话都从头讲上下文非常低效。让 Agent 把关键决策、当前进度、待办事项写回记忆,下一次会话直接检索。
  3. 信息密集型助手:例如客服、研究助理、数据分析助理。它们需要访问大量历史记录和领域知识,并且需要把每次交互中产生的新信息保存下来。

不适合的场景也很明确:如果只是单轮问答,用普通 RAG 就够了,不需要完整写回链路;如果数据本身没有持续积累价值,第二大脑只是在增加维护成本;如果原始数据质量很差、没有来源、没有结构,强行建库只会让智能体更自信地犯错。

使用边界必须以安全和合规为前提。第二大脑里面沉淀的内容不一定是事实,Agent 检索到后必须能追溯来源,不能直接当成结论。涉及隐私、医疗、金融、人脸、声音等敏感信息时,尽量只使用本地模型和本地存储,不要上传到不可控的远端服务。涉及版权材料或他人数据,要先获得授权并做脱敏处理。AI 生成的内容需要人工复核后才能进入长期记忆,否则错误会越攒越多。

3. 系统设计:从“笔记仓库”升级为“外部记忆”

给 AI 智能体用的第二大脑,不能只是一个 Markdown 文件夹。它需要至少包含下面几个层次:

模块职责最小可选实现
采集层接收新内容,支持手动写入、文件导入、Agent 写回目录扫描 + 函数调用
解析层把 Markdown、TXT、PDF 等内容切分成可检索片段文本切分函数
索引层生成向量,保留标题、来源、时间等元信息Embedding API / 本地模型
存储层保存原始内容、向量和元信息SQLite / 向量数据库
检索层根据用户问题返回最相关的记忆片段向量相似度 + 关键词过滤
写回层让 Agent 能通过工具写入新记忆HTTP API / Function Calling
治理层控制访问范围、记录写入来源、支持删除和审计权限配置 + 操作日志

3.1 采集与解析

采集层解决的是“内容怎么进来”。手动写一段笔记、批量导入旧文档、Agent 在对话中自动保存,这三种入口都要支持。

解析层的关键在文本切分。直接整篇向量化的问题是有用信息被稀释,长文档检索质量很差。更稳妥的做法是按语义边界切分,例如 Markdown 标题、段落、代码块、列表,同时保留标题和来源信息。每条记忆应该包含内容体本身和元数据:标题、来源、时间、标签、状态、关联项目等。

一个合理记录格式应该是这样的:

id: kb-20250601-001 type: note source: meeting-notes.md title: 项目例会记录 tags: [project, action] created_at: 2025-06-01T10:00:00+08:00 updated_at: 2025-06-01T10:00:00+08:00 status: active content: | 结论:第二大脑采用检索增强方式实现。 行动:本周完成 API 原型。

这种结构化元信息不只是为了好看。它决定了检索层能不能做过滤,比如只查project标签、只查最近一周的action、只查特定来源的笔记。没有元数据的向量库,检索时只能做全量相似度,数据量一大就会变慢且不准确。

3.2 索引与存储

切分完成后,每条文本片段需要转成向量。这一步通常由 Embedding 模型完成,可以是本地模型,也可以是远端服务。向量质量直接决定检索效果,不同模型对中文、代码、专业术语的处理能力有明显差异,需要在真实数据上对比。

存储层有两部分:原始文本和向量。对个人项目,SQLite 已经足够;对几千条甚至几万条笔记,SQLite 配合向量扩展也能跑;数据量更大时,再切换到支持向量索引的数据库,比如 PostgreSQL + pgvector、Chroma、FAISS 等。不要一开始就上分布式系统,复杂度会淹没核心价值。

3.3 检索与写回

检索层不只是“算相似度”。好的第二大脑应该支持:

  • 关键词过滤,缩小候选范围
  • 向量相似度召回,找到语义相关片段
  • 重排序或评分,把最相关的内容排到前面
  • 结果附带来源,Agent 回答时能说明依据

写回层是很多人忽略的部分。智能体在对话中会不断收到新信息,例如用户说“我更喜欢 PostgreSQL”“这个项目已经进入测试阶段”。这些事情如果不写回,下次对话又要重新说一遍。写回的关键不是记录所有内容,而是让 Agent 判断哪些信息有长期价值,再调用写入接口保存。

4. 环境准备与前置条件

在开始写代码之前,先把环境检查一遍。这个方案不一定需要 GPU。如果 Embedding 和对话模型都使用远端服务,本地只负责文本处理和 API 转发,一台普通开发机就够。如果使用本地模型,才需要根据模型参数量评估显存和内存。

推荐的最小环境结构:

  • 操作系统:Windows / macOS / Linux 均可
  • Python:较新的 3.x 稳定版本
  • 依赖:FastAPI、Uvicorn、Requests
  • 存储:SQLite 或你选择的向量数据库
  • 模型服务:可用的 Embedding 接口和 LLM 接口
  • 磁盘空间:除了代码,预留文档存储空间
  • 端口:预留一个本地 API 端口,比如 8000

建议先创建独立虚拟环境,避免依赖相互污染:

# 新建虚拟环境并激活 python -m venv .venv source .venv/bin/activate # Windows 使用下面的命令激活 # .venv\Scripts\activate # 安装基础依赖 pip install fastapi uvicorn requests python-multipart

如果你希望完全本地运行,可以准备一个本地 Embedding 服务,路径通过环境变量控制。这样即使后续更换模型,也不需要改业务代码。建议把服务地址和密钥都放到环境变量里:

# 根据实际服务地址和模型名配置 export MEMORY_DB_PATH="./memory.db" export EMBEDDING_SERVICE_URL="http://127.0.0.1:1234/v1/embeddings" export EMBEDDING_MODEL="your-local-embedding-model" export EMBEDDING_API_KEY="your-token"

注意,不同服务商的接口返回格式可能不同。如果接的是本地 Ollama、LM Studio 等工具,就把地址换成对应本机端口;如果接云端服务,就换成服务商提供的地址。密钥不要写死在代码仓库里,用环境变量或本地配置文件管理。

5. 最小实现:让 AI 智能体读写第二大脑

下面给出一套可运行的最小参考实现。它足够简单,适合个人笔记和中小规模 Agent 项目。代码核心思路是:SQLite 存文本和向量,通过本地或远端 Embedding 接口生成向量,用余弦相似度做检索。这个方案在数据量比较小时完全够用,跑通后再考虑换成专业向量库。

5.1 初始化数据库

先用 SQLite 建一张notes表,保存原始文本、向量 JSON、标题、来源和时间。

import json import os import sqlite3 import hashlib import requests from datetime import datetime, timezone from pathlib import Path DB_PATH = Path(os.getenv("MEMORY_DB_PATH", "memory.db")) def init_db(): con = sqlite3.connect(DB_PATH) con.execute(""" CREATE TABLE IF NOT EXISTS notes ( id TEXT PRIMARY KEY, title TEXT NOT NULL, source TEXT DEFAULT '', content TEXT NOT NULL, chunk_index INTEGER NOT NULL, embedding TEXT NOT NULL, created_at TEXT NOT NULL ); """) con.commit() return con

字段embedding存的是 JSON 字符串,方便在 SQLite 里直接保存向量。如果你后续改用专业向量库,只需要替换这里的读写逻辑。

5.2 文本切分

文本切分直接决定检索精度。整篇保存会让向量包含太多无关信息,建议把 Markdown 按标题和长度边界切成块:

def split_markdown(content: str, max_chars: int = 800): chunks = [] current = [] length = 0 for line in content.splitlines(): if current and (line.startswith("#") or length + len(line) > max_chars): chunks.append("\n".join(current)) current = [] length = 0 current.append(line) length += len(line) if current: chunks.append("\n".join(current)) return chunks

max_chars可以按实际效果调整。过大会稀释语义,过小会切碎上下文。常见范围在几百到一千字左右,具体以你手机器上的检索结果为准。

5.3 生成向量

这里的embed_text函数调用外部 Embedding 服务。你需要根据实际服务商的返回格式调整取值路径:

def embed_text(text: str) -> list[float]: url = os.environ["EMBEDDING_SERVICE_URL"] headers = { "Authorization": f"Bearer {os.environ.get('EMBEDDING_API_KEY', '')}" } payload = { "model": os.environ.get("EMBEDDING_MODEL", "default"), "input": [text] } resp = requests.post(url, headers=headers, json=payload, timeout=30) resp.raise_for_status() data = resp.json() # 不同服务商返回字段可能不同,这里按常见格式取 data[0].embedding return data["data"][0]["embedding"]

如果服务商返回的是embedding字段而不是data,需要改成对应结构。为了避免每次请求都重复这段逻辑,后续可以封装成模块。

5.4 写入记忆

写入时把笔记切块、生成向量、写入 SQLite。这里用source + title生成一个稳定 ID,重复写入时执行INSERT OR REPLACE,相当于更新旧版本。

def add_note(title: str, content: str, source: str = "manual") -> str: con = init_db() raw_id = hashlib.sha1(f"{source}:{title}".encode("utf-8")).hexdigest()[:16] chunks = split_markdown(content) now = datetime.now(timezone.utc).isoformat(timespec="seconds") for idx, chunk in enumerate(chunks): vec = embed_text(chunk) con.execute( "INSERT OR REPLACE INTO notes " "(id, title, source, content, chunk_index, embedding, created_at) " "VALUES (?, ?, ?, ?, ?, ?, ?)", (f"{raw_id}-{idx}", title, source, chunk, idx, json.dumps(vec), now), ) con.commit() con.close() return raw_id

5.5 检索记忆

检索时先把用户问题转成向量,再和库里的所有向量计算余弦相似度,返回最相关的几条:

def cosine_similarity(a: list[float], b: list[float]) -> float: dot = sum(x * y for x, y in zip(a, b)) norm_a = sum(x * x for x in a) ** 0.5 norm_b = sum(x * x for x in b) ** 0.5 return dot / (norm_a * norm_b + 1e-12) def query_notes(query_text: str, top_k: int = 5): q_vec = embed_text(query_text) con = init_db() rows = con.execute( "SELECT content, embedding, title, source FROM notes" ).fetchall() scored = [] for content, embedding, title, source in rows: vec = json.loads(embedding) score = cosine_similarity(q_vec, vec) scored.append({ "score": round(score, 4), "title": title, "source": source, "content": content, }) scored.sort(key=lambda x: x["score"], reverse=True) con.close() return scored[:top_k]

这套实现适合个人笔记规模。如果数据量继续增长,全表扫描会越来越慢,届时可以把向量检索部分替换成 Chroma、FAISS 或 PostgreSQL pgvector。

5.6 跑通最小闭环

按顺序验证三步:

# 第一步:写入一条记忆 python -c 'from memory import add_note; add_note("用户技术栈", "用户主要使用 Python、FastAPI、PostgreSQL。")' # 第二步:检索这条记忆 python -c 'from memory import query_notes; print(query_notes("用户的数据库选型是什么"))'

预期结果是返回包含“PostgreSQL”的记忆片段。如果返回空,先检查写入是否成功、向量是否生成、查询问题是否和原文差异过大。这一个闭环能跑通,后续接入 Agent 才有意义。

6. 接口 API 与批量任务接入

要让 AI 智能体使用第二大脑,最直接的方式是暴露 HTTP API。下面用 FastAPI 写一个最小服务,提供健康检查、写入记忆、检索记忆三个接口:

# server.py from fastapi import FastAPI from pydantic import BaseModel from memory import add_note, query_notes app = FastAPI() class WriteRequest(BaseModel): title: str content: str source: str = "agent" class QueryRequest(BaseModel): query: str top_k: int = 5 @app.get("/health") def health(): return {"status": "ok"} @app.post("/memory/write") def write_memory(request: WriteRequest): note_id = add_note(request.title, request.content, request.source) return {"ok": True, "note_id": note_id} @app.post("/memory/query") def query_memory(request: QueryRequest): docs = query_notes(request.query, request.top_k) return {"docs": docs}

启动服务:

uvicorn server:app --host 127.0.0.1 --port 8000

启动后可以用 curl 验证:

curl -X POST "http://127.0.0.1:8000/memory/query" \ -H "Content-Type: application/json" \ -d '{"query": "用户的数据库选型是什么", "top_k": 3}'

接口能跑通,就可以把写记忆和查记忆包装成智能体的工具。常见做法是通过 Function Calling 暴露两个函数:

[ { "type": "function", "function": { "name": "write_memory", "description": "把当前对话中的重要信息写入外部记忆", "parameters": { "type": "object", "properties": { "title": {"type": "string"}, "content": {"type": "string"} }, "required": ["title", "content"] } } }, { "type": "function", "function": { "name": "query_memory", "description": "根据用户问题检索外部记忆", "parameters": { "type": "object", "properties": { "query": {"type": "string"}, "top_k": {"type": "integer", "default": 5} } } } } ]

接入 Agent 后,还要在系统提示词里写清楚触发规则。例如:当用户提供新的长期事实、修改偏好、完成项目关键节点时,调用write_memory;当用户问题涉及历史信息或项目上下文时,先调用query_memory。不要所有对话都写入,写太频繁会导致记忆库噪声变大,检索质量下降。

批量导入是第二大脑落地的重要环节。你通常有一堆旧笔记要一次性建索引,建议做成目录扫描模式:

# 示例命令,实际参数按你的 CLI 实现调整 python ingest.py --dir ./notes --incremental --max-workers 4 --retry-failed

增量导入的核心是避免重复计算。可以按文件路径和修改时间判断,也可以先计算文件内容哈希。大批量导入时,给每个文件记录导入状态,失败的任务单独重试。定时同步可以通过系统定时任务触发:

# 每天凌晨 1 点重建增量索引示例,实际路径按环境调整 0 1 * * * cd /opt/second-brain && python ingest.py --dir ./notes --incremental >> logs/ingest.log 2>&1

7. 资源占用与性能观察

第二大脑系统的资源占用没有固定数字,它主要由三部分组成:Embedding 模型的算力和显存、向量检索的数据规模、LLM 服务的上下文长度。如果 Embedding 和 LLM 都走远端 API,本地进程只需要处理文本和 HTTP 请求,资源占用很低。如果完全本地推理,就要按模型参数量评估显存。

优化的关键点在于:先定位瓶颈在哪个阶段。建议用下面这些简单命令观察状态:

# 查看数据库体积 du -sh memory.db # 查看本地服务进程 ps aux | grep uvicorn # 查看端口和连接状态 curl http://127.0.0.1:8000/health

判断资源消耗最容易踩的误区是只盯着显存。实际使用中,更常见的瓶颈是:Embedding 接口响应慢、向量检索全表扫描耗时高、大量智能体并发写库导致 SQLite 锁等待。下表可以按阶段对号入座:

阶段观察点优化方向
文本入库CPU 占用、写入耗时异步处理、批量 Embedding、控制文件并发
向量生成API 延迟、GPU 占用批量请求、换更快的 Embedding 模型
检索单次查询耗时、内存占用加向量索引、缩候选范围、用元数据过滤
Agent 调用响应时间、Token 开销控制top_k、限制返回片段长度、加缓存
批量导入日志、失败率增量更新、断点续传、失败重试

观察性能时,每次只改一个变量。比如先固定top_k,测试不同max_chars对检索效果的影响;再固定切分方式,测试不同 Embedding 模型的召回质量。慢慢调出一套适合你数据的最小配置。

8. 常见问题与排查方法

下面的排查表覆盖了从部署到使用的常见问题,遇到异常按表格对号排查。

问题现象可能原因排查方式解决方案
Agent 回答完全不使用记忆工具调用没有配置,或模型没有触发query_memory查看调用日志,确认 Agent 是否发起工具调用在系统提示词里明确触发规则,简化工具名称
检索结果明显不相关切分过大、向量模型不匹配、缺少元数据过滤复现一条查询,查看返回片段和来源调整max_chars,换 Embedding 模型,增加关键词过滤
写入后查询不到Embedding 接口失败、向量写入失败、ID 冲突检查写入日志,确认向量数组长度增加异常捕获,确认 Embedding 响应格式正确
服务启动失败依赖缺失或端口被占用查看 Uvicorn 启动日志,检查 8000 端口安装依赖,更换端口,例如--port 8001
API 返回 401 或超时密钥错误、服务地址不可达、网络不稳定用 curl 单独测试 Embedding 服务检查环境变量,确认服务地址,延长超时时间
中文内容乱码文件编码没有使用 UTF-8查看原始文件编码统一保存为 UTF-8,读取时显式指定编码
SQLite 写入报 database is locked多进程并发写同一个库查看写入日志和进程数使用单进程写入队列,或改用 PostgreSQL
记忆库越积越大,检索变慢所有旧数据都参与全表扫描查看数据库行数和查询耗时引入向量索引,增加时间过滤,定期清理过期记忆

如果你给智能体接入了外部记忆,还要注意一个问题:模型可能把记忆当作权威事实,而实际上记忆库里全是旧版本或未经确认的信息。排查时要看“内容是否引用了来源”,而不只是“有没有返回内容”。没有来源的记忆,权威性默认存疑。

9. 最佳实践与合规边界

构建第二大脑不是一次完成后就结束的工程,它是一个需要持续维护的数据系统。下面是几条比较实用的实践建议。

先做最小闭环,再扩展复杂性。很多人一开始就规划知识图谱、权限系统、多人协作,结果一个月后连一条检索都跑不通。第一周只需要完成:手动写入一条笔记、用接口查出来、让 Agent 把这段内容拼进上下文。三个环节全部打通,再逐步增加功能。

给智能体一个明确的记忆策略。不要让 Agent 自由决定写什么。更好的做法是在系统提示词里给出规则,例如“只在用户明确表达偏好、项目状态发生变化、完成重要决策时写入”“检索不到答案时先查记忆,不要硬编”。这样记忆库的噪声会小很多。

每条记忆尽量保留来源和创建时间。Agent 基于记忆回答时,至少要能定位到原始文件或原始对话。没有来源的记忆,时间越久越不可信。结构化元数据还能帮助你后续做清理,例如批量删除某个来源的全部记录。

权限和隐私要前置设计。如果第二大脑只跑在本地,不要无脑开放网络端口。服务默认绑定127.0.0.1,不要绑定0.0.0.0。如果需要对外提供服务,要加上身份认证和请求频率限制。涉及他人数据、版权材料、人脸声音、医疗金融信息时,必须确认授权并做脱敏处理。

最后,AI 生成的内容在进入长期记忆前要做人工复核。第二大脑的价值是“积累可信上下文”,不是“囤积 AI 幻觉”。每条新记忆如果来自模型总结,建议标记为generated,并要求人工确认后才转为verified。这样即便模型出错,错误也不会被下一次任务自动放大。

下一步可以继续扩展的方向包括:把向量检索升级成“向量 + 关键词 + 时间过滤”的多路召回;在记忆里加入知识图谱,把实体关系显式存下来;把短期工作记忆和长期知识记忆分层管理;让 Agent 定期复盘旧记忆,删除过期内容。基础闭环稳定后,这些方向都可以一步步接进去。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 2:34:18

EFAST全局敏感性分析驱动DHSVM参数筛选与率定实践

简介:EFAST_DHSVM share.zip是一套基于EFAST方法对DHSVM水文模型进行参数敏感性分析的工具包,面向环境科学、水资源管理、防洪规划等领域的研究人员与建模工程师。该压缩包共166个文件,包含Python/m等编程脚本、可运行的exe程序、文本说明文档…

作者头像 李华
网站建设 2026/10/20 2:33:26

矩阵补全与SVT算法:一份可直接运行的MATLAB实现与调参指南

简介:面向数据分析与机器学习初学者的矩阵补全MATLAB实现资料包,聚焦利用低秩假设恢复缺失数据,解决推荐系统、图像恢复、信号处理等场景中的不完整矩阵问题。压缩包共4个文件,含3个MATLAB脚本和1个Markdown说明文档,整…

作者头像 李华
网站建设 2026/10/20 2:32:53

OpenCvSharp4.1官方例程:C#调用OpenCV的工程化落地指南

简介:本资源是OpenCvSharp 4.1官方配套的完整C#例程集合,面向使用.NET平台进行计算机视觉开发的初学者与中级开发者,解决OpenCV在C#环境下缺乏权威参考实现、API调用不熟悉、图像处理流程难复现等实际问题。压缩包共176个文件,涵盖…

作者头像 李华
网站建设 2026/10/20 2:32:48

DHCP工作过程详解:DORA四步交互与网络自动配置原理

1. 什么是DHCP工作过程:从“自动获取IP”到网络通信的底层逻辑你有没有遇到过刚连上公司Wi-Fi,电脑右下角就弹出“已连接,IP地址已获取”的提示?或者在宿舍插上网线,手机几秒钟内就显示“已联网”,根本不用…

作者头像 李华
网站建设 2026/10/20 2:32:22

Linux运维必备:rarlinux-x64-6.1.b1.tar.gz部署与实战技巧

简介:rarlinux-x64-6.1.b1.tar.gz 是面向 64 位 Linux 用户的 RAR 命令行压缩工具测试版,适用于 Ubuntu、Fedora、Debian 等 x86_64 发行版,解决在 Linux 环境下创建、解压 RAR 档案及处理多卷压缩、自解压文件、加密与修复等需求。压缩包共 …

作者头像 李华