AI对话应用接口开发全解析:同步接口+SSE流式+智能体+前端对接

AI对话应用接口开发全解析:同步接口+SSE流式+智能体+前端对接

AI对话应用接口开发

我们平时开发的大多数都是同步接口,也就是扥后端处理完再返回。但是对于AI应用,特别是响应时间较长的应用,可能会让用户失去耐心等待,因此推荐使用SSE技术实现实时流式输出,类似打字机效果,大幅度提升用户体验

开发AI对话同步接口

接下来我们先开发AI同步接口,对比学习。首先我们编写一个与"科泰旅游大师"对话的接口,使用常规同步的方式获得对话结果。

在controller包中新建ChatWithAIController,如下图所示:

编写同步接口:

 @RestController @RequestMapping("/ai") public class ChatWithAIController { // 注入TravelApp实例 @Resource private TravelApp travelApp; /* * 前端可以通过此方法获得一个ID * */ @GetMapping("/chat/new") public String newChat(){ return UUID.randomUUID().toString(); } // 与AI聊天(同步) @GetMapping("/chat/travel/sync") public String chat(String chatId,String message){ return travelApp.chat(chatId,message); } } 

通过/chat/new接口,让前端利用UUID生成会话ID,避免A用户能够通过猜测会话ID获取用户B的信息

同步接口代码也非常简单,直接使用注入的TravelApp实例对用户的问题进行回复

测试同步接口

启动springBoot项目,浏览器打开Swagger页面测试

浏览器打开http://localhost:8080/api/doc.html

获取到ChatId后测试/api/ai/chat/travel/sync时会出现很长的时间进度提示

开发AI对话SSE流式接口

Server-Sent Events(SSE,服务器推送事件)

SSE 是 HTML5 定义的基于 HTTP 的单向实时数据推送技术,客户端通过EventSource API 发起长连接,服务器以文本流持续推送事件,适配实时通知、行情更新等场景。

  1. 核心特性
    • 单向通信:仅服务器向客户端推送数据,客户端反馈需另发 HTTP 请求。
    • HTTP 原生支持:基于 HTTP/1.1 分块传输编码,无需协议升级,易穿透代理与防火墙。
    • 自动重连:浏览器内置重连机制,默认 3 秒重试,可通过retry字段自定义。
    • 轻量协议:文本流格式,支持dataeventidretry等字段,开销低。
  2. 应用场景:实时通知、股票行情、系统日志流、进度更新、IoT 状态推送等单向推送场景。

让TravelApp应用支持流式输出

要让接口支持流式输出,首先我们的TravelApp也不能使用同步的call()+chatResponse()方法返回String类型的全部内容

需要使用到Flux响应式对象进行异步操作。在TravelApp类增加一个StreamChat方法,(SpringAI系列的文章都是围绕一个项目写没有的内容在前面几期)

 /* * 与AI大模型流式对话接口 * @param chatId 会话ID * @param message 用户输入 * @return AI模型返回结果 * */ public Flux<String> streamChat(String chatId, String message){ FunctionCallback[] mcpTools = toolCallbackProvider.getToolCallbacks(); Flux<String> result = chatClient.prompt() .user(message) .advisors(spec -> spec .param(CHAT_MEMORY_CONVERSATION_ID_KEY,chatId) .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY,10) ) .tools(aiTools) .tools(McpToolCallbackProxy.proxyAll(mcpTools)) .toolContext(Map.of("chatId",chatId)) .stream().content(); // log.info("chatResponse: {}",chatResponse); return result; }

实现SSE流式接口

实现流式接口一共有三种方式

1.直接返回Flux对象,但是必须添加SSE对应的MediaType

修改ChatWithAIController,增加方法chatWithTravelAppSEE_Flux:

 /* * SSE实现方式一:使用Flux * 必须设置produces为MediaType.TEXT_EVENT_STREAM_VALUE * */ @GetMapping(value = "/chat/travel/sse/flux",produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> chatWithTravelAppSEE_Flux(String chatId, String message){ return travelApp.streamChat(chatId,message); }

2.返回Flux对象,并设置泛型为ServerSentEvent

这种方式可省略MediaType的设置,但是要对String进行转化

 /* * SSE实现方式二:使用ServerSentEvent泛型 * */ @GetMapping("/chat/travel/sse/generic") public Flux<ServerSentEvent<String>> chatWithTravelAppSEE_Generic(String chatId, String message){ return travelApp.streamChat(chatId,message) .map(result -> ServerSentEvent.builder(result).build()); }

3.使用SseEmiter类型,通过send方法持续向SseEmiter对象发送消息,这种方式代码会稍微大一些,但是通用性和可控制性更强

 // 实现方式三使用SseEmitter @GetMapping("/chat/travel/sse/emitter") public SseEmitter chatWithTravelAppSEE_Emitter(String chatId, String message){ // 创建SseEmitter对象,设置超时时间为5分钟 SseEmitter emitter = new SseEmitter(300000L); // 订阅流 travelApp.streamChat(chatId,message).subscribe( result -> { try { // 发送数据 emitter.send(result); } catch (Exception e) { emitter.completeWithError(e); } }, // 错误处理 error -> emitter.completeWithError(error), () -> emitter.complete() ); return emitter; }

AI智能体应用接口开发

改造智能体支持流式运行

那么首先类似于TravelApp增加流式对话StreamChat(),我们也需要改造智能体BaseAgent增加支持流式运行的streamRun()方法:

 /* * 增加流式输出的运行方法 * @param userPrompt 用户输入 * @return SseEmitter * */ public SseEmitter streamRun(String userPrompt){ SseEmitter emitter = new SseEmitter(300000L); //使用异步线程处理,避免线程阻塞 CompletableFuture.runAsync(() -> { try{ if (this.state != AgentState.IDLE){ emitter.send("当前Agent正在运行中,请勿重复运行!"); emitter.complete(); return; } if (StrUtil.isBlank(userPrompt)){ emitter.send("请输入用户提示词!"); emitter.complete(); return; } // 添加用户输入 chatMemory.add(chatId,new UserMessage(userPrompt)); this.state = AgentState.RUNNING; try{ for (int i = 0; i < maxSteps && state != AgentState.FINISHED; i++) { currentStep = i + 1; log.info("开始执行第{}步...",currentStep); String result = "Step " + currentStep + ": " + step(); emitter.send(result); } if(currentStep >= maxSteps){ state = AgentState.FINISHED; emitter.send("已执行最大步骤数,任务结束!,最大步数为"+maxSteps); } // 正常完成 emitter.complete(); } catch (IOException e) { state = AgentState.ERROR; try{ emitter.send("执行出错,请检查!"); emitter.complete(); } catch (IOException ex) { throw new RuntimeException(ex); } }finally { cleanResources(); } } catch (IOException e) { emitter.completeWithError(e); } }); // 设置超时回调 emitter.onTimeout(() -> { this.state = AgentState.ERROR; this.cleanResources(); log.warn("SSE连接超时,会话ID: {}", chatId); }); // 设置完成回调 emitter.onCompletion(() -> { if(state == AgentState.RUNNING){ this.state = AgentState.FINISHED; } this.cleanResources(); log.debug("SSE连接完成,会话ID: {}", chatId); }); return emitter; }

上述代码看上去很复杂,但是大部分都是在原有run方法上改造,将结果信息交给SseEmitter通过send方法推送

添加智能体异步接口

有了异步运行的方法,我们就可以添加智能体接口了,修改ChatWithAIController
式调用 ktTravelManus 智能体的接口,

 @Resource private ToolCallback[] toolCallbacks; @Resource private ChatModel dashscopeChatModel; /* * 流式调用智能体 * @param message * @return * */ @GetMapping("/chat/manus/sse") public SseEmitter chatWithManus(String message){ TravelManus travelManus = new TravelManus(toolCallbacks,dashscopeChatModel); travelManus.setMaxSteps(20); return travelManus.streamRun(message); }

为了保证每次请求都是一个新的TravelManus实例,我们通过自己new 来创建,那么构造函数必要的工具列表aiTools 和dashscopeChatModel对象就需要注入进来
当然我们也可以直接注入 TravelManus,然后 Controller 添加@Scope 注解值为request,这样每次请求都会创建一个新的控制器,由于KtTravelManus 的@Scope是prototype,也会注入一个新的KtTravelManus 实例。

测试智能体接口

使用ApiPost测试

重启SpringBoott项目在,ApiPost中新增如下接口测试

接口链接:http://localhost:8080/api/ai/chat/manus/sse

参考参数:message=我想在长沙旅游三天预算3000,帮我规划旅程,并安排附近公里的酒店,并生成PDF

前端项目的生成和对接

由于我们这个项目不需要复杂的前端页面,我们可以使用AI编程工具来快速生成前端代码,极大提高开发效率

创建前端项目

(写这个项目需要具备前端的一些基础知识)在vscode打开终端,输入npm create vite@latest使用vite去创建一个最新的vue版本的项目

接着我们编写提示词给AI编程工具

参考提示词

你是一位专业的前端开发,请帮我根据下列信息来生成对应的前端项目代码。#需求
1、提供主页:可以切换不同应用(考虑页面样式美观,操作流畅)
2、页面1:AI旅游大师应用。页面风格为聊天室,仿Deepseek等AI聊天界面,首次运行创建新会话,每次创建新会话调用后端newChat接口获得新的聊天ID,老的会话可以通过列表保留(仅界面支持,后端暂不支持返回会话列表)。聊天室内通过SSE的方式调用chatwithTravelAppsSE Flux接口,实时显示对话内容。
3、页面2:AI旅游智能体应用。页面风格同页面1,但不保留历史会话,调用chathithManus接口,实时显示对话内容,按Step x:的格式分割气泡展示步骤(x为步骤数字值)
###技术选型
1.Vue3+ ElementPlus布局
2.Axios请求库
## 后端接口信息
接口地址前缀:http://localhost:8080/api
# SpringBoot 后端接口代码
与AI聊天的控制器
@RestController
@RequestMapping("/ai")
public class ChatWithAIController {
    // 注入TravelApp实例
    @Resource
    private TravelApp travelApp;

    @Resource
    private ToolCallback[] aiTools;

    @Resource
    private ChatModel dashscopeChatModel;

    /*
    * 流式调用智能体
    * @param message
    * @return
    * */
    @GetMapping("/chat/manus/sse")
    public SseEmitter chatWithManus(String message){
        TravelManus travelManus = new TravelManus(aiTools,dashscopeChatModel);
        travelManus.setMaxSteps(20);
        return travelManus.streamRun(message);
    }

    /*
    * 前端可以通过此方法获得一个ID
    * */
    @GetMapping("/chat/new")
    public String newChat(){
        return UUID.randomUUID().toString();
    }

    /*
     * SSE实现方式一:使用Flux
     * 必须设置produces为MediaType.TEXT_EVENT_STREAM_VALUE
     * */
    @GetMapping(value = "/chat/travel/sse/flux",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chatWithTravelAppSEE_Flux(String chatId, String message){
        return travelApp.streamChat(chatId,message);
    }

然后把提示词发送给AI后他会根据我们提示词生成一个页面

页面如果觉得不好看可以按照想法去修改

但是在我们运行过程中会有一个错误返回,会话创建失败,然后我们端口控制台可以看到是一个跨域错误

使用CorsConfig配置跨域

在我们后端项目的config包中新建CorsConfig

 @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowCredentials(true) .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .exposedHeaders("*"); } }

配置完成后我们再次重启后端项目,测试是否可以正常运行了

如下图所示,会话创建成功,并且给出了正确的回复

优化项目展示最终结果

优化工具提示词

        由于工具的使用由AI决定,如果它胡乱使用就可能会出现一些异常,例如原本有图片搜索工具,它却选择去使用“网页爬取工具”去爬取百度图片搜索结果,导致读取的页面数据得到大量乱码(可能百度做了技术防护)。

        另外还有一个问题,就是我们仅仅告知了最大执行步骤,并未告知大模型目前在第几步,大模型由于上下文限制,可能会忘记自己现在做到第几步了,我们也可以在提示词中再次提示它。
        我们修改一下 KtTravelManus 智能体的 NEXT_STEP_PROMPT 提示词,把这两个问题一起解决一些,看下情况是否能够改善。

根据用户需求,主动选择最合适的工具或工具组合。对于复杂的任务, 你可以将问题分解开来,然后分步骤使用不同的工具来解决它。你应该尽量在 %d 个步骤内完成任务,否则任务将可能提前结束, 当前已经是第 %d 步。在使用每种工具之后,要清晰地说明执行结果,并提出下一步的建议。如果您想在任何时刻终止这种交互, 请使用 “终止” 工具 / 函数调用重要的选择工具规则: 搜索和爬取1、图片搜索只能使用 searchImage 工具,不允许通过 searchWeb 和 scrapeWebContent 工具 2、如果需要搜索信息,请使用 searchWeb 工具,不允许使用 scrapeWebContent工具 3、如果需要爬取信息,请使用 scrapeWebContent 工具,不允许使用 searchWeb 工具 4、如果需要地理位置信息,请使用高德地图相关工具 5、如果需要图文并茂的 PDF,先下载图片,并使用 Markdown 格式引用相对路径的图片地址,再传递此Markdown 内容,使用 generatePdf 工具生成 6、如果用户要求提供文件,在最终响应时提供生成文件的下载链接(A 标签)

智能体注册全部工具

        如果想要给智能体用所有工具,那么我们就需要合并本地工具和 MCP工具提供给KtTravelManus 的构造函数。
修改 ChatWithAIController

 // 合并工具 private ToolCallback[] mergeAiTools() { // 注意到进行代理,避免MCP工具出现ToolContext问题 FunctionCallback[] mcpFunctionCallbacks = McpToolCallbackProxy.proxyAll(toolCallbackProvider.getToolCallbacks()); // 转化为ToolCallBack数组 ToolCallback[] mcpToolCallbacks = Arrays.stream(mcpFunctionCallbacks) .map(tool -> (ToolCallback)tool).toArray(ToolCallback[]::new); // 合并本地工具和MCP工具 ToolCallback[] allTools = ArrayUtil.addAll(aiTools,mcpToolCallbacks); return allTools; }

让最终结果展示

因为我们只发送了步骤信息,最终结果信息并没有发送,所以我们需要改造BaseAgent,在最终完成状态时,发送最后一次的AI消息。

修改BaseAgent代码

对应代码

 // 完成之前获取最后一次有效的AI响应并发送 if(state == AgentState.FINISHED){ String finalAiResponse = getLastValidAiResponse(); // 发送最终响应给前端 if(StrUtil.isNotBlank(finalAiResponse)){ emitter.send("\n=== 智能体最终响应 ==="); emitter.send(finalAiResponse); emitter.send("\n=== 智能体最终响应结束 ==="); } }
 /** * @return 最后一次 AI 的有效响应文本,如果没有则返回空字符串 */ protected String getLastValidAiResponse() { // 获取所有消息 List<Message> messageList = chatMemory.get(this.chatId, Integer.MAX_VALUE); // 如果消息为空,则最后的响应也为空 if (messageList == null || messageList.isEmpty()) { return ""; } // 从后往前遍历消息列表,查找最后一个助手消息 for (int i = messageList.size() - 1; i >= 0; i--) { Message message = messageList.get(i); // 获取最后一条助手消息 if (message instanceof AssistantMessage) { AssistantMessage assistantMessage = (AssistantMessage) message; String content = assistantMessage.getText(); // 返回非空的助手消息内容 if (StrUtil.isNotBlank(content)) { log.info("Final response: {}", content); return content; } } } return "智能体未返回最终响应"; }

注意后端数据发送逻辑

  • 先通过getLastValidAiResponse()方法获取到最后一次的助理消息
  • 然后通过emitter.send("\n=== 智能体最终响应===");发送了响应开始标识,这需要前端代码的支持。
  • 然后发送响应内容 emitter.send(finalAiResponse);
  • 最后发送结束符号emitter.send("=== 响应结束===");

Read more

手把手教你开发“AI数据分析师”:利用IPIDEA + 智能体实现全网数据洞察

手把手教你开发“AI数据分析师”:利用IPIDEA + 智能体实现全网数据洞察

前言:为何需要构建一个更智能的数据助手 在当前人工智能的浪潮中,大语言模型(LLM)驱动的智能体(Agent)展现了巨大的潜力。理论上,它们可以自动化执行任务、分析数据,成为我们的得力助手。但在实际开发和使用中,我们常常会遇到一个瓶颈:智能体似乎“不够聪明”,无法获取最新、最真实的数据。这篇将记录并分享如何解决这一核心痛点,通过将智能体与专业的网络数据采集服务(IPIDEA)相结合,从零到一构建一个真正具备全网数据洞察能力的“AI数据分析师”。 第一章 为何我们的智能体“不够聪明” 在着手解决问题之前,首先需要清晰地界定问题本身。智能体在数据获取层面的“不聪明”主要源于两个相互关联的障碍:大模型自身的局限性和传统网络数据抓取的技术壁垒。 1.1 大模型的数据滞后与“幻觉”痛点 大语言模型的能力根植于其庞大的训练数据。然而,这些数据并非实时更新的。绝大多数模型的知识都存在一个“截止日期”,它们无法知晓在该日期之后发生的新闻、发布的财报、变化的商品价格或网络热点。当我们向智能体询问这些实时性要求高的问题时,它可能会坦白自己的知识局限,或者更糟糕地,它会根据已有的模式“

当人人都会用AI,你靠什么脱颖而出?

当人人都会用AI,你靠什么脱颖而出?

文章目录 * 一、引言:AI时代,你真的准备好了吗? * 二、脉向AI:连接AI与普通人的桥梁 * 2.1 什么是脉向AI? * 2.2 脉向AI的合作生态 * 2.3 为什么你需要关注脉向AI? * 三、本期重磅:《小Ni会客厅×AI熊厂长》深度对话 * 3.1 访谈背景 * 3.2 核心观点一:商业认知决定变现能力 * 3.3 核心观点二:个人标签决定商业价值 * 3.4 核心观点三:爆款策略决定起步速度 * 3.5 核心观点四:产品思维决定变现上限 * 四、从认知到行动:如何真正用AI赚到钱? * 4.1 建立正确的商业认知 * 4.2 找到你的70分领域

【企业级】RuoYi-Vue-Plus AI 智能开发助手 | Claude Code + Codex 双引擎 | 40+ 专业技能包 | 10 大快捷命令 | 开箱即用

【企业级】RuoYi-Vue-Plus AI 智能开发助手 | Claude Code + Codex 双引擎 | 40+ 专业技能包 | 10 大快捷命令 | 开箱即用

RuoYi-Vue-Plus AI 智能编程助手 商品简介 基于 RuoYi-Vue-Plus 5.X 企业级后端框架,深度定制的 AI 智能编程助手配置包。支持 Claude Code 和 OpenAI Codex 双 AI 引擎,内置 40+ 专业开发技能、10 大快捷命令、智能钩子系统,让 AI 真正理解您的项目架构和开发规范,实现 10 倍开发效率提升。 核心亮点 🚀 双 AI 引擎支持 引擎配置目录说明Claude Code.claude/Anthropic Claude 官方 CLI 工具配置OpenAI Codex.codex/OpenAI Codex CLI

【AI大模型前沿】Baichuan-M1-14B:百川智能推出专为医疗优化的开源大语言模型

【AI大模型前沿】Baichuan-M1-14B:百川智能推出专为医疗优化的开源大语言模型

系列篇章💥 No.文章1【AI大模型前沿】深度剖析瑞智病理大模型 RuiPath:如何革新癌症病理诊断技术2【AI大模型前沿】清华大学 CLAMP-3:多模态技术引领音乐检索新潮流3【AI大模型前沿】浙大携手阿里推出HealthGPT:医学视觉语言大模型助力智能医疗新突破4【AI大模型前沿】阿里 QwQ-32B:320 亿参数推理大模型,性能比肩 DeepSeek-R1,免费开源5【AI大模型前沿】TRELLIS:微软、清华、中科大联合推出的高质量3D生成模型6【AI大模型前沿】Migician:清华、北大、华科联手打造的多图像定位大模型,一键解决安防监控与自动驾驶难题7【AI大模型前沿】DeepSeek-V3-0324:AI 模型的全面升级与技术突破8【AI大模型前沿】BioMedGPT-R1:清华联合水木分子打造的多模态生物医药大模型,开启智能研发新纪元9【AI大模型前沿】DiffRhythm:西北工业大学打造的10秒铸就完整歌曲的AI歌曲生成模型10【AI大模型前沿】R1-Omni:阿里开源全模态情感识别与强化学习的创新结合11【AI大模型前沿】Qwen2.5-Omni: