Agent+Milvus,告别静态知识库,打造具备动态记忆的智能AI助手

在AI技术飞速迭代的今天,越来越多的开发者开始搭建属于自己的AI助手,但很多人都会遇到一个棘手的问题:明明给AI助手配置了向量数据库,它却始终记不住历史对话,每次交互都像"重新认识"一样。这背后的核心原因的很简单,我们最初搭建的基于RAG架构的AI助手,本质上只是给大语言模型(LLM)配备了一个外部查询工具,而非为动态记忆管理设计的系统。

随着AI助手的应用场景越来越复杂,从简单的问答交互到个性化的长期服务,静态知识库的局限性愈发明显。那么,具备动态记忆的AI助手到底该如何搭建?RAG架构、agentic RAG、Agent记忆系统的演化路线是怎样的?我们该如何根据自身需求进行选型,又该如何配置AI的记忆能力?

今天,我们就从技术演进的角度,一步步拆解从RAG到Agent记忆的完整路径,结合Milvus向量数据库的实操案例,手把手教你搭建具备动态记忆与按需检索能力的智能AI助手,让你的AI助手真正拥有"长期记忆",实现更自然、更个性化的交互体验。

一、认知前提:为什么静态知识库会被淘汰?

在深入技术细节之前,我们先搞清楚一个核心问题:为什么静态知识库无法满足现代AI助手的需求?

我们常见的AI助手知识库,大多是基于传统RAG架构搭建的。简单来说,就是先在离线状态下,把所有的文档、资料切成小块,转换成向量后存入向量数据库,等到用户提问时,再把用户的问题转换成向量,去数据库里检索最相似的内容,最后把检索结果和问题一起交给LLM生成答案。

这种模式在简单的问答场景下确实好用,比如查询产品说明书、技术文档等固定内容,但一旦涉及到动态交互,就会暴露三个致命问题。

第一,知识库无法实时更新。离线构建的知识库,在运行过程中无法添加新内容、修改旧内容,一旦知识过时,就必须停止服务,重新切分文档、生成向量、构建索引,效率极低。比如你更新了产品的功能参数,静态知识库无法实时同步,用户查询时得到的依然是旧信息。

第二,强制检索,浪费资源。不管用户的问题是否需要外部知识,传统RAG都会强制去数据库检索一遍。比如用户问"你还记得我喜欢简洁的回复吗",这种属于历史对话记忆,根本不需要去外部知识库检索,但传统RAG依然会执行检索操作,不仅浪费计算资源,还会降低响应速度。

第三,无法实现个性化记忆。静态知识库是"通用的",所有用户共享同一套知识,无法记录单个用户的偏好、习惯、历史对话细节。比如用户A喜欢用emoji交流,用户B喜欢详细的文字说明,静态知识库无法区分这两种需求,自然无法提供个性化的回复。

随着Agent技术的兴起,AI助手不再是简单的"问答机器",而是需要具备自主决策、动态学习、长期记忆的能力。这就要求我们跳出传统RAG的局限,从"只读的外部知识库"向"可读写的动态记忆系统"演进,而Milvus向量数据库,正是支撑这一演进的核心工具。

二、演进第一阶段:传统RAG,只读的外部知识库(基础入门)

虽然静态知识库有局限性,但它是搭建AI助手的基础,也是我们理解后续技术演进的关键。我们先从传统RAG的核心逻辑入手,搞清楚它的工作原理、技术难点,以及如何用Milvus实现一个基础的RAG系统。

2.1 什么是RAG?核心逻辑是什么?

RAG的全称是检索增强生成,最早在2020年由Lewis等人提出,核心目的是解决LLM的"知识截止日期"问题。我们都知道,LLM的训练数据是有时间限制的,比如某个LLM训练到2023年,就无法知道2024年的新信息,而RAG的作用,就是给LLM"外接"一个实时更新的知识库,让它在回答问题前,先去知识库中查询最新、最相关的信息,再结合自身的知识生成答案。

传统RAG的工作流程可以分为四个步骤,非常简单易懂。

第一步,离线准备。把需要用到的文档(比如产品手册、技术文档、行业报告等)切成合适的小块,这个过程叫做"文档切片"。切片不能太大,否则检索精度会下降;也不能太小,否则会丢失上下文信息,一般建议每块100-500字。

第二步,向量转换与存储。通过嵌入模型(比如OpenAI的text-embedding-ada-002),把每一块文档转换成向量,然后将向量和对应的文本、来源等信息一起存入向量数据库。这里的核心是"向量",它能把文本的语义信息转换成计算机能理解的数值,方便后续的相似性检索。

第三步,在线检索。当用户提出问题时,同样通过嵌入模型把问题转换成向量,然后用这个向量去向量数据库中检索最相似的Top-K块文档(一般K取5-10)。这个过程就像是"大海捞针",但向量数据库能通过索引技术,快速找到最相关的内容。

第四步,生成答案。把检索到的Top-K文档和用户的问题一起输入LLM,让LLM结合这些外部知识,生成准确、全面的答案。

2.2 传统RAG的核心技术难点:毫秒级检索

在搭建传统RAG系统时,最核心的技术挑战不是文档切片,也不是向量转换,而是如何在百万级、亿级的向量数据中,实现毫秒级的检索。

很多开发者一开始会尝试用传统数据库(比如MySQL、PostgreSQL)来存储向量,要么是直接存储向量数据,要么是使用简单的向量检索插件。但这种方式存在明显的短板:传统数据库本身不擅长处理向量数据,没有专门的向量索引,检索时只能逐一遍历所有向量,计算相似度,在百万级数据量下,检索延迟会达到几秒甚至几十秒,根本无法满足AI助手的实时交互需求。

