内容提要
本文档介绍基于AgentScope与DeepSeek构建的生产级记忆型Agent后端系统,采用DDD四模块分层与依赖倒置,支持SSE流式对话、HITL人工确认、命令审批、请求级幂等四态、跨会话记忆、经验自沉淀与Skill渐进式披露。全文共十篇,涵盖项目架构、核心概念、后端链路、数据持久化、记忆增强、MCP工具服务器、状态重建与缓存一致性、前端交互及优雅停机。
延伸解读
架构决策的实证依据
本文强调所有结论均源自项目设计文档、记忆沉淀或真实代码,尤其对AgentScope 2.0.1进行了字节码级验证。例如,ReActAgent的Toolkit在构造期被final绑定,无法运行时切换,这直接导致采用每会话独立构建Agent的方案。这种基于实证的架构决策,避免了臆测,为类似项目提供了可靠参考。
生产级Agent的关键挑战
文章指出,将LLM包装成对话接口不难,难的是在生产环境中稳定、正确、安全地运行。本项目针对SSE流式、HITL人工确认、请求级幂等、多用户隔离等痛点,给出了具体解法。这些挑战在真实高并发场景中普遍存在,其解决方案具有借鉴意义,尤其是幂等四态状态机和HITL挂起续传机制。
记忆分层的工程实现
Agent的记忆分为短期状态、长期记忆和经验记忆。由于AgentScope 2.0.1的LongTermMemory接口已废弃,本项目在应用层自建了conversation_log和experience_entry表,并通过定时任务提炼高发问题为SOP。这种分层设计既利用了框架的短期状态管理,又弥补了框架在长期记忆上的不足,实现了跨会话记忆和经验沉淀。
优雅停机的时序陷阱
框架自带的GracefulShutdownManager注册了JVM钩子,但JVM钩子在Spring容器销毁后执行,此时数据库连接池已关闭,导致状态落盘失败。应用层通过SmartLifecycle在连接池存活时提前介入,确保在途请求落盘和流水账排空。这一细节揭示了框架与Spring生命周期整合时的常见陷阱,对生产部署至关重要。
Q&A
AgentScope 项目是什么?它解决了哪些生产环境中的痛点?
该项目是基于 AgentScope 2.0.1 和 DeepSeek 构建的生产级记忆型 AI Agent 后端系统。它解决了多个痛点:通过 SSE 流式实现打字机效果;通过 HITL 人工确认和命令审批保障危险工具安全;通过请求级幂等四态防止重复计费和重复执行;通过 JWT 鉴权实现多用户隔离;通过短期状态存储、跨会话长期记忆和经验沉淀让 Agent 记住用户并越用越聪明;通过 Skill 技能体系和渐进式披露积累复用能力;通过 DDD 分层和依赖倒置保证代码可演进。
这个项目的 DDD 四模块分层具体是如何划分的?依赖方向是怎样的?
项目分为四个 Maven 模块:starter(Spring Boot 启动模块,含配置和建表脚本)、domain(领域核心,定义端口接口、只读视图和流式接口,不依赖任何 Web/持久化技术)、infra(技术实现,实现 domain 端口,装配模型、MCP、存储等)、client(入口与装配,含 Web 控制器、SSE 桥接、JWT 鉴权、定时任务)。依赖方向为 client → domain ← infra,即 infra 依赖 domain(依赖倒置),而非传统 domain 依赖 infra。
请求级幂等四态是如何实现的?为什么幂等判定必须在建立 SSE 连接之前?
幂等通过 request_idempotency 表和四态状态机(PROCESSING/DONE/FAILED/CANCELLED)实现。核心是四条原子 SQL:tryOccupy 用 INSERT IGNORE 原子占位;findByTriple 按三元组查状态;takeoverLease 用 CAS 抢占过期租约;finish 更新终态。Web 层根据状态分流:FIRST_SEEN 放行,DONE 重放,PROCESSING 返回 409,CANCELLED 返回 409。幂等判定必须在建 SseEmitter 之前完成,因为 SSE 一旦创建即返回 HTTP 200,之后无法再改状态码,想返 409 就来不及了。
HITL 人工确认和命令审批有什么区别?它们分别如何处理?
HITL 权限确认由 RequireUserConfirmEvent 触发,会挂起整条 Agent 流(SDK 持久化 ASKING 态),通过 ask 事件请求确认,用户回复后走 /permission/reply 触发 resumeStream 二次 streamEvents 续传。命令审批针对 execute_shell_command 工具,不挂起流,仅阻塞工具线程,通过 command_confirm 事件请求审批,用户回复后走 /command/reply 仅唤醒阻塞的工具线程。两者在触发条件、是否挂起流、SSE 事件、回复接口和 sink 绑定上均有不同。
Skill 混合式渐进披露是如何工作的?pinned 和目录段有什么区别?
SkillInjectionService.resolveAndRender 采用混合式策略:pinned 正文直注段将用户手动置顶的 Skill 完整 SOP 直接注入上下文,不走目录且不计热度;目录段只注入其余 active Skill 的名称和简述,模型按需调用 skillRecall 工具拉取全文。最终注入 = (自主命中 ∪ pinned) − muted。只有真正被 skillRecall 读到正文的 Skill 才自增 hit_count,pinned 直注不计数。注入结果以 SkillUsage 名单经 skill 事件推给前端,实现透明化。
经验沉淀闭环是如何让 Agent 越用越聪明的?
经验沉淀闭环包括落账、聚合、提炼、回填四个环节。每轮对话带语义指纹落 conversation_log;ExperienceDistillJob 定时(默认每天 04:00)聚合高发问题(窗口 7 天、阈值 5 次),取近 20 行语料,直调 Model.stream 提炼成 SOP;回填时写入 experience_entry 表(user_id 为 __global__ 全局共享),同时固化为 Skill 文件并索引,进入渐进披露体系被复用。固化失败不回滚经验,仅记 warn 下轮重试。
git-mcp 如何保证每个诊断会话都能读到最新全量代码?
通过三层机制保证:第一层,git-mcp 的数据源是本机一份真实 clone 的 git 仓库,读的是活的 .git 对象库;第二层,sync_remote 工具执行 git fetch origin --tags 主动将远端最新全量代码同步到本机,对远端零写入,只更新本地远端跟踪引用;第三层,所有会话共享同一个 REPO_PATH 仓库,查询默认锁定 origin/uat,sync_remote 刷新后所有会话读到的都是最新全量代码。会话隔离留给主应用侧的对话状态,代码这类全局客观事实则共享一份权威源。
短期状态重建(StateRebuildService)在什么情况下触发?回放哪些数据?tool_trace 参与吗?
重建仅在 cache-miss 且流水账有历史时触发。回放数据来自 conversation_log,只取 USER 行和正常完成的 ASSISTANT 行(finish_reason='stop'),error/cancelled 的残缺回复不进上下文,且只取最近 200 行(可配)。tool_trace 不参与重建,它仅用于前端历史轨迹回显。重建产物是一个 AgentState,只重建 context 对话缓冲,summary/replyId/curIter 等运行态字段留空,通过预写 agent_state 让框架首轮 call 自动加载。重建失败不阻断主流程,退化为全新会话。
优雅停机是如何实现的?为什么需要应用层自己实现而不是依赖框架?
优雅停机通过 GracefulShutdownLifecycle 实现,它实现 SmartLifecycle,phase 略低于 Tomcat,在 Spring 容器销毁前执行。stop() 方法四步串行:设有限超时(30s)、触发停机、等在途收尾、排空流水账(15s)。需要应用层实现是因为框架的 JVM 钩子在 Spring 容器销毁之后才执行,此时 HikariCP 连接池已关闭,ShutdownStateSaver 落盘会全部失败。应用层将落盘动作提前到 SmartLifecycle.stop() 阶段,确保连接池仍存活,落盘必达。