Agent学习记录-6

阅读学习时间:约5分钟

笔记

Agent的学习记录--6

来源Github的学习指南Agent Learning Hub的学习:

Stage 2——记忆

Agent的记忆主要分为三个方面:

  • 短期记忆(Short-term Memory):即上下文记忆:为当前请求送入 Prompt 的临时消息窗口,受上下文长度与成本制约
  • 会话记忆(Session Memory):记录同一会话 (session_id) 内的多轮对话历史,按会话隔离进行记忆
  • 长期记忆(Long-term Memory): 跨会话沉淀的用户偏好、个性化画像与核心事实
import os
import sys
import json
from typing import List, Dict, Any, Optional

from config import get_llm
from langchain_core.messages import (
    BaseMessage,
    SystemMessage,
    HumanMessage,
    AIMessage,
    trim_messages
)
from langchain_core.chat_history import BaseChatMessageHistory, InMemoryChatMessageHistory

短期记忆管理

对于短期记忆,当消息列表过长时,直接全部送入模型不仅昂贵,还会导致模型注意力分散

因此我们需要动态修剪短期上下文:

  • 必须始终保留最开头的 SystemMessage
  • 保留最近的 max_messages 条历史对话消息(滑动窗口)
  • 修剪丢弃过早的消息
def trim_context_window(messages: List[BaseMessage], max_messages: int = 4) -> List[BaseMessage]:
    if len(messages) <= max_messages + 1:
        return messages
    if messages and isinstance(messages[0], SystemMessage):
        system_anchor = messages[0]
        last_anchor = messages[1:][-max_messages:]
        return [system_anchor] + last_anchor
    else:
        return messages[-max_messages:]

会话记忆管理

主要原则为负责按 session_id 隔离管理不同会话的多轮对话历史。

class SessionMemoryManager:
    def __init__(self):
        # 内存存储映射表:session_id -> InMemoryChatMessageHistory
        self._session_store: Dict[str, InMemoryChatMessageHistory] = {}

    def get_history(self, session_id: str) -> InMemoryChatMessageHistory:
        # 如果是新会话,新建一个历史记录本;如果是老会话,取出历史记录本
        if not session_id in self._session_store:
            self._session_store[session_id] = InMemoryChatMessageHistory()
        return self._session_store[session_id]

    def add_user_message(self, session_id: str, content: str):
        # 追加一条用户发言到该 session 的历史记录本中
        history = self.get_history(session_id)
        history.add_user_message(content)

    def add_ai_message(self, session_id: str, content: str):
        # 追加一条 AI 回复到该 session 的历史记录本中
        history = self.get_history(session_id)
        history.add_ai_message(content)

长期记忆管理

长期记忆,其跨越所有 session,提炼出少量核心标签(如姓名、偏好、习惯)。管理跨越所有会话的永久用户画像与核心事实 (Profile & Facts)。以 JSON 文件形式持久化存储在磁盘上。

LONG_TERM_MEMORY_FILE = os.path.join(os.path.dirname(__file__), "sample_data", "user_long_term_memory.json")

Class LongTermMemoryStore:
    def __init__(self, filepath: str = LONG_TERM_MEMORY_FILE):
        self.filepath = filepath
        self._ensure_file()
        
    def _ensure_file(self):
        os.makedirs(os.path.dirname(self.filepath), exist_ok = True)
        if not os.path.exist(self.filepath):
            with open(self.filepath, "w", encoding="utf-8") as f:
                json.dump({"user_profile": {}, "learned_fact": []}, f, ensure_ascii=False, indent=2)
                
    def read_memory(self) -> Dict[str, Any]:
        try:
            with open(self.filepath, "r", encoding="utf-8") as f:
                return json.load(f)
        except Exception:
            return {"user_profile": {}, "learned_fact": []}
        
    def update_profile(self, key: str, value: Any):
        # 更新用户画像
        data = self.read_memory()
        data["user_profile"][key] = value
        with open(self.filepath, "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
    
    def add_fact(self, fact:str):
        # 更新用户事实
        data = self.read_memory()
        if fact not in data["learned_fact"]:
            data["learn_fact"].append(fact)
            with open(self.filepath, "w", encoding="utf-8") as f:
                json.dump(data, f, ensure_ascii=False, indent=2)
            

然后我们通过LLM为我们创建画像:

def extract_and_save_long_term_facts(dialogue_history: List[BaseMessage], memory_store: LongTermMemoryStore):
    """
    使用 LLM 分析对话内容,提取出值得永久记忆的用户个性化信息或事实(如研究方向、常用编程语言、喜好)。
    """
    llm = get_llm(temperature=0.0)
    
    # 拼接对话历史文本
    conversation_text = "\n".join(f"{msg.type}: {msg.content}" for msg in dialogue_history if msg.type in ("human", "ai"))
    
    prompt = (
        "你是一个行为学家,擅长从用户对话里提取出关于用户的长期偏好与事实画像。\n"
        "请严格以纯 JSON 格式输出,其格式如下:\n"
        "{\n"
        '  "user_profile": {\n'
        '    "research_interest": "用户研究方向",\n'
        '    "preferred_language": "偏好使用的框架或语言"\n'
        "  },\n"
        '  "learned_facts": ["值得长期记住的事实1", "事实2"]\n'
        "}\n\n"
        f"【对话记录】:\n{conversation_text}"
    )
    response = llm.invoke([HumanMessage(content = prompt)])
    content = response.content.strip()

    # 清洗可能存在的 ```json 代码块包裹
    if content.startswith("```"):
        lines = content.split("\n")
        if lines[0].startswith("```"):
            lines = lines[1:]
        if lines and lines[-1].strip() == "```":
            lines = lines[:-1]
        content = "\n".join(lines).strip()

    try:
        content_json = json.loads(content)
        profile = content_json.get("user_profile", {})
        fact = content_json.get("learned_facts", [])
        for k, v in profile.items():
            if k and v:
                memory_store.update_profile(k, v)
        for f in fact:
            if f:
                memory_store.add_fact(f)
    except Exception as e:
        print(f"解析提取结果异常: {e}, 原文: {content}")

这样子我们就梳理完了这三种记忆

现在我们将这三种记忆打包:

综合

综合应用三层记忆架构进行对话:

  1. 长期记忆:从磁盘读取长期画像,注入 SystemMessage
  2. 会话记忆:加载该 session_id 下的历史消息
  3. 短期上下文:使用 trim_context_window 裁剪超长历史,避免超限
def chat_with_full_memory(session_id: str, user_input: str, session_manager: SessionMemoryManager, memory_store: LongTermMemoryStore) -> str:
    llm = get_llm(temperature=0.0)
    
    # 取出长期记忆组装SystemMessage
    long_term_memory = memory_store.read_memory()
    profile = long_term_memory.get("user_profile", {})
    facts = long_term_memory.get("learned_facts", [])
    
    system_prompt = (
        "你是一名个人专属科研助手。\n"
        f"【已知用户长期偏好与画像】: {json.dumps(profile, ensure_ascii=False)}\n"
        f"【已知用户长期事实库】: {json.dumps(facts, ensure_ascii=False)}\n"
        "请根据用户偏好量身定制回答!"
    )
    
    # 获取之前对话记录
    history = session_manager.get_history(session_id)
    
    # 组装短期上下文并进行裁切
    raw_messages = [SystemMessage(content = system_prompt)] + history.messages + [HumanMessage(content = user_input)]
    active_messages = trim_context_window(raw_messages)
    response = llm.invoke(active_messages)
    
    history.add_user_message(user_input)
    history.add_ai_messaga(response.content)
    
    return response.content