Day 4:让 Agent 记住你——短期记忆实现

Day 4:让 Agent 记住你------短期记忆实现

《7天从零手搓 AI Agent》第4篇 · 今日成果:Agent 能记住对话历史,支持真正的多轮对话

大家好,欢迎来到小撒的私房菜,我是小撒。

做了3天,你的 Agent 现在面临一个很尴尬的问题。

你告诉它:"我叫小明,我想了解 Python。"

然后你问:"刚才我说我叫什么来着?"

它不知道。

因为它天生失忆


为什么 AI 天生失忆

每次你调用 OpenAI API,对 AI 来说都是一个全新的请求

上一次调用发生了什么?它不知道。

API 本身是无状态的,就像 HTTP 请求一样,每一次都是独立的。

那 ChatGPT 是怎么做到记住你之前说的话的?

它把所有历史消息都带进了每次 API 请求里。

发给 API 的不只是你这次说的话,而是:

css 复制代码
[你第1次说的话]
[AI 第1次回答]
[你第2次说的话]
[AI 第2次回答]
[你第3次说的话]  ← 当前问题

把这一长串消息都塞进请求,AI 就能"看到"之前发生了什么,就好像它记得一样。

记忆的本质:把历史消息带进每次请求。


短期记忆实现

新建 memory/short_term.py

python 复制代码
# memory/short_term.py
from dataclasses import dataclass, field
from typing import Literal

MessageRole = Literal["system", "user", "assistant"]

@dataclass
class Message:
    role: MessageRole
    content: str


@dataclass
class ShortTermMemory:
    """
    保存对话历史,控制上限。
    system 消息永远保留;超出上限时删除最旧的非 system 消息。
    """
    max_messages: int = 20
    _messages: list[Message] = field(default_factory=list)

    def add(self, role: MessageRole, content: str) -> None:
        self._messages.append(Message(role=role, content=content))
        self._trim()

    def _trim(self) -> None:
        """超过上限时,删除最旧的非 system 消息。"""
        non_system = [m for m in self._messages if m.role != "system"]
        while len(non_system) > self.max_messages:
            for i, msg in enumerate(self._messages):
                if msg.role != "system":
                    self._messages.pop(i)
                    break
            non_system = [m for m in self._messages if m.role != "system"]

    def to_api_format(self) -> list[dict]:
        """转成 OpenAI API 需要的格式。"""
        return [{"role": m.role, "content": m.content} for m in self._messages]

    def clear_non_system(self) -> None:
        """清除对话历史(保留系统提示)。"""
        self._messages = [m for m in self._messages if m.role == "system"]

    def count(self) -> int:
        return len([m for m in self._messages if m.role != "system"])

把记忆接入 Agent

agent.py 里的函数改成一个类:

python 复制代码
# agent.py
import json
import re
from llm import chat
from tool_registry import get_tools_description, execute_tool
from memory.short_term import ShortTermMemory

SYSTEM_PROMPT_TEMPLATE = """你是一个智能助手,可以使用工具,也能记住对话历史。

{tools_description}

每次回复必须是 JSON,格式二选一:

使用工具:
{{"action": "use_tool", "tool": "工具名", "params": {{"参数名": "参数值"}}}}

直接回答:
{{"action": "answer", "content": "你的回答"}}

只返回 JSON。"""


def safe_parse_json(text: str) -> dict:
    try:
        return json.loads(text)
    except json.JSONDecodeError:
        pass
    match = re.search(r'\{.*\}', text, re.DOTALL)
    if match:
        try:
            return json.loads(match.group())
        except json.JSONDecodeError:
            pass
    return {"action": "answer", "content": text}


class Agent:
    def __init__(self) -> None:
        self.memory = ShortTermMemory(max_messages=20)
        # 系统提示只加一次,永远保留
        self.memory.add("system", SYSTEM_PROMPT_TEMPLATE.format(
            tools_description=get_tools_description()
        ))

    def chat(self, user_input: str) -> str:
        # 把用户消息存入记忆
        self.memory.add("user", user_input)

        # 带上完整历史调用 AI
        ai_response = chat(self.memory.to_api_format())
        print(f"[AI 决策]: {ai_response}")

        # 把 AI 回复也存入记忆
        self.memory.add("assistant", ai_response)

        decision = safe_parse_json(ai_response)

        if decision["action"] == "answer":
            return decision.get("content", ai_response)

        if decision["action"] == "use_tool":
            tool_name = decision.get("tool", "")
            params    = decision.get("params", {})
            print(f"[执行工具]: {tool_name},参数:{params}")
            tool_result = execute_tool(tool_name, params)
            print(f"[工具结果]: {tool_result}")

            # 把工具结果存入记忆,让 AI 基于结果给出最终答案
            self.memory.add("user",
                f"[工具 {tool_name} 返回结果]:{tool_result}\n"
                f"请基于此结果,用自然语言回答用户的问题。"
            )
            final = chat(self.memory.to_api_format())
            final_parsed = safe_parse_json(final)
            final_text = final_parsed.get("content", final)
            self.memory.add("assistant", final_text)
            return final_text

        return f"(未知 action:{decision.get('action')})"

    def clear(self) -> None:
        self.memory.clear_non_system()

