智能体长期记忆设计:该记什么、何时遗忘,以及如何落地

2026-09-15 18 预计阅读时间: 1 分钟
来源: oschina.net AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:11 分钟

2026 年,大模型智能体正在进入办公、研发和个人助理场景。它们不再只完成一次问答,而是持续参与项目、处理文档并适应用户习惯。长期协作要求智能体记住一些信息,但“记得越多越好”是一个危险的设计方向:偏好和稳定配置能够提升效率,临时状态与敏感内容却可能变成隐私、合规和错误传播的来源。

真正需要设计的不是一个无限增长的聊天记录库,而是一套包含写入判断、生命周期、访问控制和删除能力的记忆系统。

记忆不是聊天记录的另一个名字

完整保存对话最容易实现,却会把不同性质的信息混在一起。工程上可以把智能体接触到的信息分为四类:

信息类型 示例 默认处理
稳定偏好 使用中文回答、代码示例优先 Python 可长期保存,并允许用户修改
工作配置 仓库路径、测试命令、文档模板 保存到限定作用域,定期确认
临时上下文 本轮调试假设、今天的会议安排 设置较短 TTL,到期删除
敏感信息 密码、令牌、身份证号、私密原文 默认拒绝写入长期记忆

这套划分的关键不是名称,而是不同类别必须有不同的保留策略。一个“回答尽量简洁”的偏好可能几个月都有效;“服务现在运行在 8080 端口”也许十分钟后就失效。若两者以相同方式进入向量库,检索系统就会持续把过期事实注入模型上下文。

长期记忆还应区分作用域。团队代码规范不该变成用户在所有项目中的个人偏好,某个仓库的构建命令也不该泄漏到另一个客户项目。常见作用域可以设计为:

  • user:跨会话的个人偏好。
  • workspace:只对当前工作区有效的路径与工具配置。
  • task:仅服务于当前任务,完成后即可清理。
  • session:只在本次会话中使用,不进入长期存储。

写入前要经过一道“记忆闸门”

记忆系统至少需要回答五个问题:信息是否稳定、未来是否有用、来自谁、保存多久、是否包含敏感内容。不要让模型仅凭一句“这似乎很重要”就直接写数据库。模型可以提出候选记忆,但最终写入应经过确定性的安全规则和权限检查。

一条可操作的写入流程如下:

  1. 从对话中提取候选事实,同时保留来源和时间。
  2. 检测凭据、身份信息等敏感内容,命中后默认拒绝。
  3. 判断作用域、类型和有效期;无法判断时使用短期存储。
  4. 对影响较大的长期记忆请求用户确认。
  5. 写入后保留审计字段,并提供查询、更正和删除入口。

“用户喜欢深色主题”适合成为候选偏好;“这是我的 API Key”即使用户明确说“记住”,系统也不应直接放进普通记忆库。确有凭据托管需求时,应交给专门的密钥管理服务,只在执行工具调用时按权限临时获取。

一个可运行的最小记忆闸门

下面是一个仅使用 Python 标准库的示例。它不是特定产品的官方实现,而是一种可以这样实践的最小方案:敏感文本拒绝持久化,工作区信息设置有效期,用户偏好可以长期保存,并支持列出和删除记录。

将代码保存为 memory_gate.py,使用 Python 3.10 或更高版本运行:

import argparse
import re
import sqlite3
import time
import uuid

DB_PATH = "agent_memory.db"
SENSITIVE_PATTERNS = [
    re.compile(r"\b(?:sk|ghp)_[A-Za-z0-9_-]{16,}\b"),
    re.compile(r"\bpassword\s*[:=]\s*\S+", re.I),
    re.compile(r"\b\d{17}[0-9Xx]\b"),
]
TTL_BY_SCOPE = {
    "session": 0,
    "task": 24 * 3600,
    "workspace": 30 * 24 * 3600,
    "user": None,
}


def connect():
    db = sqlite3.connect(DB_PATH)
    db.execute("""
        CREATE TABLE IF NOT EXISTS memories (
            id TEXT PRIMARY KEY,
            scope TEXT NOT NULL,
            content TEXT NOT NULL,
            source TEXT NOT NULL,
            created_at INTEGER NOT NULL,
            expires_at INTEGER
        )
    """)
    return db


def contains_sensitive_data(text):
    return any(pattern.search(text) for pattern in SENSITIVE_PATTERNS)