而Milvus作为专门的向量数据库,就能完美解决这个问题。它支持多种专业的向量索引(比如HNSW、IVF_FLAT等),能够实现百亿级向量数据的毫秒级检索,而且还支持标量过滤、动态插入等功能,为后续的技术演进打下了基础。

2.3 Milvus实现传统RAG的完整实操(附代码)

下面我们用具体的代码,手把手教你用Milvus搭建一个基础的传统RAG系统。这里我们使用Python语言,结合OpenAI的嵌入模型,实现文档的批量插入和在线检索。

首先,我们需要安装必要的依赖包:

bash 复制代码
pip install pymilvus openai

然后,编写完整的代码,实现从连接Milvus、创建集合、构建索引,到批量插入文档、在线检索的全流程。

python 复制代码
from pymilvus import connections, Collection, FieldSchema, CollectionSchema, DataType
import openai

# ===== 工具函数:统一的向量化接口 =====
def embed(text):
    """将文本转换为向量,使用OpenAI的text-embedding-ada-002模型"""
    response = openai.Embedding.create(
        input=text,
        model="text-embedding-ada-002"
    )
    return response["data"][0]["embedding"]

# ===== 步骤1:连接Milvus并创建Collection(集合,类似数据库中的表) =====
# 连接本地Milvus服务(如果是远程服务,替换host和port即可)
connections.connect(host="localhost", port="19530")

# 定义集合的字段(类似表的列)
fields = [
    # 主键字段,自增,用于唯一标识每条数据
    FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
    # 文本内容字段,存储文档切片后的文本
    FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=65535),
    # 向量字段,存储文本对应的向量,维度为1536(对应text-embedding-ada-002模型)
    FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1536),
    # 来源字段,存储文档的来源(比如文档路径、网址等)
    FieldSchema(name="source", dtype=DataType.VARCHAR, max_length=512),
]

# 创建集合 schema(类似表结构)
schema = CollectionSchema(fields=fields, description="RAG Knowledge Base(传统RAG知识库)")
collection = Collection(name="rag_knowledge", schema=schema)

# ===== 步骤2:创建向量索引,提升检索速度 =====
# 这里使用HNSW索引,适合高维向量的快速检索,metric_type为COSINE(余弦相似度)
index_params = {
    "index_type": "HNSW",
    "metric_type": "COSINE",
    "params": {"M": 16, "efConstruction": 256}
}
# 为embedding字段创建索引
collection.create_index(field_name="embedding", index_params=index_params)

# ===== 步骤3:离线索引,批量插入文档 =====
def ingest_documents(documents):
    """批量插入文档到Milvus向量库,适合离线准备阶段"""
    texts = []
    embeddings = []
    sources = []
    # 遍历文档列表,提取文本、生成向量、记录来源
    for doc in documents:
        texts.append(doc["text"])
        embeddings.append(embed(doc["text"]))
        sources.append(doc["source"])
    # 组装数据,注意顺序要和fields的顺序一致(除了auto_id的id字段)
    data = [texts, embeddings, sources]
    # 插入数据
    collection.insert(data)
    # 刷新数据,确保持久化到磁盘
    collection.flush()
    print(f"✅ 已索引 {len(documents)} 条文档,成功存入Milvus向量库")

# ===== 步骤4:在线检索,RAG查询 =====
def rag_retrieval(query, top_k=5):
    """传统RAG检索:每次查询都强制检索,返回最相似的top_k条文档"""
    # 将集合加载到内存,提升检索速度(首次加载较慢,后续复用)
    collection.load()
    # 将用户查询转换为向量
    query_embedding = embed(query)
    # 检索参数,ef越大,检索精度越高,但速度越慢
    search_params = {"metric_type": "COSINE", "params": {"ef": 100}}
    # 执行检索
    results = collection.search(
        data=[query_embedding],  # 查询向量(可批量查询,这里单次查询)
        anns_field="embedding",  # 用于检索的向量字段
        param=search_params,     # 检索参数
        limit=top_k,             # 返回的最相似文档数量
        output_fields=["text", "source"]  # 检索结果中需要返回的字段
    )
    # 返回第一条查询的结果(批量查询时可遍历results)
    return results[0]

# ===== 使用示例:批量插入文档 + 在线检索 =====
if __name__ == "__main__":
    # 模拟需要插入的文档列表(实际应用中可从文件、数据库中读取)
    docs = [
        {"text": "Milvus是一款开源的向量数据库,支持HNSW、IVF_FLAT等多种向量索引,能够实现百亿级向量的毫秒级检索", "source": "docs/milvus_intro.md"},
        {"text": "RAG(检索增强生成)的核心是给LLM配备外部知识库,让LLM回答问题前先查询相关资料,提升答案的准确性和时效性", "source": "docs/rag_intro.md"},
        {"text": "OpenAI的text-embedding-ada-002模型是一款常用的文本嵌入模型,生成的向量维度为1536,适合用于文本相似性检索", "source": "docs/embedding_model.md"},
        {"text": "Milvus支持多种数据操作,包括插入、查询、更新、删除,同时支持标量过滤,能够满足复杂场景的检索需求", "source": "docs/milvus_operation.md"},
        {"text": "传统RAG的局限性在于知识库是只读的,运行时无法更新,且每次查询都强制检索,浪费计算资源", "source": "docs/rag_limit.md"}
    ]
    # 批量插入文档
    ingest_documents(docs)
    
    # 模拟用户查询
    user_query = "什么是向量数据库?它有什么优势?"
    print(f"\n用户查询:{user_query}")
    print("检索结果:")
    # 执行检索
    results = rag_retrieval(user_query, top_k=3)
    # 遍历检索结果,打印相似度、文本内容和来源
    for i, hit in enumerate(results, 1):
        print(f"{i}. 相似度: {hit.score:.3f} | 内容: {hit.entity.get('text')} | 来源: {hit.entity.get('source')}")