为什么要改成类?

因为现在 Agent 有了状态(记忆)。有状态的东西,用类来管理比用函数更自然。self.memory 跟着实例存在,跨多次 chat() 调用保持。


更新 main.py

python 复制代码
# main.py
from agent import Agent

def main() -> None:
    print("=== Day 4 Agent:有记忆了!===")
    print("命令:/clear 清除记忆 | quit 退出\n")

    agent = Agent()

    while True:
        user_input = input("你:").strip()

        if not user_input:
            continue
        if user_input.lower() in ("quit", "exit"):
            print("再见!")
            break
        if user_input.lower() == "/clear":
            agent.clear()
            print("记忆已清除,开始新对话。\n")
            continue

        result = agent.chat(user_input)
        print(f"Agent:{result}")
        print(f"(当前记忆:{agent.memory.count()} 条消息)\n")

if __name__ == "__main__":
    main()

效果演示

arduino 复制代码
你:我叫小明,我在学 Python
Agent:你好小明!Python 是一门很棒的编程语言...
(当前记忆:2 条消息)

你:刚才我说我叫什么名字?
Agent:你说你叫小明,并且正在学习 Python。
(当前记忆:4 条消息)

你:/clear
记忆已清除,开始新对话。

你:我叫什么名字?
Agent:您还没有告诉我您的名字。
(当前记忆:2 条消息)

为什么需要 max_messages 上限

每条消息都占用 token(AI 的"计费单位")。

对话越长,每次请求携带的 token 越多,两个后果:

  1. 费用增加(按 token 计费)
  2. 超过模型上限会报错(gpt-4o-mini 上限128K token,很难超,但别的模型可能更小)

max_messages=20 是个合理默认值。超过就自动删最旧的消息。

System 消息为什么永远保留?

System 消息里有工具说明和行为规范。如果被删掉,AI 就不知道有哪些工具可以用了。所以删消息时跳过 system。


一个常见误解

有人以为"记忆"是 AI 把对话内容"学进去了"。

不是的。

每次对话结束,这些消息就消失了。下次新建一个 Agent() 对象,记忆是空的。

这叫短期记忆(Session Memory)------只在当前会话里有效。

如果你想让记忆跨会话持久化(比如第二天还记得你上次说的话),需要把消息存到文件或数据库,这是长期记忆,是更进阶的话题。


今天的项目结构

bash 复制代码
my_agent/
├── .env
├── llm.py
├── agent.py           # 重构成 Agent 类,集成记忆
├── tool_registry.py
├── memory/
│   ├── __init__.py
│   └── short_term.py  # 新增:短期记忆
├── tools/
│   ├── __init__.py
│   ├── search.py
│   ├── weather.py
│   ├── calculator.py
│   └── datetime_tool.py
└── main.py

小结

今天做了一件看起来简单但非常重要的事:

让 AI "记住"对话历史,本质是把历史消息带进每次 API 请求。

这是所有对话型 AI 应用的基础机制,包括 ChatGPT 本身。

ShortTermMemory 这个类,你在 LangChain 里会看到对应的 ConversationBufferMemory,在 OpenAI Assistants API 里会看到 thread------都是同一个思路,只是封装程度不同。


明天,Day 5:《Agent Loop------整个系列里最关键的一天》

Agent 真正的力量,在于它能反复思考、反复使用工具,直到任务完成。

这就是 ReAct 模式。明天讲。


代码在 GitHub,文末有链接。

如果本教程对你有所帮助,留下一个免费的三连吧,这是对我最大的鼓励♥️!

相关推荐
古希腊掌管代码的神THU40 分钟前
【清华代码熊】MTP (Multi-Token Prediction)源码详解
人工智能·深度学习·自然语言处理
极客老王说Agent41 分钟前
实在Agent委外加工智能化管控方案与落地案例:从数字劳动力到离散制造全链路闭环
人工智能·ai·制造
Elastic 中国社区官方博客41 分钟前
jina-embeddings-v5-omni:用于文本、图像、音频和视频的 embeddings
大数据·人工智能·elasticsearch·搜索引擎·ai·音视频·jina
木雷坞43 分钟前
Jellyfin 媒体库为空:NAS Docker Compose 挂载路径排查
后端
郑寿昌44 分钟前
AI时代动画游戏新职业方向
人工智能·游戏
一次旅行44 分钟前
今日AI 新闻简报
人工智能·ai编程·ai写作
小仙女的小稀罕44 分钟前
外教课转写工具选择建议 | 实测筛选高口碑实用方案
大数据·人工智能·学习·自然语言处理·语音识别
啦啦啦_99991 小时前
1. 深度学习概述
人工智能·深度学习
入门工作者1 小时前
opencv 胶路检测实战
人工智能·opencv·计算机视觉