惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Fortinet All Blogs
aimingoo的专栏
aimingoo的专栏
人人都是产品经理
人人都是产品经理
G
GRAHAM CLULEY
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
L
LangChain Blog
AWS News Blog
AWS News Blog
V
Vulnerabilities – Threatpost
博客园 - 司徒正美
Last Week in AI
Last Week in AI
P
Privacy International News Feed
C
CERT Recently Published Vulnerability Notes
Simon Willison's Weblog
Simon Willison's Weblog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
Schneier on Security
Y
Y Combinator Blog
月光博客
月光博客
博客园 - Franky
T
Threatpost
Security Latest
Security Latest
C
Cybersecurity and Infrastructure Security Agency CISA
博客园 - 【当耐特】
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
大猫的无限游戏
大猫的无限游戏
A
Arctic Wolf
T
The Exploit Database - CXSecurity.com
A
About on SuperTechFans
I
Intezer
C
CXSECURITY Database RSS Feed - CXSecurity.com
C
Cisco Blogs
S
Securelist
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
美团技术团队
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Microsoft Security Blog
Microsoft Security Blog
Know Your Adversary
Know Your Adversary
IT之家
IT之家
D
Docker
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
NISL@THU
NISL@THU
博客园 - 三生石上(FineUI控件)
L
Lohrmann on Cybersecurity
小众软件
小众软件
PCI Perspectives
PCI Perspectives
N
News and Events Feed by Topic
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Google DeepMind News
Google DeepMind News
爱范儿
爱范儿
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Engineering at Meta
Engineering at Meta

博客园_首页