运行上述代码后,我们就能实现一个基础的传统RAG系统。但通过代码和实际操作,我们很容易发现传统RAG的局限性:每次查询都必须执行检索,哪怕是简单的历史对话查询;知识库一旦插入文档,运行时无法更新、删除;所有文档都存在同一个集合中,无法区分领域、类型,检索精度会随着数据量的增加而下降。

这些问题在简单的问答场景下影响不大,但如果我们要搭建一个能够长期交互、个性化服务的AI助手,就必须进入下一个阶段,agentic RAG。

三、演进第二阶段:agentic RAG,按需检索,让AI学会"自主决策"

传统RAG的核心问题,在于把"检索"当成了必选项,而不是可选项。就像一个人不管遇到什么问题,都先去查字典,哪怕是自己已经知道答案的问题,效率极低。而agentic RAG的突破,就是把检索变成了一种"工具",让Agent(智能体)自主决策:是否需要检索、检索什么来源、检索结果是否可信。

简单来说,agentic RAG就是给AI助手增加了一个"大脑",让它在回答问题前,先判断这个问题是否需要去外部知识库检索。比如用户问"你好",AI助手可以直接回复"你好,有什么可以帮你?",不需要检索;如果用户问"Milvus支持哪些向量索引?",AI助手判断自己的内置知识不够准确,就会去外部知识库检索相关内容,再生成答案。

3.1 agentic RAG的核心突破:检索决策与多Collection隔离

agentic RAG有两个核心突破,一个是"检索决策机制",另一个是"多Collection隔离架构"。

检索决策机制,就是Agent通过分析用户的问题,自主判断是否需要检索。它的核心逻辑是:先判断问题是否属于内置知识(比如简单的问候、基础概念),如果是,直接生成答案;如果不是,再判断需要检索哪个领域的知识,然后执行检索;检索完成后,还要评估检索结果的质量,如果结果质量太低(比如相似度不足),就会触发 fallback 策略(比如去网页搜索)。

而多Collection隔离架构,是为了解决传统RAG中"所有文档混存"的问题。在传统RAG中,所有领域的文档都存在同一个Collection中,检索时很容易出现"无关结果",比如检索"Milvus的API使用",却返回了"Milvus的安装教程",检索精度下降。

多Collection隔离架构的思路很简单:为不同的领域、不同类型的知识,创建独立的Collection。比如产品文档创建一个Collection,API参考创建一个Collection,客户案例创建一个Collection,技术博客创建一个Collection。这样Agent在检索时,就可以根据问题类型,精准路由到对应的Collection,避免无关结果的干扰,同时还能为不同的Collection设置差异化的检索策略(比如不同的索引参数、过滤条件)。

3.2 Milvus实现agentic RAG的实操(附代码)

下面我们依然用Python代码,结合Milvus,实现一个agentic RAG系统。核心要点是创建多个独立的Collection,让Agent根据问题类型,动态路由检索,同时实现检索质量评估和决策。

python 复制代码
from pymilvus import connections, Collection
import openai

# 先初始化向量化函数(和传统RAG一致)
def embed(text):
    response = openai.Embedding.create(
        input=text,
        model="text-embedding-ada-002"
    )
    return response["data"][0]["embedding"]

# 连接Milvus服务(和传统RAG一致)
connections.connect(host='localhost', port='19530')

# ===== 第一步:创建多个专业领域的Collection =====
# 这里我们创建4个Collection,分别对应不同的知识领域
# 注意:实际应用中,每个Collection的创建过程和传统RAG的Collection创建一致,这里简化展示
class MultiSourceRAG:
    def __init__(self):
        # 存储不同领域的Collection,key为领域名称,value为Collection实例
        self.collections = {
            "product_docs": Collection("product_docs"),      # 产品文档(比如Milvus的产品介绍、功能说明)
            "api_reference": Collection("api_reference"),    # API参考(比如Milvus的Python SDK API文档)
            "customer_cases": Collection("customer_cases"),  # 客户案例(比如Milvus的实际应用案例)
            "tech_blogs": Collection("tech_blogs")           # 技术博客(比如Milvus的技术解析、最佳实践)
        }
        # 将所有Collection加载到内存,提升检索速度(可根据实际情况动态加载/卸载)
        for coll in self.collections.values():
            coll.load()

