当前位置: 首页 > news >正文

【SpringAI】3.结构化输出,初级版

SpringAI结构化输出

官方文档 https://www.spring-doc.cn/spring-ai/1.0.0/api_structured-output-converter.html

现有一个普通的流式接口

    @Operation(summary = "基础班流式生成")@CrossOrigin(origins = "*")@GetMapping(value = "/ai/generateStream", produces = "text/event-stream;charset=UTF-8")public Flux<ChatResponse> generateStream(@RequestParam(value = "message", defaultValue = "讲个笑话") String message) {List<Message> messageList = new ArrayList<>();messageList.add(new UserMessage(message));messageList.add(new SystemMessage("中文回答"));Prompt prompt = new Prompt(messageList);return this.chatModel.stream(prompt);}

这个接口返回的默认官方的ChatResponse,收到响应数据如下:

{"result":{"metadata":{"finishReason":null,"contentFilters":[],"empty":true},"output":{"messageType":"ASSISTANT","metadata":{"messageType":"ASSISTANT"},"toolCalls":[],"media":[],"text":"好的"}},"metadata":{"id":"","model":"qwen2.5vl:3b","rateLimit":{"requestsRemaining":0,"requestsLimit":0,"tokensLimit":0,"tokensRemaining":0,"tokensReset":"PT0S","requestsReset":"PT0S"},"usage":{"promptTokens":0,"completionTokens":0,"totalTokens":0},"promptMetadata":[],"empty":false},"results":[{"metadata":{"finishReason":null,"contentFilters":[],"empty":true},"output":{"messageType":"ASSISTANT","metadata":{"messageType":"ASSISTANT"},"toolCalls":[],"media":[],"text":"好的"}}]}
{"result":{"metadata":{"finishReason":"stop","contentFilters":[],"empty":true},"output":{"messageType":"ASSISTANT","metadata":{"messageType":"ASSISTANT"},"toolCalls":[],"media":[],"text":""}},"metadata":{"id":"","model":"deepseek-r1:1.5b","rateLimit":{"requestsRemaining":0,"tokensReset":"PT0S","requestsReset":"PT0S","requestsLimit":0,"tokensRemaining":0,"tokensLimit":0},"usage":{"promptTokens":48,"completionTokens":905,"totalTokens":953},"promptMetadata":[],"empty":false},"results":[{"metadata":{"finishReason":"stop","contentFilters":[],"empty":true},"output":{"messageType":"ASSISTANT","metadata":{"messageType":"ASSISTANT"},"toolCalls":[],"media":[],"text":""}}]}

这种数据前端解析时,其实只关心输出的text,其他信息不重要,
某些推理模型,前端可能需要判断输出的文本是思考内容还是正文然后使用不同的输出样式

我为了简化输出内容,定义VO如下:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FluxVO {private String text;private String status = "running";
}

修改原来的接口返回Flux

