别卷 Python 了:我用 Java 21 + Spring Boot 3 打造了一个企业级 RAG + 智能体工作流引擎(附架构与源码解析)

别卷 Python 了:我用 Java 21 + Spring Boot 3 打造了一个企业级 RAG + 智能体工作流引擎(附架构与源码解析)

声明:本文介绍的是笔者本人的开源项目 MaxKB4j,重点聊架构设计与技术选型,代码均节选自真实源码,欢迎拍砖。

先交结果:MaxKB4j(Max Knowledge Brain for Java),一个纯 Java 原生的大模型应用平台------RAG 知识库 + 可视化工作流 + 多 Agent 协作,对标 Dify / MaxKB / FastGPT 这一品类,但技术栈是 Java 21 + Spring Boot 3.5 + LangChain4j 1.20。

几个硬数据:

  • 21 个月、2270 次提交、887 个 Java 文件、约 5.6 万行代码,12 个发布版本(最新 v2.9.0)
  • 32 种工作流节点18 家模型提供商 (OpenAI / DeepSeek / 通义 / Kimi / GLM / Ollama......)、7 种模型类型
  • 仓库:Gitee(主) · GitHub(镜像) · 在线 Demo(demo / demo@123456)

这篇文章不复述 README,专注回答四个工程师视角的问题:整体架构长什么样?虚拟线程在 LLM 场景里怎么用?工作流引擎怎么抽象?混合检索怎么落地?


一、整体架构:五层 Maven 模块,契约与实现分离

sql 复制代码
┌────────────────────────────────────────────────┐
│  maxkb4j-start        启动模块 / Flyway / Dockerfile │
└───────────────────────┬────────────────────────┘
┌───────────────────────▼────────────────────────┐
│  maxkb4j-service      业务实现(9 个子模块)          │
│  application · chat · knowledge · model · oss   │
│  system · tool · trigger · workflow             │
└───────────────────────┬────────────────────────┘
┌───────────────────────▼────────────────────────┐
│  maxkb4j-service-api  对外契约 DTO/VO(8 个子模块)  │
└───────────────────────┬────────────────────────┘
┌───────────────────────▼────────────────────────┐
│  maxkb4j-core         助手抽象 · 护栏 · LC4J 扩展    │
└───────────────────────┬────────────────────────┘
┌───────────────────────▼────────────────────────┐
│  maxkb4j-common       通用工具                      │
└────────────────────────────────────────────────┘

技术选型没有一个冷门组件,全是 Java 圈存量设施:

维度 技术
运行时 Java 21(LTS)+ 虚拟线程
框架 Spring Boot 3.5 + Undertow
LLM 编排 LangChain4j 1.20(agentic / mcp / pgvector / reactor)
ORM / 迁移 MyBatis-Plus 3.5 + Flyway
鉴权 Sa-Token + JWT
存储 PostgreSQL + pgvector(向量)/ MongoDB(全文、文件、图谱)
解析 Apache Tika、Docling、EasyExcel、OFDRW、RapidOCR
沙箱 Groovy + groovy-sandbox

这个选型的动机很直接:中国企业市场的主力存量栈是 Java。大模型应用不是孤立系统,它要嵌进已有的权限体系、审批流和数据中台------让现有 Java 工程师直接读、直接改、直接上线,比"团队集体转 Python"现实得多。

二、虚拟线程为什么是 LLM 应用的"版本答案"

先看配置(节选自 maxkb4j-start/src/main/resources/application.yml):

yaml 复制代码
server:
  undertow:
    # 与 multipart 上限保持一致,防止超大请求体耗尽内存
    max-http-post-size: 300MB

spring:
  threads:
    virtual:
      enabled: true   # Java 21 虚拟线程

一行配置,但它背后是对 LLM 应用负载特性的判断。

大模型应用的 IO 画像很极端:一次模型调用阻塞几秒到几十秒,一路用户请求里还串着向量检索、全文检索、Reranker 重排、工具 HTTP 调用、多 Agent 并行调度------全是长阻塞 IO,CPU 几乎闲着

传统平台线程模型下,200 个并发用户就能把 Tomcat 默认线程池打满,剩下的请求排队;上 Reactor 响应式又意味着全团队陪葬一套心智负担。虚拟线程正好卡在中间:保持"一个请求一个线程"的朴素编程模型,阻塞代价却趋近于零,官方口径单机可支撑数千级并发。

两个实践细节,掘金的老哥们大概率会问,先说了:

  1. 虚拟线程只解决 IO 并发,流式输出另算 ------流式响应用的是 LangChain4j 的 reactor 模块(langchain4j-reactor),SSE 推流不占请求线程;
  2. JDK 21 上要注意 pinning :虚拟线程在 synchronized 块内阻塞会钉住载体线程,JDK 24 的 JEP 491 才彻底解决------项目里长 IO 段一律避免 synchronized,用 ReentrantLock 替代。

三、工作流引擎:32 种节点,一个接口

