资讯详情

RuoYi-Vue-Plus 集成 AgentScope Harness:在若依后台跑通 AI 智能体编排

📅 2026/10/12 16:52:33 | 华诺云谱 👁 阅读
RuoYi-Vue-Plus 集成 AgentScope Harness:在若依后台跑通 AI 智能体编排
1. 为什么我要把 AgentScope Harness 塞进一个若依系后台先交代背景我在做某公司内部运维工单系统时用的是 RuoYi-Vue-Plus 这套增强版后台框架。它原生自带用户、角色、菜单、字典、操作日志、代码生成这些能力出业务 CRUD 页面非常快。但问题是现在很多需求已经不止“增删改查”了比如用户直接说一句“帮我查一下这台服务器近三天的告警并按严重程度整理成报告”系统就得自己去查告警表、调监控接口、汇总数据、生成文本。这种需求用传统硬编码去做当然也能做但每来一个新场景就要写一套新链路非常累。我当时的想法是能不能直接在 RuoYi-Vue-Plus 这套 Spring Boot 3 工程里装一个 AgentScope Harness让整个项目具备“多智能体编排执行”的能力同时仍然保持纯 Java 开发不额外引入 Python 微服务折腾了一段时间后效果还不错。这篇就记录一下整个集成决策、实现过程、踩过的坑以及最终跑起来后的真实表现。如果你正在用若依系框架想低成本接入 AI Agent 能力这篇应该能帮你少走不少弯路。先说清楚 AgentScope Harness 扮演的角色。你可以把它理解成一个“会调用工具的工作流引擎”上层给它一个任务描述它负责拆解目标、编排步骤、轮询调用模型接口、触发注册在 Spring 容器里的业务工具方法再汇总结果。它不关心你到底用的是哪个大模型厂商也不关心你的业务数据存在哪张表只负责把“意图”变成“一连串可执行的动作序列”并且把每一步执行过程和结果都记录下来。这个定位恰好和 RuoYi-Vue-Plus 互补后台管理系统擅长管人和管数据而 Harness 擅长把“人的需求”翻译成“数据的操作”。如果你没有接触过这类东西可以先回想一下工作流引擎以前我们用 Flowable 这类 BPM 工具是先把流程图设计好再按图执行。Harness 的思路不一样它没有预设的固定流程图而是每次根据任务目标和可用工具现场生成执行计划更像是“动态编排”。这种灵活性在大量非标准化需求场景里特别好用但同时也带来一个问题你不能完全信任它的自主决策必须在关键环节加上人工审核位和熔断机制这个后面会细说。2. 落地前的架构选择与模块拆分2.1 三条集成路线我为什么选“进程内嵌”当时摆在我面前的主要有三条路线我简单做了个对比集成方式优点缺点适合场景Harness 独立部署后台通过 HTTP 调用隔离性强Harness 挂掉不影响主应用可单独扩容需要多维护一个服务跨服务调用有网络开销业务工具暴露成 HTTP 接口后权限控制变麻烦团队已经有多服务部署习惯且需要大规模并发Harness 作为本地轻进程主应用通过本地 HTTP 管理请求Java 工程不引入重依赖调用接口与远端一致还是有个额外进程要维护进程间通信和调试略麻烦本质没省多少事不太推荐两头不讨好Harness 以 Java 依赖形式嵌入 RuoYi-Vue-Plus 进程部署最简单一个 jar 全搞定工具方法直接注入 Spring 容器天然复用事务、AOP、Sa-Token 权限调用链短、延迟低类库依赖可能出现冲突需要关注线程池与主应用资源的隔离Harness 出问题可能影响主进程中小团队、项目初期、想快速试错落地我最终选了第三条进程内嵌。核心原因是工具调用太方便了。RuoYi-Vue-Plus 里已经写好的 Service、Mapper只要通过注解暴露成 Agent 可调用工具Harness 在编排时就能直接调 Spring 容器里的 Bean连 HTTP 封装都省了。而且后台系统的数据权限已经基于 Sa-Token 登录用户做好了Agent 调工具时只要带上当前用户上下文就能继承这套权限非常自然。当然进程内嵌也不是没有代价。最明显的问题是依赖管理Harness 传递过来的第三方库很容易和项目里原有的依赖打架这个我在第 5 节会专门讲。另外如果 Agent 任务里有非常吃 CPU 的模型推理逻辑它确实会占用主应用线程。我们的解决方式是给 Harness 单独配一个线程池并把模型调用超时卡死避免个别慢请求拖垮整个应用。2.2 新建 agent-harness 模块别在原工程里到处乱插若依增强版的工程结构通常是多模块 Maven 项目我建议不要直接在 admin 或 system 模块里写 Agent 编排代码而是单独建一个ruoyi-agent-harness模块让业务模块依赖它。这样做的好处很直接以后 Harness 升级版本或者你想把它摘出去变成独立服务改动范围都被限制在一个模块里不会搞得整个工程都是散落的 AI 代码。我的模块结构大致如下ruoyi-agent-harness/ ├── pom.xml ├── src/main/java/.../agent/ │ ├── core/ // 与 Spring 无关的纯编排核心 │ ├── spring/ // Spring Boot Starter 装配 │ ├── tool/ // AgentTool 注解与扫描器 │ ├── execution/ // 任务执行器、步骤状态机 │ ├── context/ // 会话上下文、用户上下文 │ └── web/ // 给管理端用的 Controller └── src/main/resources/ └── META-INF/spring/... // 自动装配配置这里有个容易被忽略的点Harness 的核心编排逻辑最好做成跟 Spring 无关的纯 Java 类Spring 相关的东西只是“适配层”。这样万一以后要把它独立出去或者想单测都不用启动整个 Spring 容器。2.3 数据表设计四张表把编排过程全部落库Agent 执行和普通 CRUD 不一样它是个“过程型”任务用户提一个目标Harness 要拆成多个步骤每个步骤可能要调不同的工具中间还可能失败重试。所以数据库里得把整个执行过程记录下来否则出问题都没法定位。我设计了四张核心表都放在agent前缀下表名作用关键字段agent_bot智能体登记表一个智能体对应一套系统提示词和可用工具集bot_key、name、system_prompt、tool_keys、statusagent_tool工具登记表记录有哪些业务方法被暴露给 Agenttool_key、bean_name、method_name、description、param_schemaagent_job任务表一次用户请求就是一个 jobjob_no、bot_key、user_id、task_desc、status、created_atagent_step步骤执行记录表每个编排步骤一条记录job_no、step_no、tool_key、input_json、output_json、status、retry_count有了这几张表管理端页面直接查数据就能看到某个任务执行到哪一步了哪一步调了哪个工具、传了什么参数、返回了什么结果全部可追溯。这也是我敢让 Agent 动业务数据的前提——每一步都留痕出了问题能复盘。3. 核心集成实操从依赖到第一个 Agent 任务3.1 Maven 依赖与版本锁定既然选择进程内嵌第一步就是把 Harness 的包引进来。我当时在处理依赖时踩过一次坑Harness 的某个传递依赖把项目里的guava从 33 降到了 31结果一个定时任务模块当场编译报错。后面我统一在父 POM 里用 dependencyManagement 锁版本再对 Harness 的传递依赖做排除。dependency groupIdio.agentscope/groupId artifactIdagentscope-harness-spring-boot-starter/artifactId version2.4.1/version exclusions exclusion groupIdcom.google.guava/groupId artifactIdguava/artifactId /exclusion exclusion groupIdio.netty/groupId artifactIdnetty-common/artifactId /exclusion /exclusions /dependency然后在父 POM 的 dependencyManagement 里统一定义 guava 版本dependencyManagement dependencies dependency groupIdcom.google.guava/groupId artifactIdguava/artifactId version33.0.0-jre/version /dependency /dependencies /dependencyManagement这个做法的原则是子模块不要自己乱写版本号所有第三方版本尽量收口到父 POM 统一管理。特别是有 Spring Cloud Alibaba 或其它全家桶的项目Harness 的自动配置类里可能引用了不同版本的 Jackson 或 Netty必须在集成早期就做一次依赖树分析。3.2 application.yml 配置Harness 集成进来后需要配置模型端点、线程池、超时时间这些核心参数。我贴一份当时线上用的核心配置片段agentscope: harness: enabled: true # 模型服务配置兼容 OpenAI 风格接口 model: endpoint: http://127.0.0.1:8899/v1 api-key: sk-placeholder model-name: deepseek-chat temperature: 0.3 max-tokens: 2048 timeout: 30s # 执行线程池 executor: core-pool-size: 4 max-pool-size: 8 queue-capacity: 200 keep-alive: 60s # 任务全局超时避免一个任务跑太久 task-timeout: 10m # 最大编排步数防止模型无限拆步骤 max-steps: 15 # 工具调用失败重试次数 tool-retry: 2temperature我给的 0.3不是 0。原因很实际Agent 编排任务如果完全确定性遇到工具返回结果的写法稍有变化就容易死板但温度太高又容易编出超出工具范围的东西。0.3 是平衡点。max-steps必须设置宁可任务做不完也不让它无限循环否则模型会为了同一个目标反复尝试同一个工具非常烧钱还占线程。3.3 用 AgentTool 把普通 Service 变成 Agent 能力这是整个集成里最优雅的一环。RuoYi-Vue-Plus 里封装好的业务方法比如“查工单列表”“更新工单状态”“发送站内通知”我只要写一个类把这些方法暴露出来Component public class OrderAgentTools { private final IOrderService orderService; private final NoticeService noticeService; public OrderAgentTools(IOrderService orderService, NoticeService noticeService) { this.orderService orderService; this.noticeService noticeService; } AgentTool( key query_order, name 查询工单, description 根据关键词或状态查询工单列表返回工单编号、标题、当前状态, paramSchema {\type\:\object\,\properties\:{\keyword\:{\type\:\string\},\status\:{\type\:\string\}},\required\:[\keyword\]} ) public ListOrderVO queryOrder(String keyword, String status) { // 直接复用已有的 service 查询逻辑 return orderService.search(keyword, status); } AgentTool( key notify_user, name 发送站内通知, description 给指定用户发送站内消息通知需要提供用户名和通知内容, paramSchema {\type\:\object\,\properties\:{\username\:{\type\:\string\},\content\:{\type\:\string\}},\required\:[\username\,\content\]} ) public void notifyUser(String username, String content) { noticeService.send(username, content); } }用注解而不是用配置文件注册工具最大的好处是类型安全和编译期检查。工具方法就是普通的 Java 方法参数签名写死idea 里能直接跳转改起来也放心。配置文件的方式看起来灵活实际上 paramSchema 写错了都不知道等运行时模型传参才发现那感觉太糟了。启动时扫描器会把这些带AgentTool的方法注册进 Harness 的工具表同时在agent_tool表里维护一份元数据。我建议工具描述写详细一点因为模型是靠 description 来判断“什么时候调用这个工具”的。描述含糊模型就不会用编排效果会大打折扣。3.4 触发编排与执行生命周期用户从前端点一下“开始处理”后端 Controller 收到请求后调用agentHarnessService.touchForHandle(...)发起任务。整个执行过程我描述成一段文字流程比代码更容易看懂前端传入botKey和taskDesc例如 botKey 为ops_assistanttaskDesc 为“帮我查一下近三天服务器告警并按严重程度排序”。后端取出智能体配置和可用工具列表生成job记录状态为RUNNING。Harness 把 taskDesc 和工具描述一起交给模型模型返回一个执行计划包含若干步骤每个步骤指定调用哪个工具、传什么参数。执行器按顺序执行步骤先从 Spring 容器取对应 Bean反射调用标注的方法然后把输出 JSON 塞回上下文。如果一个步骤执行完后模型认为需要根据结果继续下一步就再走一次“模型 - 工具调用”循环直到达到max-steps或者模型给出最终总结。最终结果被写入agent_job状态变为SUCCESS或FAILED。这里有个小细节步骤执行不一定是严格串行的。有些步骤之间没有依赖关系Harness 可以并行去调多个工具。但我在项目初期没有开并行原因很简单——工具方法里可能操作同一张表并行带来并发问题之后不好排查。先把串行跑通再逐步优化性能。事实证明这个决策很明智后面排查问题的时候全靠串行日志才捋清了调用链。触发任务的调用代码大致这样public AgentJobVO submitTask(String botKey, String taskDesc, Long userId) { BotConfig bot botService.getByKey(botKey); if (bot null || !bot.isEnabled()) { throw new ServiceException(智能体不存在或已停用); } AgentJob job agentJobService.create(bot, userId, taskDesc); agentHarnessService.asyncExecute(job.getJobNo()); return AgentJobVO.from(job); }异步执行用的是 Harness 自带的线程池没有直接在主请求线程里跑否则前端请求会长时间阻塞。返回 jobNo 给前端前端用它订阅 WebSocket 查看执行进度。3.5 异步线程里保留用户上下文RuoYi-Vue-Plus 基于 Sa-Token 做登录认证用户信息存在 ThreadLocal 里。问题来了Harness 的执行线程是异步线程池进去之后StpUtil.getLoginId()直接报“未登录”。数据权限、操作日志、用户归属全部拿不到工具查询也没法按用户隔离。我当时比较粗暴的办法是在提交任务时把 userId 存进AgentJob工具方法需要用户信息时手动查一遍。但这样太啰嗦了每个工具都要写获取用户参数的逻辑。后来改用线程装饰器把主线程里的 LoginContext 复制到异步线程public class UserContextTaskDecorator implements TaskDecorator { Override public Runnable decorate(Runnable runnable) { String loginId StpUtil.getLoginIdAsString(); String tokenValue StpUtil.getTokenValue(); return () - { try { // 创建临时登录会话 StpUtil.setTokenValue(tokenValue); StpUtil.setLoginId(loginId); runnable.run(); } finally { StpUtil.logout(); } }; } }然后在配置 Harness 线程池时传入这个 TaskDecorator。这一步解决得很关键不然所有带用户权限过滤的工具方法在 Agent 执行时都会静默失效而且测试还不容易发现因为只查几条数据时结果看着挺正常。4. 控制台接入与操作收敛4.1 菜单、权限与数据字典Agent 能力集成进来后必然要给管理员一个管理界面。RuoYi-Vue-Plus 的菜单权限都是入库的所以我在 SQL 脚本里初始化了“AI 控制台”菜单下面分三个页面智能体管理、任务执行记录、工具注册列表。页面对应的权限标识分别是agent:bot:list、agent:job:list、agent:tool:list。这里要特别处理的一个业务点是“谁能看谁的任务”。如果是协同办公场景用户 A 发起的 Agent 任务不能让用户 B 也看到全部上下文否则就是越权。我实现的方式是在查询列表时自动加数据权限过滤普通用户只能查user_id 当前登录用户的任务管理员可以看全部。这一步其实不是技术问题而是产品设计问题别等上线了才发现 Agent 任务记录里的业务数据泄露给了不该看的人。工具注册列表我一开始是只读的因为工具是通过 Java 注解注册的运行期动态加工具需要额外的 agent 执行机制复杂度高。先做只读等稳定了再考虑可视化注册。管理员能看懂“哪些服务已经暴露给模型”这个信息比“能改工具的参数结构”更重要。4.2 WebSocket 流式推送执行进度任务异步执行后用户不可能一直刷新列表。我用 WebSocket 把执行步骤推送到前端。后端核心代码其实不复杂Component public class AgentStepWebSocket { private final SimpMessagingTemplate messagingTemplate; public void pushStep(AgentStep step) { AgentStepVO vo AgentStepVO.from(step); // 按 jobNo 发到对应订阅频道 messagingTemplate.convertAndSend(/topic/agent/ step.getJobNo(), vo); } }这里我踩过一个坑RuoYi-Vue-Plus 本身没有启用 Spring Messaging直接引入spring-boot-starter-websocket后和 Sa-Token 的鉴权过滤器顺序有冲突。耗时一晚上才定位到是握手阶段的拦截器没有放行 WebSocket 请求导致前端一直在 pending。解决方式是在 Sa-Token 配置里把 WebSocket 端点路径加入排除列表握手阶段不鉴权连接建立后再在回调里校验 token。前端部分我用的是 Vue 3 的原生 WebSocket 封装没有引额外的库因为就一个订阅场景引库反而加重。前端收到步骤事件后直接在页面上渲染一个“步骤时间线”每步显示工具名、输入参数摘要、执行状态。用户能实时看到 Agent 在干什么就减少了“卡死还是正在思考”的困惑。4.3 限流、熔断与人工审批位Agent 调用模型接口是要花钱的而且一次任务可能循环调用多次模型所以必须有限流。我做了两层一是 Nginx 层对/agent/**接口按 IP 做请求频率限制二是应用层用 Redisson 的分布式限流器对每个用户的并发任务数做限制默认一个用户同时最多 2 个任务超过直接报“任务太密集请稍后再试”。比限流更重要的是人工审批位。虽然 Harness 能编排工具调用但我并不信任它直接执行所有高危险动作。举个例子工单的状态改成“已完成”这种事不能让模型直接调工具改。我的做法是在 AgentJob 状态机里加一个WAIT_CONFIRM状态当工具注册表里某个工具被标记为need_confirmtrue时执行器不会真的调用而是把参数存到待审批表里用户在前端点“确认执行”后才会真正触发调用。这其实就是人在回路本质上是在自主性和可控性之间取平衡。5. 实战踩坑与故障修复实录5.1 依赖冲突Netty、Guava、Jackson 三连击这是集成过程中最折磨人的问题。Harness 底层用的 Netty 版本和 RuoYi-Vue-Plus 引入的 WebSocket 相关依赖版本不一致启动时出现过NoSuchMethodError。这类报错的排查思路很简单先看异常类是从哪个 jar 加载的然后在 IDEA 的 Maven 面板里跑dependency:tree直接查冲突节点。我的做法是写一份固定版本的依赖清单把容易打架的 jar 全部显式声明依赖锁定版本说明com.google.guava:guava33.0.0-jre若不锁可能被传递依赖降级io.netty:netty-common4.1.100.FinalWebSocket 与 Harness 都需要com.fasterxml.jackson.core:jackson-databind2.15.3防止序列化行为不一致com.alibaba:fastjson2.0.42RuoYi 家喜欢用 fastjson务必显式声明顺便说一句RuoYi-Vue-Plus 老版本里大量使用 fastjsonHarness 内部用的是 Jackson两边在序列化工具类上经常互相干扰。我的建议是所有对外 JSON 返回统一用 Jackson内部临时对象序列化可以看情况但不要混用两个框架去处理同一个对象图否则会出现“A 接口正常B 接口字段丢了”的诡异问题。5.2 多线程环境下 Transactional 失效这个坑是典型的 Spring 多线程事务问题。我一开始把整条 Agent 执行链路都包在一个大事务方法里想着“要么全成功要么全回滚”。实际上 Harness 是异步线程池执行主线程把任务丢给线程池后事务就断了。而且工具方法里操作数据库时如果工具方法本身没有加事务注解那每条 SQL 是自动提交的任务中途失败根本没法回滚前面的操作。后来我调整了事务策略主任务创建和状态更新单独事务快速提交。每个工具调用在工具方法上加Transactional(rollbackFor Exception.class)保证单个工具内部的数据库操作原子性。步骤记录表用REQUIRES_NEW单独事务写入不管业务操作成不成功执行轨迹都必须留下来。这个策略有个代价整个任务不是原子性的。但仔细想一下就明白Agent 参与的任务几乎不可能全链路原子化——它已经调用了外部模型接口模型调用没法回滚工具调用的副作用也没法完全撤销。所以不要追求“任务级事务”应该追求“可追踪、可人工修正”这才是 Agent 集成和普通接口事务最大的思维差异。5.3 模型工具参数解析失败JSON 序列化的兼容性模型返回的工具调用参数是 JSON 字符串Harness 要把这个 JSON 反序列化成 Java 方法的实际参数。本来这是框架内部做的事但我自己写的paramSchema和实际的方法参数不完全对应比如方法参数是Integerschema 里写成string模型传了个2024进来反序列化直接就报类型不匹配。这个问题排查起来很费劲因为模型输出是不稳定的不会每次都在同一个地方失败。我的解决思路是两条腿走路第一在 schema 里严格定义类型而且尽量简单能用字符串就不搞对象嵌套能平铺就不搞复杂结构降低模型的理解成本。第二加一个“工具调用参数校验”的兜底方法在反射调用前把参数 JSON 解析成JsonNode再根据方法签名做一次宽松转换比如数字字符串转 Integer空值给默认值。这种方法治标但能极大提升成功率。经验是给模型用的工具接口一定要设计得“蠢一点”参数越少越好每个参数含义越直白越好。如果你把业务对象二十个字段全部对外暴露模型绝对会选错或漏传那不是模型问题是你接口设计问题。5.4 循环依赖与启动失败Harness 的编排服务如果放在一个AgentOrchestrator里它要依赖业务 Service但业务 Service 里可能也有一个“手动调用 Agent 生成报告”的功能反过来依赖AgentOrchestrator。在 Spring 启动阶段直接注入就会出现Currently in creation循环依赖异常。解决方式我用了三层Service public class OrderServiceImpl implements IOrderService { // 用 ObjectProvider 延迟获取避免启动期循环 private final ObjectProviderAgentOrchestrator orchestratorProvider; public OrderServiceImpl(ObjectProviderAgentOrchestrator orchestratorProvider) { this.orchestratorProvider orchestratorProvider; } public String generateReport(Long orderId) { AgentOrchestrator orchestrator orchestratorProvider.getIfAvailable(); //... } }这里不建议直接无脑加Lazy解决所有循环引用。Lazy只是在代理上绕了一下如果两个 Bean 之间运行时确实会双向调用还是会出现代理失效的问题。ObjectProvider的方式更明确它表达的是“我不是一定需要这个依赖只是需要的时候再取”语义更清晰。5.5 模型接口超时导致的线程耗尽有段时间线上的任务频繁失败看监控发现 Harness 的核心线程池线程数量一直处于满占用状态。日志里全是某模型接口的超时异常。排查后发现是配置的模型服务本身问题但更根本的原因是整个网关线程池都被慢调用拖住了。修复措施有两个场景一把单个模型调用的超时缩短到 10 秒全局任务超时 5 分钟。宁可任务失败也不要让线程无限挂起等着模型响应。场景二给 Agent 任务用独立线程池和业务主线程池分离Maven 侧配置 Harness 时不要直接用默认的全局线程池。你可能会问任务失败了怎么办我的答案是允许 Agent 任务失败然后由系统发送失败通知给发起人发起人重新组织描述后再发起一次。别强求高成功率更重要的是失败能被快速发现、快速重试。6. 跑起来之后的一些真实体会集成跑通后我最大的感受是“工具注册的边界决定了 Agent 能力的边界”。前期我只暴露了三四个工单查询类工具Agent 的表现只能说中规中矩能完成单步查询后面我陆续把工单流转、通知发送、告警查询、报表生成这些工具都注册进去Harness 能自己组合出“查告警 - 归并同类项 - 生成工单 - 通知负责人”这样四步链路才真正感觉到这套东西的威力。但边界感也很重要。我现在把工具分成三类只读查询类工具可以全自动执行状态变更类工具需要人工确认外部写入类工具比如发短信、调用第三方接口默认禁用除非管理员手工开启。这个分类不是技术上的而是业务风险上的。Agent 可以帮你想、帮你查、帮你建议但“动手改系统状态”这件事必须保留人的决定权。如果你也想在若依系工程里做类似集成我的建议是三步走第一步先只接一个查询类工具把 Harness 的编排链路完整跑通第二步再加状态变更类工具配合人工确认机制测试第三步再考虑多个 Agent 协同或者把 Harness 拆成独立服务去应对更大并发。全部跑通之后你会发现RuoYi-Vue-Plus 不再只是一个后台管理系统脚手架而是一个能听懂业务语言的业务操作平台。这个转变的价值比单纯多几个 AI 界面大得多。最后再分享一个小技巧集成后的日志配置一定要单独给agent包开一个 logger 级别控制台里只输出任务摘要详细的工具调用入参出参写进独立日志文件。排查问题时grep jobNo就能看到一次 Agent 任务从开始到结束的完整生命周期比翻全量日志高效太多。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