@PostMapping(value = "/generateStreamWithFile", consumes = "application/json", produces = "text/event-stream;charset=UTF-8")public Flux<FluxVO> generateStreamWithFile(@RequestBody QuestionVO body) {String model = body.getModel();List<Message> messageList = new ArrayList<>();UserMessage userMessage = new UserMessage(body.getMessage());//其他消息(图片、历史消息)messageList.add(userMessage);// 使用自定义系统提示词或默认提示词String systemPrompt = body.getSystemPrompt();String finalSystemPrompt = (systemPrompt != null && !systemPrompt.trim().isEmpty())? systemPrompt: "中文回答";messageList.add(new SystemMessage(finalSystemPrompt));// 通过DynamicModelFactory查找模型DynamicModelFactory.MyModel myModel = dynamicModelFactory.getModelByName(model);if (myModel == null) {throw new RuntimeException("未找到指定模型: " + model);}return getFluxVOFlux(messageList, myModel);}

我这里单独写一个处理输出内容的方法来处理

/*** 输出处理后端流式结果* @param messageList 模型消息,包括系统提示词、用户提示词、历史对话和文件* @param myModel 指定模型名称* @return 过滤掉思考标签,给思考内容文本打标记*/
private Flux<FluxVO> getFluxVOFlux(List<Message> messageList, DynamicModelFactory.MyModel myModel) {if ("ollama".equalsIgnoreCase(myModel.getVendor()) && myModel.getChatModel() != null) {Prompt prompt = new Prompt(messageList);AtomicBoolean inThinking = new AtomicBoolean(false);return myModel.getChatModel().stream(prompt).filter(response -> {String text = response.getResult().getOutput().getText();if ("<think>".equals(text)) {inThinking.set(true);return false; // 不输出}if ("</think>".equals(text)) {inThinking.set(false);return false; // 不输出}return true; // 其它内容输出}).map(response -> {String text = response.getResult().getOutput().getText();Boolean isStop = response.getResult().getMetadata().getFinishReason() != null;String status = inThinking.get() ? "think" : (isStop ? "stop" : "running");return FluxVO.builder().text(text != null ? text : "").status(status).build();}).onErrorResume(error -> {System.err.println("流式处理异常: " + error.getMessage());return Flux.just(FluxVO.builder().text("AI服务异常,请稍后重试").status("stop").build());});}throw new RuntimeException("暂不支持该供应商: " + myModel.getVendor());}

处理后得到的流式消息如下:

data:{"text":"我会","status":"think"}data:{"text":"尽","status":"think"}data:{"text":"我","status":"think"}data:{"text":"所能","status":"think"}data:{"text":"为您提供","status":"think"}data:{"text":"帮助","status":"think"}data:{"text":"。\n","status":"think"}data:{"text":"\n\n","status":"running"}data:{"text":"您好","status":"running"}
http://www.lryc.cn/news/578254.html

相关文章:

  • Spring Boot + ONNX Runtime模型部署
  • springboot中多个定时任务(@Scheduled)如何互不影响
  • 大数据(4)-spark
  • Webpack优化详解
  • Unity性能优化-渲染模块(1)-CPU侧(2)-DrawCall优化(2)GPUInstancing
  • 浪潮和曙光服务器的ipmi配置教程
  • 图灵完备之路(数电学习三分钟)----开关与延迟线
  • Ubuntu更换Home目录所在硬盘的过程
  • Pyhton-EXCEL与Mysql数据对比
  • 从设计到开发一个小程序页面
  • 鸿蒙NEXT-鸿蒙三层架构搭建,嵌入HMRouter,实现便捷跳转,新手攻略。(2/3)
  • HTML之常用基础标签
  • JavaScript异步编程的五种方式
  • 力扣 hot100 Day30
  • Spring生态:云原生与AI的革新突破
  • 七天学会SpringCloud分布式微服务——06——Sentinel
  • 从零到一通过Web技术开发一个五子棋
  • CSDN博客大搬家(本地下载markdown合适和图片本地化)
  • Stable Diffusion 项目实战落地:从0到1 掌握ControlNet 第四篇 风格化字体大揭秘:从线稿到涂鸦,ControlNet让文字焕发新生
  • 【机器人】复现 HOV-SG 机器人导航 | 分层 开放词汇 | 3D 场景图
  • 文心大模型正式开源,开启AI普惠新时代
  • vue上传各种文件,并预览组件,(预览,下载),下载resources目录下文件
  • 云上攻防—Docker安全容器逃逸特权模式危险挂载
  • 计算机网络(一)层
  • 3.前端和后端参数不一致,后端接不到数据的解决方案
  • 基于C#的OPCServer应用开发,引用WtOPCSvr.dll
  • 中钧科技亮相2025 亚欧商品贸易博览会,赋能数字经济新未来!
  • C#索引和范围:简化集合访问的现代特性详解
  • 2025年 UI 自动化框架使用排行
  • 深度模型结构选择和应用分析