可视化工作流是这类平台的核心,MaxKB4j 有 32 种节点:条件分支、循环(Loop / Break / Continue)、变量聚合 / 赋值 / 拆分、LLM 对话、意图分类、参数提取、NL2SQL、知识库搜索 / 写入、文档抽取 / 分割、图像生成 / 理解、语音转文本 / 文本转语音、HTTP 请求、MCP、工具、应用子流程、表单、用户选择......

听起来吓人,但引擎侧的抽象只有一个接口(节选自 workflow/handler/node/INodeHandler.java):

java 复制代码
public interface INodeHandler {

    /**
     * Executes the node.
     * 同步 Handler 返回已完成的 future;
     * 流式 Handler 返回的 future 在流结束时完成。
     */
    CompletableFuture<NodeResult> execute(IWorkflow workflow, AbsNode node) throws Exception;

    /**
     * 节点执行后是否暂停工作流(例如表单节点等待用户输入)。
     */
    default boolean shouldInterrupt(INode node) {
        return false;
    }
}

三个设计决策值得展开:

1. 返回 CompletableFuture 而不是同步结果。 同步节点直接返回已完成的 future,流式节点(LLM 对话、TTS)在流结束时完成 future------统一了同步/流式两种节点形态,引擎不用写 if-else 分叉。

2. 生命周期在抽象模板里。 计时、起始消息发射、失败传播这些公共逻辑收在 AbsNodeHandler 模板类中,32 个实现类只写节点本身的业务逻辑。加一种新节点 = 加一个类,不碰引擎内核。

3. shouldInterrupt 默认方法支持"暂停"。 表单节点、用户选择节点执行完后工作流挂起等待外部输入,恢复执行时上下文不丢------这是很多开源工作流缺的"人机协同"能力,靠这个 default 方法接住。

多 Agent 协作(多角色并行/串行、任务拆解汇总、共享记忆总线)构建在这套节点机制之上,底层用 LangChain4j 的 langchain4j-agentic 模块,core 模块里预置了 Router、IntentClassify、QueryCompress 等助手抽象。

四、混合检索:双写、并发召回、单路降级

RAG 的效果上限由检索决定。MaxKB4j 的检索链路是:向量 + 全文双路召回 → Reranker 精排 → 溯源返回 ,存储层抽象成 IDataStore,有三个实现:pgvector(向量)、MongoDB(全文)、以及把两者组合起来的 CompositeStoreImpl

CompositeStoreImpl 的设计比较有味道,直接看源码(节选):

java 复制代码
/**
 * 组合 store:同时写入向量与全文两路,搜索时按来源类型混合两路结果。
 * 写操作通过 dualWrite 串行委托并对失败做统一日志/异常包装;
 * 搜索通过 safeSearchAsync 并发执行并支持单路降级(一路故障仍返回另一路结果)。
 * 段落路与问题路的编排、问题到段落的映射以及去重排序由 SearchOrchestrator 负责,
 * 本类只做同源两后端融合。
 */
@Component("compositeStore")
public class CompositeStoreImpl extends BaseStoreImpl {

    private final IDataStore vectorStore;
    private final IDataStore fullTextStore;

    public CompositeStoreImpl(@Qualifier("vectorStore") IDataStore vectorStore,
                              @Qualifier("fullTextStore") IDataStore fullTextStore) {
        this.vectorStore = vectorStore;
        this.fullTextStore = fullTextStore;
    }

    @Override
    public void upsert(EmbeddingModel model, List<EmbeddingEntity> entities) {
        // 双写:同一份数据同时落 pgvector 与 MongoDB 全文索引
        dualWrite("upsert", s -> s.upsert(model, entities));
    }
    // search:safeSearchAsync 并发两路召回,按 sourceId 取较高分融合,排序截断 topK
}

注意几个点:

  • 写入双路、读取并发 :写路径 dualWrite 串行委托并统一异常包装;读路径并发打两个后端,互不等待;
  • 单路降级:向量库抖了,全文检索的结果照样返回------检索可用性 > 检索完备性,这是生产环境和 Demo 的分水岭;
  • 职责切分干净 :段落路/问题路的编排、问题到段落的映射、去重排序在上层 SearchOrchestrator,这个类只做"同源两后端融合",单一职责。

为什么不用 Milvus / 专用向量库?运维经济学:pgvector 对企业是"存量 PG 加一个 extension",百万级段落 + HNSW 索引完全够用,少一个分布式组件就少一分专人运维成本------这个取舍见仁见智,欢迎评论区battle。

五、18 家模型提供商:注解驱动,加一家不改内核

模型接入最容易写烂------初版代码里往往是一个巨型 switch (provider)。MaxKB4j 用注解 + 自动注册解决了这个问题(节选自 model/annotation/ModelProviderType.java):

java 复制代码
/**
 * 标注一个 AbsModelProvider 实现为可被自动发现的模型供应商。
 * 由 ModelProviderAutoRegistrar 在所有单例就绪后扫描,
 * 注册到 ModelProviderRegistry,替代原先枚举中硬编码的 switch 静态工厂。
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ModelProviderType {
    /** 供应商唯一标识 */
    String provider();
    /** 展示名称 */
    String name();
    /** 图标文件名(classpath:model-icons/ 下) */
    String icon();
}

