乐于分享
好东西不私藏

JMAA 全新升级AI助手微应用:零侵入赋能财务、生产、仓管全业务模块

JMAA 全新升级AI助手微应用:零侵入赋能财务、生产、仓管全业务模块

一、功能更新概述

本次 JMAA 企业微应用平台重磅上线AI智能助手微应用,为平台现有财务、仓管、质量、生产、排产等全业务模块,新增智能对话式操作能力。用户可通过自然语言交互,快速完成业务查询、数据统计、流程操作、单据处理等工作,极大降低企业信息化系统的操作门槛,提升办公与生产管理效率。

JMAA 是一款开源企业信息化管理系统,覆盖企业全链路业务场景。在传统企业级系统的AI升级场景中,绝大多数系统面临改造难度大、代码侵入性强、原有功能需重构、适配成本高等痛点。

而 JMAA 依托独特的元数据模型架构,实现了颠覆性升级:无需修改原有任何业务模块代码、无需重构现有功能,通过独立全新开发AI助手微应用,即可完成全业务模块的AI智能化赋能,真正做到低侵入、高适配、可快速落地。

二、JMAA AI助手核心开发思路

本次AI助手基于大语言模型(LLM)结合JMAA元数据体系开发,通过「模型识别-元数据查询-工具调用-结果渲染」的闭环逻辑,实现自然语言到企业业务操作的精准转化,核心设计思路分为4步:

  1. 全模块模型上下文注入:在LLM对话上下文预载入JMAA所有业务模块模型信息,让大模型可根据用户自然语言指令,智能识别对应需要操作的业务模型。

  2. 元数据查询工具赋能:封装模型元数据查询能力,支持LLM动态获取业务模型的字段名称、数据类型、展示标题、业务方法、接口参数等完整信息,保障指令解析的准确性。

  3. RPC远程方法调用:提供标准化模型RPC调用工具,LLM解析用户需求后,可自动调用对应业务模型的后端方法,完成数据查询、新增、修改、审批等全流程业务操作。

  4. 标准化结果渲染输出:规范LLM输出格式,统一生成指定HTML结构化结果,直接在前端对话窗口可视化展示,无需二次解析,交互体验更流畅。

三、AI助手功能效果展示

  1. 查询【已提交】采购订单并审核

  2. 根据采购订单生成来料接收单
  3. 分析指定时间的采购入库数据

四、手把手实战开发教程

下文完整还原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<StringObject>> 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升级的门槛:

  1. 架构零侵入,升级成本极低:独立微应用架构,完全不改动原有财务、生产、仓管等核心业务代码,无业务风险,支持一键部署、按需启停。

  2. 元数据适配,全模块通用:依托系统元数据自动适配所有业务模型,无需为单个模块单独适配AI能力,一次开发、全平台复用。

  3. RPC接口更适配大模型:对比传统Java三层架构的复杂接口,JMAA标准化JsonRPC接口简洁规范,天然适配LLM工具调用逻辑,对接效率更高、兼容性更强。

  4. AI赋能开发提效:框架虽有一定基础学习成本,但标准化的模型、接口、微应用规范,可直接通过AI生成合规业务代码,大幅降低二次开发难度。

六、应用价值

本次AI助手升级,让传统企业管理系统从「手动操作、菜单点击、表单录入」的传统模式,升级为自然语言智能交互模式。员工无需熟悉复杂的系统操作流程,通过对话即可完成数据查询、报表统计、单据操作、流程跟进,全方位赋能企业降本提效,让信息化系统更智能、更易用。