智能体Manus改进——人机交互
智能体Manus改进——人机交互
首先,需要完成鱼皮的 XXXManus 智能体(包括SSE流式接口改造),这个是必须的
这是我的系统提示词 以及步骤提示词,可以参考(也是我根据效果不断调试出来的,大家伙可以根据自己的AI 调教出对应效果更好的提示词)
▼java复制代码String SYSTEM_PROMPT_C = """ 你是一个ZNManus 全能的人工智能助手,你可以调用各种工具来完成用户给你的任务。 接收到用户的任务,你需要根据用户提出的要求,分析出完成用户的步骤所需的流程,以第一步、第二步的方式来逐步完成用户的需求。 为完成用户的需求,你可以调用提供的工具,但是需要在有正确的(非报错或没有找到)结果之后,能够进行下一步操作,减少重复调用工具的行为,以浪费系统资源。 如果执行任务产生报错或者搜索失败,请务必再次尝试,可以切换搜索关键词,这有利于提升用户体验 """; String NEXT_STEP_PROMPT_C = """ 根据用户需求,主动选择最合适的工具或工具组合。对于复杂的任务,您可以分解问题并逐步使用不同的工具来解决它。 使用每个工具后,清楚地解释执行结果并建议后续步骤。 如果执行任务产生报错,请再次尝试,这有利于提升用户体验 如果要在任何时候停止交互,请使用 'terminate' 工具调用,但是调用之前,确保完成任务的最后一步,不要没有完成任务提前退出 """;
想法
然后,相对于原版的YuManus 我觉得没有用户交流功能(不知道为什么,我是用Scanner 不能调用出控制台打印,SpringBoot默认的控制台为只读状态,希望有大佬能帮我解决下),于是想到使用消息队列的方式进行消息传递,我想过使用RabbitMQ,但是需要额外引入依赖,并且需要额外的环境,所以选择了本地的消息队列的方式——BlockingQueue 阻塞队列
具体代码
以下是AI写的,别问为什么不自己写,😢写不出来,AI 又准又快
UserInputQueue 消息队列实例,存储消息
▼java复制代码import org.springframework.stereotype.Component; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; /** * 用户消息队列 采用BlockingQueue 阻塞队列作 */ @Component public class UserInputQueue { private final BlockingQueue<String> queue = new LinkedBlockingQueue<>(); public void putResponse(String response) throws InterruptedException { queue.put(response); } public String takeResponse() throws InterruptedException { return queue.take(); } }
QueryUserTool Tool 方法,用于给AI 向用户发起提问
▼java复制代码import jakarta.annotation.Resource; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Component; @Component public class QueryUserTool { @Resource private UserInputQueue userInputQueue; /** * 工具方法:向用户提问,并等待用户输入 */ @Tool(description = "向用户提问,并等待用户输入") public String askUserAndWait(@ToolParam(description = "提问问题") String question) { System.out.println("【系统提问】" + question); try { // 阻塞等待用户输入 String userAnswer = userInputQueue.takeResponse(); System.out.println("【用户回答】" + userAnswer); return userAnswer; } catch (InterruptedException e) { Thread.currentThread().interrupt(); return "用户取消了操作"; } } }
userInput接口 用于用户发送请求进行回答
▼java复制代码@Resource private UserInputQueue userInputQueue; /** * 询问用户后,用户输入接口后,前端进行调用该接口,把用户输入的内容传递到内容中 * * @param input 用户输入内容 * @return */ @GetMapping("/user/input") public void userInput(@RequestParam("input") String input) { // 回答问题 try { // 非空判断 if (StrUtil.isBlank(input)) { userInputQueue.putResponse("用户输入为空"); } else { userInputQueue.putResponse(input); } } catch (InterruptedException e) { throw new RuntimeException(e); } }
整个流程就是,当AI调用QueryUserTool的 询问用户方法之后,用户发送消息(前端用户输入,点击发送按钮),服务器收到消息,添加消息到消息队列中,队列写入成功,唤醒阻塞线程,该方法获取到了用户输入的内容,返回给AI 大模型,继续任务……
实现效果
发送请求
▼http复制代码GET http://localhost:8080/api/ai/chat/ZnManus? query=我想要参加BiliBiliWorld 活动, 制定一份详细的活动计划,编写活动计划内容。保存活动计划内容为 markdown文件保存在本地中。内容中需要有保存文件的具体时间,例如 2025-06-06 23:06:05
系统内部日志
【这里询问用户预算】
▼bash复制代码执行步骤:[1 / 20] ZNManus的思考: ZNManus选择了:1 个工具来调用 工具名称:askUserAndWait,参数{"question": "请问你参加BiliBiliWorld活动的具体预算是多少?"} 工具:askUserAndWait,完成了他的任务!结果为:"2000" 【系统提问】请问你参加BiliBiliWorld活动的具体预算是多少? 【用户回答】2000
【这里询问用户活动日期】用户给出模糊日期(这里留个坑)
▼bash复制代码执行步骤:[2 / 20] ZNManus的思考: ZNManus选择了:1 个工具来调用 工具名称:askUserAndWait,参数{"question": "请问你参加BiliBiliWorld活动的日期是什么时候?"} 工具:askUserAndWait,完成了他的任务!结果为:"7月份中旬" 【系统提问】请问你参加BiliBiliWorld活动的日期是什么时候? 【用户回答】7月份中旬
【这里询问用户活动地点】用户说自己查询
▼bash复制代码执行步骤:[3 / 20] ZNManus的思考: ZNManus选择了:1 个工具来调用 工具名称:askUserAndWait,参数{"question": "请问你参加BiliBiliWorld活动的地点是哪里?"} 工具:askUserAndWait,完成了他的任务!结果为:"你自己查询" 【系统提问】请问你参加BiliBiliWorld活动的地点是哪里? 【用户回答】你自己查询
【这里询问交通方式】用户说推荐
▼bash复制代码执行步骤:[4 / 20] ZNManus的思考: ZNManus选择了:1 个工具来调用 工具名称:googleSearch,参数{"searchQuery": "BiliBiliWorld 活动时间 2025"} 调用 SerpApi Google 搜索关键词:BiliBiliWorld 活动时间 2025 工具:googleSearch,完成了他的任务!结果为:"【结果 1】\n标题: BilibiliWorld 2025年将在国家会展中心举办4天时间暂定7月 ..." ## 这里后面是搜到的网页,就不放出来了 【系统提问】请问你参加BiliBiliWorld活动的交通方式是什么? 【用户回答】你推荐
▼bash复制代码执行步骤:[5 / 20] ZNManus的思考: ZNManus选择了:1 个工具来调用 工具名称:askUserAndWait,参数{"question": "请问你参加BiliBiliWorld活动的交通方式是什么?"} 工具:askUserAndWait,完成了他的任务!结果为:"你来推荐"
▼bash复制代码执行步骤:[6 / 20] ZNManus的思考: ZNManus选择了:1 个工具来调用 工具名称:googleSearch,参数{"searchQuery": "上海国家会展中心 交通指南 地铁 公交 路线"} 调用 SerpApi Google 搜索关键词:上海国家会展中心 交通指南 地铁 公交 路线 工具:googleSearch,完成了他的任务!结果为:"【结果 1】\n标题: 上海国家会展中心怎么去(公交+地铁+自驾+停车)\n链接:……"
▼bash复制代码执行步骤:[7 / 20] ZNManus的思考: ZNManus选择了:1 个工具来调用 工具名称:googleSearch,参数{"searchQuery": "上海国家会展中心 停车场信息"} 调用 SerpApi Google 搜索关键词:上海国家会展中心 停车场信息 工具:googleSearch,完成了他的任务!结果为:"【结果 1】\n标题: 交通服务 - 国家会议中心(上海)\n链接: h………"
【这里询问用户展位】用户说推荐
▼bash复制代码执行步骤:[8 / 20] ZNManus的思考: ZNManus选择了:1 个工具来调用 工具名称:askUserAndWait,参数{"question": "请问你是否有特定的活动或展位想要了解?"} 工具:askUserAndWait,完成了他的任务!结果为:"没有,推荐几个" 【系统提问】请问你是否有特定的活动或展位想要了解? 【用户回答】没有,推荐几个
▼bash复制代码执行步骤:[9 / 20] ZNManus的思考: ZNManus选择了:1 个工具来调用 工具名称:googleSearch,参数{"searchQuery": "BiliBiliWorld 2025 活动亮点 展位推荐"} 调用 SerpApi Google 搜索关键词:BiliBiliWorld 2025 活动亮点 展位推荐 工具:googleSearch,完成了他的任务!结果为:"【结果 1】\n标题: 展览展会| BILIBILI WORLD 2025-腾讯新闻\n链接: https:……"
这里保存到MarkDown文件中
▼bash复制代码执行步骤:[10 / 20] AI 使用了工具 :saveContentAsMarkdown 调用的参数为:{"content": "# BiliBiliWorld 活动计划\n\n## 活动详情\n- **预算**: 2000元\n-……………… ZNManus的思考: ZNManus选择了:1 个工具来调用 工具名称:saveContentAsMarkdown,参数{"content": "# Bi…………………… 工具:saveContentAsMarkdown,完成了他的任务!结果为:Done
▼bash复制代码AI 使用了工具 :doTerminate 调用的参数为:{} ZNManus的思考: ZNManus选择了:1 个工具来调用 工具名称:doTerminate,参数{} 工具:doTerminate,完成了他的任务!结果为:"任务结束"
查看结果
▼markdown复制代码# BiliBiliWorld 活动计划 ## 活动详情 - **预算**: 2000元 - **日期**: 2025年7月11日-13日 - **地点**: 国家会展中心(上海) ## 交通方式 - **地铁**: 地铁2号线徐泾东站4、5、6号出口,步行即可到达。 - **公交**: 可乘坐865路、706路、797路、197路等公交线路。 - **自驾**: 国家会展中心内设停车场,北广场可提供车位1500个;南广场可提供300个地面车位、2500个地下车位。 ## 活动亮点推荐 - **遇见动漫**:展示最新动漫作品和角色扮演。 - **游戏世界**:各种游戏试玩和电竞比赛。 - **UP主小镇**:与知名UP主互动交流。 - **特别舞台**:精彩演出和签售活动。 ## 其他建议 - 提前下载“BilibiliWorld”官方App,获取每日活动排期。 - 注意保存电子票二维码,以防网络卡顿。 - 合理安排时间,避免错过自己喜欢的活动。 ## 文件保存时间 - **保存时间**: 2025-06-06 23:06:05
可以看到,效果是比较好的,记得刚刚留的坑吗,用户回答模糊的时间,然后它给的这个时间是去年的时间,虽然是错误的,但是确实进行查询了的。其他用户提到的预赛于推荐也都给出了,总之,这个就是对 智能体的一点小小的改进,其实并没有很多很牛的改动,但是我觉得这样的《人机交互》能够增加用户的体验,类似于大模型帮你写代码的时候询问是否继续,而不是直接夸夸一顿操作,你无从插手的时候,就应该来句伟大,无需多言
忘大佬指正,欢迎有更多更好的优化改进,大家一起进步!