# ===== 第二步:Agent决策,智能检索路由 =====
def smart_retrieve(question, agent_decision):
    """
    Agent决策驱动的检索:根据Agent的决策,动态路由到对应的Collection进行检索
    agent_decision:Agent的决策结果,字典格式,包含以下字段:
        - need_retrieval:是否需要检索(True/False)
        - target_collections:需要检索的Collection列表(比如["api_reference", "tech_blogs"])
        - top_k:需要返回的最相似文档数量
        - filters:检索过滤条件(比如按发布日期过滤)
    """
    # 如果Agent判断不需要检索,直接返回空列表
    if not agent_decision["need_retrieval"]:
        return []
    
    # 初始化多源RAG,获取所有领域的Collection
    rag = MultiSourceRAG()
    results = []
    
    # 遍历需要检索的Collection,执行检索
    for coll_name in agent_decision["target_collections"]:
        # 获取当前Collection
        collection = rag.collections[coll_name]
        
        # 构建动态过滤表达式(比如过滤出2024年之后发布的文档)
        filter_expr = None
        if "filters" in agent_decision:
            filters = agent_decision["filters"]
            # 这里以发布日期过滤为例,实际可根据字段灵活扩展
            if "publish_date" in filters:
                filter_expr = f'publish_date {filters["publish_date"]}'
        
        # 执行检索(这里使用IP相似度,适合高维向量检索,可根据实际情况调整)
        search_params = {"metric_type": "IP", "params": {"nprobe": 16}}
        search_results = collection.search(
            data=[embed(question)],
            anns_field="embedding",
            param=search_params,
            limit=agent_decision["top_k"],
            expr=filter_expr,  # 应用过滤条件(Milvus支持标量过滤,非常灵活)
            output_fields=["text", "source", "publish_date"]  # 返回需要的字段
        )
        # 将当前Collection的检索结果添加到总结果中
        results.extend(search_results[0])
    
    # 返回所有检索结果
    return results

# ===== 第三步:检索质量评估,Agent自主判断检索结果是否可用 =====
def retrieve_with_quality_check(question, threshold=0.7):
    """
    Agent评估检索质量,决定下一步行动:
    - 如果检索结果的相似度都低于阈值,触发fallback策略(比如网页搜索)
    - 如果有高质量结果,直接使用这些结果生成答案
    """
    # 这里以检索产品文档为例,实际可根据Agent决策动态选择Collection
    collection = Collection("product_docs")
    collection.load()
    
    # 执行检索
    results = collection.search(
        data=[embed(question)],
        anns_field="embedding",
        param={"metric_type": "IP", "params": {"nprobe": 16}},
        limit=5
    )
    
    # 过滤低质量结果(相似度低于threshold的结果)
    high_quality_results = [
        hit for hit in results[0] 
        if hit.score >= threshold
    ]
    
    # Agent根据过滤结果做决策
    if not high_quality_results:
        # 没有高质量结果,触发fallback
        return {"action": "FALLBACK_TO_WEB_SEARCH", "reason": "本地知识库召回质量不足,需补充网页搜索"}
    else:
        # 有高质量结果,使用这些结果生成答案
        return {"action": "USE_RESULTS", "data": high_quality_results}

# ===== 使用示例:Agent决策检索 + 质量评估 =====
if __name__ == "__main__":
    # 模拟Agent的决策结果(实际应用中,Agent可通过LLM分析问题生成决策)
    # 示例1:用户问"Milvus的Python SDK如何创建Collection?",需要检索API参考和技术博客
    agent_decision1 = {
        "need_retrieval": True,
        "target_collections": ["api_reference", "tech_blogs"],
        "top_k": 3,
        "filters": {"publish_date": ">= 2024-01-01"}  # 只检索2024年之后的文档
    }
    question1 = "Milvus的Python SDK如何创建Collection?"
    print(f"用户查询1:{question1}")
    results1 = smart_retrieve(question1, agent_decision1)
    print("检索结果:")
    for i, hit in enumerate(results1, 1):
        print(f"{i}. 相似度: {hit.score:.3f} | 内容: {hit.entity.get('text')[:50]}... | 来源: {hit.entity.get('source')} | 发布日期: {hit.entity.get('publish_date')}")
    
    # 示例2:用户问"你好,我想了解Milvus",Agent判断不需要检索,直接回复
    agent_decision2 = {
        "need_retrieval": False
    }
    question2 = "你好,我想了解Milvus"
    print(f"\n用户查询2:{question2}")
    results2 = smart_retrieve(question2, agent_decision2)
    if not results2:
        print("Agent决策:不需要检索,直接回复用户")
        print("回复:你好!Milvus是一款开源向量数据库,专注于海量向量数据的存储和检索,广泛应用于AI助手、推荐系统等场景。如需了解更多细节,可以问我具体问题哦~")
    
    # 示例3:检索质量评估
    question3 = "Milvus 2.5版本新增了哪些功能?"
    print(f"\n用户查询3:{question3}")
    quality_check_result = retrieve_with_quality_check(question3, threshold=0.7)
    print(f"Agent质量评估结果:{quality_check_result}")
    if quality_check_result["action"] == "USE_RESULTS":
        print("高质量检索结果:")
        for hit in quality_check_result["data"][:2]:
            print(f"  相似度: {hit.score:.3f} | 内容: {hit.entity.get('text')[:50]}...")
    else:
        print("执行Fallback策略:启动网页搜索,补充最新信息")

通过上面的代码,我们实现了一个简单的agentic RAG系统。可以看到,Agent能够根据问题类型自主决策是否检索、检索哪个领域的知识,还能评估检索结果的质量,避免无效检索。

但agentic RAG依然有一个核心问题没有解决:它的知识库依然是"只读"的。Agent可以决定什么时候读、读什么,但不能在运行时写入新知识、更新旧知识、删除过时知识。比如用户告诉AI助手"我喜欢简洁的回复,多用emoji",agentic RAG无法把这个偏好存储起来,下次用户交互时,依然无法提供个性化的回复;再比如用户修正了某个信息"我之前说的旅行时间是11月,不是10月",agentic RAG也无法更新这个记忆。

这个问题,就需要我们进入第三个阶段,Agent memory(智能体记忆)系统,实现记忆的完整CRUD能力。