Plist 二进制格式 Milvus 和 PGVector,哪个更好? OpenClaw 已过时?在 VS Code 中运行 Hermes Agent! 第30篇文章:一个大三计科生的自白 Manim如何在数学公式中完美显示中文? Docker 部署 RocketMQ 5 并发编程核心概念辨析 C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生 CLI 是什么?为什么大厂突然集体卷命令行? 【从0到1构建一个ClaudeAgent】协作-自主Agent UIImageView 设置图片不生效的原因排查 最小二乘问题详解20:无先验约束下的增量式SFM自由网平差 痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里借助MU实现可靠Flash IAP的方法 AI Chat 封装, SemanticKerne.AiProvider.Unified 已发布 Windows下右键编辑js文件无法打开记事本——在注册表中使用环境变量 在后台服务中使用 Scoped 服务,为什么总是报错? H200 安装驱动并使用sglang启动模型 wireshark 抓包Trap上报告警内容 我用 AI 辅助开发了一系列小工具(2):图片压缩工具 [A Primer On MC and CC] 2.1 Memory Consistency 1 - 指令重排序和 SC 模型 Oracle数据库SCN推进技术详解与实践指南 玩转控件:封装个带图片的Label控件 Claude Code 4.7 真正该升级的不是模型,而是你的工作流 前端小白一句话,AI 帮我做了个颜值拉满的桌面媒体播放器。当代码不再是门槛,一句话编程就是现实。 5. WorkBuddy: 小龙虾的灵魂三件套,让你的小龙虾不只是工具 SQLite 分片方案实战:三种分片策略的深度对比 告别简陋 UI!一款基于 Fluent Design 和基于 WinUI 的开源免费、现代化的 Avalonia UI 控件库 关于二进制排列组合枚举的总结 AI开发-python-LangGraph框架(3-27-LangGraph从零实现大模型智能决策工作流) ElasticSearch主分片和副本分片概念详解 【002】HTTPS 粗解:证书、TLS 握手与对后端配置的影响 Hermes Agent 一周暴涨五万 Star,但我劝你别急着追 明明连接的是Redis的DB0,为什么能查到DB3的数据? 【从0到1构建一个ClaudeAgent】协作-Agent团队 熟悉电子元器件之后,电子小白下一步该怎么走? MAF快速入门(23)通过C#类定义Skills .NET 高级开发 | 手写一个对象映射框架 FastAPI数据库ORM怎么选?我肝了三个Demo后,终于不再纠结了 mysqldump 参数拾遗:在遗忘与铭记之间 C# .NET 周刊|2026年3月5期 Claude code入门 - 陈彦斌 一文学习入门 ThingsBoard 开源物联网平台 GitHub 热门项目 | 2026年04月16日 如何为GIT设置全局勾子,为每次提交追加信息 Number.isFinite和isFinite与isNaN()和Number.isNaN的区别 PortSwigger SQL注入LAB2 推荐一个测试人必备的Skills,从功能到性能全搞定(附详细实操和安装下载方式) 筑基期:掌握Odoo基础核心知识点02(Odoo XML 开发方式详解) GLM模型这么火,咱们用vllm也咧一个呗! 深入理解 AbortController:从底层原理到跨语言设计哲学 字符串学习笔记 多租户系统框架的基础模块设计和分析设计 Apache SeaTunnel Zeta 为什么能做到“又快又稳”? AI开发-python-LangGraph框架(3-26-LangGraph基本概念及第一个简单样例) Vue 3 组件通信,别只会用 Props 和 Emits 了,这几个狠活儿你得看看 ElasticSearch7.X版本配置密码 用Manim实现动态交点计算--从一个动点问题说起 团结引擎+Addressable+Instant Game打包抖音小游戏 function call 实战:让 LLM 自动判断 pod 异常、调用日志工具并完成故障分析 bubseek —— 让 Agent 的足迹,变成团队的洞察 通过 C# 读取并导出 PDF 书签 如何用 GitHub Actions 实现 Steam 自动化发布 【从0到1构建一个ClaudeAgent】并发-后台任务 .NET 高级开发 | 定制 ASP.NET Core 框架 电子小白:什么是运算放大器(运放) zero2Agent:面向大厂面试的 Agent 工程教程,从概念到生产的完整学习路线 堆上的ORW HC32F460 USB CDC通信异常:非对齐访问异常排查 20260413-Hyperbridge 攻击事件:发生在默克尔山上的验证绕过 那些喊着AI 要淘汰你的人,正在靠你的焦虑赚大钱! 深度学习进阶(八)Swin Transformer 最小二乘问题详解19:带先验约束的增量式SFM优化与实现 SnapTranslate 3.0 正式发布:全局划词翻译 + 完整英语学习闭环,一站式搞定查词、记词、复习 工作的意义、工作的困难认知再思考 .NET + AI 进阶实战:基于类的技能开发 - 打造可治理的 Agent 能力模块 【从0到1构建一个ClaudeAgent】规划与协调-技能 上周热点回顾(4.6-4.12) 电子小白的工具三件套:面包板、杜邦线、万能板 单表五亿数据的查询优化 | Mysql、StarRocks 2. WorkBuddy:从“我是谁”到“帮我干活” C# 如何减少代码运行时间:7 个实战技巧 基于HelixToolkit.SharpDX 渲染3D模型 - 笺上知微 从零开始的双臂具身VLA起源及现阶段发展综述 - SkyXZ 记对 xonsh shell 的使用, 脚本编写, 迁移及调优 - pluvium27 受够了Vibe Coding的失控?换个起点,让AI事半功倍 从开始配置漏洞环境到漏洞复现流程 - 難しい 关于10年工作经验的程序员对OpenClaw的实战经验分享以及看法 - 虚无境 Any metadata 的内存布局 C# .NET 周刊|2026年3月2期 - InCerry 我帮你测过了,测试圈排名第二的 Skill 依然很牛逼 Skill Discovery | 无监督技能发现的经典工作总结 - MoonOut 上下文工程是什么?过时了么?一文讲明白! - 一枫说码 开了 TUN 模式还是直连?90% 的人都踩过这个坑 AScript扩展多种脚本语言 - rockey627 AI 学习笔记:Agent 的记忆机制 你能被装进一个文件里吗?——7 万人把同事"蒸馏"成了 AI - 我没有三颗心脏 Claude Code 通关手册(七):给 AI 装上技能包——Skills 完全指南 - 暮色之狐 在浏览器中快速编辑代码:VSCode Web 集成实践 - Newbe36524 蒸馏自己 skill?基于 Deepseek 的蒸馏器,丐版蒸馏方式,简单便捷 - To_Carpe_Diem Spring AI Aliababa和AgentScope,哪个更好? - 苏三说技术
让FastAPI Agent真正记住你:聊聊会话记忆与持久化存储的落地实践
一名程序媛呀 · 2026-05-12 · via 博客园_首页

你是不是也遇到过这种让人抓狂的场景?🎯 你正跟AI聊得火热,说了半天自己的喜好、项目的背景,结果你只是去倒杯水的功夫,回来问它一句“刚才咱们聊到哪儿了?”,它给你来一句标准的“您好,我是AI助手,有什么可以帮您?”。

得,白聊了。那一刻,真的想把键盘吃了的心都有。这不是人工智能,这是“人工智障”。我最开始自己倒腾Agent的时候,也在这个坑里趴了好久。

今天咱们就直击痛点,聊聊怎么让我们的 FastAPI Agent 真正拥有“记忆”,从一个转身就失忆的“金鱼”,变成一个能陪你深聊的靠谱伙伴。

