尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Spring Boot后台接入多Agent:RuoYi集成AgentScope Harness实战
如果你正维护一套 Java 技术栈的运营后台又突然被要求“给系统接上 AI最好还能跑多 Agent 协同”大概率会和我遇到同样的问题RuoYi-Vue-Plus 这类 Spring Boot 管理平台权限、菜单、代码生成都很顺手但真要承载 Agent 编排、LLM 调用、会话记忆、任务流调度这些能力时靠硬编码和简单 HTTP 调用会很快失控。我这段时间做的核心工作就是想办法把 AgentScope Harness 装进 RuoYi-Vue-Plus让整套系统既能保留 Java 后端的稳定事务能力又能借助 Harness 拿到真正的多 Agent 运行能力形成一套“纯 Java 业务平台 外部 Agent 运行时”的架构。下面把选型思路、对接细节、绕过的圈子和最后沉淀下来的方案完整记录下来。1. Java 后台接入 AI卡点不在模型而在编排层先说一个很多人刚开始容易误解的点给 Java 后台接入 AI“接入大模型”其实是最容易的一步。现在各家模型服务都提供标准 OpenAI 风格接口Java 用 Spring 的 RestClient 也能轻松拼出对话请求。真正麻烦的是多 Agent 场景。1.1 管理后台的 Java 底色决定了集成路线RuoYi-Vue-Plus 是典型的企业级后台脚手架底层是 Spring Boot前端 Vue带权限、部门、角色、菜单、定时任务、代码生成这一整套。这类平台的特点是业务都是“表单 列表 审批流”数据敏感权限体系复杂通常还要迁就老数据库。你不可能因为它要接 AI就把核心业务拆成微服务也不可能在 JVM 里塞一个 Python Agent 运行时。我刚接到需求时也想过直接在系统里写一个 Java 工具类把大模型调用、上下文拼接、多轮对话都封装进去。初期做个聊天机器人没问题但做到后面问题就来了多 Agent 协作时每个 Agent 需要独立的系统提示词、工具列表、上下文窗口这些配置放 Java 里得靠硬编码。Agent 之间的调用链、重试、暂停、恢复靠手写状态机极其痛苦。长任务执行过程中需要逐步输出过程信息HTTP 请求很难把“半成品结果”逐步推给前端。日志审计、Token 统计、任务轨迹每一个都值得造轮子但又都不是业务核心。所以我最后定的路线很清晰Java 端继续只做业务编排、权限管控、数据持久化真正的 Agent 执行部分交给独立部署的 AgentScope Harness。1.2 多 Agent 运行时的核心诉求可以把 Harness 理解成一个“Agent 运行车间”。它负责把多个 Agent 组织起来执行一个完整任务比如“客服问答、工单分类、售后建议生成”这种链路。一个任务包含Agent 注册与模型配置。Agent 之间的调用关系比如顺序执行、分支判断、并行调用。每轮调用的工具集合。任务状态管理比如 pending、running、success、failed、cancelled。过程日志和 Token 计费数据。这些跑在 Harness 内天然比你在 Java 里手搓框架要成熟。而 RuoYi-Vue-Plus 作为“业务前台”职责则是接收用户请求、校验权限、记录任务主数据、展示执行状态和结果。把这两者分开是集成方案的第一条原则。2. 远程 Harness我给 Agent 单独开了个“运行车间”选型阶段最纠结的是连接方式。我梳理了三种主流方案各有优缺点最终选择的是远程 HTTP 模式。2.1 方案对比进程内包、远程 HTTP、消息队列我当时做了个简单对比表基本能说明问题方案集成成本隔离性流式支持运维复杂度适用场景进程内嵌 Python 引擎低但 JVM 内混运行不现实差一般极高不推荐JVM 内自写 Agent 编排中高功能全靠自己造中可实现中仅限极简对话远程 HTTP 调用 Harness中契约清晰好好用中低推荐我采用的方案消息队列 Harness 消费者较高需要 MQ 运维好难做流式高适合大量异步批任务进程内嵌这个想法第一时间被我放弃。AgentScope Harness 本身是一套独立运行时有自己的进程生命周期、并发调度、模型连接池和日志体系。你不可能把一个多进程、多线程、还带长连接的服务塞进 Tomcat 容器里更别说 Java 和 Python 之间的 GIL、线程模型、内存回收机制完全不同混在一起出事很难定位。与其强行融合不如把它当作一个独立的“引擎节点”Java 只是它的调度方。2.2 我选的远程 Harness 边界与控制流架构落地后边界是这样的RuoYi-Vue-Plus 负责用户认证、租户隔离、任务单管理、前端接口、结果归档。AgentScope Harness 负责Agent 编排、模型调用、工具执行、流式事件输出、任务生命周期管理。控制流上我设计了两个方向同步控制流用于创建任务和查询状态。Java 端向 Harness 的 HTTP 接口提交一个任务请求Harness 返回一个全局唯一的任务 IDJava 端把任务 ID 和业务单号绑定存到本地业务表里。异步事件流用于状态和过程消息推送。Harness 在执行过程中会向外推送任务状态变更和 Agent 步骤日志Java 端通过 Webhook 回调或者拉取接口把最新状态同步进 RuoYi 的任务表再通过 WebSocket 或 SSE 推给前端用户。这么做的直接好处是RuoYi 的业务事务与 Harness 的执行过程不再强耦合。哪怕 Harness 重启Java 端早就把任务记录持久化了恢复后可以继续查询状态不至于丢数据。2.3 HTTP 契约与代码层封装远程连接最忌讳把调用代码散落在 Controller 里。我在 RuoYi 的服务层单独封装了一个 AgentTaskClient所有与 Harness 的交互都走这一个组件。代码大概长这样Service public class AgentTaskClient { Value(${agent.harness.base-url}) private String baseUrl; private final RestTemplate restTemplate; public AgentTaskClient(RestTemplate restTemplate) { this.restTemplate restTemplate; } public AgentSubmitResult submit(AgentTaskRequest request) { String url baseUrl /api/v1/tasks; HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntityAgentTaskRequest entity new HttpEntity(request, headers); ResponseEntityAgentSubmitResult response restTemplate.exchange(url, HttpMethod.POST, entity, AgentSubmitResult.class); return response.getBody(); } public AgentTaskStatus getStatus(String taskId) { String url baseUrl /api/v1/tasks/ taskId /status; return restTemplate.getForObject(url, AgentTaskStatus.class); } public void cancel(String taskId) { String url baseUrl /api/v1/tasks/ taskId /cancel; restTemplate.postForObject(url, null, Void.class); } }封装之后业务代码里面只需要关心 AgentTaskRequest 和 AgentTaskStatus 这两个对象不需要关心 HTTP 细节。这里有个特别容易出问题的点请求和响应里的字段命名。Java 端默认是驼峰命名而 Harness 的协议可能使用蛇形命名。如果两边不约定清楚就会出现字段全程为 null但任务其实运行正常的情况。我的做法是在 AgentTaskRequest 上用 JSON 注解显式声明字段名比如Data public class AgentTaskRequest { JsonProperty(agent_key) private String agentKey; JsonProperty(session_id) private String sessionId; JsonProperty(user_input) private String userInput; }宁可写全不要靠默认序列化推断这套契约一旦定下来后面扩展也轻松。3. 把 Agent 任务当成一种新的“业务数据”来设计很多人在做集成的时候只想着把任务提交过去、拿到结果就完事结果就是任务记录在数据库里没有任何痕迹出了问题根本追溯不了。正确的做法是把 Agent 任务看成一种和“订单”“工单”同等级别的业务数据认真设计表结构和状态流转。3.1 任务表字段设计与通用结构我在 RuoYi 里新增了一张 agent_task 表字段比普通业务表稍微多几个核心字段如下task_idHarness 返回的任务唯一 ID。biz_code业务单号比如工单号或咨询单号。agent_key使用哪个 Agent 编排流程。input_payload用户提交的原始请求JSON 格式存储。output_payload最终结果JSON 格式存储。status任务状态包括 pending、running、success、failed、cancelled。current_progress当前执行进度比如 0 到 100。trace_id链路追踪 ID把 RuoYi 日志和 Harness 日志串联起来。user_id 和 tenant_id权限隔离字段谁发起的任务属于哪个租户。total_tokensToken 总消耗用于后续计费和成本分析。error_msg失败时的错误摘要。create_time、update_time常规审计字段。建表 SQL 可以按照 RuoYi 的规范写重点是最起码要给 status、tenant_id、user_id 建索引。因为列表页需要频繁查询用户的任务如果没索引数据量上来以后查询会非常难受。3.2 租户与权限透传RuoYi-Vue-Plus 自带租户体系但 Harness 那边默认是不了解你的租户模型的。你不把租户信息传过去就会出现一个租户查到另一个租户任务的风险。我的做法是在提交任务时把 tenant_id 塞进请求头由 Harness 侧的网关层统一校验同时也在业务表里存一份双保险。HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.set(X-Tenant-Id, tenantId); headers.set(X-User-Id, userId);这里有个细节RuoYi 的权限拦截器一般只拦截 /admin 或者特定路径如果你给 Harness 单独设立了网关路由一定要确保网关层也校验了租户头不能只依赖业务表。权限这种事多做一层永远比少做一层安全。3.3 状态流转模型与回写我在 Service 层做了一个状态机避免各处随意更新状态提交后立刻写入 pending。收到 running 回调或轮询到 running改为 running并记录开始时间。收到成功回调改为 success并写入 output_payload、total_tokens。收到失败回调改为 failed并写入 error_msg。用户主动取消且 Harness 确认取消改为 cancelled。为什么要走状态机因为实际运行中会收到重复回调比如网络重发导致同样的成功事件推了两次如果你不是幂等处理状态就可能从 success 被错误覆盖成 running。我通过 task_id 更新并且只允许单向流转cancelled 和 success 是终态其他状态不能再变更。这一条极其重要。4. 流式输出RuoYi 前端的连接重建与消息推送多 Agent 任务和普通接口有个很大的体验差异执行时间可能长达几十秒甚至几分钟。如果让用户盯着空白页面等最终结果体验非常差。所以流式输出是必须做的。4.1 SSE 要过的三关我选择用 SSEServer-Sent Events而不是 WebSocket主要是因为 SSE 是单向文本流实现简单适合这种“服务端往浏览器推送执行过程”的场景。但 SSE 在 RuoYi 里要过三个关卡第一关是 Spring MVC 的支持。RuoYi 的 Controller 里需要返回 SseEmitter并且设置一个合理的超时时间比如 5 分钟。PostMapping(/agent/stream) public SseEmitter stream(RequestBody AgentStreamRequest request) { SseEmitter emitter new SseEmitter(300000L); agentTaskService.executeAndPush(request, emitter); return emitter; }第二关是安全过滤链。RuoYi 的 Spring Security 配置一般会拦截所有请求如果你新加的 SSE 接口没有加入白名单就会被权限过滤器挡住。这里要看你的业务要求如果允许登录用户访问就不需要加白名单但要在 Security 配置中放行该接口的匿名访问或者确保携带 Token。我的建议是保持登录鉴权使用 Header 传入 Token这样安全性和长连接可以共存。第三关是代理服务器缓冲。如果你前端访问域名走的是 NginxNginx 默认会缓冲 SSE 响应直到响应结束才发给浏览器这样你就收不到过程事件。需要在 Nginx 配置里关掉该接口的缓冲location /prod-api/agent/stream { proxy_set_header Connection ; proxy_http_version 1.1; proxy_buffering off; proxy_cache off; proxy_read_timeout 3600s; }这一条非常关键我第一次没设置 proxy_buffering off前端永远只能收到最后一次消息还以为是自己代码写错了。4.2 断线重连与消息补推前端 EventSource 自带断线重连但默认行为比较简单。我遇到的情况是Harness 执行时间特别长用户中间切了网络重新连接后之前的过程消息已经丢了前端只会显示空白一段时间。解决办法是在 RuoYi 后端维护一份任务过程消息的缓存比如存 Redis 列表。SSE 断线重连时前端带着 lastEventId 重新建立连接后端从 Redis 里把 lastEventId 之后的消息一次性补推过去。ListString missedEvents redisTemplate.opsForList().range(agent:events: taskId, lastIndex, -1); for (String event : missedEvents) { emitter.send(event); }省事的同时也要注意清理策略任务结束之后把 Redis 里的 key 设置过期时间比如 24 小时防止缓存堆积。4.3 前端侧怎么接前端我用的是原生 EventSource原因是不需要额外引入依赖。RuoYi-Vue-Plus 的请求封装基于 axios但 SSE 不能直接用 axios 的普通请求方式需要单独处理。const es new EventSource(/prod-api/agent/stream?taskId${taskId}, { withCredentials: true }) es.onmessage (event) { const data JSON.parse(event.data) if (data.type progress) { updateProgress(data.value) } else if (data.type tool_call) { updateToolLog(data.content) } else if (data.type done) { es.close() } } es.onerror () { // EventSource 会自动重连不需要主动关闭 }需要注意前端如果放在若依的 iframe 或 Tab 页里切换 Tab 时 SSE 连接不会被自动关闭会产生无用连接。我在页面销毁钩子里手动调用了 es.close()避免对后端造成连接堆积。5. 集成踩坑实录三次排查把心态都查稳了任何集成项目都躲不开“看起来哪里都对但就是跑不通”的时期。我记录三个印象最深的坑每个都是完整排查链路希望能帮你少走弯路。5.1 中文乱码和序列化字段不一致第一次跑通提交任务时Harness 返回的 Agent 回答里中文全部变成乱码同时 input_payload 里的字段丢失。那天我排查了三小时最后发现是两层问题叠加。第一层是 HTTP 请求头缺少编码声明。RuoYi 的 RestTemplate 底层默认使用 ISO-8859-1如果你没有显式设置 UTF-8中文参数在传输过程中就坏掉了。排查方法很简单先看服务端日志里收到的原始字符串再对比数据库中保存的输入发现从 Controller 传到 Service 还是正常从 Service 发给 Harness 之后才变乱码定位到 RestTemplate 的编码。restTemplate.getMessageConverters().forEach(converter - { if (converter instanceof StringHttpMessageConverter) { ((StringHttpMessageConverter) converter).setDefaultCharset(StandardCharsets.UTF_8); } });第二层是字段命名不一致。Harness 的接口文档里写的是 agent_key我 Java 端默认序列化成了 agentKey服务端不识别导致 Agent 配置一直用默认值。这个问题靠打印请求 JSON 才发现所以在 2.3 节我特意说了 JsonProperty 的重要性。5.2 反向代理超时任务还在跑前端却收到 504第二次踩坑更隐蔽。任务是能提交了但执行时间一旦超过 60 秒前端就收到 504页面提示网关超时。一开始我以为是自己代码超时但看 Harness 日志发现任务还在正常执行最终结果也生成了只是 ruoyi 前端等不到回调。排查链路是逐步确认的先直接访问后端接口耗时 90 秒接口正常返回说明 Spring Boot 没问题。再走前端域名访问60 秒整返回 504基本断定是 Nginx 层的 proxy_read_timeout。查看 Nginx 配置确认默认 60 秒调整为 3600 秒后恢复正常。这里还要注意如果 Harness 执行时间真的会超过几分钟更好的做法是把“同步等待”改成“异步任务 轮询”。因为浏览器、Nginx、中间代理每一层都有超时你不可能把所有超时时间都调到无限长。推荐模式是提交任务后立即返回 taskId前端通过 SSE 或轮询获取进度不要在 HTTP 请求里同步等最终结果。5.3 孤儿任务进程与优雅停机第三个坑是运维层面的。起初我直接通过进程脚本拉起 Harness但 RuoYi 应用重启的时候Harness 并不会跟着自动关闭也不会自动清理己方超时任务。结果就是服务器上残留好几个孤儿 Harness 进程占用 GPU 显存和内存有一个还在持续调用模型产生了不必要的费用。后来我在 RuoYi 的服务关闭钩子里加了一个清理逻辑应用停机时调用 Harness 的管理接口把当前租户还在 running 的任务标记为 interrupted并关闭进程。同时配合进程守护保证 Harness 异常退出后能自动拉起。PreDestroy public void shutdown() { ListString runningTasks agentTaskMapper.selectRunningTaskIds(); for (String taskId : runningTasks) { agentTaskClient.cancel(taskId); } }这一点如果忽略了长时间运行后会因为资源泄漏导致系统越来越卡而且很难排查。6. 稳定跑起来之后我对这套集成的一些个人总结项目跑稳之后我复盘过一轮觉得有几个判断是值得坚持的。第一个判断是隔离而不是融合。Java 平台和 Harness 各管各的通过 HTTP 契约协作虽然运行时会有一次网络开销但换来的维护性是值得的。RuoYi-Vue-Plus 的升级、Harness 的升级都可以各自独立进行不受对方版本影响。第二个判断是任务数据必须落在自己的库里。很多人觉得 Agent 执行结果是临时的直接返回给前端就行但业务平台不一样每个任务都要有审计、追溯、统计。把 Agent 任务当成正式业务表来设计后续做报表、做对账、做成本分析都会非常轻松。第三个判断是流式和异步必须一开始就做。如果第一版用同步 HTTP 写死后面再改成 SSE 会动到接口协议、前端代码、权限配置改动面很大。我就因为前期偷懒后面为了兼容老接口多写了一个同步包装层属于典型的“省小钱花大钱”。最后分享一个自己用得很顺手的小技巧在任务表里增加一个 trace_id每次提交 Harness 任务时生成一个 UUIDRuoYi 日志里打印业务单号、trace_id、task_id 三者对应关系。出问题的时候用 trace_id 就能把用户请求、后端处理、Harness 执行三段日志串成一条完整链路。这个习惯帮我省了至少十次排查时间强烈建议集成时从一开始就加上。
RELATED

相关推荐

模态与非模态对话框:消息循环原理与实战解析

模态与非模态对话框:消息循环原理与实战解析

1. 先搞清楚一个最基本的问题:这俩对话框到底差在哪很多人刚接触界面编程时,都会遇到"模态对话框"和"非模态对话框"这两个概念。网上教程翻来覆去就说"模态会阻塞,非模态不会阻塞",听着简单&#x…

📅 2026/10/22 10:40:11
给软件装上授权大脑:小白网络验证卡密系统与一键加密教程

给软件装上授权大脑:小白网络验证卡密系统与一键加密教程

1. 项目整体定位与价值拆解1.1 一句话定位:给软件装上“授权管理大脑”第一次看到类似标题的时候,我的第一反应是:关键词真多。小白、网络验证、卡密系统、一键加密exeDLL、免费使用、支持所有语言对接API开发、一键开端。但静下心来拆一下&a…

📅 2026/10/22 10:40:11
算法习题3.3:分治求解最大子数组和全解析

算法习题3.3:分治求解最大子数组和全解析

拿到这道“算法设计与分析-习题3.3”,我第一反应是这不只是一道题,而是一套完整的分治思想训练。课程里常把这种题放在分治章节的第三道位置,说明难度比前两道稍高,又不至于让初学者直接放弃。我自己当年做这类题时,最…

📅 2026/10/22 10:40:11
MORE NEWS

更多资讯

📰

基于STFT-CNN-BiGRU的故障诊断方法:从时频图到深度学习分类

搞设备故障诊断这几年,我最大的感受就是:信号处理方法和深度学习模型的组合,不是越新越好,而是匹配度越高越好。早先处理振动数据,大家习惯先做FFT看频谱,再找特征频率;可一旦设备转速波动、负载…

📰

Oracle大表回表过滤性能优化:从诊断到索引设计实战

1. 从一次凌晨告警说起:大表回表过滤为什么成了性能瓶颈凌晨两点多,监控系统弹出一条告警:某核心业务库的CPU使用率持续15分钟超过90%,同时伴随大量会话处于活跃等待状态。登录数据库一看,罪魁祸首是一条看起来并不复杂…

📰

一键解包打包dtbo.img工具:Windows下修改屏幕刷新率与设备树叠加层

简介:这是一款面向电脑端的dtbo.img分区一键解包与打包工具,主要服务于需要修改部分机型屏幕刷新率、且具备一定分区操作与刷机常识的用户,小白需谨慎使用。工具仅负责分解与打包两个环节,分解后的代码修改需自行完成,…

📰

APK反编译工具ApkToolkit实战:拆包、改smali、回编译与签名全链路

简介:ApkToolkit v3.0 绿色中文版是一款面向安卓应用 DIY 爱好者与逆向分析入门者的 APK 反编译工具,集成反编译、重建、签名、优化等常用能力,可解决安卓软件修改、二次打包与调试中的基础需求。压缩包为 zip 格式,整体约 23.52M…

📰

Spring AI Alibaba停更后,Java开发者如何应对AI应用技术选型

1. 一个停更消息引发的技术选型焦虑前几天在技术群里看到有人转了一张截图,说 Spring AI Alibaba 这个项目停更了,群里瞬间炸了锅。做 Java 后端的兄弟们纷纷冒泡,有人感叹“Java 在 AI 这条路上是不是真的走不通了”,有人直接说“…

📰

端侧LLM推理性能排查实录:0.5到30 token/s的优化之路

别人手机跑 7B 模型轻轻松松 40 token/s,我手机装上同一个推理框架、同一个模型文件,测出来 0.5 token/s 出头。两个数字摆一起,差了 80 倍。这不是夸张,是我最近调端侧推理时真实踩过的坑。当时第一反应是手机太老、硬件不行&…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