2. 实现Agent的记忆模块

任务目标

为Agent实现双层记忆系统:短期记忆(当前会话上下文)与长期记忆(跨会话持久化),使Agent在生成回复时能够检索并利用历史对话信息。

核心实现

import json
import os
from datetime import datetime
from typing import List, Dict, Optional

class ShortTermMemory:
    """短期记忆:维护当前会话的对话历史,有最大长度限制"""
    def __init__(self, max_turns: int = 10):
        self.max_turns = max_turns
        self.history: List[Dict] = []
    
    def add(self, role: str, content: str) -> None:
        self.history.append({"role": role, "content": content, "time": datetime.now().isoformat()})
        if len(self.history) > self.max_turns * 2:
            self.history = self.history[-(self.max_turns * 2):]
    
    def get_recent(self, turns: Optional[int] = None) -> List[Dict]:
        if turns is None:
            return self.history
        return self.history[-(turns * 2):]
    
    def clear(self) -> None:
        self.history = []

class LongTermMemory:
    """长期记忆:基于关键词的持久化记忆存储,跨会话保留"""
    def __init__(self, storage_path: str = "agent_memory.json"):
        self.storage_path = storage_path
        self.memories: Dict[str, List[Dict]] = self._load()
    
    def _load(self) -> Dict[str, List[Dict]]:
        if os.path.exists(self.storage_path):
            with open(self.storage_path, "r", encoding="utf-8") as f:
                return json.load(f)
        return {}
    
    def _save(self) -> None:
        with open(self.storage_path, "w", encoding="utf-8") as f:
            json.dump(self.memories, f, ensure_ascii=False, indent=2)
    
    def _extract_keywords(self, text: str) -> List[str]:
        """简单关键词提取:按长度过滤的词语(实际可替换为更智能的抽取)"""
        stopwords = {"的", "了", "是", "我", "你", "他", "她", "它", "们", "在", "有", "和", "就", "不", "人", "都", "一", "一个", "上", "也", "很", "到", "说", "要", "去", "会", "着", "没有", "看", "好", "自己"}
        words = [w for w in text.replace(",", " ").replace("。", " ").replace("?", " ").replace("!", " ").split() if len(w) > 1 and w not in stopwords]
        return list(set(words))[:5]
    
    def add(self, content: str, metadata: Dict = None) -> None:
        """将重要信息存入长期记忆,按关键词索引"""
        keywords = self._extract_keywords(content)
        entry = {
            "content": content,
            "time": datetime.now().isoformat(),
            "metadata": metadata or {}
        }
        for kw in keywords:
            if kw not in self.memories:
                self.memories[kw] = []
            self.memories[kw].append(entry)
        self._save()
    
    def recall(self, query: str, top_k: int = 3) -> List[Dict]:
        """根据查询召回相关长期记忆"""
        keywords = self._extract_keywords(query)
        results = []
        seen = set()
        for kw in keywords:
            for mem in self.memories.get(kw, []):
                mem_id = id(mem)
                if mem_id not in seen:
                    seen.add(mem_id)
                    results.append(mem)
        # 按时间排序,返回最近top_k条
        results.sort(key=lambda x: x["time"], reverse=True)
        return results[:top_k]

class AgentWithMemory:
    """带记忆能力的Agent:结合短期与长期记忆生成回复"""
    def __init__(self, name: str = "Agent"):
        self.name = name
        self.short_term = ShortTermMemory(max_turns=10)
        self.long_term = LongTermMemory()
    
    def _build_prompt(self, user_input: str) -> str:
        """构建包含记忆的提示词"""
        # 1. 召回相关长期记忆
        relevant_memories = self.long_term.recall(user_input)
        memory_context = ""
        if relevant_memories:
            memory_context = "【相关历史记忆】\n" + "\n".join([f"- {m['content']}" for m in relevant_memories]) + "\n\n"
        
        # 2. 获取短期对话历史
        history = self.short_term.get_recent()
        history_text = ""
        if history:
            history_text = "【当前对话历史】\n" + "\n".join([f"{h['role']}: {h['content']}" for h in history]) + "\n\n"
        
        # 3. 组装完整提示
        prompt = f"{memory_context}{history_text}【用户当前输入】\n{user_input}\n\n请基于以上记忆和对话历史,以{self.name}的身份回复用户。"
        return prompt
    
    def respond(self, user_input: str) -> str:
        """处理用户输入并生成回复"""
        # 将用户输入加入短期记忆
        self.short_term.add("user", user_input)
        
        # 构建带记忆的提示词
        prompt = self._build_prompt(user_input)
        
        # 模拟LLM调用(实际项目中替换为真实模型API)
        # 此处演示记忆如何影响回复——根据记忆内容构造回复
        relevant = self.long_term.recall(user_input)
        response = f"[{self.name}回复] 我记住了您刚才说的内容。"
        if relevant:
            response += f" 根据我的长期记忆,您之前提到过:{relevant[0]['content']}"
        if len(self.short_term.history) > 2:
            response += f" 这是我们对话的第{len(self.short_term.history)//2}轮。"
        
        # 将回复加入短期记忆
        self.short_term.add("assistant", response)
        return response
    
    def remember_important(self, content: str) -> None:
        """主动将重要信息存入长期记忆"""
        self.long_term.add(content, {"source": "user_stated"})

# ===== 使用示例 =====
if __name__ == "__main__":
    agent = AgentWithMemory("记忆助手")
    
    # 第一轮对话:用户告知偏好
    print("用户:我喜欢喝美式咖啡,不加糖")
    agent.remember_important("用户喜欢喝美式咖啡,不加糖")
    r1 = agent.respond("你好,请记住我的咖啡偏好")
    print(r1)
    
    # 第二轮对话:测试短期记忆
    print("\n用户:我刚才说了什么?")
    r2 = agent.respond("我刚才说了什么?")
    print(r2)
    
    # 第三轮对话:测试长期记忆召回
    print("\n用户:帮我推荐一杯咖啡")
    r3 = agent.respond("帮我推荐一杯咖啡")
    print(r3)
    
    # 验证长期记忆持久化
    print("\n=== 验证 ===")
    print(f"长期记忆文件已保存: {os.path.exists('agent_memory.json')}")
    with open("agent_memory.json", "r", encoding="utf-8") as f:
        data = json.load(f)
    print(f"记忆关键词: {list(data.keys())}")
    print(f"记忆条数: {sum(len(v) for v in data.values())}")

运行与验证

运行方式:

python agent_memory.py

验证路径:

  1. 程序执行后,观察三轮对话输出:第一轮建立长期记忆;第二轮通过短期记忆回答“我刚才说了什么”;第三轮通过长期记忆召回咖啡偏好。
  2. 检查当前目录生成 agent_memory.json 文件,内容包含关键词“咖啡”“美式”等及对应记忆条目。
  3. 重新运行程序(新会话),输入“帮我推荐一杯咖啡”,若仍能召回之前的偏好,说明长期记忆跨会话生效。

通过条件:短期记忆能正确引用当前会话前文;长期记忆在程序重启后仍可召回;记忆文件结构完整且可读。

关键说明

  • 短期记忆采用滑动窗口(默认保留最近10轮),超出后自动丢弃最早记录,模拟有限上下文窗口。
  • 长期记忆使用关键词索引的JSON文件持久化,关键词提取为简化实现,实际生产可替换为嵌入向量检索。
  • 示例中respond()方法用规则模拟LLM回复以演示记忆机制,接入真实模型时只需将_build_prompt()的输出传给模型API即可。