四、演进第三阶段:Agent Memory,动态记忆,让AI拥有"长期记忆"

Agent Memory的核心,是让AI助手拥有和人类一样的"记忆能力":能够实时保存对话中的用户偏好、事件细节,能够检索历史会话中的相关记忆,能够修正用户提供的新信息,能够清理过期或无效的记忆。这就要求底层的存储系统,必须支持运行时的写入、读取、更新、删除(CRUD)操作,而Milvus的多Collection隔离、动态CRUD、混合索引等功能,正是实现Agent Memory的关键。

4.1 Agent Memory的核心需求:记忆分类与生命周期管理

人类的记忆是有分类的,比如我们会记住自己的长期偏好(比如喜欢的颜色、沟通方式),会记住近期的事件(比如明天要去开会),会记住一些事实性知识(比如地球是圆的)。不同类型的记忆,生命周期、重要性都不同,如果混合存储,就会出现很多问题。

比如,把用户的长期偏好和临时的对话记录混存,检索"用户沟通偏好"时,就会混杂大量无关的临时对话,检索精度下降;无法为不同类型的记忆设置差异化的过期策略,要么误删长期偏好,要么临时对话无限膨胀,拖垮系统性能。

因此,Agent Memory的核心需求,就是"记忆分类"和"生命周期管理"。我们可以按照记忆的生命周期和用途,将其分为三类,每类记忆使用独立的Collection存储,设置差异化的策略。

第一类,程序性记忆(Procedural Memory)。主要存储用户的长期偏好、行为规则、习惯等,比如"用户喜欢简洁的回复,多用emoji""用户对技术术语敏感,需要通俗解释"。这类记忆的生命周期很长,通常保留数月甚至数年,重要性很高。

第二类,情景记忆(Episodic Memory)。主要存储对话历史、用户提到的事件、临时需求等,比如"用户10月15日要去巴黎旅行,需要推荐景点""用户今天问了Milvus的安装方法"。这类记忆的生命周期中等,通常保留30-90天,超过期限后可以自动清理。

第三类,语义记忆(Semantic Memory)。主要存储事实性知识、用户提供的特定信息等,比如"埃菲尔铁塔高330米,建于1889年""用户的公司是做人工智能的"。这类记忆的生命周期较长,通常长期有效,但可以被用户修正。

通过这种分类,我们可以为每类记忆设置独立的Collection,配置不同的索引参数、过滤条件、过期策略,既保证检索精度,又能合理管理内存和存储资源。

4.2 Milvus实现Agent Memory的完整架构(附代码)

下面我们用Milvus实现一个完整的Agent Memory系统,包含记忆的分类存储、写入、检索、更新、删除(CRUD)全操作,结合具体的应用场景,让大家能够直接复用。

python 复制代码
from pymilvus import connections, FieldSchema, CollectionSchema, DataType, Collection
from datetime import datetime
import openai

# 初始化向量化函数(和之前一致)
def embed(text):
    response = openai.Embedding.create(
        input=text,
        model="text-embedding-ada-002"
    )
    return response["data"][0]["embedding"]

# 连接Milvus服务
connections.connect(host='localhost', port='19530')

# ===== 第一步:定义标准化的记忆Collection Schema =====
def create_memory_collection(name, description):
    """创建标准化的记忆Collection,适用于所有类型的记忆"""
    fields = [
        # 主键字段,自增,唯一标识每条记忆
        FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
        # 用户ID字段,用于隔离不同用户的记忆(多用户场景必备)
        FieldSchema(name="user_id", dtype=DataType.VARCHAR, max_length=64),
        # 记忆内容字段,存储具体的记忆文本
        FieldSchema(name="content", dtype=DataType.VARCHAR, max_length=5000),
        # 向量字段,存储记忆内容的向量,维度1536(对应text-embedding-ada-002)
        FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1536),
        # 重要性字段,用于过滤低重要性记忆(0-1之间,值越高越重要)
        FieldSchema(name="importance", dtype=DataType.FLOAT),
        # 创建时间字段,用于生命周期管理(时间戳格式)
        FieldSchema(name="created_at", dtype=DataType.INT64),
        # 元数据字段,存储额外信息(比如记忆类型、分类等),JSON格式
        FieldSchema(name="metadata", dtype=DataType.JSON),
    ]
    # 创建Collection Schema
    schema = CollectionSchema(fields=fields, description=description)
    # 创建Collection
    collection = Collection(name=name, schema=schema)
    # 为向量字段创建HNSW索引,提升检索速度
    collection.create_index(
        field_name="embedding",
        index_params={"index_type": "HNSW", "metric_type": "IP", "params": {"M": 16}}
    )
    # 为用户ID字段创建标量索引,加速用户隔离过滤
    collection.create_index(field_name="user_id", index_params={"index_type": "TRIE"})
    # 加载Collection到内存
    collection.load()
    return collection

# ===== 第二步:初始化三种类型的记忆Collection =====
class AgentMemorySystem:
    def __init__(self):
        # 程序性记忆:用户偏好与行为规则(长期有效)
        self.procedural_memory = create_memory_collection(
            "procedural_memory", "用户偏好与行为规则,长期有效"
        )
        # 情景记忆:对话历史与事件记录(30-90天过期)
        self.episodic_memory = create_memory_collection(
            "episodic_memory", "对话历史与事件记录,短期有效"
        )
        # 语义记忆:事实性知识(长期有效,可修正)
        self.semantic_memory = create_memory_collection(
            "semantic_memory", "事实性知识,长期有效可修正"
        )

