把 AgentScope Harness 装进 RuoYi-Vue-Plus:纯 Java AI 平台的集成实践

前两篇讲了「是什么」和「权限怎么落地」。这一篇讲工程:智能体内核怎么与一个成熟的 Java 中台对接,以及我们踩过的坑。

一、目标:让中台「长出」智能体内核

我们不想要一个独立的 Agent 服务,再让业务系统去调它。目标是把智能体能力做成中台的一个模块:

  • 一个进程、一个 jar、一套构建;
  • 复用底座的权限、事务、缓存、审计;
  • 前端仍是一个 Vue 工程,通过 SSE 拿流式回答。

落点就是 ruoyi-modules/ruoyi-ai,包 org.dromara.ai。

二、依赖与版本

分类 组件 版本
语言 / 运行时 Java 21
业务框架 Spring Boot 4.1.1(Jetty)
智能体内核 AgentScope Harness 2.0.3
MCP 官方 MCP Java SDK 0.17.2
状态存储 Redis(经 Redisson) AgentScope RedisAgentStateStore
技能仓库 PostgreSQL AgentScope PostgresSkillRepository
向量库 Milvus v2.6.13
权限 Sa-Token 1.46.0

三、装配一个 HarnessAgent

装配集中在 AgentRegistry。它的职责是:按「智能体定义 × 会话资源集」把模型、工具、权限、中间件、策略拼成一个可复用的 HarnessAgent 实例,并按指纹缓存------定义或资源组合变化时自动重建,避免每轮对话都新建模型 HTTP 客户端。

装配主干大致是这样:

java 复制代码
HarnessAgent.Builder builder = HarnessAgent.builder()
    .name(definition.getAgentCode())
    .description(...)
    .sysPrompt(...)                     // 智能体提示词(专家装配时追加「技能优先」纪律)
    .model(model)                       // OpenAI 兼容客户端,含超时与重试
    .toolkit(toolkit)                   // 业务工具 + MCP 工具 + 协议工具
    .stateStore(stateStore)             // Redis 状态存储(多轮记忆)
    .workspace(workspace)               // 每个智能体一个工作目录
    .maxIters(maxIters)                 // 单轮最大迭代次数
    .toolsConfig(buildToolsConfig(...)) // allow / deny 白名单裁剪
    .permissionContext(...)             // 三档授权规则
    .middlewares(buildMiddlewares(definition));

// 关闭平台不需要的能力:无沙箱,禁文件/命令工具
builder.disableFilesystemTools().disableShellTool().disableMemoryTools();
// 技能来自本项目数据库;关闭框架默认的工作区技能来源
builder.skillRepositories(List.of(aiSkillRepository))
       .skillFilter(SkillFilter.only(skillNames));
builder.disableDefaultWorkspaceSkills();

几个值得展开的点。

3.1 工具白名单裁剪(allow / deny)

框架的默认工具箱会附带联网检索、文件、命令等内置工具。企业平台没有沙箱,所以必须双重收敛:

java 复制代码
private ToolsConfig buildToolsConfig(List<String> registeredCodes, List<String> skillNames,
                                     McpClientAssembly mcpAssembly) {
    List<String> allow = new ArrayList<>(registeredCodes);
    if (!skillNames.isEmpty()) {
        allow.addAll(SKILL_BUILTIN_TOOLS);        // 技能内置工具须显式放行
    }
    if (!mcpAssembly.toolNames().isEmpty()) {
        allow.addAll(mcpAssembly.toolNames());    // MCP 工具名同样须显式放行
    }
    ToolsConfig config = new ToolsConfig();
    config.setAllow(allow);                       // 白名单:非平台工具一律移除
    config.setDeny(List.of("web_search", "web_fetch"));  // 显式黑名单:禁联网检索
    return config;
}

教训:技能内置工具、MCP 工具都必须显式放进 allow 名单 ,否则会被框架的 ToolFilter 直接裁掉------模型看不到,你还找不到原因。

3.2 中间件注入

平台级中间件对所有智能体自动生效:

  • CurrentTimeMiddleware:每轮现算当前时间(日期 + 星期 + 时分 + 时区)注入 system prompt。模型自己不知道「今天几号」,框架原生注入的又是英文日期。
  • ExpertRosterMiddleware:只对入口智能体注入「本轮候选专家清单」。
  • SlotInheritanceMiddleware:只对入口智能体注入「槽位继承」。

中间件的好处是每轮现算、不进装配指纹------清单变了不必重建 Agent 实例。

四、状态与持久化

4.1 会话状态:Redis

多轮记忆、暂停恢复都依赖状态存储。我们直接复用项目已有的 RedissonClient:

java 复制代码
@Bean
public AgentStateStore aiAgentStateStore(RedissonClient redissonClient) {
    return RedisAgentStateStore.builder()
        .redissonClient(redissonClient)
        .keyPrefix("bizbuddy:ai:agentscope")
        .build();
}

用的是 RedisAgentStateStore 而非已 @Deprecated 的 RedissonAgentStateStore,两者键布局一致,切换无需数据迁移。

4.2 技能仓库:复用业务表

技能的建表与写入由项目自己的 SQL 与服务负责(保留审计列与生效范围治理),框架侧只读:

java 复制代码
return PostgresSkillRepository.builder(dataSource)
    .schemaName("public")
    .skillsTableName("ai_skill")
    .resourcesTableName("ai_skill_resource")
    .createIfNotExist(false)   // 不让框架建表
    .writeable(false)          // 框架只读
    .build();

五、工具接入:业务工具工厂

平台的工具注册表 ai_tool 只存元数据(编码、名称、描述、参数 Schema、类型、实现标识),真正的实现必须存在于代码侧:

