职业规划Agent项目复盘笔记
·
企业级 AI 职业规划智能体(Career Planning Agent)— 项目复盘笔记
1. 本次项目核心功能与技术栈(含实现要点)
1.1 产品侧核心能力
| 能力 | 说明 |
|---|---|
| 职业规划师对话 | 固定「资深职业规划师」系统提示,强调可落地、拒绝空话;支持多轮上下文。 |
| SSE 流式输出 | 职业规划师与 Manus 智能体均通过服务端推送(SSE)把生成过程流式交给前端,改善首字延迟与体验。 |
| 持久化会话记忆 | 以 chatId 为键,将 USER / ASSISTANT 行追加写入 chat-memory/<chatId>.log,新请求时拼接最近若干行进 Prompt。 |
| RAG 检索增强 | 文档经嵌入模型写入向量库;对话前可做查询重写;检索结果通过 Spring AI 的 QuestionAnswerAdvisor 注入到对话中。 |
| 工具调用 + 类 ReAct 智能体(Manus) | 基于 ToolCallingManager 与 ToolCallback,集成 PDF 生成、文件操作、网页搜索/爬取、资源下载、终端操作、终止工具等;多步循环直至结束或达最大步数。 |
| 生成物下载 | 工具生成文件注册到内存 GeneratedFileStore,通过 fileId + 会话 chatId 校验后提供下载。 |
| 前端双模式 | 首页切换「职业规划师」与「智能体」两个聊天室;统一霓虹/粒子视觉;分别消费 SSE(职业规划师用 EventSource + GET URL;Manus 用自定义 artifact 事件等)。 |
1.2 技术栈与关键依赖
后端
- Java 21 + Spring Boot 3.4.4:Web、校验、异步 SSE(
SseEmitter)、Reactive 的Flux(职业规划师流式接口)。 - Spring AI Alibaba DashScope(
spring-ai-alibaba-starter-dashscope):统一对接通义等模型;ChatClient流式/同步调用;EmbeddingModel用于向量。 - Spring AI 向量存储:默认
SimpleVectorStore(内存);可选 PgVector(spring-ai-starter-vector-store-pgvector+ JDBC + PostgreSQL 驱动);application.yml中通过app.rag.use-pgvector等开关切换实际用于 RAG 的VectorStore。 - RAG 周边:
spring-ai-markdown-document-reader读 Markdown 知识库;自定义QueryRewriter(内部用RewriteQueryTransformer)把用户问题改写成更利于检索的表述;可选云顾问ObjectProvider<Advisor>与QuestionAnswerAdvisor组合。 - 其他:Knife4j / springdoc 文档;Jsoup 网页解析;iText 生成 PDF;Hutool;Kryo(
FileBasedChatMemory实现);MCP Client 依赖在配置中默认enabled: false。
前端(ai-agent-fronted/)
- Vue 3 + TypeScript + Vite + Vue Router;Axios 配
baseURL指向后端/api。 - SSE:职业规划师侧用
EventSource+ GET 查询参数(createSseGetUrl);Manus 侧同样EventSource,并解析自定义事件artifact(JSON:fileId、filename)以触发下载。 - 体验:
createTypewriter做打字机队列;CareerChatView中对 AI 气泡使用reactive包裹消息对象,避免流式push时 Vue 对深层属性追踪不到导致「空白气泡」类问题。
1.3 值得单独展开的技术细节
(1)多轮记忆:PersistentMemoryService
- 实现是纯文本日志:
buildMessageWithHistory把最近 N 行读入,拼成「历史对话 + 用户最新问题」一段user文本,而不是用 Spring AI 官方的ChatMemoryAdvisor 贯穿ChatClient。 - 流式持久化:
doChatByStream使用doFinally在流结束、取消或出错时统一append,避免仅用doOnComplete时在客户端提前断开导致本轮 assistant 内容未落盘、下一轮丢失上下文(代码里已有明确注释说明根因)。
(2)RAG 管线
- 异步启动加载:
ApplicationRunner在应用启动后再loadMarkDowns()→ 关键词增强MyKeywordEnricher→vectorStore.add,避免启动阶段网络/嵌入超时拖垮启动;无有效 API Key 时以空库启动并打日志。 - 查询重写:
RewriteQueryTransformer用同一ChatModel对「带历史的整段文本」做 transform,再交给向量检索,缓解指代不明、省略主语等造成的检索失败。
(3)Manus / ToolCallAgent
- DashScope 选项里
withInternalToolExecutionEnabled(false):由 Spring AI 的ToolCallingManager显式执行 tool call,便于记录工具结果再二次调用模型生成最终回答。 think→act循环:think里发起带toolCallbacks的call;若有 tool call 则act里executeToolCalls并再call一次产出最终中文回答;terminate工具会结束状态。
(4)接口形态说明(与文档对齐)
- 当前
AiController中职业规划师 SSE 为GET /ai/career_app/chat/sse?message=&chatId=返回Flux<ServerSentEvent<String>>;README中仍写 POST + JSON,属于文档与实现不一致,接入或联调时若以 README 为准会踩坑(应以前端与AiController为准)。
2. 技术难点与「当时可能不熟」的知识点
- Spring AI 版本矩阵:
pom.xml中同时存在 M6 / M7 等不同里程碑版本的 Spring AI 组件,与 Alibaba Starter 组合时易出现类路径、API 变更、Bean 定义冲突(Git 中有「Bean 注入冲突报错解决」类提交)。需要理解 Spring Boot 自动配置、@Qualifier、@ConditionalOnProperty与ObjectProvider的用法。 - Reactive
FluxSSE 与 ServletSseEmitter混用:职业规划师用 WebFlux 风格的Flux<ServerSentEvent>,Manus 用 MVCSseEmitter+CompletableFuture.runAsync;要分别理解背压、线程模型、客户端断开(如GlobalExceptionHandler对ClientAbortException的处理)。 - DashScope 工具调用:关闭内部工具执行、自行
ToolCallingManager执行,需理解 AssistantMessage 中的 toolCalls、ToolResponseMessage 与多轮 message list 的维护顺序。 - 向量检索质量:仅 embedding + topK 往往不够;本项目通过 查询重写、关键词 enrich metadata、(可选)过滤表达式 组合提升召回与精度,涉及 RAG 各层(预检索、检索、后处理)的分工。
- 前端流式 + Vue 响应式:增量更新
ref数组内普通对象的字段可能不触发视图更新;需熟悉reactive包裹或不可变替换等范式。 - EventSource 限制:仅 GET、无法自定义部分请求头;与 POST SSE(fetch + ReadableStream)的取舍、以及连接关闭时
onerror与正常结束的区分(代码里对CONNECTING状态做了区分)。
3. 出现的 Bug 及根因(基于代码与演进记录)
| 现象 / 主题 | 可能根因 | 依据 |
|---|---|---|
| 流式对话「记不住」上一轮 assistant | 若只在 doOnComplete 写日志,用户中途关页面或取消订阅,完成回调不触发,记忆未追加。 |
CareerPlanningAgentApp#doChatByStream 使用 doFinally 修复。 |
| 前端 AI 气泡流式时空白、结束才一次性显示 | 对数组元素用普通对象,content 增量未建立正确响应式追踪。 |
CareerChatView.vue 注释与 reactive 用法。 |
| SSE 客户端断开刷错误日志 / 异常 | 断开连接时服务端仍写响应,抛出 ClientAbortException / AsyncRequestNotUsableException。 |
GlobalExceptionHandler 单独捕获并 debug 记录。 |
| README 与真实接口不一致 | 接口从 POST JSON 改为 GET 查询参数后,文档未完全同步。 | README vs AiController。 |
| 启动卡住或向量加载失败 | 启动同步调用嵌入 API 或读大文档易导致超时;网络不稳定时失败。 | 改为 ApplicationRunner 异步加载 + try/catch 降级。 |
多个 VectorStore Bean 冲突 |
内存库、PgVector、自动配置同时注册,注入点未限定 @Qualifier。 |
Git 提交信息「Bean注入冲突报错解决」+ 当前 resolveRagVectorStore 与双 Bean 设计。 |
4. 代码与架构层面的不足
AIManus默认 Spring 单例 + 有状态BaseAgent:messageList、state等在实例字段上;并发两个 Manus 请求会共享同一 agent 状态,存在竞态与串话风险。更合理的是每请求/每会话一个 Agent 实例(prototype Bean、AgentInstance工厂或把状态抽到会话级对象)。- 记忆机制双轨:存在
ChatMemoryBean(FileBasedChatMemory+ Kryo)与PersistentMemoryService(纯文本 log)两套实现,主流程职业规划师只用后者,易造成维护困惑与目录混用(若 Kryo 与 log 同时写同一目录需格外小心;当前需再确认是否仅一处生效)。 ReActAgent#step异常处理:printStackTrace()与吞掉细节不利于生产排障;应统一日志与错误码。- 依赖版本不统一:Spring AI 相关 artifact M6/M7 混用,长期增加安全补丁与升级成本,建议对齐到同一 BOM 或版本线。
- Manus 步数:
AIManus中setMaxSteps(3)较激进,复杂任务易未执行完即截断,与「全能助手」人设可能冲突,宜配置化或按任务类型区分。 - 下载与元数据:
GeneratedFileStore仅内存 map,进程重启即丢失 fileId;无 TTL 清理,长期运行可能泄漏路径引用(若配合磁盘文件生命周期需再设计)。 - 文档/接口契约:缺少 OpenAPI 与真实 SSE 参数源的单一事实来源(README 过时)。
5. 时间与流程问题(结合 Git 阶段划分)
从提交信息可见大致节奏:后端能力(RAG、工具、记忆)→ PgVector / 跨域 / Bean 修复 → 企业级强化 → 前端两页 + UI → 精简接口与下载能力 → README 更新。
可反思的流程点:
- 文档与实现同步:接口方法(GET/POST)变更后应同 PR 更新 README 与前端 config,避免后人按文档联调失败。
- 阶段二「企业级」与后续删接口:若曾对外暴露多接口再删减,说明早期接口边界未先冻结,可增加「最小可用 API 列表」评审。
- 密钥与本地配置:DashScope、Search API 等依赖环境变量;需在 README 或
application-local.yml.example中写清无 Key 时的降级行为(如空向量库、搜索失败),减少新人排查时间。
6. 可改进与优化方向
架构与可靠性
- Manus:按请求创建 Agent 或显式会话锁;SSE 异步线程池使用命名线程池(便于监控)而非默认
ForkJoinPool。 - 记忆:统一为 一种实现(要么 Spring AI
ChatMemoryAdvisor,要么自研 log),并定义最大上下文 token/字符截断策略,避免超长 log 撑爆模型窗口。 GeneratedFileStore:增加 TTL + 定期清理;可选落库或 Redis,支持多实例部署。
效果与成本
- RAG:对重写查询做 A/B 或离线评测(命中率、回答引用率);调节
topK、阈值与 chunk 策略。 - 模型调用:对简单轮次可跳过重写以省一次 LLM;缓存常见问题的检索结果。
工程化
- 统一 Spring AI 版本;引入
spring-ai-bom(若官方提供与 Boot 3.4 匹配的组合)。 - 契约测试:SSE 集成测试(TestRestTemplate / WebTestClient)覆盖
done事件与[DONE]。 - 国际化与文案:后端错误信息中英混用(如 chatId 校验),可统一为产品语言策略。
安全
app.security.enabled关闭时便于开发;生产应开启 API Key 拦截器(项目中已有ApiSecurityInterceptor雏形)并限制 Manus 工具(尤其终端操作)的白名单与沙箱。
小结
本项目完整串联了 大模型对话、文件记忆、向量 RAG、查询重写、工具调用、SSE 前后端协同与生成物下载,技术密度适合作为「AI 应用工程化」学习样本。最值得带走的三条经验是:(1)流式场景下持久化时机要用 doFinally 等覆盖取消/错误;(2)有状态 Agent 勿用单例共享;(3)文档与接口必须同源,否则复盘成本会转嫁给下一个自己或队友。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)