# 初始化Agent记忆系统(全局单例,避免重复创建)
memory_system = AgentMemorySystem()

# ===== 核心操作1:记忆写入(Create),实时存储用户记忆 =====
def store_memory(memory_type, user_id, content, importance=0.5, metadata=None):
    """
    实时写入记忆到对应的Collection
    memory_type:记忆类型(procedural/episodic/semantic)
    user_id:用户ID,用于隔离不同用户的记忆
    content:记忆内容(文本)
    importance:记忆重要性(0-1,默认0.5)
    metadata:元数据(字典格式,可选)
    """
    # 根据记忆类型,选择对应的Collection
    if memory_type == "procedural":
        collection = memory_system.procedural_memory
    elif memory_type == "episodic":
        collection = memory_system.episodic_memory
    else:  # semantic
        collection = memory_system.semantic_memory
    
    # 准备数据,注意字段顺序和Schema一致
    data = [{
        "user_id": user_id,
        "content": content,
        "embedding": embed(content),
        "importance": importance,
        "created_at": int(datetime.now().timestamp()),  # 当前时间戳
        "metadata": metadata or {}  # 元数据,默认空字典
    }]
    
    # 实时插入数据
    collection.insert(data)
    # 刷新数据,确保持久化
    collection.flush()
    print(f"✅ 已存储{memory_type}记忆(用户{user_id}): {content[:50]}...")

# ===== 核心操作2:记忆检索(Read),智能检索用户相关记忆 =====
def retrieve_memories(user_id, query, memory_type="all", top_k=5, min_importance=0.3):
    """
    智能检索用户的相关记忆,支持多类型记忆检索和过滤
    user_id:用户ID,只检索该用户的记忆
    query:检索关键词(比如"巴黎旅行""沟通偏好")
    memory_type:检索的记忆类型(all/procedural/episodic/semantic)
    top_k:返回的最相似记忆数量
    min_importance:最低重要性阈值,过滤低重要性记忆
    """
    # 将检索关键词转换为向量
    query_embedding = embed(query)
    # 存储检索结果,key为记忆类型,value为检索结果
    results = {}
    # 构建过滤表达式:用户隔离 + 重要性过滤
    filter_expr = f'user_id == "{user_id}" && importance >= {min_importance}'
    # 检索参数,ef越大,精度越高,速度越慢
    search_params = {"metric_type": "IP", "params": {"ef": 100}}
    
    # 选择需要检索的Collection
    collections_to_search = []
    if memory_type in ["all", "procedural"]:
        collections_to_search.append(("procedural", memory_system.procedural_memory))
    if memory_type in ["all", "episodic"]:
        collections_to_search.append(("episodic", memory_system.episodic_memory))
    if memory_type in ["all", "semantic"]:
        collections_to_search.append(("semantic", memory_system.semantic_memory))
    
    # 遍历需要检索的Collection,执行检索
    for mem_type, collection in collections_to_search:
        search_results = collection.search(
            data=[query_embedding],
            anns_field="embedding",
            param=search_params,
            limit=top_k,
            expr=filter_expr,
            output_fields=["content", "importance", "created_at", "metadata"]
        )
        # 将检索结果存入results字典
        results[mem_type] = search_results[0]
    
    return results

# ===== 核心操作3:记忆更新与删除(Update & Delete),动态维护记忆 =====
def update_memory(collection, memory_id, new_content, new_importance=None):
    """
    更新记忆内容(Milvus 2.3+支持upsert操作,推荐使用)
    注意:生产环境中,不推荐使用"先删后插",建议使用Milvus的upsert(原子操作,避免数据丢失)
    collection:需要更新的Collection实例
    memory_id:需要更新的记忆ID(主键)
    new_content:新的记忆内容
    new_importance:新的重要性(可选,默认沿用原重要性)
    """
    # 先查询旧记忆的信息,获取原重要性(如果未指定新重要性)
    if new_importance is None:
        # 查询旧记忆
        old_memory = collection.query(
            expr=f"id == {memory_id}",
            output_fields=["importance"]
        )
        if old_memory:
            new_importance = old_memory[0]["importance"]
        else:
            print("❌ 未找到需要更新的记忆,更新失败")
            return
    
    # 方案1:先删后插(存在数据丢失风险,仅用于演示)
    # 删除旧记忆
    collection.delete(expr=f"id == {memory_id}")
    # 插入新记忆
    data = [{
        "user_id": collection.query(expr=f"id == {memory_id}", output_fields=["user_id"])[0]["user_id"],
        "content": new_content,
        "embedding": embed(new_content),
        "importance": new_importance,
        "created_at": int(datetime.now().timestamp()),
        "metadata": collection.query(expr=f"id == {memory_id}", output_fields=["metadata"])[0]["metadata"]
    }]
    collection.insert(data)
    collection.flush()
    print(f"✅ 已更新记忆(ID:{memory_id}): {new_content[:50]}...")
    
    # 方案2:使用Milvus的upsert操作(原子操作,推荐生产环境使用)
    # data = [{
    #     "id": memory_id,  # 必须指定主键ID,upsert会根据ID更新或插入
    #     "user_id": "user_123",
    #     "content": new_content,
    #     "embedding": embed(new_content),
    #     "importance": new_importance,
    #     "created_at": int(datetime.now().timestamp()),
    #     "metadata": {}
    # }]
    # collection.upsert(data)
    # collection.flush()