🧠 记忆也分“快慢”:短期会话 vs 长期档案

在动手写代码前,咱们得先把概念掰扯清楚。Agent的记忆,跟咱们人类的记忆其实挺像,大致分两种:

🟡 短期记忆:就是当前的对话上下文。
Agent得知道你刚才说了啥,才好接着你的话茬往下聊。这玩意儿要求的就是个“快”字,像闪电一样。

🔵 长期记忆:好比你的用户档案。
你叫啥,你喜欢用React还是Vue,你对猫毛过敏……这些信息得持久化地存下来,下次你登录,哪怕隔了一个月,Agent也能像老朋友一样记得你。

🔑 第一步:给每场对话上个“会话锁”

要实现记忆,最关键的第一块积木就是会话隔离。不能让张三聊的内容,跑到李四的对话里去,那不乱套了嘛。

我的做法很简单,就是生成一个唯一的 session_id 。在FastAPI里,这活儿可以交给一个轻量级的依赖项来做,干净又利落。

你可能会问:“用Cookie或Header带过来不就行了?”
对,逻辑是这样,而且千万别偷懒,用那种自增的简单ID,太容易被遍历攻击了。务必用 uuid4 这种几乎不可能碰撞的字符串。

from fastapi import Header, HTTPException
from uuid import UUID, uuid4

async def get_session_id(x_session_id: str = Header(None, alias="X-Session-ID")):
    ''' 从 Header 里提取并校验 session_id '''
    logger.debug(f"客户端 session_id: {x_session_id}")
    # 如果请求里带了有效的session_id,直接返回
    if x_session_id:
        try:
            UUID(x_session_id)
            return x_session_id, False  # False 表示不是新会话
        except ValueError:
            raise HTTPException(400, "会话ID格式不太对哦,得是标准的UUID。")

    # 如果没带?生成一个新的返回
    return str(uuid4()), True  # True 表示是新会话

⚡️ 第二步:选好“记事本”,快慢分离

好,会话锁有了,接着选“记事本”。
工具的选择,好比选螺丝刀,不是最贵的就好,而是最顺手的。

🔥 短期记忆:Redis 异步存储

对于秒级读写、需要频繁更新的对话历史,我首选Redis。
咱们用异步Redis客户端,跟FastAPI的异步特性简直是天作之合。

每次对话来,就把新的消息追加到历史列表里,并设置一个合理的过期时间(比如30分钟),不用操心内存溢出。

🗄️ 长期记忆:PostgreSQL 持久存档

当一轮对话结束,或者中途提取到了关键的用户信息(比如“对了,我对猫毛过敏”),就需要异步地写到 PostgreSQL 里存起来。
这是咱们的用户档案库,讲究的是可靠和结构清晰。

根据以往的经验,这里有个容易翻车的点
别在请求的主流程里直接干这活儿,会让用户觉得你的Agent反应迟钝。推荐用 BackgroundTasks 或一个外部队列来异步处理,悄无声息地完成存档。

💉 第三步:FastAPI 依赖注入,全程优雅“记忆”

接下来重点来了!怎么让我们的服务端处理每一步逻辑时,都能轻松拿到属于“这个用户、这个会话”的记忆呢?

用FastAPI的依赖项注入。我们可以设计一个函数,它依赖刚才的 session_id 和咱们初始化的 Redis 连接,自动读取或新生成一个会话对象。

async def get_session_context(
    session_data: tuple = Depends(get_session_id),
    redis: Redis = Depends(get_redis),
):
    ''' 核心依赖:给你一个会“记事儿”的上下文对象 '''
    # 拼 Redis 键,每个会话一个列表
    session_id, is_new = session_data
    key = f"chat:{session_id}"

    if is_new:
        history = []
    else:
        raw_messages = await redis.lrange(key, 0, -1)
        # 反序列化历史消息
        history = [json.loads(msg) for msg in raw_messages]
        
        # # 没捞到?去数据库里翻翻旧账(伪代码展示核心思路)
        # if not history:
        #    user_profile = await load_from_pg(session_id) 
        #    return {"history": [], "profile": user_profile}

    # 把需要用到的东东全部塞进一个字典返回
    return {
        "is_new_session": is_new,  # 把是否新消息会话标记传出去用于响应头
        "session_id": session_id,
        "history": history,
        "redis": redis,
    }

🎬 实战秀:跨轮次对话,记忆不丢失

咱们来模拟一下完整流程,你就知道这事儿有多酷了。

1️⃣ 第一轮对话
用户带着 session_id 来了,说“嘿,我想吃意大利菜。”
Agent通过依赖注入拿到空白会话,把这句话存入Redis历史,然后推荐了一家意面馆。同时,后台任务默默记下:“该用户偏好西餐”。