新增一家模型提供商 = 实现一个 AbsModelProvider 子类 + 打一个注解,注册器启动时自动扫描装配。目前预置 18 家:通义千问、DeepSeek、Kimi、GLM、豆包、星火、混元、文心、SiliconFlow、MiniMax、OpenAI、Azure、Claude、Gemini、Ollama、XInference、LocalAI、本地模型。

模型类型覆盖七种------不只是一个"ChatModel 平台":

java 复制代码
public enum ModelType {
    LLM("LLM", "大语言模型"),
    EMBEDDING("EMBEDDING", "向量模型"),
    STT("STT", "语音识别"),
    TTS("TTS", "语音合成"),
    VISION("VISION", "图片理解"),
    TTI("TTI", "图片生成"),
    RERANKER("RERANKER", "重排模型");
}

六、LangChain4j 实际用了哪些模块

项目启动时(2024 年底)Spring AI 还在里程碑版本,LangChain4j 的模块完整度更高,于是选了它,从 1.0 之前一路升到 1.20。实际引入的模块:

模块 用途
langchain4j-core / langchain4j 基础抽象与模型接入
langchain4j-reactor 流式输出(SSE)
langchain4j-agentic 多 Agent 编排
langchain4j-mcp MCP 协议(SSE / stream_http)
langchain4j-pgvector pgvector 向量存储适配
langchain4j-experimental-sql NL2SQL 节点
langchain4j-document-parser-docling Docling 文档解析

顺带说句公道话:Spring AI 这两年迭代很快,两者竞品式发展对 Java 生态是好事。选型这事没有标准答案,只有"你的场景答案"。

七、5 分钟跑起来

bash 复制代码
git clone https://gitee.com/taisan/MaxKB4j.git
cd MaxKB4j
docker-compose up -d

访问 http://localhost:8080/admin/loginadmin / tarzan@123456),Compose 会拉起 pgvector + MongoDB + 应用三件套,首启自动跑 Flyway 初始化。也支持单容器、一键安装脚本(Linux/macOS/Windows)、源码构建(mvn clean package -DskipTests)四种姿势,仓库里还有 29 个开箱应用模板。

嫌麻烦就直接逛在线 Demo(demo / demo@123456)。

八、客观说明(防杠声明)

  1. 不是要"全面替代" Dify。 Dify 的社区、插件市场、贡献者规模是客观优势。MaxKB4j 的生态位是:Java 栈、要私有化、要深度二开的团队------这个细分下它可能是唯一不需要跨语言的选项;
  2. 前端源码未开源:后端 GPLv3 完全开放,前端以构建产物形式内置,源码属于赞助权益------独立开发者要维持 5 万行项目 21 个月不断更,总得有不靠情怀发电的路子;
  3. README 里与 Dify / MaxKB / FastGPT / RAGFlow 的对比表是能力取向对比不是跑分,选型请以自己场景实测为准;
  4. 一个人维护,issue 都会看,SLA 没法承诺。

写在最后

大模型应用的上半场拼模型,下半场拼工程化------而工程化是 Java 生态深耕二十年的主场。MaxKB4j 做的事一句话说完:把 LLM 编排、RAG、工作流、多 Agent 装进 Java 工程师最熟悉的技术栈里。

如果你正好在用 Java 落地 AI 应用,或者想研究工作流引擎抽象 / 虚拟线程落地 / RAG 链路设计的真实工程实现,欢迎来读源码、提 issue、拍砖:

觉得有帮助的话,点个 Star 就当请笔者喝了杯咖啡 ☕。


相关推荐
孙启超16 分钟前
【AI开发之Rust】第 3 课:字符串与复合类型 —— 数据怎么放
开发语言·人工智能·后端·rust·llm·transformer
代码调试师21 分钟前
【毕设分享】基于SpringBoot的旅游向导分配管理系统57145
spring boot·毕业设计·源码·课程设计·毕设·大作业·程序定制
_山海24 分钟前
Bun入门指南
前端·javascript·后端
2301_322414280441 分钟前
活力孕康复APP 47737- 原创(免费领源码+部署教程+开发环境)
java·vue.js·spring boot·mysql·微信小程序·idea·微信开发者工具
君顾11 小时前
外卖CPS软件开发实战:从系统架构到部署全流程指南
java·开发语言·外卖
vx_Biye_Design1 小时前
springboot游泳馆系统93765-计算机课程设计、毕业设计
java·javascript·spring boot·后端·python·spring·课程设计
名字还没想好☜2 小时前
Java NIO ByteBuffer 实战:flip/clear/compact 三个绕晕人的方法与 position/limit 心智模型
java·开发语言·后端·spring·nio
Wang's Blog2 小时前
Java框架快速入门: Spring Security+OAuth2之短信服务多供应商动态切换(阿里云与LeanCloud)
java·spring·阿里云
Doubbbbbbble云2 小时前
区间合并问题的常见算法模式与优化思路4
java·数据结构·算法
小溪学编程2 小时前
AQS 原理详解:从 CLH 队列到 ReentrantLock 的实现
java·开发语言