def remember(scope, content, source="user-confirmed"):
    if scope not in TTL_BY_SCOPE:
        raise ValueError(f"unsupported scope: {scope}")
    if scope == "session":
        return "Skipped: session data is not persisted."
    if contains_sensitive_data(content):
        return "Rejected: possible sensitive data."

    now = int(time.time())
    ttl = TTL_BY_SCOPE[scope]
    expires_at = now + ttl if ttl is not None else None
    memory_id = str(uuid.uuid4())

    with connect() as db:
        db.execute(
            "INSERT INTO memories VALUES (?, ?, ?, ?, ?, ?)",
            (memory_id, scope, content, source, now, expires_at),
        )
    return memory_id


def list_active(scope=None):
    now = int(time.time())
    with connect() as db:
        db.execute("DELETE FROM memories WHERE expires_at <= ?", (now,))
        sql = "SELECT id, scope, content, source, expires_at FROM memories"
        params = ()
        if scope:
            sql += " WHERE scope = ?"
            params = (scope,)
        return db.execute(sql, params).fetchall()


def forget(memory_id):
    with connect() as db:
        result = db.execute("DELETE FROM memories WHERE id = ?", (memory_id,))
        return result.rowcount > 0


def main():
    parser = argparse.ArgumentParser()
    sub = parser.add_subparsers(dest="command", required=True)

    add = sub.add_parser("remember")
    add.add_argument("scope", choices=TTL_BY_SCOPE)
    add.add_argument("content")

    show = sub.add_parser("list")
    show.add_argument("--scope", choices=TTL_BY_SCOPE)

    delete = sub.add_parser("forget")
    delete.add_argument("id")

    args = parser.parse_args()
    if args.command == "remember":
        print(remember(args.scope, args.content))
    elif args.command == "list":
        for row in list_active(args.scope):
            print(row)
    else:
        print("Deleted" if forget(args.id) else "Not found")


if __name__ == "__main__":
    main()

可以直接测试不同策略:

python memory_gate.py remember user "回答代码问题时优先使用 Python"
python memory_gate.py remember workspace "测试命令是 pytest -q"
python memory_gate.py remember session "当前正在排查登录超时"
python memory_gate.py remember user "password=correct-horse-battery-staple"
python memory_gate.py list

这个示例故意保持简单。进入生产环境后,敏感信息检测不能只依赖正则表达式,还应结合结构化字段校验、数据分类服务和人工确认;数据库也需要加密、租户隔离与严格授权。若引入向量检索,还应把 scopeexpires_at 和权限主体作为强制过滤条件,而不是只按语义相似度取回内容。

遗忘能力和记忆能力同样重要

删除按钮只是遗忘机制的一部分。生产系统还需要处理更新、冲突和错误传播。例如,用户先说“默认使用 Java 17”,后来改为 Java 21,此时应更新同一条偏好,而不是让两个版本同时参与检索。对于模型推断出来、但未经用户确认的信息,应降低可信度并缩短有效期。

每条记忆至少应带有以下元数据:

  • 来源:用户明确输入、工具返回,还是模型推断。
  • 创建时间与最后确认时间。
  • 作用域和数据所有者。
  • 到期时间或复核时间。
  • 可信度以及是否经过用户确认。
  • 用途限制,例如只允许用于代码生成,不允许用于推荐。

检索阶段也要遵循最小必要原则。当前任务只需要代码风格偏好时,就不该把用户全部历史记忆塞入提示词。这样既减少隐私暴露,也能降低无关上下文对模型判断的干扰。

上线前的检查清单

评估一个智能体记忆功能时,可以从以下问题入手:

  • 用户能否看到系统保存了什么,并逐条修改或删除?
  • 默认策略是否拒绝保存密码、令牌和高风险身份信息?
  • 临时事实是否有 TTL,而不是永久保留?
  • 用户、工作区、任务和会话数据是否真正隔离?
  • 记忆是否记录来源,模型推断是否与用户确认的信息区分开?
  • 检索是否同时执行权限、作用域和有效期过滤?
  • 删除是否覆盖主库、缓存、向量索引和合理范围内的备份流程?
  • 用户是否可以关闭长期记忆,并获得可理解的行为变化?

好的记忆系统不是记住最多内容,而是在正确的时间取回最少但足够的信息。把“是否写入”“保存多久”和“谁能读取”设计成明确的工程策略,智能体才能在保持连续体验的同时,避免把便利变成长期风险。


相关推荐