2️⃣ 第二轮对话(跨轮次)
过了一会,同一个 session_id 又来了,问“刚才那家店在哪儿?”
Agent再次通过依赖项,从Redis秒读出历史:“你想吃意大利菜”,于是准确地给出地址。

3️⃣ 新的会话(长期记忆验证)
几天后,用户换了个设备,带着新的 session_id 登录。问:“随便推荐点吃的。”
Agent一看,Redis里历史是空的,但依赖项从PostgreSQL里加载了用户画像,贴心地问:“之前看你喜欢西餐,要不要试试新开的牛排馆?”

原理理解了,真正写接口时才是一波三折呀,这里给出完整代码,防止一个解析不对,直接给个500错误,很多踩坑点直接在注释里面说明了:

@router.post("/chat")
async def chat(
    req: ChatRequest,
    ctx: dict = Depends(get_session_context),  # 自动注入当前会话上下文
    config: Settings = Depends(get_settings)
):  
    ''' 核心接口:聊天,自动读写记忆 '''

    is_new = ctx["is_new_session"]
    session_id = ctx["session_id"]
    redis: Redis = ctx["redis"]
    history: list = ctx["history"]   # 这个消息列表就是短期记忆
    logger.debug(f"Redis 缓存历史消息:\n{history}")

    # 1) 把用户刚说的话塞进记忆里
    user_msg = {"role": "user", "content": req.message}
    history.append(user_msg)

    # 2) 调用大模型(这里直接使用上一篇里定义的模型调用函数,带多轮历史会话消息功能)
    assistant_reply = await call_llm(
        messages=history, 
        system_prompt='你是一个能根据多轮对话综合思考后,给出贴心建议的小助手', 
        config=config
    )
    logger.debug(f"模型回复信息:\n{assistant_reply}")
    history.append(assistant_reply) # 这里一定要注意拼接时的返回格式,如果有误,下轮会话可能失败!

    # 3) 把新产生的两条消息异步写入 Redis,并刷新过期时间
    key = f"chat:{session_id}"
    # rpush 直接追加到列表尾部,因为 history 是从 Redis 里读出来的,再追加不会重复
    await redis.rpush(key, json.dumps(user_msg, ensure_ascii=False))
    await redis.rpush(key, json.dumps(assistant_reply, ensure_ascii=False))
    await redis.expire(key, 1800)  # 30分钟保鲜期,自动清理

    # 4) 可在这通过 BackgroundTasks 把用户偏好异步写进 PostgreSQL(略)

    # 构建响应
    content = {
        "reply": assistant_reply,
        "session_id": session_id,
        "memory_rounds": len(history),
    }
    if is_new:
        # 第一次会话,告诉客户端,以后带这个ID来找我
        response = JSONResponse(content=content)
        response.headers["X-Session-ID"] = session_id
        return response
    else:
        return content

🛡️ 最后啰嗦几句,都是血泪史

🟠 Redis连接池耗尽:
一定、一定、一定要用单例模式管理连接池。我早期图省事,在函数里每次读写都新建连接,流量一大,端口直接耗光。

🟠 记忆蒸馏:
别傻乎乎地把几十轮对话都塞给LLM,Token算力都是要钱的呀!只取最近N轮或做个摘要,这个优化能省下不少预算。

用户画像的长时存储也是同样的道理,总不能把所有的会话历史都存储吧,简单点可以让LLM帮我们把多轮对话的内容提取用户特征或偏好:

async def extract_key_info(messages: list[dict]) -> list[str]:
    # 取最后几轮对话拼接成 prompt
    prompt = "从以下对话中提炼用户特征或偏好,只输出JSON列表,不要其他文字:..."
    # 调用 LLM
    response = await call_llm(prompt)
    # 解析返回的列表,如 ["喜欢 Python", "对猫毛过敏", "想去意大利旅游"]
    return json.loads(response["message"]["content"])

🟠 数据清理:
Redis里的历史是临时的,但数据库里的长期档案得有个软删除或遗忘机制,万一用户哪天行使“被遗忘权”呢。


好啦,今天就先唠到这儿。让Agent拥有记忆,绝对是提升用户体验最实在的一步。从毫无感情的对话工具,到能记住你爱喝咖啡不放糖的伙伴,秘诀就在这几百行代码里。

这篇文章里藏着不少我运行翻车才换来的经验,别等下次碰到“失忆”的Agent才后悔没细看。赶紧的,收藏起来🌟,或者转给你身边同样在跟Agent斗智斗勇的战友,咱们下期见!