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

推荐订阅源

Help Net Security
Help Net Security
S
Schneier on Security
Security Latest
Security Latest
C
Cyber Attacks, Cyber Crime and Cyber Security
博客园_首页
K
Kaspersky official blog
B
Blog
雷峰网
雷峰网
MyScale Blog
MyScale Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
人人都是产品经理
人人都是产品经理
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
T
Threatpost
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Latest news
Latest news
J
Java Code Geeks
W
WeLiveSecurity
GbyAI
GbyAI
V
Vulnerabilities – Threatpost
S
Secure Thoughts
IT之家
IT之家
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 叶小钗
Engineering at Meta
Engineering at Meta
S
Security @ Cisco Blogs
罗磊的独立博客
M
MIT News - Artificial intelligence
Blog — PlanetScale
Blog — PlanetScale
Stack Overflow Blog
Stack Overflow Blog
月光博客
月光博客
P
Proofpoint News Feed
博客园 - 聂微东
云风的 BLOG
云风的 BLOG
N
Netflix TechBlog - Medium
D
DataBreaches.Net
S
SegmentFault 最新的问题
V
Visual Studio Blog
V
V2EX
The Register - Security
The Register - Security
N
News | PayPal Newsroom
aimingoo的专栏
aimingoo的专栏
C
Cybersecurity and Infrastructure Security Agency CISA
N
News and Events Feed by Topic
Hacker News: Ask HN
Hacker News: Ask HN
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Google DeepMind News
Google DeepMind News
Webroot Blog
Webroot Blog
NISL@THU
NISL@THU
D
Darknet – Hacking Tools, Hacker News & Cyber Security

博客园 - 一名程序媛呀

Anki插件开发必知必会:钩子函数与右键菜单定制 httpx 传参总报错?这次把 GET、POST、文件上传到响应处理的坑给你一次填平 从卡顿到丝滑:FastAPI 调用外部 API 的正确姿势(httpx 实战) 还在 XHR、Fetch 和 Axios 之间纠结?我踩过的坑,希望你一个都不用碰到 你的REST接口还在“过度投喂”数据吗?——FastAPI + GraphQL实战避坑指南 Uvicorn、Gunicorn 傻傻分不清?FastAPI 生产部署避坑指南 Termux里的二进制和脚本,到底怎么运行才不踩坑?Termux-service 保活妙招! 刚部署的 LibreTranslate 频频翻车?我掏出了 20 年前的 StarDict 词典,用 FastAPI 搭了个本地词典翻译 API 别再用网页翻译看源码了!你的私人翻译神器LibreTranslate,部署避坑指南来了 掏出手机就能搭个 WebDAV 同步服务器?这操作有点香 别只盯着GitBook了!这个文档神器让你的笔记秒变网站 写爬虫时用了代理还被封?Python 代理的那些隐藏坑,我替你踩明白了 FastAPI 身份验证总踩坑?这份 FastAPI Users “避坑指南”请收好 旧手机别扔!用 Termux 搭个私人云盘,比网盘香多了 你的FastAPI又在服务器上“跑不起来”了?来,今天咱把打包这件事彻底聊透 写页面时别再把 Element Plus 整个搬进来啦!Vue3按需加载的坑我帮你踩平了 前端包管理咋选?我从npm叛逃到pnpm的血泪史(附避坑指南) 聊聊 fetch 使用中我踩过的那些坑和正确打开方式 FastApiAdmin 后端接口开发好了,前端管理界面怎么调用与显示? 给 FastApiAdmin 加个“会议纪要”模块,我把后端二次开发的坑踩了个遍 我用了FastApiAdmin后,连夜把踩过的坑都整理出来了 告别 Typora 后的新欢:我把所有笔记迁移到了 Obsidian 这个“第二大脑” 你的Agent API还在裸奔?从认证到沙箱,我用FastAPI搭了几道防线 让 FastAPI Agent 思考不阻塞:手把手教你实现异步任务与后台处理方案 FastAPI Agent 函数调用实战:我让 AI 学会了“自己动手查天气“ 初探:用 FastAPI 搭建你的第一个 AI Agent 接口 FastAPI 少有人提的实用技巧:把 Depends 依赖提到路由层,代码少写60% FastAPI 生产环境静态文件完全指南:从 /favicon.ico 404 到 HSTS 混合内容,一次全根治 用了loguru我才明白,Python日志还能这么写 FastAPI 后台任务:BackgroundTasks 的使用场景与注意事项 FastAPI配置管理避坑指南:从硬编码到 .env 与 pydantic_settings 类,连路由用法都给你捋清楚 FastAPI 文件上传避坑全指南:分块存盘、类型校验与安全兜底 FastAPI + Pydantic 模型终极实战手册:从能跑就行到固若金汤,这些技巧你一定用得上 FastAPI + SQLAlchemy 2.0 通用CRUD操作手册 —— 从同步到异步,一次讲透 FastAPI订单防超卖实战:从数据库锁到Saga分布式事务,这一篇给你理清了 FastAPI 生产环境避坑指南:用 Alembic 管理数据库迁移,别再手动改表结构了! FastAPI服务半夜又挂了?先别急着重启,查查你的数据库连接池“池子”是不是漏了 FastAPI数据库ORM怎么选?我肝了三个Demo后,终于不再纠结了 Vue 3 组件通信,别只会用 Props 和 Emits 了,这几个狠活儿你得看看 Vue 3 组合式 API 香是香,但从Vue2迁移时你可别像我当初一样踩进这 3 个深坑里 我用fastapi-scaff搭了个项目,两天工期缩到两小时,老板以为我开挂了 FastAPI+Vue:文件分片上传+秒传+断点续传,这坑我帮你踩平了! FastAPI自动生成的API文档太丑?我花了一晚上把它改成了客户愿意付费的样子 告别手写 API 胶水代码:FastAPI 与 Vue 的“契约自动机” OpenAPI 实战 FastAPI + Vue 前后端分离实战:我的项目结构“避坑指南” FastAPI + Celery 实战:异步任务里调用 Redis 和数据库的全解析,及生产级组织方案 FastAPI里玩转Redis和数据库的正确姿势,别让异步任务把你坑哭了! FastAPI + Celery 实战:异步任务的坑与解法,我帮你踩了一遍 FastAPI子应用挂载:别再让root_path坑你一夜 FastAPI项目半夜报警吵醒你?聊聊告警这事儿怎么搞! 别再数据线了!用FastAPI 5分钟搭个局域网文件+剪贴板神器 FastAPI单元测试实战:别等上线被喷才后悔,TestClient用对了真香! FastAPI状态共享秘籍:别再让中间件、依赖和路由“各自为政”了! FastAPI实战:WebSocket vs Socket.IO,这回真给我整明白了! 从0到1,FastAPI + PostgreSQL + Tortoise ORM 实战避坑指南 FastAPI + PostgreSQL 实战:给应用装上“缓存”和“日志”翅膀 FastAPI + PostgreSQL 实战:从入门到不踩坑,一次讲透
让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斗智斗勇的战友,咱们下期见!