java 复制代码
// 注册页只登记元数据;implName 对应代码侧已实现的 AgentTool
toolFactory.create(definition, kbIds).ifPresent(toolkit::registerAgentTool);

这道设计有意为之:避免出现「裸 SQL / 裸 Shell / 裸 HTTP」的万能工具。目前代码侧登记了 25 个工具实现,覆盖部门 / 用户 / 公告 / 角色 / 菜单权限 / 知识库检索等,其中写入类工具统一走 HITL。

六、MCP 双向集成

6.1 出口:把只读工具暴露出去

平台自建 /mcp 服务端(不依赖 spring-ai),可把标记为「对外暴露」的只读 工具提供给外部调用方,并支持访问口令校验(Authorization: Bearer <token> 或 X-MCP-Token)。

6.2 接入:外部 MCP 工具

接入外部 MCP Server 时,我们没有 走框架的 ToolsConfig.mcpServers,而是自行注册「归一化名装饰后」的客户端:

java 复制代码
// 与框架 McpServerRegistrar 内部一致:registerMcpClient(...).block()
// 且在 build() 之前完成,故仍受 ToolFilter 的 allow 名单管辖
toolkit.registerMcpClient(client).block(Duration.ofSeconds(20));

原因:远端工具名可能不满足 LLM 的 function name 规范(例如 weather.search_local 带点号),在严格校验的模型上会整轮 400 。平台因此在装配期做工具名归一化 :合法名原样保留,非法字符替换为下划线,撞名时追加原名哈希后缀。这样 weather.search_local 会以 weather_search_local 注册,模型即可正常调用。

另外,框架的 McpClientManager 不负责关闭客户端 ,所以注册失败时必须由装配方 close(),否则连接泄漏。

七、装配单元:从「智能体 × 场景包」到「智能体 × 资源集签名」

入口智能体「小Z」不绑定业务工具 ,它的工具来自会话级资源集 (对话底栏 + 选择:专家 / 场景包 / 工具 / 技能 / MCP 工具)。

由于工具集是装配期 决定的(allow 名单在装配时固定),运行期没有等价的「工具可见面覆盖」通道。所以资源集被压成确定性签名,直接进装配缓存键:

bash 复制代码
小Z   : agentId # R:<sig>            // sig = SHA-256(排序后的 type:key 列表) 前 8 字节
专家  : agentId # __expert__ # R:<sig>

好处是完全复用既有的装配与权限机制,改选择后下一轮即重新装配生效;代价是装配实例数随「资源组合种类」增长(同组合的多个会话共享实例)。

八、踩坑记录

坑 1:状态版本 CAS 与实例复用冲突

框架的 AgentState 走版本化 CAS (saveIfVersion → getVersioned)。当某个实例先服务过某会话、随后该会话状态被另一实例推进时,再复用这个实例会反序列化失败(Failed to get versioned state: agent_state)。

规避方式:为每个会话记录「最近一次装配用的资源集签名」,会话内切换资源组合时主动丢弃目标实例,下次新建即可。

坑 2:MCP 工具名不合规导致整轮 400

见 §6.2。归一化是同款问题的通用解法。

坑 3:框架不释放 MCP 连接

见 §6.2。注册失败路径必须自行关闭。

坑 4:「我配了 MCP 客户端,为什么小Z 还是不知道天气?」

这是一个被真实用户报上来的问题,很典型。用户已经在「MCP 客户端」页配好了天气工具,于是认为小Z 应该会用。排查后发现:

  • 那两个报「不知道」的会话,ai_session_resource 里一行资源都没有;
  • 装配日志显示 mcpTools=[]、空集签名;
  • 而更早一个会话(当时通过底栏 + 选中了该 MCP 工具)确实成功调用过天气工具。

结论:MCP 工具是「会话级」的。 「MCP 客户端」页的配置只是让工具可被选中 ,新会话默认是空集,必须在底栏 + 里勾选。这是平台既定的会话级设计,不是缺陷。

这个坑值得所有做企业 Agent 的团队注意:「配置好了」和「对本轮可用」是两件事,产品上要用 UI 把这个区别讲清楚,否则用户会认为工具坏了。

九、小结

把 Harness 装进中台,核心就三件事:

  1. 装配要可控:allow / deny 白名单 + 装配期权限规则,工具可见面在装配时定死;
  2. 状态要持久:Redis 状态存储承载多轮记忆与 HITL 暂停恢复;
  3. 边界要显式:技能工具、MCP 工具、技能仓库都要显式接入,框架不会替你猜。

再叠加前两篇讲的「三层权限」,一个纯 Java 的企业级 Agent 平台就立起来了。


相关仓库

作者:AI架构师张磊

相关推荐
思考着亮43 分钟前
3.什么是Harness Engineering?什么又是Loop Engineering?
人工智能
dora43 分钟前
LangChain4j 新手入门实战教程(Java版)
后端·langchain·agent
蜗牛互联网43 分钟前
MongoDB Atlas Agent Engine之后,如何用版本门禁防止陈旧写入
java·数据库·人工智能·后端·mongodb
长弓三石44 分钟前
企业级智能体的权限到底怎么落地?以 BizBuddy 为例
java·人工智能·agent
慢云智慧空间1 小时前
从智能终端到空间AI,慢云科技如何重新定义智慧建筑的核心能力?
人工智能·python·科技
栈知见1 小时前
04-让 Agent 会"用工具": Tool Calling 实战
agent
合调于形1 小时前
Jusshen zhzzneng《具身智能》词条汉语拼音字母标调拼写实测案例
人工智能·自然语言处理·人机交互·语音识别·学习方法
DP DPharness1 小时前
cc-safety-net 上手指南:从 npx install 到 doctor 自检
人工智能·dpharness
用户4270276726811 小时前
一次缓存优化:SQL 从 502 降到 5
java