一、功能更新概述
本次 JMAA 企业微应用平台重磅上线AI智能助手微应用,为平台现有财务、仓管、质量、生产、排产等全业务模块,新增智能对话式操作能力。用户可通过自然语言交互,快速完成业务查询、数据统计、流程操作、单据处理等工作,极大降低企业信息化系统的操作门槛,提升办公与生产管理效率。
JMAA 是一款开源企业信息化管理系统,覆盖企业全链路业务场景。在传统企业级系统的AI升级场景中,绝大多数系统面临改造难度大、代码侵入性强、原有功能需重构、适配成本高等痛点。
而 JMAA 依托独特的元数据模型架构,实现了颠覆性升级:无需修改原有任何业务模块代码、无需重构现有功能,通过独立全新开发AI助手微应用,即可完成全业务模块的AI智能化赋能,真正做到低侵入、高适配、可快速落地。
二、JMAA AI助手核心开发思路
本次AI助手基于大语言模型(LLM)结合JMAA元数据体系开发,通过「模型识别-元数据查询-工具调用-结果渲染」的闭环逻辑,实现自然语言到企业业务操作的精准转化,核心设计思路分为4步:
全模块模型上下文注入:在LLM对话上下文预载入JMAA所有业务模块模型信息,让大模型可根据用户自然语言指令,智能识别对应需要操作的业务模型。
元数据查询工具赋能:封装模型元数据查询能力,支持LLM动态获取业务模型的字段名称、数据类型、展示标题、业务方法、接口参数等完整信息,保障指令解析的准确性。
RPC远程方法调用:提供标准化模型RPC调用工具,LLM解析用户需求后,可自动调用对应业务模型的后端方法,完成数据查询、新增、修改、审批等全流程业务操作。
标准化结果渲染输出:规范LLM输出格式,统一生成指定HTML结构化结果,直接在前端对话窗口可视化展示,无需二次解析,交互体验更流畅。
三、AI助手功能效果展示
查询【已提交】采购订单并审核

