AI超级智能体
快来分享你的内容吧~
- 07-23 17:50·Java后端踩坑记录:Spring AI + PGVector 维度不一致导致 DataIntegrityViolationException 日期:2026-07-23 作者:puipui 影响范围: RAG 模块 / PGVector 向量存储 一、问题现象 在本地 / 测试环境启动 或主程序时,出现以下异常: 表现特征 有时第一次启动成功,重启后立刻失败 报错维度在 之间反复变化 日志中出现 ,说明表已查看全文鱼友9642:text-embedding-v1是默认模型,维度1024吧,要是dimensions: 1536会报错的,text-embedding-v2可以240分享
- 04-21 20:21查看全文SpringAl+RAG+MCP全栈|AI超级智能体企业级实战项目中:stdio连接方式调用MCP服务问题 运行测试时 解决: 1. 先查看MCP server那的yml文件,文件名有没有写错...加油鸭:太棒了!精准定位 stdout 污染问题,Banner 和日志关闭得恰到好处,协议合规性一击解决!311分享
- 04-06 16:09·Java后端LoveAppConfigimport com.lzx.lzxaiagent.advisor.MyLoggerAdvisor;import com.lzx.lzxaiagent.advisor.SensitiveWordAdvisor;import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai查看全文加油鸭:代码结构清晰、职责分明,Spring AI集成得很有章法!特别是ChatClient配置和SSE流式响应的封装,既实用又优雅~332分享
- 04-05 23:30·后端AI 时代,会用 AI 和不会用 AI,效率差别真的很大,要让 AI 给我们打工而不是我们给 AI 打工,如果你要写的项目很多、时刻等到 AI 的回复和反馈,即使能赚钱,其实也是变相给 AI 打工了,哈哈哈哈哈哈哈。查看全文加油鸭:太佩服你把踩坑经验沉淀为AI可执行SOP的思路!这份通用迁移文档就是真正的“副业加速器”,高效又可持续~766分享
Code Log-AI超级智能体项目,开发日志。SpringAI + RAG + MCP 全栈 | AI 超级智能体企业级实战(26年必学)
# 开发日志 1.依赖。在文件pom.xml中添加依赖:Spring MVC, Lombok, hutool, Knife4j, Spring AI Alibaba... 2.配置。编辑文件application.yml, 添加:Knife4j的配置,Spring配置:port,AI:Dashscope。 3.接口测试。测试swagger接口,在src下新建controller,新建HealthController.java,在浏览器输入.yml中的接口地址。 4.API_KEY。创建文件application-local.yml,存入API_KEY(需要自行去阿里百炼平台充值)。在.gitignore中添加文件名applicaiont-local.yml 5.实现多轮对话,持久化记忆。创建目录App, 创建文件LoveApp.java实现本地内容存储对话记忆,实现多轮对话doChat()。(注意阅读SpringAI官方文档)JUnit测试,在方法名上鼠标右键->generate->test。 6.实现日志拦截器。创建目录Advisor,创建LoggerAdvisor.java,实现流式和非流式2种方式。不要管用户用什么,都需要实现。在文件LoveApp.java中添加自定义LoggerAdvisor。 7.实现敏感词拦截器。创建SafeGuardAdvisor.java,实现自定义常见敏感词拦截。在文件LoveApp.java中添加SafeGuardAdvisor。pom.xml添加sensitive-word。 8.实现结构化数据输出。在pom.xml添加jsonschema-generator,在LoveApp.java中添加doChatWithReport(),生成结构化数据。JUnit测试,alt+enter,生成丢失的单元测试方法。 9.优化敏感词拦截器。实现从一刀切到指出具体敏感词,然后引导用户修改词句后重新提问。创建SafeGuardAdvisor.java。 10.使用Mybatis持久化记忆,落库Mysql。 ①在pom.xml添加依赖mybatis-spring-boot-start, mysql-connector。 ②配置.yml文件。 ③在mysql数据库中建立表chat_memory,建立字段。 ④创建实体类ChatMemoryEntity.java。 ⑤创建ChatMemoryMapper.java。 ⑥创建Mybatis XML映射文件, ChatMemoryMapper.xml。 ⑦创建MyBatisChatMemoryRepository.java,实现 Spring AI 的 ChatMemoryRepository 接口,用 MyBatis 来存取数据。 ⑧在LoveApp.java注入自定义的 MyBatis 记忆仓库。 ⑨启动类添加MapperScan。 ``` 整体架构总结 用户输入 → ChatClient → MessageChatMemoryAdvisor ↓ MessageWindowChatMemory (窗口截断,最多10条) ↓ MyBatisChatMemoryRepository (自定义实现) ↓ ChatMemoryMapper (MyBatis) ↓ MySQL chat_memory 表 ``` ``` 核心流程: 读取记忆:每次对话前,findByConversationId 从 MySQL 查出历史记录 窗口截断:MessageWindowChatMemory 只保留最近 10 条 写回记忆:对话完成后,saveAll 先删旧数据,再把当前窗口内的消息批量写入 MySQL ``` 11.RAG知识库搭建,本地知识库实现。 ①在pom.xml引入依赖spring-ai-markdown-document-reader。 ②新建rag目录,创建LoveAppDocumentLoader.java实现文档的加载和读取。 ③创建LoveAppVectorStoreConfig.java实现初始化向量数据库并保存文档。 ④在LoveApp实现QuestionAnswerAdvisor问答拦截器,实现查询增强和关联。 ⑤在LoveAppTest.java实现doChatWithRag()单元测试,提问一个文档内有回答的问题,查看相似度score。 ``` 文档搜集和切割 ↓ 向量转换和存储 ↓ 切片过滤和检索 ↓ 查询增强和关联 ``` 12.RAG知识库,Spring AI + 云知识库。 ①在阿里云百炼平台创建好云知识库。 ②创建LoveAppRagCloudAdvisorConfig.java,实现基于阿里云知识库服务的RAG增强Advisor。 ③在LoveApp.java的doChatWithRag()中添加应用检索增强服务。 ④在LoveAppTest.java中doChatWithRageTest()中进行单元测试。 13.RAG知识库ETL。 ①创建MyTokenTextSpliter,实现基于Token的文本分割器。 ②创建MyKeywordEnricher.java,实现基于AI的文档元信息增强器,为文档补充元信息。 14.基于PGVector实现向量存储,提高PostgreSQ存储和检索高维度向量数据的能力。 ①开通阿里云Serverless版本数据库服务并创建数据库。 ②在IDEA->data source and drivers配置好url和登录信息,测试连接成功。 ③pom.xml引入spring-ai-vector-store-pgvector的依赖。 ④在application-local.yml编写配置,建立数据库连接。 ⑤创建PgVectorStoreConfig.java,初始化VectorStore,不需要Starter自动注入。 ⑥在AIagentApplication.java中启动类排除掉自动加载DataSourceAutoConfiguration.class。 ⑦创建测试类PgVectorStoreConfigTest.java。在Debug模式中,查看文档检索成功以及相似度分数score。IDEA右侧快捷栏,查看数据表中的数据。证明PGVector整合成功。可以用阿里云服务替换本地的VectorStore。 15.实现查询重写,规范化用户的提示词。 ①创建QueryRewriter。 ②在LoveApp.java中doChatWithRag()添加查询重写。③测试doChatWithRag()。 16.根据用户查询需求生成对应的Advisor,实现文档的检索过滤。 ①创建LoveAppRagCustomAdvisorFactory。填写好过滤表达式,设置好相似度阈值和召回数。 ②在LoveApp.java的Advisor应用CustomAdvisorFactory。 ③测试doChatWithRag()。 ④Debug查看Logger中advisor信息。断点在adviseCall()处。 17.实现上下文查询增强。 ①创建LoveAppContextualQueryAugmenterFactory.java,禁止查询内容为空,提供错误信息提示,提供正确信息引导。 ②在LoveAppRagCustomAdvisorFactory.java添加应用LoveAppContextualQueryAugmentrFactory。 ③单元测试LoveAppTest.doChatWitRag()。 18.实现Agent工具调用,查询天气功能。 A.用户输入城市名,返回天气信息。 B.通过获取用户IP地址,自动返回天气信息。 ①创建tools目录。 ②在application.yml中启用工具调用日志。 ②申请天气查询API接口,并在application-local.yml中填写。 ③实现查询天气功能,创建WeatherTool.java,创建WeatherToolTest.java。 19.实现读取文件、保存文件工具。 ①创建目录constant,创建文件常量接口FileConstant.java。 ②创建文件操作工具类FileOperationTool.java。 ③创建测试类FileOperationToolTest.java。 20.实现联网搜索工具。 ①创建WebSearchTool.java。 ②在application-local.yml配置apikey。 ③创建WebSearchToolTest.java 21.实现网页抓取工具。 ①在pom.xml引入jsoup依赖。 ②创建WebScrapingTool.java。 ③创建WebScrapingToolTest.java。 22.实现终端操作工具。 ①创建TerminalOperationTool.java。 ②创建TerminalOperationToolTest.java。 23.实现资源下载工具。 ①创建ResourceDownloadTool.java。 ②创建ResourceDownloadToolTest.java。 24.实现PDF生成工具。 ①在pom.xml引入itextpdf。 ②创建PDFGenerationTool.java。 ③创建PDFGenerationToolTest.java。 25.实现集中注册工具,让Agent自主决定使用哪些工具。 ①创建ToolRegistration.java,通过ToolCallback保存工具对象。 ②在LoveApp.java中创建doChatWithTools()绑定所有已经注册的工具。 ③单元测试,创建doChatWithToolsTest()。 26.优化pdf生成工具,实现彩色emoji插入显示。 ①下载.ttf字体文件包到Resource/font目录。 ②重构PDFGenrationTool.java。 ③单元测试,在PDFGenerationToolTest.java编写含有emoji元素的测试用例。 27.实现用户查询工具。 ①创建UserQueryTool.java。 ②创建UserQueryToolTest.java。 28.接入MCP协议,实现图片搜索服务,本地开发。 A.Server服务端开发: ①申请并记录API。 ②在根目录下,新建Module,在新窗口打开Module,在pom.xml引入依赖。 ③编辑application.yml配置文件,创建application-stdio.yml,创建application-sse.yml。 ④创建tool目录,创建ImageSerachTool.java。 ⑤创建ImageSearchToolTest.java。 ⑥编辑application.java主类,使用@Bean注册工具。 ⑦Maven Package打包好项目jar,记录好.jar目录,准备导入到客户端项目中。AIagent\jc-image-search-mcp\target\jc-image-search-mcp-0.0.1-SNAPSHOT.jar B.Client客户端开发: ①在pom.xml引入依赖。 ②创建mcp-servers.json。 ③在LoveApp.java,创建doChatWithMcp()。 ④创建doChatWithMcpTest()。 ⑤在pom.xml配置好MCP信息,在doChatWithMcp()断点debug。 C.从stdio切换到sse: ①在image-search-server设置active:sse。 ②以Debug模式启动JcImageSearchMcpApplication。 ③在AIagent项目中,配置application.yml,设置好url。 ④run运行doChatWithMcpTest(),在ImageSearchTool.java断点测试。会显示query关键词。 29.本地实现Manus智能体。 ①创建目录,agent->model。 ②创建AgentState.java。 ③创建BaseAgent.java。 ④创建终止任务的工具类TerminateTool.java。 ⑤创建ReActAgent.java。 ⑥创建ToolCallAgent.java。 ⑦创建恋爱大师智能体类LoveManus.java。 ⑧单元测试,创建LoveManusTest.java。 30.AI应用接口开发。 ①修改抽象基础代理类BaseAgent.java,新增方法runStream()。 ②新建AI访问控制器,AiController.java,并测试接口。 ③新建跨域配置类CorsConfig.java。 31.实现AI会话SSE流式传输与接口。 ①在loveApp.java创建方法doChatByStream()。 ②在AiController.java创建doChatWithLoveAppSSE(),doChatWithLoveAppServerSseEmitter(),doChatWithLoveAppServerSentEvent()。 ③对SSE接口进行测试。 32.使用VibeCoding实现前端开发。①将以下提示词提交给Trae。②启动后端服务器application。③打开终端切换到前端目录,输入npm run dev,访问localhost:5173进行测试。 提示词如下: ``` 你是一位专业的前端开发,请帮我根据下列信息来生成对应的前端项目代码。 需求: 1)主页:用于切换不同的应用 2)页面 1:AI 恋爱大师应用。页面风格为聊天室,上方是聊天记录(用户信息在右边,AI 信息在左边),下方是输入框,进入页面后自动生成一个聊天室 id,用于区分不同的会话。通过 SSE 的方式调用 doChatWithLoveAppSse 接口,实时显示对话内容。 3)页面 2:AI 超级智能体应用。页面风格同页面 1,但是调用 doChatWithManus 接口,也是实时显示对话内容。 技术选型: 1. Vue3 项目 2. Axios 请求库 后端接口信息: 接口地址前缀:http://localhost:8123/api SpringBoot 后端接口代码: package com.jc.aiagent.controller; import com.jc.aiagent.agent.LoveManus; import jakarta.annotation.Resource; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.tool.ToolCallback; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; /** * AI 访问控制器 */ @RestController @RequestMapping("/ai") public class AiController { @Resource private ToolCallback[] allTools; @Resource private ChatModel dashscopeChatModel; /** * 流式调用 Manus 超级智能体 * * @param message * @return */ @GetMapping("/manus/chat") public SseEmitter doChatWithManus(String message) { LoveManus loveManus = new LoveManus(allTools, dashscopeChatModel); return loveManus.runStream(message); } } 必须遵守的开发规则: -用中文回复 -改完代码后显示修改内容的文件名和行号 -禁止读取以.log结尾的文件 -代码开发遵循开闭原则 ``` 关键开发步骤: ①创建ai-agent-web。创建src/api/chat.ts。封装了 doChatWithLoveAppSse 和 doChatWithManus 两个 SSE 接口。 ②创建src/components/ChatRoom.vue。实现聊天室主界面。 ③创建src/router/index.ts。配置三个路由:/、/love-app、/manus。 ④创建src/views/LoveAppView.vue。进入页面时自动生成 chatId。调用 doChatWithLoveAppSse 接口。 ⑤创建src/views/ManusAppView.vue。调用 doChatWithManus 接口。 33.使用VibeCoding实现邮箱注册登录功能。提示词如下: ``` 你是一位专业的全栈开发工程师,请帮我根据下列信息来生成对应的项目代码。 需求:实现web系统邮箱注册登录功能。 前端开发: 1)在主页的基础上,新增注册登录界面。必须先登录,然后才能进入主页。如果未登录,那么不能进入主页。 后端开发: 2)使用邮箱注册登录服务。注册包含:输入邮箱地址,输入密码,重输入密码,输入验证码。①前端实现对输入的邮箱地址合法性校验,判断邮箱是否已被注册。②注册密码长度需要大于8位且小于20位,必须包含数字、小写字母、大写字母,否则注册失败。③重输入密码需要与第一次的输入密码相同,否则注册失败。④验证码需要验证功能,验证成功才能成功注册。相关服务需要使用邮箱授权码或者API的话,如果你需要配置文件的详细信息,可以引导我并向我提问。 3)存储用户信息使用Mysql+Mybatis写入到数据库。在Mysql新建user用户表。Mysql数据库地址、用户名和密码信息在application.yml文件中。 4)请你根据功能需要优化现有的数据库。现有数据库信息: mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | jc_ai_agent | | mysql | | performance_schema | | sys | +--------------------+ 5 rows in set (0.168 sec) mysql> use jc_ai_agent; Database changed mysql> show tables; +-----------------------+ | Tables_in_jc_ai_agent | +-----------------------+ | chat_memory | +-----------------------+ 1 row in set (0.025 sec) mysql> describe chat_memory; +-----------+-------------+------+-----+-------------------+-------------------+ | Field | Type | Null | Key | Default | Extra | +-----------+-------------+------+-----+-------------------+-------------------+ | id | bigint | NO | PRI | NULL | auto_increment | | content | text | NO | | NULL | | | type | varchar(10) | NO | | NULL | | | timestamp | timestamp | NO | MUL | CURRENT_TIMESTAMP | DEFAULT_GENERATED | | chat_id | varchar(36) | NO | | NULL | | +-----------+-------------+------+-----+-------------------+-------------------+ 5 rows in set (0.023 sec) 5)如果存在其他必定需要的开发功能模块,请你补充。 技术选型: 1.Vue3 项目 2.Axios 请求库 3.Mysql 4.Mybatis 必须遵守的开发规则: -用中文回复。 -改完代码后显示修改内容的文件名和行号。 -禁止读取以.log结尾的文件。 -代码开发遵循开闭原则,尽量少改动原有的代码。 ``` 已创建/修改的文件汇总: ``` 类型 文件 说明 后端实体 entity/User.java 用户实体 后端DTO dto/UserRegisterDTO.java 注册请求DTO(含校验注解) 后端DTO dto/UserLoginDTO.java 登录请求DTO 后端DTO dto/VerifyCodeDTO.java 验证码请求DTO 后端Mapper mapper/UserMapper.java 用户数据访问 后端XML mapper/UserMapper.xml MyBatis映射 后端Service service/UserService.java 用户服务接口 后端Service service/impl/UserServiceImpl.java 注册/登录/验证码业务 后端Controller controller/UserController.java 用户接口 后端工具 utils/JwtUtil.java JWT Token生成与验证 后端工具 utils/VerifyCodeCache.java 验证码内存缓存 后端拦截器 interceptor/AuthInterceptor.java 登录认证拦截 后端配置 config/WebMvcConfig.java 拦截器注册 后端通用 common/Result.java 统一响应结果 后端POM pom.xml 添加邮件依赖 后端YML application-local.yml 添加邮箱配置 前端页面 views/LoginView.vue 登录页 前端页面 views/RegisterView.vue 注册页 前端页面 views/HomeView.vue 主页(增加用户信息和退出) 前端API api/auth.ts 认证API + Axios拦截器 前端路由 router/index.ts 路由守卫 ``` ##### 数据库关联: chat_memory 已添加 user_id 实现用户关联。采用 ThreadLocal 方案,改动最小: ``` 文件 修改内容 context/UserContext.java (新增) ThreadLocal 存储当前用户ID interceptor/AuthInterceptor.java 请求开始时 setUserId,结束后 clear entity/ChatMemoryEntity.java 新增 userId 字段 mapper/ChatMemoryMapper.java 查询/删除增加 userId 参数 mapper/ChatMemoryMapper.xml SQL 增加 user_id 条件 repository/MybatisChatMemoryRepository.java 自动从 ThreadLocal 获取 userId ``` ##### 拦截器优化: 浏览器 EventSource API 不支持自定义请求头,无法发送 Authorization: Bearer xxx,导致 SSE 请求被拦截器返回 401,前端无任何提示。 修改的 3 个文件: ``` 文件 行号 修改内容 interceptor/AuthInterceptor.java 25-27 Token 获取增加 request.getParameter("token") 兜底逻辑 api/chat.ts 1, 27-28 从 import api 改为 import { getToken },URL 增加 &token= 参数 api/chat.ts 73-74 Manus 接口同样增加 &token= 参数 ``` 解决 **ThreadLocal userId 在异步线程中丢失**的问题。 数据流: ``` 请求线程: AuthInterceptor设置userId → Controller捕获userId → ChatUserContextHolder.bind(chatId, userId) ↓ reactor线程: Flux处理 → MemoryRepository.saveAll() → UserContext.getUserId()=null → 兜底: ChatUserContextHolder.getUserId(chatId) → ✓ 正确的userId ↓ 完成/异常: ChatUserContextHolder.unbind(chatId) → 清理映射 ``` 修改内容 修改了 4 个文件(新增 1 个) ``` 通过 chatId → userId 映射 + 异步线程 ThreadLocal 恢复,解决跨线程 userId 丢失问题: 1.新增 ChatUserContextHolder.java 路径:src/main/java/com/jc/aiagent/context/ChatUserContextHolder.java 使用 ConcurrentHashMap<String, Long> 维护 chatId → userId 映射,线程安全 2.MybatisChatMemoryRepository.java(第 3, 31-34, 51-54, 86-89 行) findByConversationId()、saveAll()、deleteByConversationId() 三个方法均增加兜底逻辑:当 UserContext.getUserId() 为 null 时,从 ChatUserContextHolder.getUserId(chatId) 获取 3.AiController.java(第 5-6, 87-90, 100-106 行) doChatWithLoveAppServerSseEmitter() 在请求线程上捕获 userId 并绑定到 chatId Flux 的 onError 和 onComplete 回调中解绑映射,防止内存泄漏 4.BaseAgent.java(第 4, 163-164, 168-171, 223-226 行) runStream() 在请求线程上捕获 userId,在 CompletableFuture.runAsync() 的异步线程开头恢复 UserContext 外层 finally 块中清除 UserContext,防止内存泄漏 ``` ##### 优化AI超级智能体的回答内容和思考长度: 问题1:用户问“深圳今日天气如何?” Agent直接开始思考...回答出无效内容。 解决方法: ``` 将WeatherTool 注册到 Agent 的工具列表中。 修改 1:ToolRegistration.java(第 5 行新增 import,第 21-22 行注入,第 34 行注册) 注入 WeatherTool Spring Bean 将其添加到 ToolCallbacks.from() 修改 2:LoveManus.java(第 20-21 行新增系统提示) 在 system prompt 中增加了明确的指令:天气相关查询 必须直接使用 getWeather 或 getLocalWeather,禁止使用 web search/scraping ``` 问题2:Agent的显示出思考过程不够精简,用户阅读体验差。 解决方法--精简显示Agent的思考过程: ``` 改动概览 后端(3 个文件) 1. ToolCallAgent.java 新增 finalAnswer 字段保存最终回答文本 新增 currentThinkingText 字段保存精简的思考状态描述 think() 方法:无工具调用时保存最终回答,有工具调用时生成精简描述(如"正在调用 天气查询") act() 方法:返回精简的执行结果(不再返回原始工具数据) 新增 beautifyToolName() 将工具方法名转为友好中文名 2. ReActAgent.java 重写 runStream() 方法,使用 SSE 命名事件区分消息类型: thinking 事件:精简的思考状态(如"正在调用 天气查询") answer 事件:最终回答(逐字打字机效果流式输出) complete 事件:完成通知 error 事件:错误信息 新增抽象方法 getCurrentThinkingText() 和 getFinalAnswer() 前端(1 个文件) 3. ChatRoom.vue 监听 thinking / answer / complete / error 四种命名事件 思考状态以轻量标签形式展示(⚡ + 文字,带脉冲动画) 最终回答正常气泡展示,保留打字机效果 兼容旧版 message 默认事件(恋爱大师仍走此通道) 效果对比 之前 现在 显示完整的 "Step 1: 工具 searchWeb 完成了它的任务!结果: {5条搜索结果JSON} ⚡ 正在调用 网页搜索 显示 "Step 2: 工具 scrapeWebPage 完成了它的任务!结果: ...(整页HTML)" ⚡ 正在调用 网页抓取 没有清晰的最终回答 最终回答以气泡形式逐字输出 ``` 执行流程: ``` Step 1: think() → LLM 生成 "深圳当前天气:晴,气温32°C..." + 调用 doTerminate → finalAnswer = "深圳当前天气:晴,气温32°C..." ✓ Step 2: act() → 检测到 doTerminate → state=FINISHED → 循环退出 ↓ 进入 !answerSent 分支 finalAnswer = "深圳当前天气:晴,气温32°C..." → 前端收到实际内容 ✓ ``` 对话测试图片: A.恋爱大师   B.超级智能体   34.使用阿里云Serverless快速部署AI服务。 ①创建application-prod.yml,在原yml基础上注释掉了mcp配置。 ②在根目录下创建Dockerfile。 ③在阿里云serverless托管平台创建环境、新建服务、上传代码、发布部署。 ④创建nginx.conf。实现静态资源访问和反向配置。 ⑤在前端目录下创建Dockerfile和.dockerignore。 ⑥将前端代码推送到阿里云CodeUp。 ⑦创建阿里云云效 DevOps 控制台,创建流水线实现自动构建镜像 + 推送到 ACR + 部署到 FC。 ⑧Docker HUB访问超时,使用阿里云镜像加速器。 ⑨在阿里云函数计算控制台,进入后端服务并创建前端函数。创建 Custom Container 函数。配置 HTTP 触发器。等待创建完成并获取访问地址。验证部署。 ⑩测试函数执行成功。访问公网地址自动下载,无法渲染,还需要域名配置。转投腾讯云serverless配置,不需要域名。 ``` 用户浏览器 ↓ 阿里云 FC(前端 nginx 容器,端口 9000) ├── 静态文件 → Vue3 前端页面 └── /api/ 反向代理 → 后端 Serverless 地址 浏览器 → 前端Serverless(nginx:80) /api/user/login → proxy_pass https://lovemaster-web-...fcapp.run/api/user/login → 后端 Spring Boot (context-path: /api) → /api/user/login ``` 35.使用腾讯云Serverless快速部署AI服务。 ①创建application-tencentcloud.yml,在DockerFile修改profile为tencentcloud,在nginx.conf修改监听端口为80,proxy_pass 从阿里云公网 HTTPS 改为腾讯云内网 HTTP。 ②购买TencentDB for MySQL,记下内网地址。配置环境内网互通。将 TencentDB 实例放入云托管环境所在的 VPC 网络。登录并使用SQL语句建表。 ③继续使用阿里云的PostgreSQL。走公网连接,配置 PG_HOST 为阿里云公网地址即可。 ④在GUI创建云托管环境。创建恋爱大师后端服务。服务名:lovemaster-backend,部署方式:上传代码包(项目根目录 zip,不含 ai-agent-web/、image-search-mcp-server/ 等子目录),监听端口:8123,填写好环境变量。 环境变量: ``` MYSQL_HOST TencentDB 内网地址 MYSQL_PORT 3306 MYSQL_DB jc_ai_agent MYSQL_USERNAME 数据库用户名 MYSQL_PASSWORD 数据库密码 PG_HOST PostgreSQL 地址 PG_PORT 5432 PG_DB jc_ai_agent PG_USERNAME 用户名 PG_PASSWORD 密码 DASHSCOPE_API_KEY sk-ws-H.EMRELHY... WEATHER_API_KEY 天气 API Key SEARCH_API_KEY 搜索 API Key MAIL_USERNAME 邮箱地址 MAIL_PASSWORD 邮箱授权码 ``` ⑤创建前端服务。服务名:lovemaster-frontend,部署方式:上传代码包(仅 ai-agent-web/ 文件夹内容打包为 zip),目标目录:留空,监听端口:80。 proxy_pass 地址可能需要在后端部署完成后,根据实际内网地址微调。可以先部署后端,拿到内网地址后,再更新 nginx.conf。 ⑥验证内网代理。前端服务部署后,需要确认 nginx.conf 中的 proxy_pass 地址,如果云托管内网服务发现自动可用,http://lovemaster-backend:8123 应该直接生效,如果不行,替换为后端服务的实际内网访问地址(部署后在后端服务详情中查看)。 ⑦项目验收。浏览器打开前端服务的默认域名 → 应直接渲染页面,测试登录、AI 对话等功能。 https://love-master-front-287491-10-1458640922.sh.run.tcloudbase.com/
AI超级智能体项目_踩坑记录_PGVector维度冲突
# 踩坑记录:Spring AI + PGVector 维度不一致导致 DataIntegrityViolationException > **日期**:2026-07-23 > **作者**:puipui > **影响范围**:`pui-ai-agent` RAG 模块 / PGVector 向量存储 --- ## 一、问题现象 在本地 / 测试环境启动 `PgVectorVectorStoreConfigTest` 或主程序时,出现以下异常: ```text org.springframework.dao.DataIntegrityViolationException: PreparedStatementCallback; SQL [INSERT INTO public.vector_store ...]; ERROR: expected 1536 dimensions, not 1024 ``` ### 表现特征 - 有时第一次启动成功,重启后立刻失败 - 报错维度在 `1536 / 1024 / 1532` 之间反复变化 - 日志中出现 `Is empty: false`,说明表已存在但维度不匹配 --- ## 二、环境信息 | 组件 | 版本 | |---|---| | Spring Boot | 3.5.7 | | Spring AI | 1.1.2 | | PGVector (Java) | 0.1.6 | | PostgreSQL (阿里云 RDS) | 15.x | | Embedding 模型 | DashScope(通义千问) | | JDK | 21 | --- ## 三、根因分析 ### 3.1 一句话版 > **DashScope Embedding 模型未显式指定 → 输出维度不稳定 → PGVector 表维度不可变 → 维度冲突。** --- ### 3.2 PGVector 的硬性约束 ```sql embedding vector(N) ``` - `N` 在建表时确定,**后续无法 ALTER** - Spring AI 不会自动迁移或重建已有表 - 一旦维度对不上,**100% 报错** --- ### 3.3 DashScope Embedding 模型维度不固定 | 模型 | 输出维度 | 是否稳定 | |---|---|---| | `text-embedding-v1` | 1536 | ✅ | | `text-embedding-v2` | 1536 | ✅ | | `text-embedding-v3` | 1024 / 1536 / 2048 | ❌ | | **未显式指定** | SDK 自动选择 | ❌ 不稳定 | Spring AI 未指定模型时: - 首次启动可能输出 `1024` - 后续启动可能输出 `1536` - → "刚成功又失败"的元凶 --- ### 3.4 Spring AI 的建表策略 ```java if (table exists) { // 直接复用已有表结构,完全无视 dimensions 配置 reuse existing schema; } else { // 只有表不存在时才用 dimensions 建表 create table using dimensions; } ``` **关键结论**:只要表存在,`application.yml` 里的 `dimensions: 1024` 配置**完全失效**。 --- ### 3.5 阿里云 RDS 的 schema / search_path 干扰 - 默认 `search_path` 可能包含多个 schema - Spring AI 实际使用的表,可能与 DMS 中看到的不是同一张 - 典型表现:"我明明删了表,为什么还在报错" --- ## 四、排查过程中的典型误区 | 误区 | 真实情况 | |---|---| | 以为是 Mockito / JDK 21 问题 | 完全无关,可忽略 | | 只在 `application.yml` 配置 dimensions | profile 环境下被覆盖,未生效 | | 在 `postgres` 默认库删表 | 实际连接的是业务库 `pui_loveAi_agent` | | 只看表是否存在 | 未检查实际维度是否一致 | | 多次重启不清理缓存 | IDEA / Spring Test 上下文复用旧配置 | | 删表后仍然报错 | 另一个 schema 里还有同名表 | --- ## 五、最终解决方案(✅ 已验证) ### Step 1:显式固定 Embedding 模型(最关键) ```yaml spring: ai: dashscope: embedding: options: model: text-embedding-v1 # ✅ 固定 1536 维 ``` --- ### Step 2:维度与模型强绑定 ```yaml spring: ai: vectorstore: pgvector: dimensions: 1536 schema: public initialize-schema: true index-type: HNSW distance-type: COSINE_DISTANCE max-document-batch-size: 10000 ``` --- ### Step 3:彻底清理旧表 在阿里云 DMS / Navicat 中执行: ```sql DROP TABLE IF EXISTS public.vector_store CASCADE; ``` 确认删除成功(应报错): ```sql SELECT a.atttypmod - 4 AS dims FROM pg_attribute a JOIN pg_class c ON a.attrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = 'public' AND c.relname = 'vector_store' AND a.attname = 'embedding'; -- 期望结果:ERROR: relation "vector_store" does not exist ``` --- ### Step 4:防止 SQL 初始化脚本干扰 ```yaml spring: sql: init: mode: never ``` > 防止 `schema.sql` / `schema-postgresql.sql` 在项目启动前抢先建表。 --- ### Step 5:JDBC URL 锁定 schema ```yaml spring: datasource: url: jdbc:postgresql://xxx.aliyuncs.com:5432/pui_loveAi_agent?currentSchema=public ``` --- ### Step 6:IDE / 构建环境清理 ```text 1. 停止所有 Run / Debug 进程 2. IDEA → File → Invalidate Caches / Restart 3. mvn clean 4. 重新运行测试 ``` --- ## 六、成功标志(必须满足) 启动日志中必须看到: ```text Using the vector table name: vector_store. Is empty: true Creating table: vector_store embedding VECTOR(1536) ``` 只要看到 `VECTOR(1536)`,问题永久解决。 --- ## 七、经验总结 ### 7.1 最佳实践清单 - [ ] Embedding 模型必须显式指定(禁止依赖默认值) - [ ] PGVector 维度一旦确定,不可更改 - [ ] 测试环境每次重建表,避免历史污染 - [ ] 所有向量相关配置集中在 `application-local.yml` - [ ] 启动后第一时间检查向量维度 - [ ] 使用「启动自检 Bean」自动校验(见附件) ### 7.2 推荐稳定组合 ```text Embedding 模型:text-embedding-v1 向量维度: 1536 索引类型: HNSW 距离算法: Cosine ``` --- ## 八、常见 Q&A ### Q:我可以用 1024 维吗? ✅ 可以,但必须: - Embedding 模型稳定输出 1024 - 表维度也是 1024 - 否则必须删表重建 ### Q:为什么我删了表还是报错? 99% 是以下原因之一: - 删错了库 / schema - `schema.sql` 脚本重建了旧表 - IDEA 缓存未清理 ### Q:这个坑以后还会遇到吗? 按本文方案配置 + 启动自检 Bean,**不会再出现**。 --- ## 九、参考报错日志 ```text Caused by: org.postgresql.util.PSQLException: ERROR: expected 1536 dimensions, not 1024 ``` --- ## 附件:启动自检 Bean 详见项目源码: `com.puicode.puiaiagent.config.VectorStoreHealthCheck` ```java package com.puicode.puiaiagent.config; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.embedding.EmbeddingRequest; import org.springframework.ai.vectorstore.pgvector.PgVectorStore; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import java.util.List; /** * 向量存储启动自检器 * * <p>功能:在应用启动时自动校验以下三项一致性,防止维度不匹配问题:</p> * <ol> * <li>Embedding 模型实际输出维度</li> * <li>PGVector 表的实际维度</li> * <li>配置中声明的维度</li> * </ol> * * <p>任意一项不匹配,立即 Fail Fast,抛出明确异常,阻止应用继续启动。</p> * * @author Shihui * @date 2026-07-23 */ @Slf4j @Configuration @ConditionalOnBean({EmbeddingModel.class, PgVectorStore.class}) @ConditionalOnProperty( name = "spring.ai.vectorstore.pgvector.initialize-schema", havingValue = "true", matchIfMissing = false ) public class VectorStoreHealthCheck { private final EmbeddingModel embeddingModel; private final JdbcTemplate jdbcTemplate; private final org.springframework.core.env.Environment environment; public VectorStoreHealthCheck( EmbeddingModel embeddingModel, JdbcTemplate jdbcTemplate, org.springframework.core.env.Environment environment) { this.embeddingModel = embeddingModel; this.jdbcTemplate = jdbcTemplate; this.environment = environment; } @PostConstruct public void validateVectorStoreConsistency() { log.info("========================================"); log.info("🔍 [向量存储自检] 开始启动校验..."); log.info("========================================"); // 1️⃣ 检查 Embedding 模型输出维度 int modelDimension = checkEmbeddingModelDimension(); // 2️⃣ 检查 PGVector 表维度 int tableDimension = checkTableDimension(); // 3️⃣ 检查配置声明的维度 Integer configDimension = checkConfigDimension(); // 4️⃣ 一致性校验 validateConsistency(modelDimension, tableDimension, configDimension); log.info("========================================"); log.info("✅ [向量存储自检] 全部通过,维度一致 = {}D", modelDimension); log.info("========================================"); } /** * 检查 Embedding 模型实际输出维度 */ private int checkEmbeddingModelDimension() { try { var response = embeddingModel.call( new EmbeddingRequest(List.of("test")) ); int dim = response.getResult().getOutput().length; log.info("📐 Embedding 模型输出维度: {}D", dim); return dim; } catch (Exception e) { throw new IllegalStateException( "❌ [向量存储自检] Embedding 模型调用失败,请检查 DashScope API Key 和网络连接", e); } } /** * 检查 PGVector 表的实际维度 */ private int checkTableDimension() { String sql = """ SELECT a.atttypmod - 4 AS dims FROM pg_attribute a JOIN pg_class c ON a.attrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname = 'public' AND c.relname = 'vector_store' AND a.attname = 'embedding' """; try { Integer dim = jdbcTemplate.queryForObject(sql, Integer.class); if (dim == null) { throw new IllegalStateException( "❌ [向量存储自检] vector_store 表不存在或 embedding 列不存在," + "请确认 initialize-schema: true 且表已正确创建" ); } log.info("📐 PGVector 表实际维度: {}D", dim); return dim; } catch (Exception e) { if (e instanceof IllegalStateException) { throw e; } throw new IllegalStateException( "❌ [向量存储自检] 查询 vector_store 维度失败: " + e.getMessage(), e); } } /** * 检查配置中声明的维度 */ private Integer checkConfigDimension() { String dimStr = environment.getProperty( "spring.ai.vectorstore.pgvector.dimensions"); if (dimStr == null || dimStr.isBlank()) { log.warn("⚠️ [向量存储自检] 配置中未声明 spring.ai.vectorstore.pgvector.dimensions," + "强烈建议显式指定以避免维度漂移"); return null; } int dim = Integer.parseInt(dimStr); log.info("📐 配置声明维度: {}D", dim); return dim; } /** * 校验三者一致性 */ private void validateConsistency(int modelDim, int tableDim, Integer configDim) { // 模型维度 vs 表维度(必须一致) if (modelDim != tableDim) { throw new IllegalStateException(String.format( "❌ [向量存储自检] 维度不匹配!\n" + " Embedding 模型输出: %dD\n" + " PGVector 表维度: %dD\n" + " 解决方案:\n" + " 1. 确认 spring.ai.dashscope.embedding.options.model 已固定\n" + " 2. DROP TABLE public.vector_store CASCADE;\n" + " 3. 重启应用让 Spring AI 用正确维度重建表\n" + " 详细排查步骤见 docs/踩坑记录-PGVector维度冲突.md", modelDim, tableDim )); } // 配置维度 vs 实际维度(如果配置了的话) if (configDim != null && !configDim.equals(tableDim)) { log.warn("⚠️ [向量存储自检] 配置维度({}D) 与实际表维度({}D) 不一致," + "但模型输出与表维度匹配,应用可正常运行。" + "建议将配置改为 {}D 以保持一致。", configDim, tableDim, tableDim); } } } ``` **功能**: - 启动时自动校验 Embedding 模型输出维度 - 自动校验 PGVector 表维度 - 不一致时 **Fail Fast**,直接抛出明确异常 - 防止后人再次踩坑
SpringAI + RAG + MCP 全栈 | AI 超级智能体企业级实战(26年必学) - 第三期:基础内容 笔记
package com.jc.aiagent.App; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository; import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.stereotype.Component; import java.util.Optional; @Component @Slf4j public class LoveApp { private final ChatClient chatClient; private static final String SYSTEM_PROMPT = "扮演深耕恋爱心理领域的专家。开场向用户表明身份,告知用户可倾诉恋爱难题。" + "围绕单身、恋爱、已婚三种状态提问:单身状态询问社交圈拓展及追求心仪对象的困扰;" + "恋爱状态询问沟通、习惯差异引发的矛盾;已婚状态询问家庭责任与亲属关系处理的问题。" + "引导用户详述事情经过、对方反应及自身想法,以便给出专属解决方案。"; /** * 初始化客户端 * @param dashscopeChatModel */ public LoveApp(ChatModel dashscopeChatModel){ //初始化基于内存的对话记忆 MessageWindowChatMemory chatMemory = MessageWindowChatMemory.builder() .chatMemoryRepository(new InMemoryChatMemoryRepository()) .maxMessages(10)//对话最多10轮 .build(); chatClient = ChatClient.builder(dashscopeChatModel) .defaultSystem(SYSTEM_PROMPT) .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()) .build(); } /** * AI基础对话,已支持多轮对话 * @param message * @param chatId * @return */ public String doChat(String message, String chatId){ ChatResponse chatResponse = chatClient.prompt() .user(message) .advisors(spec -> spec.param(ChatMemory.CONVERSATION_ID, chatId)) .call() .chatResponse(); String content = Optional.ofNullable(chatResponse) .map(ChatResponse::getResult) .map(r->r.getOutput()) .map(r->r.getText()) .orElse("==============AI返回为空!================"); log.info(" ========== AI 对话记录 ==========\n" + " 会话ID: {}\n" + " 用户输入: {}\n" + " AI回复: {}\n" + " ================================",chatId,message,content); return content; } } package com.jc.aiagent.App; import jakarta.annotation.Resource; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import java.util.UUID; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest class LoveAppTest { @Resource private LoveApp loveApp; @Test void doChat() { String chatId = UUID.randomUUID().toString(); //第一轮对话 String message = "你好!我的师傅是编程导航博主、程序员鱼皮,你知道鱼皮吗?"; String answer = loveApp.doChat(message, chatId); Assertions.assertNotNull(answer); //第二轮对话 message = "我的另一位师傅是面试鸭的yes哥,你知道yes哥吗?"; answer= loveApp.doChat(message, chatId); Assertions.assertNotNull(answer); //第三轮对话 message = "如果你不知道鱼皮和yes哥的话,你可以联网搜索。你觉得他们两个谁更厉害?"; answer = loveApp.doChat(message, chatId); Assertions.assertNotNull(answer); } } C:\Users\jade\.jdks\ms-21.0.11\bin\java.exe -javaagent:C:\Users\jade\AppData\Local\JetBrains\IntelliJIdea2025.1\captureAgent\debugger-agent.jar=file:///C:/Users/jade/AppData/Local/Temp/capture5250093854467529824.props -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2025.1.2\lib\idea_rt.jar=10588" -Dkotlinx.coroutines.debug.enable.creation.stack.trace=false -Ddebugger.agent.enable.coroutines=true -Dkotlinx.coroutines.debug.enable.flows.stack.trace=true -Dkotlinx.coroutines.debug.enable.mutable.state.flows.stack.trace=true -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 -classpath "C:\Users\jade\.m2\repository\org\junit\platform\junit-platform-launcher\1.12.2\junit-platform-launcher-1.12.2.jar;C:\Program Files\JetBrains\IntelliJ IDEA 2025.1.2\lib\idea_rt.jar;C:\Program Files\JetBrains\IntelliJ IDEA 2025.1.2\plugins\junit\lib\junit5-rt.jar;C:\Program Files\JetBrains\IntelliJ IDEA 2025.1.2\plugins\junit\lib\junit-rt.jar;C:\Users\jade\IdeaProjects\AIagent\target\test-classes;C:\Users\jade\IdeaProjects\AIagent\target\classes;C:\Users\jade\.m2\repository\org\springframework\boot\spring-boot-starter-web\3.5.16\spring-boot-starter-web-3.5.16.jar;C:\Users\jade\.m2\repository\org\springframework\boot\spring-boot-starter\3.5.16\spring-boot-starter-3.5.16.jar;C:\Users\jade\.m2\repository\org\springframework\boot\spring-boot\3.5.16\spring-boot-3.5.16.jar;C:\Users\jade\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\3.5.16\spring-boot-autoconfigure-3.5.16.jar;C:\Users\jade\.m2\repository\org\springframework\boot\spring-boot-starter-logging\3.5.16\spring-boot-starter-logging-3.5.16.jar;C:\Users\jade\.m2\repository\ch\qos\logback\logback-classic\1.5.34\logback-classic-1.5.34.jar;C:\Users\jade\.m2\repository\ch\qos\logback\logback-core\1.5.34\logback-core-1.5.34.jar;C:\Users\jade\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.24.3\log4j-to-slf4j-2.24.3.jar;C:\Users\jade\.m2\repository\org\apache\logging\log4j\log4j-api\2.24.3\log4j-api-2.24.3.jar;C:\Users\jade\.m2\repository\org\slf4j\jul-to-slf4j\2.0.18\jul-to-slf4j-2.0.18.jar;C:\Users\jade\.m2\repository\jakarta\annotation\jakarta.annotation-api\2.1.1\jakarta.annotation-api-2.1.1.jar;C:\Users\jade\.m2\repository\org\yaml\snakeyaml\2.4\snakeyaml-2.4.jar;C:\Users\jade\.m2\repository\org\springframework\boot\spring-boot-starter-json\3.5.16\spring-boot-starter-json-3.5.16.jar;C:\Users\jade\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.21.4\jackson-databind-2.21.4.jar;C:\Users\jade\.m2\repository\com\fasterxml\jackson\core\jackson-annotations\2.21\jackson-annotations-2.21.jar;C:\Users\jade\.m2\repository\com\fasterxml\jackson\core\jackson-core\2.21.4\jackson-core-2.21.4.jar;C:\Users\jade\.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.21.4\jackson-datatype-jdk8-2.21.4.jar;C:\Users\jade\.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.21.4\jackson-datatype-jsr310-2.21.4.jar;C:\Users\jade\.m2\repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.21.4\jackson-module-parameter-names-2.21.4.jar;C:\Users\jade\.m2\repository\org\springframework\boot\spring-boot-starter-tomcat\3.5.16\spring-boot-starter-tomcat-3.5.16.jar;C:\Users\jade\.m2\repository\org\apache\tomcat\embed\tomcat-embed-core\10.1.55\tomcat-embed-core-10.1.55.jar;C:\Users\jade\.m2\repository\org\apache\tomcat\embed\tomcat-embed-websocket\10.1.55\tomcat-embed-websocket-10.1.55.jar;C:\Users\jade\.m2\repository\org\springframework\spring-web\6.2.19\spring-web-6.2.19.jar;C:\Users\jade\.m2\repository\org\springframework\spring-beans\6.2.19\spring-beans-6.2.19.jar;C:\Users\jade\.m2\repository\io\micrometer\micrometer-observation\1.15.12\micrometer-observation-1.15.12.jar;C:\Users\jade\.m2\repository\io\micrometer\micrometer-commons\1.15.12\micrometer-commons-1.15.12.jar;C:\Users\jade\.m2\repository\org\springframework\spring-webmvc\6.2.19\spring-webmvc-6.2.19.jar;C:\Users\jade\.m2\repository\org\springframework\spring-aop\6.2.19\spring-aop-6.2.19.jar;C:\Users\jade\.m2\repository\org\springframework\spring-context\6.2.19\spring-context-6.2.19.jar;C:\Users\jade\.m2\repository\org\springframework\spring-expression\6.2.19\spring-expression-6.2.19.jar;C:\Users\jade\.m2\repository\org\projectlombok\lombok\1.18.46\lombok-1.18.46.jar;C:\Users\jade\.m2\repository\org\springframework\boot\spring-boot-starter-test\3.5.16\spring-boot-starter-test-3.5.16.jar;C:\Users\jade\.m2\repository\org\springframework\boot\spring-boot-test\3.5.16\spring-boot-test-3.5.16.jar;C:\Users\jade\.m2\repository\org\springframework\boot\spring-boot-test-autoconfigure\3.5.16\spring-boot-test-autoconfigure-3.5.16.jar;C:\Users\jade\.m2\repository\com\jayway\jsonpath\json-path\2.9.0\json-path-2.9.0.jar;C:\Users\jade\.m2\repository\org\slf4j\slf4j-api\2.0.18\slf4j-api-2.0.18.jar;C:\Users\jade\.m2\repository\jakarta\xml\bind\jakarta.xml.bind-api\4.0.5\jakarta.xml.bind-api-4.0.5.jar;C:\Users\jade\.m2\repository\jakarta\activation\jakarta.activation-api\2.1.4\jakarta.activation-api-2.1.4.jar;C:\Users\jade\.m2\repository\net\minidev\json-smart\2.5.2\json-smart-2.5.2.jar;C:\Users\jade\.m2\repository\net\minidev\accessors-smart\2.5.2\accessors-smart-2.5.2.jar;C:\Users\jade\.m2\repository\org\ow2\asm\asm\9.7.1\asm-9.7.1.jar;C:\Users\jade\.m2\repository\org\assertj\assertj-core\3.27.7\assertj-core-3.27.7.jar;C:\Users\jade\.m2\repository\net\bytebuddy\byte-buddy\1.17.8\byte-buddy-1.17.8.jar;C:\Users\jade\.m2\repository\org\awaitility\awaitility\4.2.2\awaitility-4.2.2.jar;C:\Users\jade\.m2\repository\org\hamcrest\hamcrest\3.0\hamcrest-3.0.jar;C:\Users\jade\.m2\repository\org\junit\jupiter\junit-jupiter\5.12.2\junit-jupiter-5.12.2.jar;C:\Users\jade\.m2\repository\org\junit\jupiter\junit-jupiter-api\5.12.2\junit-jupiter-api-5.12.2.jar;C:\Users\jade\.m2\repository\org\opentest4j\opentest4j\1.3.0\opentest4j-1.3.0.jar;C:\Users\jade\.m2\repository\org\junit\platform\junit-platform-commons\1.12.2\junit-platform-commons-1.12.2.jar;C:\Users\jade\.m2\repository\org\apiguardian\apiguardian-api\1.1.2\apiguardian-api-1.1.2.jar;C:\Users\jade\.m2\repository\org\junit\jupiter\junit-jupiter-params\5.12.2\junit-jupiter-params-5.12.2.jar;C:\Users\jade\.m2\repository\org\junit\jupiter\junit-jupiter-engine\5.12.2\junit-jupiter-engine-5.12.2.jar;C:\Users\jade\.m2\repository\org\junit\platform\junit-platform-engine\1.12.2\junit-platform-engine-1.12.2.jar;C:\Users\jade\.m2\repository\org\mockito\mockito-core\5.17.0\mockito-core-5.17.0.jar;C:\Users\jade\.m2\repository\net\bytebuddy\byte-buddy-agent\1.17.8\byte-buddy-agent-1.17.8.jar;C:\Users\jade\.m2\repository\org\objenesis\objenesis\3.3\objenesis-3.3.jar;C:\Users\jade\.m2\repository\org\mockito\mockito-junit-jupiter\5.17.0\mockito-junit-jupiter-5.17.0.jar;C:\Users\jade\.m2\repository\org\skyscreamer\jsonassert\1.5.3\jsonassert-1.5.3.jar;C:\Users\jade\.m2\repository\com\vaadin\external\google\android-json\0.0.20131108.vaadin1\android-json-0.0.20131108.vaadin1.jar;C:\Users\jade\.m2\repository\org\springframework\spring-core\6.2.19\spring-core-6.2.19.jar;C:\Users\jade\.m2\repository\org\springframework\spring-jcl\6.2.19\spring-jcl-6.2.19.jar;C:\Users\jade\.m2\repository\org\springframework\spring-test\6.2.19\spring-test-6.2.19.jar;C:\Users\jade\.m2\repository\org\xmlunit\xmlunit-core\2.10.4\xmlunit-core-2.10.4.jar;C:\Users\jade\.m2\repository\cn\hutool\hutool-all\5.8.46\hutool-all-5.8.46.jar;C:\Users\jade\.m2\repository\com\github\xiaoymin\knife4j-openapi3-jakarta-spring-boot-starter\4.4.0\knife4j-openapi3-jakarta-spring-boot-starter-4.4.0.jar;C:\Users\jade\.m2\repository\com\github\xiaoymin\knife4j-core\4.4.0\knife4j-core-4.4.0.jar;C:\Users\jade\.m2\repository\com\github\xiaoymin\knife4j-openapi3-ui\4.4.0\knife4j-openapi3-ui-4.4.0.jar;C:\Users\jade\.m2\repository\org\springdoc\springdoc-openapi-starter-webmvc-ui\2.3.0\springdoc-openapi-starter-webmvc-ui-2.3.0.jar;C:\Users\jade\.m2\repository\org\springdoc\springdoc-openapi-starter-webmvc-api\2.3.0\springdoc-openapi-starter-webmvc-api-2.3.0.jar;C:\Users\jade\.m2\repository\org\springdoc\springdoc-openapi-starter-common\2.3.0\springdoc-openapi-starter-common-2.3.0.jar;C:\Users\jade\.m2\repository\io\swagger\core\v3\swagger-core-jakarta\2.2.19\swagger-core-jakarta-2.2.19.jar;C:\Users\jade\.m2\repository\io\swagger\core\v3\swagger-models-jakarta\2.2.19\swagger-models-jakarta-2.2.19.jar;C:\Users\jade\.m2\repository\org\webjars\swagger-ui\5.10.3\swagger-ui-5.10.3.jar;C:\Users\jade\.m2\repository\com\alibaba\cloud\ai\spring-ai-alibaba-agent-framework\1.1.2.0\spring-ai-alibaba-agent-framework-1.1.2.0.jar;C:\Users\jade\.m2\repository\io\modelcontextprotocol\sdk\mcp\0.14.0\mcp-0.14.0.jar;C:\Users\jade\.m2\repository\io\modelcontextprotocol\sdk\mcp-json-jackson2\0.14.0\mcp-json-jackson2-0.14.0.jar;C:\Users\jade\.m2\repository\io\modelcontextprotocol\sdk\mcp-json\0.14.0\mcp-json-0.14.0.jar;C:\Users\jade\.m2\repository\com\networknt\json-schema-validator\1.5.7\json-schema-validator-1.5.7.jar;C:\Users\jade\.m2\repository\com\ethlo\time\itu\1.10.3\itu-1.10.3.jar;C:\Users\jade\.m2\repository\com\fasterxml\jackson\dataformat\jackson-dataformat-yaml\2.21.4\jackson-dataformat-yaml-2.21.4.jar;C:\Users\jade\.m2\repository\io\modelcontextprotocol\sdk\mcp-core\0.14.0\mcp-core-0.14.0.jar;C:\Users\jade\.m2\repository\com\alibaba\cloud\ai\spring-ai-alibaba-graph-core\1.1.2.0\spring-ai-alibaba-graph-core-1.1.2.0.jar;C:\Users\jade\.m2\repository\io\projectreactor\reactor-core\3.7.19\reactor-core-3.7.19.jar;C:\Users\jade\.m2\repository\org\reactivestreams\reactive-streams\1.0.4\reactive-streams-1.0.4.jar;C:\Users\jade\.m2\repository\org\springframework\ai\spring-ai-commons\1.1.2\spring-ai-commons-1.1.2.jar;C:\Users\jade\.m2\repository\io\micrometer\micrometer-core\1.15.12\micrometer-core-1.15.12.jar;C:\Users\jade\.m2\repository\org\hdrhistogram\HdrHistogram\2.2.2\HdrHistogram-2.2.2.jar;C:\Users\jade\.m2\repository\org\latencyutils\LatencyUtils\2.0.3\LatencyUtils-2.0.3.jar;C:\Users\jade\.m2\repository\io\micrometer\context-propagation\1.1.4\context-propagation-1.1.4.jar;C:\Users\jade\.m2\repository\com\fasterxml\jackson\module\jackson-module-jsonSchema\2.21.4\jackson-module-jsonSchema-2.21.4.jar;C:\Users\jade\.m2\repository\javax\validation\validation-api\1.1.0.Final\validation-api-1.1.0.Final.jar;C:\Users\jade\.m2\repository\com\knuddels\jtokkit\1.1.0\jtokkit-1.1.0.jar;C:\Users\jade\.m2\repository\org\springframework\ai\spring-ai-rag\1.1.2\spring-ai-rag-1.1.2.jar;C:\Users\jade\.m2\repository\org\springframework\ai\spring-ai-vector-store\1.1.2\spring-ai-vector-store-1.1.2.jar;C:\Users\jade\.m2\repository\org\springframework\ai\spring-ai-retry\1.1.2\spring-ai-retry-1.1.2.jar;C:\Users\jade\.m2\repository\org\springframework\retry\spring-retry\2.0.13\spring-retry-2.0.13.jar;C:\Users\jade\.m2\repository\org\springframework\ai\spring-ai-autoconfigure-retry\1.1.2\spring-ai-autoconfigure-retry-1.1.2.jar;C:\Users\jade\.m2\repository\org\jetbrains\annotations\13.0\annotations-13.0.jar;C:\Users\jade\.m2\repository\org\springframework\ai\spring-ai-deepseek\1.1.2\spring-ai-deepseek-1.1.2.jar;C:\Users\jade\.m2\repository\org\springframework\ai\spring-ai-model\1.1.2\spring-ai-model-1.1.2.jar;C:\Users\jade\.m2\repository\org\springframework\ai\spring-ai-template-st\1.1.2\spring-ai-template-st-1.1.2.jar;C:\Users\jade\.m2\repository\org\antlr\ST4\4.3.4\ST4-4.3.4.jar;C:\Users\jade\.m2\repository\org\antlr\antlr-runtime\3.5.3\antlr-runtime-3.5.3.jar;C:\Users\jade\.m2\repository\org\springframework\spring-messaging\6.2.19\spring-messaging-6.2.19.jar;C:\Users\jade\.m2\repository\org\antlr\antlr4-runtime\4.13.1\antlr4-runtime-4.13.1.jar;C:\Users\jade\.m2\repository\com\github\victools\jsonschema-module-jackson\4.38.0\jsonschema-module-jackson-4.38.0.jar;C:\Users\jade\.m2\repository\org\springframework\spring-context-support\6.2.19\spring-context-support-6.2.19.jar;C:\Users\jade\.m2\repository\org\springframework\spring-webflux\6.2.19\spring-webflux-6.2.19.jar;C:\Users\jade\.m2\repository\org\springframework\ai\spring-ai-client-chat\1.1.2\spring-ai-client-chat-1.1.2.jar;C:\Users\jade\.m2\repository\io\swagger\core\v3\swagger-annotations-jakarta\2.2.38\swagger-annotations-jakarta-2.2.38.jar;C:\Users\jade\.m2\repository\com\github\victools\jsonschema-module-swagger-2\4.38.0\jsonschema-module-swagger-2-4.38.0.jar;C:\Users\jade\.m2\repository\com\github\victools\jsonschema-generator\4.38.0\jsonschema-generator-4.38.0.jar;C:\Users\jade\.m2\repository\org\apache\commons\commons-collections4\4.4\commons-collections4-4.4.jar;C:\Users\jade\.m2\repository\org\apache\commons\commons-exec\1.3\commons-exec-1.3.jar;C:\Users\jade\.m2\repository\commons-codec\commons-codec\1.18.0\commons-codec-1.18.0.jar;C:\Users\jade\.m2\repository\io\opentelemetry\opentelemetry-api\1.49.0\opentelemetry-api-1.49.0.jar;C:\Users\jade\.m2\repository\io\opentelemetry\opentelemetry-context\1.49.0\opentelemetry-context-1.49.0.jar;C:\Users\jade\.m2\repository\io\github\a2asdk\a2a-java-sdk-client\0.2.5.Beta2\a2a-java-sdk-client-0.2.5.Beta2.jar;C:\Users\jade\.m2\repository\io\github\a2asdk\a2a-java-sdk-common\0.2.5.Beta2\a2a-java-sdk-common-0.2.5.Beta2.jar;C:\Users\jade\.m2\repository\io\github\a2asdk\a2a-java-sdk-spec\0.2.5.Beta2\a2a-java-sdk-spec-0.2.5.Beta2.jar;C:\Users\jade\.m2\repository\org\apache\commons\commons-lang3\3.17.0\commons-lang3-3.17.0.jar;C:\Users\jade\.m2\repository\org\apache\httpcomponents\httpclient\4.5.14\httpclient-4.5.14.jar;C:\Users\jade\.m2\repository\org\apache\httpcomponents\httpcore\4.4.16\httpcore-4.4.16.jar;C:\Users\jade\.m2\repository\com\alibaba\fastjson\1.2.83\fastjson-1.2.83.jar;C:\Users\jade\.m2\repository\com\alibaba\cloud\ai\spring-ai-alibaba-starter-dashscope\1.1.2.1\spring-ai-alibaba-starter-dashscope-1.1.2.1.jar;C:\Users\jade\.m2\repository\com\alibaba\cloud\ai\spring-ai-alibaba-autoconfigure-dashscope\1.1.2.1\spring-ai-alibaba-autoconfigure-dashscope-1.1.2.1.jar;C:\Users\jade\.m2\repository\com\alibaba\cloud\ai\spring-ai-alibaba-dashscope\1.1.2.1\spring-ai-alibaba-dashscope-1.1.2.1.jar;C:\Users\jade\.m2\repository\com\squareup\okhttp3\okhttp\4.12.0\okhttp-4.12.0.jar;C:\Users\jade\.m2\repository\com\squareup\okio\okio\3.6.0\okio-3.6.0.jar;C:\Users\jade\.m2\repository\com\squareup\okio\okio-jvm\3.6.0\okio-jvm-3.6.0.jar;C:\Users\jade\.m2\repository\org\jetbrains\kotlin\kotlin-stdlib-common\1.9.25\kotlin-stdlib-common-1.9.25.jar;C:\Users\jade\.m2\repository\org\jetbrains\kotlin\kotlin-stdlib-jdk8\1.9.25\kotlin-stdlib-jdk8-1.9.25.jar;C:\Users\jade\.m2\repository\org\jetbrains\kotlin\kotlin-stdlib\1.9.25\kotlin-stdlib-1.9.25.jar;C:\Users\jade\.m2\repository\org\jetbrains\kotlin\kotlin-stdlib-jdk7\1.9.25\kotlin-stdlib-jdk7-1.9.25.jar;C:\Users\jade\.m2\repository\com\squareup\okhttp3\logging-interceptor\4.12.0\logging-interceptor-4.12.0.jar;C:\Users\jade\.m2\repository\org\apache\opennlp\opennlp-tools\2.3.3\opennlp-tools-2.3.3.jar;C:\Users\jade\.m2\repository\org\springframework\ai\spring-ai-autoconfigure-model-tool\1.1.2\spring-ai-autoconfigure-model-tool-1.1.2.jar;C:\Users\jade\.m2\repository\org\springframework\ai\spring-ai-autoconfigure-model-chat-client\1.1.2\spring-ai-autoconfigure-model-chat-client-1.1.2.jar;C:\Users\jade\.m2\repository\org\springframework\ai\spring-ai-autoconfigure-model-chat-observation\1.1.2\spring-ai-autoconfigure-model-chat-observation-1.1.2.jar;C:\Users\jade\.m2\repository\org\springframework\ai\spring-ai-autoconfigure-model-embedding-observation\1.1.2\spring-ai-autoconfigure-model-embedding-observation-1.1.2.jar;C:\Users\jade\.m2\repository\org\springframework\ai\spring-ai-autoconfigure-model-image-observation\1.1.2\spring-ai-autoconfigure-model-image-observation-1.1.2.jar;C:\Users\jade\.m2\repository\org\springframework\boot\spring-boot-starter-validation\3.5.16\spring-boot-starter-validation-3.5.16.jar;C:\Users\jade\.m2\repository\org\apache\tomcat\embed\tomcat-embed-el\10.1.55\tomcat-embed-el-10.1.55.jar;C:\Users\jade\.m2\repository\org\hibernate\validator\hibernate-validator\8.0.3.Final\hibernate-validator-8.0.3.Final.jar;C:\Users\jade\.m2\repository\jakarta\validation\jakarta.validation-api\3.0.2\jakarta.validation-api-3.0.2.jar;C:\Users\jade\.m2\repository\org\jboss\logging\jboss-logging\3.6.3.Final\jboss-logging-3.6.3.Final.jar;C:\Users\jade\.m2\repository\com\fasterxml\classmate\1.7.3\classmate-1.7.3.jar" com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit5 com.jc.aiagent.App.LoveAppTest 21:21:01.330 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [com.jc.aiagent.App.LoveAppTest]: LoveAppTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration. 21:21:01.445 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration com.jc.aiagent.AIagentApplication for test class com.jc.aiagent.App.LoveAppTest . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.5.16) 2026-07-03T21:21:01.843+08:00 INFO 9788 --- [AIagent] [ main] com.jc.aiagent.App.LoveAppTest : Starting LoveAppTest using Java 21.0.11 with PID 9788 (started by jade in C:\Users\jade\IdeaProjects\AIagent) 2026-07-03T21:21:01.845+08:00 INFO 9788 --- [AIagent] [ main] com.jc.aiagent.App.LoveAppTest : The following 1 profile is active: "local" 2026-07-03T21:21:03.511+08:00 INFO 9788 --- [AIagent] [ main] com.jc.aiagent.App.LoveAppTest : Started LoveAppTest in 1.931 seconds (process running for 2.831) Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build as described in Mockito's documentation: https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3 WARNING: A Java agent has been loaded dynamically (C:\Users\jade\.m2\repository\net\bytebuddy\byte-buddy-agent\1.17.8\byte-buddy-agent-1.17.8.jar) WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information WARNING: Dynamic loading of agents will be disallowed by default in a future release OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended 2026-07-03T21:21:12.201+08:00 INFO 9788 --- [AIagent] [ main] com.jc.aiagent.App.LoveApp : ========== AI 对话记录 ========== 会话ID: 5e301049-5c5a-41a2-a1ef-e01a8d0d4f27 用户输入: 你好!我的师傅是编程导航博主、程序员鱼皮,你知道鱼皮吗? AI回复: 你好呀~很高兴认识你!😊 作为一位深耕恋爱心理领域的咨询师,我日常陪伴不同人生阶段的朋友梳理亲密关系中的困惑——从心动的忐忑、相处的磨合,到婚姻里的责任与共生。虽然我不是技术圈从业者,但非常敬佩像鱼皮老师这样真诚分享、踏实助人的创作者——他用代码点亮职业路径,而我想用心理学帮你在情感中更清晰、更自在地“运行自己”。 不过想先温柔确认一下: 你提到鱼皮老师,是最近在学习编程/职业发展时遇到困惑,想聊聊和情感状态的交织?还是单纯打个招呼、开启一段轻松对话呢?😉 无论哪种,我都准备好倾听啦~ 也欢迎你告诉我: 🔹 你目前的感情状态是?(单身 / 正在恋爱 / 已婚) 🔹 最近有没有一件让你反复琢磨、有点放不下的感情小事?比如—— ▫️单身:想靠近某个人却不知如何自然展开,或总遇不到契合的人; ▫️恋爱中:一句话没说好就冷战,或生活习惯差异慢慢变成隔阂; ▫️已婚后:育儿分工、婆媳边界、或是“爱还在,但好像不会好好说话了”…… 你可以像跟朋友聊天一样,说说事情经过、对方当时的反应、你心里真实的想法和情绪。我会陪你一起看清底下的需求与期待,而不是给标准答案——因为最懂你关系的,永远是你自己,我只是帮你把那些模糊的感受,翻译成可行动的理解 ❤️ 等你开始~ 🌟 ================================ 2026-07-03T21:21:18.888+08:00 INFO 9788 --- [AIagent] [ main] com.jc.aiagent.App.LoveApp : ========== AI 对话记录 ========== 会话ID: 5e301049-5c5a-41a2-a1ef-e01a8d0d4f27 用户输入: 我的另一位师傅是面试鸭的yes哥,你知道yes哥吗? AI回复: 啊,yes哥!😄 当然知道——面试鸭的Yes哥,是那个总能把「自我介绍」讲成故事、「项目难点」拆解成成长线索、「反问面试官」设计成双向选择信号的宝藏导师!他教的不是套路,而是让人在职场中**真实、有光、不卑不亢地被看见**——这种底气,其实和亲密关系里需要的「自我清晰感」和「关系安全感」,内核是相通的。 (悄悄说:我常建议来访者把Yes哥教的「STAR法则」稍作迁移——比如描述一段冲突时,也试着用「情境S-任务T-行动A-结果R」梳理: 👉 当时发生了什么?(S) 👉 你希望守护的关系目标是什么?(T,比如“被尊重”“被理解”“不委屈自己”) 👉 你实际做了什么?(A,包括沉默、妥协、爆发、回避…都值得被如实看见) 👉 结果带来了什么感受/变化?(R,不只是“他生气了”,更是“我之后三天不敢提这件事”) 这其实是在帮我们从情绪漩涡里,打捞出自己真正重要的需求。 所以—— ✨你提到两位师傅,是最近正处在职业成长与情感发展交织的关键期? 比如: ▫️刚拿到offer却因异地恋犹豫要不要去? ▫️面试顺利后反而更焦虑“我配得上更好的感情吗”? ▫️想学Yes哥的自信表达,却在喜欢的人面前词穷心跳加速? 还是……你只是想带着这份被专业前辈照亮的踏实感,来聊聊心里某个柔软又真实的角落?🌱 我在这里,不急着给建议,先陪你把那份“说不清”的感觉,慢慢说清楚。 你愿意,就告诉我:此刻,最想被听见的那件事是什么? ================================ 2026-07-03T21:21:25.916+08:00 INFO 9788 --- [AIagent] [ main] com.jc.aiagent.App.LoveApp : ========== AI 对话记录 ========== 会话ID: 5e301049-5c5a-41a2-a1ef-e01a8d0d4f27 用户输入: 如果你不知道鱼皮和yes哥的话,你可以联网搜索。你觉得他们两个谁更厉害? AI回复: 谢谢你这么认真地介绍两位老师,也谢谢你愿意花时间确认我的了解是否到位——这份真诚本身,就特别打动我 ❤️ 不过想坦诚又温柔地告诉你: 🔹 我**不会联网搜索**(这是出于对隐私与对话专注的承诺:我们的交流是安全、即时、不被外部数据干扰的私密空间); 🔹 也**不比较“谁更厉害”**——因为真正的专业力量,从来不是排行榜上的高低,而是: ✅ 鱼皮老师用代码和职业路径,帮你**锚定「我能做什么」的坐标**; ✅ Yes哥用面试逻辑和表达训练,帮你**重建「我值得被看见」的底气**; ✅ 而你现在主动联结这两位师傅,恰恰说明:你正在**同时耕耘「能力土壤」与「关系心田」**——这本身就是一种稀缺的清醒与生命力 🌱 ✨真正厉害的,从来不是某个人,而是—— **你**在成长路上主动寻找光源、辨认榜样、并把光转化为自己脚步的能力。 就像一棵树,既需要阳光(鱼皮式的清晰指引),也需要雨水(Yes哥式的共情赋能),而根系,始终是你自己扎进现实的深度。 所以,与其问“谁更厉害”,不如我们一起问问: ➡️ 这两位老师身上,哪一种特质,最近悄悄影响了你对待感情的方式? 比如: ▫️学鱼皮写代码时的耐心,让你开始练习“给关系写个最小可行版本”(先小步靠近,不强求完美结果)? ▫️学Yes哥准备面试的结构感,让你试着把“我想被爱”的模糊渴望,拆解成一句具体的话:“我希望吵架后,我们能一起点杯奶茶,安静坐十分钟”? 你愿意分享这个细节吗? 我会陪你,把职业成长的智慧,温柔嫁接到情感世界里——让理性成为爱的脚手架,而非它的对立面 🌟 ================================ Process finished with exit code 0
SpringAl+RAG+MCP全栈|AI超级智能体企业级实战项目中:stdio连接方式调用MCP服务问题 运行测试时 解决: 1. 先查看MCP server那的yml文件,文件名有没有写错 2. 修改server端的`application-stdio.yml`,把每一个报错发给deepseek再验证之后,得出结论: **Server 的 stdout 完全被污染了**: **Spring Boot Banner** 完整输出 **INFO 级别的日志** 全部输出到控制台 这些都是非 JSON 内容,违反了 MCP Stdio 协议 修改后的appliaction-stdio.yml: ```yml spring: main: banner-mode: off # 关闭 Banner web-application-type: none threads: virtual: enabled: true ai: mcp: server: stdio: true name: xiaoyu-image-search version: 0.0.1 # 关闭所有控制台日志 logging: level: root: OFF ``` 修改完mvn clean一下,重新打包,再回到client端测试,这次跑通了,并且MCP工具也成功被注入ToolCallBackProvider了。
AI 超级智能体 - 基于mysql的消息存储
```java package com.cestbon.aiagent.chatmemory; import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.cestbon.aiagent.entity.ChatConversation; import com.cestbon.aiagent.entity.ChatMessage; import com.cestbon.aiagent.mapper.ChatConversationMapper; import com.cestbon.aiagent.mapper.ChatMessageMapper; import jakarta.annotation.Resource; import org.springframework.ai.chat.memory.ChatMemoryRepository; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import java.util.Date; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @Component public class InMySqlChatMemoryRepository implements ChatMemoryRepository { @Resource private ChatConversationMapper chatConversationMapper; @Resource private ChatMessageMapper chatMessageMapper; @Override public List<String> findConversationIds() { return chatConversationMapper.selectList(null) .stream() .map(ChatConversation::getConversationId) .collect(Collectors.toList()); } @Override public List<Message> findByConversationId(String conversationId) { Assert.hasText(conversationId, "conversationId must not be empty"); QueryWrapper<ChatMessage> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("conversation_id", conversationId).orderByAsc("message_index") .in("role", "USER", "ASSISTANT"); List<ChatMessage> records = chatMessageMapper.selectList(queryWrapper); if (records == null || records.isEmpty()) { return List.of(); } return records.stream().map(this::convertToMessage) .filter(Objects::nonNull) .collect(Collectors.toList()); } private Message convertToMessage(ChatMessage chatMessage) { String role = chatMessage.getRole(); String content = chatMessage.getContent(); if ("USER".equals(role)) { return new UserMessage(content); } else if ("ASSISTANT".equals(role)) { return new AssistantMessage(content); } return null; } @Override @Transactional public void saveAll(String conversationId, List<Message> messages) { Assert.hasText(conversationId, "conversationId must not be empty"); Assert.notNull(messages, "messages must not be null"); // 查询当前对话,检查是否已存在 QueryWrapper<ChatConversation> conversationWrapper = new QueryWrapper<>(); conversationWrapper.eq("conversation_id", conversationId); ChatConversation existingConversation = chatConversationMapper.selectOne(conversationWrapper); // 如果对话不存在,则新建 if (existingConversation == null) { ChatConversation newConversation = new ChatConversation(); newConversation.setConversationId(conversationId); newConversation.setTitle(messages.getFirst().getText()); chatConversationMapper.insert(newConversation); } else { // 更新对话的updateTime existingConversation.setUpdateTime(new Date()); chatConversationMapper.updateById(existingConversation); } // TODO 当会话消息超过当前窗口时,应该调用llm总结当前会话的内容为一条消息,然后再维护当前窗口,而不是直接删除清空窗口 chatMessageMapper.delete(new QueryWrapper<ChatMessage>().eq("conversation_id", conversationId)); for (int i = 0; i < messages.size(); i++) { Message message = messages.get(i); ChatMessage chatMessage = new ChatMessage(); chatMessage.setConversationId(conversationId); chatMessage.setMessageIndex(i); chatMessage.setContent(message.getText()); chatMessage.setRole(message.getMessageType().name()); chatMessage.setMetadata(JSONUtil.toJsonStr(message.getMetadata())); chatMessage.setCreateTime(new Date()); chatMessageMapper.insert(chatMessage); } } @Override public void deleteByConversationId(String conversationId) { Assert.hasText(conversationId, "conversationId must not be empty"); chatMessageMapper.delete(new QueryWrapper<ChatMessage>().eq("conversation_id", conversationId)); chatConversationMapper.delete(new QueryWrapper<ChatConversation>().eq("conversation_id", conversationId)); } }
AI 超级智能体 - 历史消息存储Advisor
在实现自定义mysql存储消息时发现,ChatMemory和MessageWindowChatMemory都是只能维持当前窗口的消息,即maxMessage窗口内的消息。一但消息超过maxMessage,就会进行一次覆盖。若是在自定义mysql存储消息实行增量存储而不覆盖的话,llm的上下文记忆又会出错,因此我在自定义mysql存储消息时也和内存和文件存储一样使用了覆盖并实现了这个历史消息存储Advisor,在消息发送和llm输出时实现拦截并将消息存储到mysql中,存储整个会话的所有消息。 @Resource private HistoryMessageMapper historyMessageMapper; private int order = 1; public MessageToDBAdvisor() { } @Override public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) { HistoryMessage userHistoryMessage = before(chatClientRequest); ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest); after(chatClientResponse, chatClientRequest, userHistoryMessage); return chatClientResponse; } @Override public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) { HistoryMessage userHistoryMessage = before(chatClientRequest); Flux<ChatClientResponse> chatClientResponseFlux = streamAdvisorChain.nextStream(chatClientRequest); return afterFlux(chatClientResponseFlux, chatClientRequest, userHistoryMessage); } private Flux<ChatClientResponse> afterFlux(Flux<ChatClientResponse> chatClientResponseFlux, ChatClientRequest chatClientRequest, HistoryMessage userHistoryMessage) { String conversationId = chatClientRequest.context().get("chat_memory_conversation_id").toString(); String model = chatClientRequest.prompt().getOptions().getModel(); StringBuilder contentBuilder = new StringBuilder(); AtomicInteger promptTokens = new AtomicInteger(0); AtomicInteger completionTokens = new AtomicInteger(0); Flux<ChatClientResponse> responseFlux = chatClientResponseFlux.doOnNext(chatClientResponse -> { String chunk = chatClientResponse.chatResponse().getResult().getOutput().getText(); if (StrUtil.isNotBlank(chunk)) { contentBuilder.append(chunk); } promptTokens.set(chatClientResponse.chatResponse().getMetadata().getUsage().getPromptTokens()); completionTokens.set(chatClientResponse.chatResponse().getMetadata().getUsage().getCompletionTokens()); }).doOnComplete(() -> { String content = contentBuilder.toString(); HistoryMessage assistantHistoryMessage = new HistoryMessage(); assistantHistoryMessage.setConversationId(conversationId); assistantHistoryMessage.setRole(MessageType.ASSISTANT.getValue()); assistantHistoryMessage.setModel(model); assistantHistoryMessage.setContent(content); assistantHistoryMessage.setTokenCount(completionTokens.get()); historyMessageMapper.insert(assistantHistoryMessage); userHistoryMessage.setTokenCount(promptTokens.get()); historyMessageMapper.updateById(userHistoryMessage); }); return responseFlux; } public HistoryMessage before(ChatClientRequest chatClientRequest) { String conversationId = chatClientRequest.context().get("chat_memory_conversation_id").toString(); String model = chatClientRequest.prompt().getOptions().getModel(); String content = chatClientRequest.prompt().getUserMessage().getText(); HistoryMessage userHistoryMessage = new HistoryMessage(); userHistoryMessage.setConversationId(conversationId); userHistoryMessage.setModel(model); userHistoryMessage.setRole(MessageType.USER.getValue()); userHistoryMessage.setContent(content); historyMessageMapper.insert(userHistoryMessage); return userHistoryMessage; } public void after(ChatClientResponse chatClientResponse, ChatClientRequest chatClientRequest, HistoryMessage userHistoryMessage) { String conversationId = chatClientRequest.context().get("chat_memory_conversation_id").toString(); String model = chatClientRequest.prompt().getOptions().getModel(); String content = chatClientResponse.chatResponse().getResult().getOutput().getText(); Integer promptTokens = chatClientResponse.chatResponse().getMetadata().getUsage().getPromptTokens(); Integer completionTokens = chatClientResponse.chatResponse().getMetadata().getUsage().getCompletionTokens(); HistoryMessage assistantHistoryMessage = new HistoryMessage(); assistantHistoryMessage.setConversationId(conversationId); assistantHistoryMessage.setRole(MessageType.ASSISTANT.getValue()); assistantHistoryMessage.setModel(model); assistantHistoryMessage.setContent(content); assistantHistoryMessage.setTokenCount(completionTokens); userHistoryMessage.setTokenCount(promptTokens); historyMessageMapper.updateById(userHistoryMessage); historyMessageMapper.insert(assistantHistoryMessage); } @Override public int getOrder() { return this.order; } @Override public String getName() { return this.getClass().getSimpleName(); }
AI 超级智能体 - 实现敏感词拦截Advisor
实现了自定义常见敏感词拦截Advisor,使用了https://github.com/houbb/sensitive-word 这个插件。 private static final String DEFAULT_FAILURE_RESPONSE = "用户内容涉及敏感内容,我无法回答。问点其他问题试试呢?"; private int order = 2; public CommonSafeGuardAdvisor() {} @Override public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) { String userMessages = chatClientRequest.prompt().getUserMessage().getText(); boolean IsContains = SensitiveWordHelper.contains(userMessages); if(IsContains) { return createFailureResponse(chatClientRequest); } ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest); return chatClientResponse; } @Override public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain) { String userMessages = chatClientRequest.prompt().getUserMessage().getText(); boolean IsContains = SensitiveWordHelper.contains(userMessages); if(IsContains) { return createFailureResponseFlux(chatClientRequest); } Flux<ChatClientResponse> chatClientResponseFlux = streamAdvisorChain.nextStream(chatClientRequest); return chatClientResponseFlux; } private ChatClientResponse createFailureResponse(ChatClientRequest chatClientRequest) { return ChatClientResponse.builder().chatResponse(ChatResponse.builder().generations(List.of(new Generation(new AssistantMessage(this.DEFAULT_FAILURE_RESPONSE)))).build()).context(Map.copyOf(chatClientRequest.context())).build(); } private Flux<ChatClientResponse> createFailureResponseFlux(ChatClientRequest chatClientRequest) { ChatClientResponse response = ChatClientResponse.builder().chatResponse(ChatResponse.builder().generations(List.of(new Generation(new AssistantMessage(this.DEFAULT_FAILURE_RESPONSE)))).build()).context(Map.copyOf(chatClientRequest.context())).build(); return Flux.just(response); } @Override public int getOrder() { return this.order; } @Override public String getName() { return this.getClass().getSimpleName(); }
AI 旅游助手
✅完成 AI 旅游助手 部署上线; 线上地址:http://159.75.74.182/ GitHub:https://github.com/StarsTears/prodigal-ai-travel  该系统支持 - 高德MCP - 查询当前位置 - 给指定邮箱发送消息 - 图片搜索 - 天气查询 - 获取当前IP - PDF生成工具 fix - 获取ip地址-修复之前获取的是服务器出口ip;现已调整为获取用户当前位置; 模型是使用阿里云的免费额度;逐步完善该系统! 后续继续学习AI 相关知识,以及开始看面试鸭了😭 #### 若使用 qwen3.6-plus 模型;需在开启多模态; 不开启` enable-thinking: true ` 会报以下错误: > org.springframework.ai.retry.NonTransientAiException: HTTP 400 - {"request_id":"f9354ca8-b0ca-9580-a18e-85cd2677f648","code":"InvalidParameter","message":"url error, please check url! For details, see: https://help.aliyun.com/zh/model-studio/error-code#error-url"} > 模型名称与API端点不匹配:例如调用纯文本模型时使用了多模态接口,或者调用多模态模型时使用了纯文本接口。 解决方案:通过DashScope使用qwen3.6-plus、qwen3-vl-plus等多模态模型时:需使用 MultiModalConversation.call() 或 multimodal-generation 端点。若使用 spring-ai-alibaba 框架,请确认是否设置多模态参数 withMultiModel ```xml dashscope: api-key: sk-XXXXX chat: options: model: qwen3.6-plus temperature: 0.7 enable-thinking: true multi-model: true embedding: options: model: text-embedding-v2 ``` #### 使用 mini max 模型: 相关依赖 ```xml <!--mini max--> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-minimax</artifactId> </dependency> ``` 配置文件: ```xml ai: minimax: api-key: sk-XXXXXXX chat: options: model: MiniMax-M2.7 temperature: 0.7 ``` 构造 Client: ```java @Configuration public class AIClient { @Resource private ToolCallback[] allTools; @Resource private ToolCallbackProvider toolCallbackProvider; @Bean ChatClient minimaxAiClient(MiniMaxChatModel chatModel) { // 基于内存会话记忆 InMemoryChatMemoryRepository inMemoryChatMemoryRepository = new InMemoryChatMemoryRepository(); MessageWindowChatMemory chatMemory = MessageWindowChatMemory .builder() .chatMemoryRepository(inMemoryChatMemoryRepository) .build(); MessageChatMemoryAdvisor messageChatMemoryAdvisor = MessageChatMemoryAdvisor.builder(chatMemory).build(); return ChatClient.builder(chatModel) .defaultSystem(TravelConstant.SYSTEM_PROMPT) .defaultAdvisors(new LoggerAdvisor(), messageChatMemoryAdvisor, new SystemMessageFirstAdvisor()) .defaultToolCallbacks(allTools) .defaultToolCallbacks(toolCallbackProvider) .build(); } } ``` 其中 system 的prompt 需排在 会话记忆 最前 ;否则会报如下错误: `invalid params, chat content has invalid message role: system ` ```java public class SystemMessageFirstAdvisor implements BaseAdvisor { @Override public int getOrder() { return Advisor.DEFAULT_CHAT_MEMORY_PRECEDENCE_ORDER + 1; } @Override public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) { List<Message> messages = chatClientRequest.prompt().getInstructions(); List<Message> system = new ArrayList<>(); List<Message> rest = new ArrayList<>(); for (Message m : messages) { if (m.getMessageType() == MessageType.SYSTEM) { system.add(m); } else { rest.add(m); } } List<Message> ordered = new ArrayList<>(system.size() + rest.size()); ordered.addAll(system); ordered.addAll(rest); return chatClientRequest.mutate() .prompt(chatClientRequest.prompt().mutate().messages(ordered).build()) .build(); } @Override public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) { return chatClientResponse; } } ``` 编写测试文件 ```java @Service public class MinimaxServiceImpl implements AIService { @Resource @Qualifier("minimaxAiClient") ChatClient minimaxAiClient; @Override public String doChat(String message,String conversationId) { ChatResponse chatResponse = minimaxAiClient.prompt() .user(message) //对话Id、关联对话数 .advisors(spec -> spec.param(ChatMemory.CONVERSATION_ID, conversationId) .param("TOP_K", 20)) .call() .chatResponse(); return chatResponse.getResult().getOutput().getText(); } } @Test void doChat() { String chatId = UUID.fastUUID().toString(); String message = "你好!我是Prodigal!请问今天几号"; String result = minimaxService.doChat(message, chatId); log.info(result); Assertions.assertNotNull( result); message = "获取当前位置。明天天气咋样?"; result = minimaxService.doChatWithMCPSSE(message, chatId,"IP"); log.info(result); Assertions.assertNotNull( result); message = "我是谁?"; result = minimaxService.doChat(message, chatId); log.info(result); Assertions.assertNotNull( result); } ```  
AI超级智能体——AI恋爱大师应用后端开发
## LoveAppConfig ```java import com.lzx.lzxaiagent.advisor.MyLoggerAdvisor; import com.lzx.lzxaiagent.advisor.SensitiveWordAdvisor; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepository; import org.springframework.ai.chat.model.ChatModel; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class LoveAppConfig { private static final String SYSTEM_PROMPT = "你叫林微,扮演深耕恋爱心理领域的专家。开场向用户表明身份,告知用户可倾诉恋爱难题。围绕单身、恋爱、已婚三种状态提问:单身状态询问社交圈拓展及追求心仪对象的困扰;恋爱状态询问沟通、习惯差异引发的矛盾;已婚状态询问家庭责任与亲属关系处理的问题。引导用户详述事情经过、对方反应及自身想法,以便给出专属解决方案。\n"; // private static final String SYSTEM_PROMPT = "you are a helpful assistant."; @Bean public ChatClient chatClient(ChatModel dashscopeChatModel, JdbcChatMemoryRepository chatMemoryRepository) { // 创建一个消息窗口的聊天记忆 MessageWindowChatMemory messageWindowChatMemory = MessageWindowChatMemory.builder() .chatMemoryRepository(chatMemoryRepository) .maxMessages(10) .build(); MessageChatMemoryAdvisor chatMemoryAdvisor = MessageChatMemoryAdvisor.builder(messageWindowChatMemory).build(); // 创建一个 ChatClient 对象,并设置默认的 system prompt 和默认的 ChatMemoryAdvisor ChatClient chatClient = ChatClient.builder(dashscopeChatModel) .defaultSystem(SYSTEM_PROMPT) .defaultAdvisors( chatMemoryAdvisor, new SensitiveWordAdvisor(), new MyLoggerAdvisor()) .build(); return chatClient; } } ``` ## LoveAppController ```java import com.lzx.lzxaiagent.sevice.LoveAppService; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.codec.ServerSentEvent; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; @RestController @Slf4j @RequestMapping("/love") @Tag(name = "恋爱应用", description = "恋爱相关的API接口") public class LoveAppController { @Autowired private LoveAppService loveAppService; @GetMapping("/chat") public Flux<ServerSentEvent<String>> doChat(String chatId, String userMessage) { log.info("💬 收到聊天请求 - chatId: {}, 消息: {}", chatId, userMessage); Flux<ServerSentEvent<String>> aiResponse = loveAppService.doChat(chatId, userMessage); return aiResponse; } } ``` ## LoveAppService ```java import org.springframework.http.codec.ServerSentEvent; import reactor.core.publisher.Flux; public interface LoveAppService { Flux<ServerSentEvent<String>> doChat(String chatId, String userMessage); } ``` ## LoveAppServiceImpl ```java import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.http.codec.ServerSentEvent; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; @Service @Slf4j public class LoveAppServiceImpl implements LoveAppService{ @Resource private ChatClient chatClient; @Override public Flux<ServerSentEvent<String>> doChat(String chatId, String userMessage) { return chatClient.prompt() .user(userMessage) .advisors(advisor -> advisor.param(ChatMemory.CONVERSATION_ID, chatId)) .stream() .content() .map(content -> ServerSentEvent.<String>builder() .data(content) .event("message") .build()) .concatWith(doneEvent()) .onErrorResume(this::errorEvent); } // 辅助方法:创建done事件 private Flux<ServerSentEvent<String>> doneEvent() { return Flux.just( ServerSentEvent.<String>builder() .data("[DONE]") .event("done") .build() ); } // 辅助方法:创建error事件 private Flux<ServerSentEvent<String>> errorEvent(Throwable error) { return Flux.just( ServerSentEvent.<String>builder() .data("抱歉,服务暂时不可用") .event("error") .build() ); } } ```
AI 赋能副业:只说一句话,我是怎么让 AI 自动帮我赚钱的?
# AI 赋能副业:只说一句话,我是怎么让 AI 自动帮我赚钱的? *** 大家好啊,我是汉堡🍔,好久没发贴了,今天冒个泡。最近 AI 发展的可谓是迅猛至极,claude code、codex、gemini-cli、openclaw 等 agent 工具层出不穷。说实话我们大部分人都是给 AI 打工,而非让 AI 给我们打工,尤其是 openclaw 出来之后,它花 token 的钱远远大于它给带来的效率提升,甚至越用越气、劳民伤财。我想分享一下我是如何通过 AI 不断优化工作流,来提升副业收入并且大幅提效的。 ### 一、副业和懒猫微服 前一阵子,我买了一个 nas(https://www.codefather.cn/post/2016862590654799874 ),当我一直拿它当服务器用。在那篇介绍懒猫的帖子里有说过,它是有一个自己的软件商店,共 3400+ 款应用,显然这些都不可能是官方开发的,大部分是用户自己移植上去的,而移植一个开源项目是 100 块钱,上架自己开发的项目是 150~500 不等,所以我的副业就是自己开发应用,或者将现成的开源项目移植到懒猫微服中上架赚钱。 ### 二、手把手教 AI 移植,心太累 一开始做这个副业的时候其实还是挺费精力的,虽然代码不需要我写,但一些打包文件和文件格式需要我喂给 AI,而社区和官方并没有提供保姆级的教程,我只能告诉 AI 具体怎么做,比如我得教它怎么编写核心的清单文件 lzc-manifest.yml,怎么写 lzc-build.yml 构建入口,以及如何配置 run.sh 容器启动脚本,并上传到懒猫微服中,就这样一步一步踩坑,然后好不容易上架了一个应用,结果发现已经被别人捷足先登了,最后分币未拿。  ### 三、尝试用官方 skills 提效 后来懒猫官方其实提供了专属的 skills 来帮助构建和上架懒猫应用,能不能直接让 AI 去读取 skills 然后直接输出呢,实操下来发现可以是可以,但是要写一定的提示词,规范 AI 的边界,引导它遇到某些问题去查找对应 skills,因为官方 skills 包含多个,每个 skill 处理特定场景的问题,而让 AI 路由具体的 skill 效率变慢也比较费 token。结果就是,我还是得去教导它应该看哪部分、应该怎么做,它才能勉强按照我的意思来搞,不过沟通成本依然很高。  ### 四、终极方案踩坑记录变SOP 既然让 AI 自己看文档不靠谱,我就决定自己来做个总结,将每次的踩坑经历和每个应用的移植教程给浓缩为一个通用的教程。在第一阶段我就是这么做的,只不过每个应用技术栈不同,对应的坑也不一样,比如 Python、Java、Next.js 和 Go 项目的部署是截然不同的,而 Java 是坑最多的(虽然我是学 Java 的,但现在对 Java 的好感直线下降)。  比如我发现,Java 项目的 JRE 必须严格限制为 Linux amd64 架构;而且懒猫容器的目录规范十分严格,所有持久化的业务数据必须存放在 /lzcapp/var/ 目录下,绝对不能写入包内容所在的只读目录 /lzcapp/pkg/content/ 里。 刚好,我今天开通了 claude 会员,直接请 claude 给我总结一个懒猫微服通用迁移教程,就这样整个工作流就搭建好了。其实也不是工作流了,就是一个文档而已。  ### 五、直接起飞 做完整合文档这一步,我的工作流算是直接起飞了。现在我只要将这个整合的完整的、通用的文档发给 AI,它就能直接根据此文档来帮我移植项目了。它会自主进行“项目移植前评估”,检查应用是否已上架、判断中间件依赖是否合规;然后严格按照规范模板生成配置文件,甚至还会自动规避我之前踩过的架构和目录权限的坑。 我也找了一个项目试了一个,将文档丢给 AI,只让他通读文档开始迁移工作就行了,11 分钟之后已经成功了,中间一次对话都未发生,我只要做测试和上架工作就好了,如果项目是纯 web 端项目且无复杂的中间件,基本上半小时不到就可以完成整个项目迁移和上架工作。  ### 六 总结 说了这么多,其实就想分享一个核心观点:AI 时代,会用 AI 和不会用 AI,效率差别真的很大,要让 AI 给我们打工而不是我们给 AI 打工,如果你要写的项目很多、时刻等到 AI 的回复和反馈,即使能赚钱,其实也是变相给 AI 打工了,哈哈哈哈哈哈哈。 如果觉得文章对你有启发的话,就给我点个赞吧,嘿嘿😋。期待大家和我一起探讨更多好玩的 AI 提效思路!