def forget_memory(collection, criteria):
    """
    选择性遗忘记忆(清理过期、低重要性的记忆)
    collection:需要清理的Collection实例
    criteria:清理条件(字典格式),支持以下条件:
        - older_than_days:清理多少天前的记忆
        - min_importance:清理低于该重要性的记忆
    """
    # 构建清理过滤表达式
    filter_expr = []
    # 时间过滤:清理指定天数前的记忆
    if "older_than_days" in criteria:
        # 计算截止时间戳(当前时间 - 天数*每天的秒数)
        cutoff_time = int(datetime.now().timestamp()) - criteria["older_than_days"] * 86400
        filter_expr.append(f"created_at < {cutoff_time}")
    # 重要性过滤:清理低于指定重要性的记忆
    if "min_importance" in criteria:
        filter_expr.append(f"importance < {criteria['min_importance']}")
    
    # 如果有过滤条件,执行删除操作
    if filter_expr:
        filter_expr = " && ".join(filter_expr)
        # 执行删除
        delete_count = collection.delete(expr=filter_expr)[0]
        print(f"🗑️  已清理符合条件的记忆 {delete_count} 条,过滤条件:{filter_expr}")
    else:
        print("❌ 未指定清理条件,无法执行遗忘操作")

# ===== 使用示例:CRUD全流程实操 =====
if __name__ == "__main__":
    # 模拟用户ID(实际应用中可从登录信息中获取)
    user_id = "user_123"
    
    # 场景1:写入三种类型的记忆
    print("=== 场景1:写入记忆 ===")
    # 写入程序性记忆(用户沟通偏好,重要性高)
    store_memory(
        memory_type="procedural",
        user_id=user_id,
        content="用户喜欢简洁的回复,多用emoji,避免使用复杂的技术术语",
        importance=0.8,
        metadata={"category": "communication_style"}
    )
    # 写入情景记忆(用户旅行计划,重要性高)
    store_memory(
        memory_type="episodic",
        user_id=user_id,
        content="用户提到11月15日要去巴黎旅行,需要推荐景点和美食",
        importance=0.9,
        metadata={"event_type": "travel_plan", "date": "2024-11-15"}
    )
    # 写入语义记忆(事实性知识,重要性中等)
    store_memory(
        memory_type="semantic",
        user_id=user_id,
        content="埃菲尔铁塔位于法国巴黎,高330米,建于1889年,是巴黎的标志性建筑",
        importance=0.7,
        metadata={"entity": "埃菲尔铁塔", "source": "wikipedia"}
    )
    
    # 场景2:检索用户相关记忆
    print("\n=== 场景2:检索记忆 ===")
    # 检索用户的巴黎旅行相关记忆(所有类型)
    query = "用户的巴黎旅行计划"
    memories = retrieve_memories(
        user_id=user_id,
        query=query,
        memory_type="all",
        min_importance=0.7
    )
    print(f"检索关键词:{query}")
    for mem_type, hits in memories.items():
        if hits:
            print(f"\n【{mem_type}记忆】:")
            for hit in hits[:2]:
                print(f"  相似度: {hit.score:.3f} | 内容: {hit.entity.get('content')[:60]}... | 重要性: {hit.entity.get('importance')}")
    
    # 场景3:更新记忆(用户修正旅行时间)
    print("\n=== 场景3:更新记忆 ===")
    # 假设我们查询到旅行计划的记忆ID是12345(实际应用中可通过检索获取)
    update_memory(
        collection=memory_system.episodic_memory,
        memory_id=12345,
        new_content="用户提到11月20日要去巴黎旅行,需要推荐景点和美食",
        new_importance=0.9
    )
    
    # 场景4:遗忘过期记忆(清理90天前的低重要性情景记忆)
    print("\n=== 场景4:遗忘记忆 ===")
    forget_memory(
        collection=memory_system.episodic_memory,
        criteria={"older_than_days": 90, "min_importance": 0.4}
    )

通过上面的代码,我们实现了一个完整的Agent Memory系统,具备记忆的分类存储、写入、检索、更新、删除全能力。这个系统能够实时记录用户的偏好、对话历史、事实知识,能够根据用户的查询精准检索相关记忆,能够动态更新和清理记忆,真正让AI助手拥有了"长期记忆"。

需要注意的是,生产环境中,我们需要优化几个细节:一是使用Milvus的upsert操作替代"先删后插",避免数据丢失;二是为情景记忆设置定时清理任务,自动删除过期记忆;三是优化检索参数,根据数据量调整HNSW的M、ef等参数,平衡检索精度和速度。

五、三个阶段的本质差异:从"只读"到"读写"的蜕变

回顾从传统RAG到Agent Memory的三个演进阶段,我们不难发现,核心变化体现在两个关键维度:数据更新时机和操作权限。这两个维度的变化,决定了AI助手从"问答机器"到"智能体"的蜕变。

5.1 三个阶段的核心差异总结

我们用一张表格,清晰总结三个阶段的核心差异,方便大家选型和理解。

第一阶段,传统RAG:只读的外部知识库。数据更新时机是离线阶段,运行时只支持查询操作,无法更新、删除数据。核心目标是解决LLM的知识截止日期问题,适合简单的问答场景,比如产品说明书查询、技术文档检索。技术难点在于向量检索的精度和速度,Milvus的向量索引能够很好地解决这个问题。

第二阶段,agentic RAG:按需检索的智能检索。数据更新时机依然是离线阶段,运行时依然只支持查询操作,但增加了检索决策机制。核心目标是提升检索效率,避免无效检索,适合中等复杂度的问答场景,比如多领域知识查询、个性化问答。技术难点在于Agent的检索决策逻辑,需要结合LLM实现精准的路由和质量评估。

