1. 项目概述:为什么AI Agent需要一个“人格”与“记忆”?
最近在折腾AI Agent的朋友,估计都绕不开一个核心痛点:这玩意儿怎么跟金鱼似的,聊完就忘?你精心调教了半天,告诉它你的工作习惯、项目背景、甚至个人偏好,结果下一次对话,它又变回了一张白纸。这种“失忆症”让Agent的实用性大打折扣,更别提构建一个能持续学习、与你共同成长的“数字伙伴”了。
这正是我启动这个开源项目的初衷。我们做的,不仅仅是为AI Agent加一个记事本,而是试图构建一套人格系统。这个词听起来有点玄乎,但拆解开来,核心就是两件事:长期记忆和行为一致性。记忆让它认识你、记住历史;一致性则让它基于这些记忆,形成稳定的“性格”和反应模式,而不是每次对话都像换了个人。
市面上已经有不少为LLM(大语言模型)增强记忆的方案,比如简单的向量数据库检索,或者更复杂的“记忆流”(Memory Stream)架构。但很多方案要么是“全有或全无”——要么把所有记忆一股脑塞给模型导致上下文爆炸,要么检索得不够精准漏掉关键信息;要么就是记忆之间孤立存在,无法形成有逻辑关联的知识网络,更谈不上基于记忆的“成长”。
我们的方案,融合了两个看似古老却极具智慧的方法论:Zettelkasten(卡片盒笔记法)和渐进解锁(Progressive Unlocking)。Zettelkasten负责将海量、碎片化的交互信息,结构化、原子化地存储为一张张互相关联的“记忆卡片”;而渐进解锁机制,则像一位智慧的图书管理员,根据当前对话的上下文,动态、精准地从卡片盒中取出最相关、最必要的记忆,喂给AI Agent,确保其“回想”的过程既高效又聚焦。
最终,我们把它做成了一个开源系统。你不需要从零开始造轮子,可以直接用它来为你的Agent注入“灵魂”,无论是个人助手、客服机器人还是创意协作伙伴,都能获得持续进化的长期记忆和稳定人格。接下来,我就带你深入这套系统的设计与实现,看看我们是如何把想法落地的。
2. 核心架构设计:Zettelkasten与渐进解锁如何协同工作?
2.1 Zettelkasten:不只是笔记法,更是记忆的“原子化”与“网络化”
Zettelkasten,中文常译作“卡片盒笔记法”,源于社会学家尼克拉斯·卢曼。他一生积累了9万张知识卡片,并以此为基础产出了70多部著作。这套方法的精髓在于“原子化”和“连接”。
- 原子化:每条笔记(在咱们系统里就是每条“记忆”)只记录一个核心想法或事实,内容尽量精简、自包含。比如,不是记录“我和用户昨天讨论了项目A的后端架构,决定用微服务,用户喜欢Python的FastAPI”,而是拆成多张卡片:
- 卡片1(事实):
用户对项目A的后端架构感兴趣。 - 卡片2(事实):
关于项目A,已达成共识:采用微服务架构。 - 卡片3(事实):
用户表达过对Python框架FastAPI的偏好。 - 卡片4(关联):
卡片2(微服务架构)是卡片1(架构讨论)的结论。 - 卡片5(关联):
卡片3(FastAPI偏好)可能影响卡片2(微服务)的技术选型。
- 卡片1(事实):
- 连接:通过唯一的ID和标签(Tags),在不同卡片之间建立双向链接。一张卡片可以链接到它的来源(上次对话)、它的结果(达成的结论)、与之相关的其他事实(用户偏好)。这样,记忆不再是孤岛,而是一张不断生长的知识网络。
在我们的系统中,Zettelkasten是如何实现的?我们为每一次有价值的Agent-用户交互,自动或半自动地生成这样的“记忆卡片”。每张卡片包含几个核心字段:
id: 唯一标识符(UUID)。content: 原子化的记忆内容(经过LLM提炼)。embedding: 内容的向量化表示,用于后续的相似性检索。tags: 关键词标签,如#用户偏好、#项目A、#技术决策。links: 指向其他卡片ID的列表,表示关联关系。metadata: 创建时间、来源对话ID、置信度等元数据。
这些卡片被持久化存储在一个图数据库(如Neo4j)或支持关系的向量数据库(如Weaviate)中。图结构天生适合表达卡片间的复杂关联。
注意:原子化提炼这一步至关重要。直接存储原始对话片段是低效的。我们通常用一个轻量级的LLM(如Qwen2.5-Coder-7B)作为“记忆提炼器”,将一段对话总结成1-3条原子事实,并自动建议标签和潜在链接。这步可以异步进行,不影响主对话流程。
2.2 渐进解锁:精准的“记忆唤起”机制
有了结构化的记忆卡片盒,下一步就是如何“用”起来。渐进解锁机制的核心思想是:不是一次性注入所有相关记忆,而是根据对话的进展,像解锁游戏关卡一样,分层、分阶段地激活相关记忆。
这解决了两个关键问题:
- 上下文长度限制:避免将成百上千条相关记忆全部塞入有限的模型上下文窗口。
- 信息过载与干扰:避免不相关或次要的记忆干扰Agent当前的核心任务判断。
渐进解锁的工作流程通常分为三层:
即时检索层(Immediate Recall):
- 触发:每次用户输入或Agent需要做出反应时触发。
- 动作:使用当前对话的最近几条消息(或它们的摘要)作为查询向量,在记忆卡片盒中进行向量相似性检索(如余弦相似度)。
- 输出:返回相似度最高的Top-K(例如3-5条)最相关的原子记忆卡片。这些记忆被直接插入到本次对话的上下文提示词(Prompt)中,作为“近期相关背景”。这是最快、最直接的记忆唤起。
关联扩散层(Associative Unlocking):
- 触发:当“即时检索”到的卡片,其
links字段指向了其他高重要性卡片时触发;或者由LLM判断当前话题需要更深入的历史背景时触发。 - 动作:沿着卡片间的链接进行图遍历。例如,检索到了卡片“用户喜欢FastAPI”,系统会自动沿着链接找到“项目A决定用微服务”,再找到“用户是后端开发工程师”等关联卡片。
- 输出:形成一个小的、与当前话题强相关的记忆子图。这个子图被整理成一段连贯的背景叙述,补充进上下文。这相当于唤起了与核心话题直接相关的“深度记忆”。
- 触发:当“即时检索”到的卡片,其
周期性摘要与人格注入层(Periodic Summary & Personality Infusion):
- 触发:以固定的时间间隔(如每24小时),或在对话自然段落结束时(如用户说“今天先这样”)触发。
- 动作:这是一个更重型的处理。系统会对过去一个周期内产生的所有新记忆卡片,以及被高频访问的核心卡片,进行一次总结与整合。用一个更强的LLM(如Qwen-Max)分析这些记忆,提炼出关于用户的“长期偏好”、项目的“核心目标”、以及Agent与用户互动中形成的“行为模式”。
- 输出:生成或更新一份“人格概要”(Persona Profile)。这份概要不是一堆卡片,而是高度凝练的陈述,例如:“用户是一位注重开发效率的后端工程师,偏好Python技术栈,在架构讨论中倾向于选择简洁、文档完善的方案。在与我的互动中,他欣赏直接给出代码示例的回应方式。” 这份“人格概要”将在未来的每一次对话中,作为系统提示词(System Prompt)的固定部分被注入,从而在底层塑造Agent的“性格”和回应风格,实现真正意义上的“长期记忆”影响“行为一致性”。
2.3 架构总览与数据流
整个系统的数据流可以概括为以下步骤:
- 对话发生:用户与Agent交互。
- 记忆捕获:对话日志被送入“记忆提炼器”,生成原子化的记忆卡片,存入Zettelkasten(图数据库)。
- 记忆检索(对话中):
- 用户新输入触发即时检索,获取最相关卡片。
- 必要时触发关联扩散,获取关联记忆子图。
- 检索到的记忆被格式化后,插入到本次对话的上下文。
- Agent推理:LLM基于增强后的上下文(包含原始对话+解锁的记忆)生成回复。
- 周期性处理(对话外):定时任务执行摘要与人格注入,更新“人格概要”文件。
- 人格引导(下一次对话):新的对话开始时,“人格概要”作为系统提示词的一部分被加载,持续影响Agent。
这个架构使得记忆的存储(结构化、网络化)和使用(动态、精准、分层)解耦,既保证了记忆的丰富性和关联性,又确保了使用时的效率和针对性。
3. 关键技术点实现与工具选型
3.1 记忆的向量化与检索
这是实现“即时检索层”的基石。我们需要把文本记忆变成计算机能快速比较的数值——向量。
嵌入模型(Embedding Model)选型:
- 考量:需要在检索精度、推理速度和成本间权衡。对于开源方案,
text2vec、BGE(BAAI General Embedding)系列是不错的选择,尤其是BGE-M3,它支持多语言、长文本,且针对检索任务优化。如果追求极致的性能且资源允许,OpenAI的text-embedding-3系列或Cohere的嵌入模型是闭源中的佼佼者。 - 我们的选择:在开源系统中,我们默认集成
BGE-M3,因为它平衡性好,且Apache-2.0协议友好。同时,我们设计了可插拔的接口,让用户能方便地替换为任何提供API的嵌入模型或本地部署的模型。 - 实操要点:嵌入模型并非“一劳永逸”。不同模型生成的向量空间不同,直接切换会导致旧的向量索引失效。因此,系统初始化时选定一个模型后,不建议频繁更换。如果必须换,需要有一个“向量迁移”或“重新生成”的过程。
- 考量:需要在检索精度、推理速度和成本间权衡。对于开源方案,
向量数据库(Vector Database)选型:
- 考量:需要支持高效的相似性搜索(K-NN),并且最好能兼顾我们“卡片链接”的图关系。纯向量数据库如
Chroma、Qdrant轻量易用;Weaviate和Milvus功能更强大,且Weaviate原生支持将对象属性(如我们的tags, links)和向量一起存储与过滤。 - 我们的选择:由于我们需要同时处理“向量相似性检索”和“图关联遍历”,我们采用了组合方案。使用
Weaviate作为主存储,因为它既能存向量又能存带属性的对象,其near_vector搜索性能足够。同时,对于复杂的多跳关联查询,我们仍然维护一个轻量的图数据库(如Neo4j的社区版或兼容Cypher查询的Memgraph)来专门处理links关系。这是一种权衡,用一定的架构复杂性换取最大的灵活性。 - 配置示例(Weaviate Schema片段):
# 定义记忆卡片的Class(类似数据表) class_obj = { "class": "MemoryCard", "vectorizer": "none", # 我们用自己生成的向量 "properties": [ {"name": "content", "dataType": ["text"]}, {"name": "tags", "dataType": ["text[]"]}, # 标签数组 {"name": "linkedCardIds", "dataType": ["text[]"]}, # 链接的卡片ID {"name": "conversationId", "dataType": ["text"]}, {"name": "timestamp", "dataType": ["date"]}, ] }
- 考量:需要支持高效的相似性搜索(K-NN),并且最好能兼顾我们“卡片链接”的图关系。纯向量数据库如
3.2 图数据库存储与关联查询
Zettelkasten的核心在于“连接”,图数据库是最自然的映射。
- 选型:
Neo4j是行业标杆,Cypher查询语言强大直观。Memgraph兼容Cypher且性能更强。对于开源项目,也可以考虑JanusGraph或ArangoDB(多模型数据库,也支持图)。 - 我们的实现:我们选择
Memgraph,因为它对Cypher支持好,内存计算性能高,适合实时检索。每张记忆卡片在图里是一个节点(Node),节点属性包含id, content, tags等。卡片间的“链接”就是节点之间的边(Edge),边可以有类型,如DERIVED_FROM(源于)、RELATED_TO(相关)。 - 关联扩散查询示例(Cypher):
这个查询会找到与// 假设我们通过向量检索到了卡片ID为 `card_123` MATCH (start:MemoryCard {id: 'card_123'}) CALL { WITH start MATCH (start)-[:RELATED_TO|:DERIVED_FROM*1..2]-(related:MemoryCard) RETURN related ORDER BY related.importance DESC // 假设卡片有重要性权重 LIMIT 5 } RETURN collect(DISTINCT related.content) as associated_memories;card_123通过1到2跳关系相连的其他卡片,并按重要性返回前5条的内容。这就实现了“关联扩散”。
3.3 记忆提炼与摘要生成的Prompt工程
这是将原始对话转化为结构化记忆,以及将碎片记忆整合为人格概要的关键。Prompt设计的好坏直接决定记忆的质量。
记忆提炼Prompt设计:
你是一个记忆提炼助手。请将下面的对话片段提炼成1-3条独立的、原子化的事实记忆。每条记忆应: 1. 陈述一个完整的事实或观点。 2. 尽可能简洁,主语明确。 3. 为每条记忆建议1-3个关键词标签(如 #技术偏好 #项目目标)。 4. 如果某条记忆与对话中其他已存在的概念明显相关,请指出潜在关联关键词。 对话片段: {conversation_chunk} 请以JSON格式输出: { "memory_cards": [ { "content": "事实陈述", "tags": ["标签1", "标签2"], "potential_links": ["关联关键词1", ...] } ] }- 心得:让LLM输出结构化JSON能极大简化后续处理流程。
potential_links字段为后续自动或人工建立卡片连接提供了线索。
- 心得:让LLM输出结构化JSON能极大简化后续处理流程。
人格概要生成Prompt设计:
你是一个人格分析专家。请基于以下一组记忆卡片,总结其中所反映的“用户”的长期特征、偏好、行为模式,以及“助理”应采取的互动风格。 记忆卡片列表: {memory_cards_summary} 请从以下维度进行总结,形成一段连贯的、描述性的“人格概要”: 1. 用户的专业领域与技能倾向。 2. 用户在决策中表现出的核心价值取向(如效率、稳健、创新)。 3. 用户与助理互动时的偏好(如喜欢详细解释、偏好代码示例、讨厌冗长)。 4. 助理应据此调整的回应风格。 输出格式:一段不超过300字的总结性文字。- 心得:这个Prompt引导LLM进行归纳和抽象,而不是罗列事实。生成的“人格概要”应该是描述性的,可以直接用作System Prompt的一部分,例如:“你正在与一位经验丰富的后端开发工程师对话。他重视代码的清晰度和执行效率,喜欢直接看到解决方案的核心代码。在回答时,请先给出关键结论或代码片段,再根据需要补充简要解释。”
3.4 与现有AI Agent框架的集成
我们的系统设计为可插拔的模块,目标是能相对容易地集成到主流的AI Agent框架中,如LangChain、LangGraph、AutoGen等。
与LangGraph集成:LangGraph通过状态图(State Graph)管理Agent流程,是很好的集成对象。
- 思路:将我们的“记忆检索”模块封装成一个
Runnable或自定义节点。在Graph的某个状态(如agent_think之前),调用该节点。该节点读取当前状态中的对话历史,执行“即时检索”和“关联扩散”,然后将检索到的记忆列表写入状态(如state[“unlocked_memories”])。后续的LLM调用节点在组装Prompt时,会读取这个状态字段。 - 示例节点伪代码:
class MemoryRetrievalNode: def __call__(self, state: dict): recent_convo = state[“messages”][-5:] # 最近5条消息 # 1. 即时检索 immediate = vector_search(recent_convo) # 2. 关联扩散 associated = graph_traversal(immediate) # 3. 合并去重 all_memories = merge(immediate, associated) state[“unlocked_memories”] = format_memories(all_memories) return state - 将“人格概要”作为Graph初始化时的一部分,注入到第一个LLM节点的System Prompt中。
- 思路:将我们的“记忆检索”模块封装成一个
与AutoGen集成:AutoGen围绕
AssistantAgent和UserProxyAgent展开。- 思路:可以创建一个
MemoryAugmentedAssistantAgent,继承自原生的AssistantAgent。在其generate_reply方法被调用前,先执行记忆检索逻辑,将检索到的记忆添加到传入的messages列表的头部(或作为系统消息的一部分)。同时,在初始化这个Agent时,将“人格概要”文件内容作为system_message的一部分传入。 - 注意事项:AutoGen的多Agent对话中,记忆可能需要区分是哪个Agent的。我们的系统可以支持多租户,为每个Agent实例维护独立的记忆卡片盒和人格概要。
- 思路:可以创建一个
工具选型总结:没有银弹。我们的开源实现提供了默认栈(BGE-M3 + Weaviate + Memgraph + Qwen),但几乎每个组件都是可配置、可替换的。关键在于定义清晰的接口(如VectorStoreInterface,GraphStoreInterface,MemoryRefinerInterface),让社区用户可以根据自己的需求和技术栈进行定制。
4. 实操部署与核心配置详解
4.1 环境准备与快速启动
假设你已经在本地或云服务器上准备好了Python环境(>=3.9),以下是快速启动的步骤。
克隆项目与安装依赖:
git clone <your-repo-url> persona-memory-system cd persona-memory-system pip install -r requirements.txtrequirements.txt会包含核心依赖,如weaviate-client,memgraph,langchain,sentence-transformers(用于BGE)等。配置服务与模型: 项目根目录下通常有一个
config.yaml或.env文件用于配置。- 数据库配置:填写Weaviate和Memgraph的连接地址。如果使用Docker Compose,项目通常会提供
docker-compose.yml一键启动这些服务。# docker-compose.yml 示例片段 services: weaviate: image: semitechnologies/weaviate:latest ports: - "8080:8080" environment: - QUERY_DEFAULTS_LIMIT=25 - AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED=true - PERSISTENCE_DATA_PATH=/var/lib/weaviate memgraph: image: memgraph/memgraph:latest ports: - "7687:7687" - 模型配置:指定嵌入模型和LLM的路径或API密钥。
embedding: model_name: “BAAI/bge-m3” device: “cuda:0” # 或 “cpu” llm: # 用于记忆提炼的轻量模型 refiner: “Qwen/Qwen2.5-Coder-7B-Instruct” # 用于人格概要生成的重型模型(可选API) summarizer: “gpt-4-turbo” # 或 “Qwen/Qwen2.5-72B-Instruct” api_base: “https://api.openai.com/v1” # 若用OpenAI api_key: ${OPENAI_API_KEY}
- 数据库配置:填写Weaviate和Memgraph的连接地址。如果使用Docker Compose,项目通常会提供
初始化与运行示例: 运行提供的初始化脚本,创建数据库Schema,并加载示例数据或开始空载运行。
python scripts/init_system.py python examples/chat_with_memory.py示例聊天脚本会启动一个简单的命令行交互界面,你可以开始对话,系统会在后台默默创建和检索记忆。
4.2 核心参数调优与性能考量
系统运行起来后,有几个关键参数直接影响效果和性能,需要根据实际场景调整。
记忆检索相关:
IMMEDIATE_RECALL_TOP_K(即时检索返回条数):默认3-5。太小可能遗漏关键信息,太大会增加上下文负担。建议从3开始,根据对话连贯性调整。VECTOR_SEARCH_SIMILARITY_THRESHOLD(向量相似度阈值):默认0.7。低于此阈值的记忆卡片被认为不相关,不返回。这个值需要根据你用的嵌入模型调整,可以通过人工评估一批查询结果来校准。ASSOCIATIVE_DEPTH(关联扩散深度):即Cypher查询中的*1..2,表示从核心卡片向外探索几跳。通常1-2跳足够,跳数太多会引入噪声。
记忆提炼相关:
CONVERSATION_CHUNK_SIZE(对话分块大小):原始对话需要分成多长的片段送入提炼器?太大(如1000字)可能导致提炼不精确;太小(如200字)可能割裂上下文。建议按“对话轮次”分块,或者按固定Token数(如512 tokens)分块,并尽量保证语义完整。REFINER_LLM_TEMPERATURE(提炼器温度):用于提炼记忆的LLM,温度应设低(如0.1),以保证输出的事实稳定、结构化。
人格概要相关:
SUMMARY_INTERVAL_HOURS(摘要生成间隔):多久触发一次人格概要更新?太频繁(如每小时)计算成本高且变化不大;太久(如每周)则人格更新滞后。24小时是一个合理的起点。MEMORY_CARDS_FOR_SUMMARY(用于摘要的记忆卡片数量):每次更新时,回顾最近多少条记忆?可以是最新的N条,也可以是过去一段时间内所有记忆。建议结合时间窗口和数量限制,例如“过去7天内,最多500条记忆”。
性能优化技巧:
- 异步处理:记忆提炼和人格概要生成都是耗时的LLM调用,务必做成异步任务(如使用Celery或BackgroundScheduler),丢到消息队列里处理,绝不阻塞主对话线程。
- 缓存:对频繁检索的、不常变的记忆(如用户的基本偏好),可以在内存(如Redis)中缓存其向量和内容,减少数据库查询和模型调用。
- 向量索引优化:确保Weaviate使用了高效的索引算法(如HNSW)。根据数据量调整
efConstruction和maxConnections参数。 - 图数据库索引:在Memgraph中,为
MemoryCard节点的id和tags属性创建索引,可以大幅加速按ID查询和按标签过滤的速度。
4.3 集成到现有Agent项目:一个LangGraph案例
假设你已有一个基于LangGraph的客服Agent,现在想为其增加我们的记忆人格系统。
安装与导入:将我们的系统打包成Python包安装,或直接作为模块导入。
from persona_memory import MemorySystem, PersonaProfile初始化记忆系统:
memory_system = MemorySystem( vector_store_url=“http://localhost:8080”, graph_store_url=“bolt://localhost:7687”, embedding_model_name=“BAAI/bge-m3”, persona_profile_path=“./data/persona_profile.json” ) # 加载已有的人格概要,如果没有则创建空的 persona = PersonaProfile.load_or_create(memory_system.persona_profile_path)修改你的LangGraph State:在Graph的状态定义中,增加一个字段来存放解锁的记忆。
from typing import TypedDict, List, Annotated import operator class AgentState(TypedDict): messages: Annotated[List, operator.add] # 原有的消息历史 unlocked_memories: List[str] # 新增:本次回合解锁的记忆 current_user_id: str # 用户标识,用于多用户记忆隔离创建记忆检索节点:将其加入到你的Graph图中。
from langgraph.graph import StateGraph, END def retrieve_memory(state: AgentState): recent_msgs = state[‘messages’][-4:] # 获取最近几轮对话 # 调用记忆系统进行检索 memories = memory_system.recall( query_context=recent_msgs, user_id=state[‘current_user_id’] ) return {“unlocked_memories”: memories} # 假设你原有的图有一个叫“process_input”的节点 workflow = StateGraph(AgentState) workflow.add_node(“retrieve_memory”, retrieve_memory) workflow.add_edge(“process_input”, “retrieve_memory”) # 在处理输入后检索记忆 workflow.add_edge(“retrieve_memory”, “call_llm”) # 检索完记忆再调用LLM修改LLM调用Prompt:在调用LLM的节点中,将解锁的记忆和人格概要插入Prompt。
def call_llm(state: AgentState): # 获取人格概要 persona_text = persona.get_summary() # 获取本次解锁的记忆 memory_context = “\n”.join(state[‘unlocked_memories’]) system_message = f“”” 你是专业的客服助手。以下是关于当前用户的背景信息: {persona_text} 此外,以下是与当前对话相关的历史记忆: {memory_context} 请基于以上信息,专业且友好地回应用户。 “”” # ... 后续组装messages,调用LLM ...在对话结束后触发记忆存储:在对话流结束或自然暂停时,异步触发记忆提炼。
# 在某个节点或回调中 def store_memory_async(state: AgentState): conversation_chunk = state[‘messages’][-10:] # 存储最近一段对话 # 异步任务,不阻塞 memory_system.refine_and_store_async( conversation=conversation_chunk, user_id=state[‘current_user_id’] )
通过以上步骤,你的LangGraph Agent就具备了长期记忆和人格。它会记住与每个用户的交流历史,并在每次对话中“想起”相关背景,回应也会越来越贴合该用户的特性和偏好。
5. 常见问题、排查与效果评估
5.1 实操中遇到的典型问题与解决方案
问题:记忆检索不准,经常召回不相关的信息。
- 可能原因1:嵌入模型不匹配。用于生成存储向量的模型和检索时计算查询向量的模型不是同一个(或同系列同版本)。解决:确保整个流程使用相同的嵌入模型。
- 可能原因2:文本分块/提炼质量差。原始对话提炼成的“原子记忆”内容模糊或包含多个主题,导致向量表示不精确。解决:优化“记忆提炼Prompt”,要求输出更纯粹、更具体的事实。可以加入少量示例(Few-shot)进行引导。
- 可能原因3:相似度阈值设置不当。解决:在管理后台做一个简单的评估界面,人工标注一批查询的预期结果,然后调整阈值,直到准确率和召回率平衡。
- 排查命令:可以单独写一个测试脚本,输入一段查询文本,打印出检索到的Top K记忆卡片及其相似度分数,人工检查问题出在哪一环。
问题:人格概要生成得过于笼统或偏离事实。
- 可能原因1:用于总结的记忆卡片数量太少或质量不高。如果近期记忆都是琐碎闲聊,总结不出有意义的模式。解决:在触发总结时,可以过滤掉低重要性(置信度低)或标签不相关的记忆卡片。
- 可能原因2:总结用的LLM能力不足或Prompt不佳。解决:如果使用开源模型,尝试更大、推理能力更强的模型(如Qwen-72B)。精心设计Prompt,要求模型从“具体事实”归纳到“抽象模式”,并给出明确的维度指引(如我们之前设计的Prompt)。
- 可能原因3:更新过于频繁。人格是相对稳定的,频繁更新会导致“性格”摇摆。解决:拉长
SUMMARY_INTERVAL_HOURS,或者引入“动量更新”机制,新的人格概要只部分覆盖旧的(例如,新概要占30%,旧概要占70%)。
问题:系统响应变慢,延迟明显增加。
- 可能原因1:向量检索或图查询慢。解决:检查数据库性能。对于Weaviate,确保使用了HNSW索引并优化了参数。对于Memgraph,检查是否有合适的索引,并考虑对大规模图数据进行分片。
- 可能原因2:同步进行LLM调用。记忆提炼和人格生成如果是同步的,会严重阻塞。解决:必须将其改为异步任务队列(如使用RQ、Celery)。
- 可能原因3:记忆卡片数量爆炸式增长。解决:实施记忆“衰减”或“合并”策略。例如,给每条记忆一个“活跃度”分数,每次被检索到就加分,定期对低分记忆进行归档或删除。对于内容相似度极高的多条记忆,可以合并成一条更概括的记忆。
问题:多轮对话后,上下文窗口依然被撑爆。
- 这是渐进解锁机制要解决的核心问题之一。确保你只注入了通过“即时检索”和“关联扩散”解锁的、最相关的少数几条记忆(比如总共5-10条),而不是全部历史。如果还是超了,可以考虑:
- 对解锁的记忆进行二次摘要:在插入上下文前,用一个快速的LLM对这几条记忆做一个极简摘要。
- 使用支持更长上下文的模型。
- 在系统设计上,将超长对话分段,每段视为一个独立的“会话”,各自维护短期记忆,仅共享长期的人格概要。
- 这是渐进解锁机制要解决的核心问题之一。确保你只注入了通过“即时检索”和“关联扩散”解锁的、最相关的少数几条记忆(比如总共5-10条),而不是全部历史。如果还是超了,可以考虑:
5.2 效果评估:如何判断系统真的在“成长”?
为这样一个系统设计评估指标是挑战,但可以从定性和定量两方面看:
定性评估(用户体验):
- 一致性测试:在不同时间,询问用户相同或相似的问题(如“我最喜欢用什么编程语言?”),看Agent的回答是否一致且符合历史。
- 深度对话测试:就一个复杂话题进行多轮深入讨论,看Agent能否引用几天甚至几周前讨论过的相关论点和事实。
- 个性化感知:用户是否能感觉到这个Agent“了解我”,回应方式是否符合自己的习惯。
定量评估:
- 记忆检索准确率:人工标注一批测试查询,计算系统召回的记忆是否相关的比例。
- 上下文利用率:统计每次LLM调用中,来自记忆系统的token数占总上下文token数的比例。一个健康的比例(如10%-30%)说明记忆被有效利用且没有过度占用。
- 任务完成率提升:在特定的、需要历史信息的任务上(如“继续完成我们上周讨论的API设计”),对比使用记忆系统前后的任务成功完成率。
- 人工评分:让测试用户对多轮对话的连贯性、个性化和帮助程度进行打分(1-5分),计算平均分变化。
5.3 一些进阶玩法与扩展思路
当基础系统跑通后,可以探索更多可能性:
- 多模态记忆:除了文本,能否存储和检索图像、音频的记忆?可以为图片生成描述向量,与文本记忆关联存储。
- 记忆的主动触发:除了被动检索,系统可以主动“提醒”。例如,当检测到用户正在制定计划,而记忆库中有相关的过往计划失败经验时,主动插入提示:“根据去年3月的记录,您在类似项目中曾因X原因延期,是否需要提前考虑Y方案?”
- 人格的演化与分支:同一个人在不同场景(工作/生活)下可能表现出不同侧面。可以维护多个“人格概要”,根据对话主题动态切换。
- 联邦学习与隐私:对于敏感信息,记忆可以加密存储,或在本地处理,不上传云端。系统可以设计为仅在本地运行,保护用户隐私。
这个开源项目只是一个起点。Zettelkasten和渐进解锁提供了一个坚实的思想框架,但如何让AI Agent真正拥有像人一样有机生长、关联紧密、随用随取的长期记忆,并由此塑造出稳定而鲜活的“数字人格”,还有很长的路要走。每一个具体的应用场景,都需要你去调整参数、优化Prompt、甚至改造架构。希望这套系统能成为你探索之旅上的一块有用的基石。