根据采购订单生成来料接收单 
分析指定时间的采购入库数据 
四、手把手实战开发教程
下文完整还原JMAA AI助手微应用的落地开发流程。
1. 新建Maven微应用模块
按照JMAA微应用开发标准,新建独立Maven模块,通过package-info.java注册AI助手微应用清单,完成模块初始化定义,隔离业务代码与AI代码,保证架构整洁性。
@Manifest( name = "ai-assistant", label = "AI助手", category = "基础模块", author = "JMAA", license = "JMAA Pro", // 其余基础配置省略)package jmaa.modules.ai.assistant;import org.jmaa.sdk.Manifest;
2. 扩展模型元数据接口
基于系统原生ir.model模型进行扩展,开发模型元数据查询服务方法。自动过滤冗余元数据信息,仅保留字段、核心方法等必要内容,大幅降低LLM Token消耗,提升对话响应速度。
@Model.Meta(inherit = "ir.model")public class IrModel extends Model { /** * 查询业务模型元数据(字段+方法清单) * @param records 上下文记录 * @param model 模型唯一编码 * @return 精简后的模型元数据列表 */ @ServiceMethod(auth = "read", label = "查询模型元数据", ids = false) public List<Map<String, Object>> findModelMeta(Records records, String model) { MetaModel meta = records.getEnv().getRegistry().get(model); // 返回过滤后的模型字段、方法清单 return getModelMeta(records, meta); }}
3. 开发LLM对接控制器
开发后端核心控制器,封装LLM接口请求构建、工具注册、异步调用、超时异常处理等能力。采用异步线程调用大模型接口,避免服务线程阻塞,同时内置双核心工具:findModelMeta元数据查询、jmaaRpc业务方法调用。
@RestControllerpublic class AssistantController extends Controller { @Value("${llm.targetUrl}") private String targetUrl; @Value("${llm.apiKey}") private String apiKey; @Value("${llm.connectTimeout}") private int connectTimeout; @Value("${llm.readTimeout}") private int readTimeout; @Value("${llm.model}") private String model; @Value("${llm.maxCompletionTokens}") private int maxCompletionTokens; @Value("${llm.temperature}") private double temperature; @Autowired @Qualifier("llmProxyExecutor") private Executor llmProxyExecutor; // 对话超时时间:2分钟,适配大模型复杂推理场景 private static final long DEFERRED_TIMEOUT = 120000L; /** * LLM对话接口(异步非阻塞) */ @PostMapping(value = "/*/llm/chat", produces = MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8") @RequestHandler(auth = RequestHandler.AuthType.USER, type = RequestHandler.HandlerType.HTTP) public DeferredResult<String> chat(@RequestBody Map<String, Object> body) { Environment env = getEnv(); try { // 构建LLM标准化请求参数 Map<String, Object> request = buildRequest(env, body); DeferredResult<String> deferredResult = new DeferredResult<>(DEFERRED_TIMEOUT); // 超时、异常兜底处理 deferredResult.onTimeout(() -> deferredResult.setResult("{\"error\":\"request timeout\"}")); deferredResult.onError(throwable -> { String msg = throwable.getMessage() == null ? "unknown error" : throwable.getMessage(); deferredResult.setResult("{\"error\":\"" + escapeJson(msg) + "\"}"); }); // 异步调用LLM接口,避免主线程阻塞 llmProxyExecutor.execute(() -> { HttpURLConnection conn = null; try { request.put("stream", false); conn = buildConnection(request); int code = conn.getResponseCode(); InputStream is = code >= 200 && code < 300 ? conn.getInputStream() : conn.getErrorStream(); StringBuilder respSb = new StringBuilder(); try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { String line; while ((line = br.readLine()) != null) { respSb.append(line); } } deferredResult.setResult(respSb.toString()); } catch (Exception e) { String msg = e.getMessage() == null ? "exception" : e.getMessage(); deferredResult.setResult("{\"error\":\"" + escapeJson(msg) + "\"}"); } finally { if (conn != null) conn.disconnect(); } }); return deferredResult; } finally { env.close(); } } /** * 构建HTTP连接 */ private HttpURLConnection buildConnection(Map<String, Object> body) throws IOException { URL url = new URL(targetUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setConnectTimeout(connectTimeout); conn.setReadTimeout(readTimeout); conn.setDoInput(true); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8"); conn.setRequestProperty("Authorization", "Bearer " + apiKey); try (OutputStream os = conn.getOutputStream()) { byte[] data = Utils.toJsonString(body).getBytes(StandardCharsets.UTF_8); os.write(data); os.flush(); } return conn; } /** * 构建LLM请求体(系统提示词+工具列表+对话上下文) */ private Map<String, Object> buildRequest(Environment env, Map<String, Object> body) { KvMap result = new KvMap(); List<Object> messages = new ArrayList<>(); result.put("messages", messages); // 注入系统提示词 messages.add(getSystemMessage(env, body.get("context"))); // 追加用户对话上下文 List<Object> list = (List<Object>) body.get("messages"); if (Utils.isNotEmpty(list)) messages.addAll(list); // 注册LLM工具 if (!"none".equals(body.get("tools"))) { result.put("tools", Utils.asList(getFindMetaTool(), getRpcTool())); } result.put("model", model); result.put("max_completion_tokens", maxCompletionTokens); result.put("temperature", temperature); return result; } /** * 注册业务RPC调用工具 */ private Object getRpcTool() { return new KvMap() .set("type", "function") .set("function", new KvMap() .set("name", "jmaaRpc") .set("description", "JMAA后端RPC接口,基于模型元数据实现单据查询、新增、修改、审批等所有业务操作") .set("parameters", new KvMap() .set("type", "object") .set("required", Utils.asList("model", "method", "args")) .set("properties", new KvMap() .set("model", new KvMap() .set("type", "string") .set("description", "业务模型唯一编码,从模型元数据中获取")) .set("method", new KvMap() .set("type", "string") .set("description", "模型操作方法名,取自模型元数据方法列表")) .set("args", new KvMap() .set("type", "object") .set("description", "方法请求参数,仅传入业务参数(查询条件、字段、分页、排序、表单值等)")) .set("context", new KvMap() .set("type", "object") .set("description", "上下文展示参数,用于控制关联字段展示格式")) ))); } /** * 注册模型元数据查询工具 */ private Object getFindMetaTool() { return new KvMap() .set("type", "function") .set("function", new KvMap() .set("name", "findModelMeta") .set("description", "查询JMAA系统业务模型完整元数据,包含字段定义、可用操作方法") .set("parameters", new KvMap() .set("type", "object") .set("properties", new KvMap() .set("model", new KvMap() .set("type", "string") .set("description", "待查询的业务模型唯一编码")) ))); }}
4. 前端对话窗口开发
通过前端XML布局扩展,在系统导航栏新增AI助手入口,引入自定义JS、CSS样式,实现弹窗对话、消息发送、接口调用、结果展示等全套前端交互能力。
页面布局配置
<?xml version="1.0" encoding="utf-8"?><data> <webid="web_app"inherit_id="base.web_app"name="web.app.notify"> <xpathexpr="[data-widget=upload]"position="after"> <liclass="nav-item dropdown"data-widget="assistant"> <aclass="nav-link"href="#"> <iclass="fa fa-bahai"></i> AI助手 </a> </li> </xpath> <xpathexpr="body"position="inside"> <scriptsrc="/res/jmaa/modules/ai/assistant/statics/js/assistant.js"></script> </xpath> <xpathexpr="head"position="inside"> <linkrel="stylesheet"href="/res/jmaa/modules/ai/assistant/statics/css/assistant.css"/> </xpath> </web></data>
核心前端交互脚本(assistant.js)
jmaa.define('JAssistant', { __init__(opt) { let me = this; jmaa.utils.apply(true, me, opt || {}); // 绑定助手弹窗点击事件 me.dom.on('click', '.nav-link', function () { me.openAssistant(); }); }, // 打开AI助手对话弹窗 openAssistant() { // 弹窗初始化、DOM渲染、历史消息加载逻辑 }, // 发送用户对话消息 async sendMessage() { // 消息校验、加载状态、内容渲染 }, // 调用后端LLM对话接口 async callLLM() { // 异步请求后端接口、接收响应、渲染AI结果 }});
五、技术架构优势总结
本次JMAA AI助手的成功落地,充分体现了元数据驱动架构相较于传统Java三层架构的核心优势,同时降低了企业系统AI升级的门槛:
架构零侵入,升级成本极低:独立微应用架构,完全不改动原有财务、生产、仓管等核心业务代码,无业务风险,支持一键部署、按需启停。
元数据适配,全模块通用:依托系统元数据自动适配所有业务模型,无需为单个模块单独适配AI能力,一次开发、全平台复用。
RPC接口更适配大模型:对比传统Java三层架构的复杂接口,JMAA标准化JsonRPC接口简洁规范,天然适配LLM工具调用逻辑,对接效率更高、兼容性更强。
AI赋能开发提效:框架虽有一定基础学习成本,但标准化的模型、接口、微应用规范,可直接通过AI生成合规业务代码,大幅降低二次开发难度。
六、应用价值
本次AI助手升级,让传统企业管理系统从「手动操作、菜单点击、表单录入」的传统模式,升级为自然语言智能交互模式。员工无需熟悉复杂的系统操作流程,通过对话即可完成数据查询、报表统计、单据操作、流程跟进,全方位赋能企业降本提效,让信息化系统更智能、更易用。
夜雨聆风