一、问题描述

原本开发的studyAgent实现了基础功能,但是并没有实现状态管理,也即持久化存储,用户看不见历史问答/规划记录,agent也没有上下文记忆。我今天的工作是让这种“阅后即焚”的无状态智能规划 Agent进化成拥有长期记忆、支持多窗口隔离、且能无缝调用外部工具的有状态 Agent。
这主要涉及从底层数据库 Schema、FastAPI 异步循环、一直到 Vue3 前端组件生命周期的全链路重构。

二、改进说明

  1. 引入会话管理
    存在的问题:无会话管理。
    解决方式
    系统的初始形态中,我们已经实现了一套成熟的基于 RAG 的知识点问答(QA)系统,并且配有完善的会话(Session)管理。然而,当我试图把新开发的“智能规划 Agent”接入这套体系时,第一个大坑出现了:底层表结构的业务耦合。
    如果让 Agent 和 QA 共用同一个 ChatSession 表,前端拉取历史记录时,普通问答和智能体规划的会话就会混杂在一起,因为系统只根据userid来查找表。为了保证系统的可扩展性,我放弃了新建一张 AgentSession 表的冗余做法,而是在原有的 SQLAlchemy 模型中引入了 session_type 字段(chatAgent vs studyAgent)。修改crud中查找会话的逻辑,增加“如果前端传入session_type”,就对查询结果进行过滤,保留session_type定义的会话类型:
def get_sessions_by_user(db: Session, user_id: int, search_keyword: str = None,session_type: str = None,):
    query = db.query(ChatSession).filter(
        ChatSession.user_id == user_id,
        ChatSession.is_deleted == False  # 只查没被删除的
    )
    
    # 如果前端传了搜索词,就加上模糊查询
    if search_keyword:
        query = query.filter(ChatSession.title.like(f"%{search_keyword}%"))
    if session_type:
        query = query.filter(ChatSession.session_type == session_type)
    # 按照置顶和时间倒序排
    return query.order_by(ChatSession.is_pinned.desc(), ChatSession.updated_at.desc()).all()

这样就能实现知识点问答和智能规划agent的会话区分。
结果展示:
在这里插入图片描述
2. 引入基于历史记忆的上下文管理
这里使用的滑动窗口法实现上下文记忆,把最近的几条信息作为上下文传递给大模型。
存在的问题:有状态 Agent 的核心在于上下文窗口的管理。在无状态时代,每次请求只是一次单纯的 Tools Calling。在有状态模式下,由于我们之前的知识点问的QA系统是一问一答,没有中间的多轮循环,所以每轮对话存入数据库的message只是一条user_content+一条asistent_content。但对于智能规划agent来说,aimessage既包含历史记忆,又包含用户最新提问,还包含了大模型每轮的response,以及工具调用过程:

ai_messages.append({
                "tool_call_id": tool_call.id,
                "role": "tool",
                "name": function_name,
                "content": function_result,
            })

如果把大模型每轮中间数据都存入数据库,下次加载时会瞬间撑爆 Token 限制。
解决方式:Agent的最后一次回答达到了可以回答的水平,于是让最终存入数据库的只有 Agent 综合出的纯文本结论。工具调用的海量中间态数据随请求结束而销毁。我们在次逻辑基础上引入历史记忆,每次加载最近 10 条记录,大模型看到的是高度浓缩的决策历史,而非冗长的full_message。每次对话构建aimessage时都注入上下文记忆:

    history_messages = crud_chat.get_messages_by_session(db=db, session_id=session_id)
    ai_messages = [{"role": "system", "content": "你是'研途 Buddy'的专属复习规划师。你可以使用工具。请一步步思考并解决用户的问题。"}]
    for h in history_messages[-10:]: 
        ai_messages.append({"role": h.role, "content": h.content})
    ai_messages.append({"role": "user", "content": user_content})

存储时只保存user的query和模型最后一条回复:

last_msg = ai_messages[-1]

测试:
用户query:“我要在三天内学会数据结构的图论部分,帮我生成一份学习计划”
模型回答:
在这里插入图片描述
之后用户再次query:“你的这份学习规划每天强度有点大,帮我每天的任务减少一些,循序渐进”,使用了代词,没有提出具体学习计划,而是要求在以往的计划上做修改;
模型回答:
在这里插入图片描述
在这里插入图片描述
成功注入上下文记忆!
3. 消失的思维导图
存在的问题:测试时发现,Agent 刚刚生成的思维导图完美渲染在屏幕上。但是只要切换到别的会话再切回来,导图就不见了,只剩下文本回答。这是因为我们没有持久化存储JSON数据,之前的逻辑是监听模型回答,回答的message中包含mindmap(JSON数据)就渲染思维导图,切换会话后只会保留数据库存储的数据,没有message没有mindmap,就不会触发思维导图渲染。
解决方法:持久化存储的时候把raw JSON绘图数据一并存入数据库,在前端的 switchSession 获取历史记录时,对每条message尝试提取JSON数据,将提取出来的 extractMindmap 挂载到当前这条消息的对象上,对mindmap赋值,mindmap被赋值后动态思维导图的div会监听到这个值,从而触发渲染

return {
          ...msg,
          role: msg.role === 'assistant' ? 'agent' : msg.role,
          content: cleanContent,
          mindmap: extractMindmap 
        }

结果展示:
修改前——切换回话再切换回去不展示思维导图,只有纯文本回答:
在这里插入图片描述
修改后——切换会话再切换回去,仍然正确渲染思维导图:
在这里插入图片描述

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