第三阶段,Agent Memory:动态可读写的记忆系统。数据更新时机是运行时,支持完整的CRUD操作,能够实时写入、更新、删除记忆。核心目标是实现AI助手的长期记忆和个性化服务,适合复杂的交互场景,比如智能助手、私人顾问、客户服务机器人。技术难点在于记忆的分类管理、生命周期控制和工程化落地。

5.2 反直觉的发现:技术难度下降,工程难度上升

在梳理这三个阶段的演进过程中,我们会发现一个反直觉的现象:技术难度在下降,而工程难度在上升。

传统RAG的核心技术难点,是如何实现海量向量的毫秒级检索,需要深入理解向量索引算法、相似度计算等技术细节,对开发者的技术能力要求较高。而到了Agent Memory阶段,Milvus已经把CRUD、向量索引、标量过滤等核心功能都封装好了,开发者不需要再深入底层技术,只需要调用API即可实现记忆的全操作,技术难度大大降低。

但工程难度却在不断上升。传统RAG只需要做好文档切片、向量插入、检索查询即可,工程复杂度较低;而Agent Memory阶段,需要解决一系列工程决策问题:什么信息值得记?记多久?什么时候更新?什么时候忘掉?这些问题没有统一的答案,需要结合具体的应用场景来决策。

比如,用户的随口抱怨要不要记?如果记,会增加记忆量,影响检索精度;如果不记,可能会错过用户的潜在需求。再比如,用户提供的信息有误,AI助手该如何修正记忆?三个月不登录的用户,记忆要不要清理?清理多少?这些问题都需要开发者结合业务场景,制定合理的记忆策略,而这正是Agent Memory工程化落地的核心难点。

六、实操建议:如何快速落地Agent+Milvus动态记忆系统?

对于大多数开发者来说,不需要从零开始搭建整个系统,可以基于我们前面的代码,结合自身的应用场景,逐步落地Agent+Milvus动态记忆系统。这里给大家提供几个实操建议,帮助大家少走弯路。

6.1 选型建议:根据场景选择合适的阶段

如果你的应用场景是简单的问答,比如产品说明书查询、技术文档检索,不需要长期记忆,那么传统RAG就足够了,搭建简单、维护成本低。

如果你的应用场景是多领域问答,需要减少无效检索,提升响应速度,那么可以选择agentic RAG,重点优化Agent的检索决策逻辑。

如果你的应用场景是个性化交互,需要长期记忆用户的偏好、习惯、历史对话,那么必须选择Agent Memory,重点做好记忆分类和生命周期管理。

6.2 Milvus配置优化建议

Milvus的配置直接影响系统的性能,这里给大家几个关键的配置建议:

第一,索引选择。如果是中小规模数据(百万级以下),可以使用HNSW索引,兼顾检索速度和精度;如果是大规模数据(亿级以上),可以使用IVF_FLAT索引,检索速度更快,适合高并发场景。

第二,参数调整。HNSW索引的M参数建议设置为16-32,efConstruction参数建议设置为256-512,ef参数建议设置为100-200,根据数据量和检索精度需求调整。

第三,多Collection优化。为不同类型的记忆创建独立的Collection,设置差异化的索引参数和过滤条件,避免数据混存导致的检索精度下降。

第四,定时任务。为情景记忆设置定时清理任务,比如每天清理一次90天前的低重要性记忆,避免内存和存储资源被占用。

6.3 工程化落地建议

第一,多用户隔离。通过user_id字段,隔离不同用户的记忆,避免记忆混淆,保护用户隐私。

第二,记忆策略明确。提前制定记忆的分类规则、重要性评估标准、过期策略,比如哪些信息属于程序性记忆,哪些属于情景记忆,重要性如何划分,过期时间如何设置。

第三,容错处理。在记忆更新、删除时,做好容错处理,比如使用Milvus的upsert操作,避免数据丢失;在检索失败时,触发fallback策略,比如网页搜索、默认回复。

第四,监控与优化。监控系统的检索速度、精度、内存占用等指标,根据实际运行情况,优化索引参数、记忆策略,提升系统性能。

相关推荐
code_pgf2 小时前
Llama 3详解
人工智能·llama
ComputerInBook2 小时前
数字图像处理(4版)——第 3 章——(图像的)强度变换和空间滤波(Rafael C.Gonzalez&Richard E. Woods)
图像处理·人工智能·计算机视觉·强度变换和空间滤波
爱写代码的小朋友2 小时前
生成式人工智能(AIGC)在开放式教育问答系统中的知识表征与推理机制研究
人工智能·aigc
技术专家2 小时前
Stable Diffusion系列的详细讨论 / Detailed Discussion of the Stable Diffusion Series
人工智能·python·算法·推荐算法·1024程序员节
m0_488913012 小时前
万字长文带你梳理Llama开源家族:从Llama-1到Llama-3,看这一篇就够了!
人工智能·学习·机器学习·大模型·产品经理·llama·uml
helpme流水2 小时前
LLaMA Factory 从入门到精通,一篇讲完
人工智能·ai·语言模型·llama
段一凡-华北理工大学2 小时前
【大模型+知识图谱+工业智能体技术架构】~系列文章01:快速了解与初学入门!!!
人工智能·python·架构·知识图谱·工业智能体
Swift社区2 小时前
AI Governance:从 Policy Engine 到完整治理体系
人工智能·openclaw
田井中律.2 小时前
知识图谱(BILSTM+CRF项目完整实现)【第六章】
人工智能·知识图谱