
我们内部从去年年底就开始憋一个大招用 Java 25 和 Spring Boot 4 从零写了一个响应式的 AI Agent 框架。前阵子终于把代码整理干净、补完文档在 GitHub 上开源了。这几天陆续有朋友来问设计思路和踩坑过程干脆梳理成一篇长文把为什么选这套技术栈、核心架构长什么样、怎么快速跑起来、以及我们踩过的几个大坑都交代清楚。这篇东西不是宣传稿更像是一份实操记录适合正在做 Java AI Agent、或者对 Spring Boot 4 响应式编程感兴趣的同学参考。很多人的第一反应是AI Agent 不都是 Python 的天下吗哪来的 Java 框架其实这个判断已经过时了。大模型调用、Prompt 编排、工具调用、记忆管理这些能力本质上都是 IO 密集 流式交互Java 的虚拟线程和响应式编程在这种场景下的优势非常明显。再加上 Spring Boot 4 已经默认拥抱响应式范式我们做这套框架的时候不需要像以前那样在 WebFlux 和 WebMvc 之间反复纠结一行配置就能把整个链路跑在 Reactor 上。对团队来说最大的收益是原来写 Python 的服务端同学要面临 GIL 和异步回调地狱换成 Java 后可以用一套逻辑同时处理高并发和流式输出后期运维也轻松很多。我们开源这个框架是想给 Java 生态补一块拼图。现在市面上叫得响的 Agent 框架几乎都是 Python 系Java 系要么是半成品要么只是把大模型 API 封装了一层离“框架”还差得远。这套框架的目标是让你用 Java 也能像用 LangChain 一样舒服地编排 Agent同时利用响应式底座拿到更低的延迟和更高的吞吐。文章最后我会把 GitHub 地址和快速开始示例放出来但建议先看完设计思路不然直接看代码容易懵。1. 为什么选 Java 25 Spring Boot 4 做底牌1.1 Java 25 的新特性哪些是真正有用的Java 25 是 2025 年 9 月发布的 LTS 版本说实话我身边不少 Java 工程师还在用 8 或者 11一听到 25 就头大觉得又有一堆语法要学。但对我们这种做框架的人来说Java 25 带来的不是“语法糖”而是切切实实的性能红利。首先是虚拟线程Virtual Threads。这是 Java 21 就正式转正的特性在 Java 25 里已经非常成熟了。AI Agent 的核心流程是什么跟大模型 API 通信请求外部工具处理流式 token这些都是典型的高 IO 阻塞场景。用传统平台线程一个 Agent 实例处理一个用户请求Thread-per-request 模型下200 个并发就能把线程池打满。用虚拟线程后线程数量不再受物理资源限制每个请求都可以拥有独立的虚拟线程阻塞时自动让出载体线程调度开销几乎可以忽略。我们用压测工具模拟过同样的硬件虚拟线程模式比旧的 RxJava 线程池模式吞吐提升了差不多 4 倍。第二个好用的是模式匹配和密封接口。设计 Agent 的状态机和工具调用结果时以前要写一大堆 if-else 或者 instanceof 强转。现在用switch模式匹配代码可读性好太多了。比如我们定义了一个AgentStepResult密封接口下面有ToolCallResult、FinalAnswer、AgentError几个实现类在处理时一段 switch 就能干净地解构出每种情况而且编译器会强制你覆盖所有分支避免漏掉某个状态。第三个是字符串模板String Templates的预览特性。这个在 Java 25 里还没有最终定稿但我们已经在项目里用了配合STR.处理器拼 Prompt 的时候比String.format直观太多。当然编译时如果用的是 preview 选项会有点麻烦所以我们在核心模块里做了一个很小的模板引擎抽象层生产环境走普通字符串拼接不跟 JDK 预览特性绑死。这里我建议你也这么做毕竟框架是要给别人用的不能要求所有用户都开 preview 参数。jpackage之类的新工具对我们意义不大因为框架是部署在服务器上的依赖库不是桌面应用。Java 25 里还有一个被忽略的好特性是增强的java.lang.invoke我们在做工具方法反射调用的时候用到了MethodHandle缓存后面调优部分会详细说。1.2 Spring Boot 4 的响应式底座到底改了什么Spring Boot 4 的正式版在 2025 年 11 月发布最大的变化就是默认构建在 Spring Framework 7 上对响应式编程的支持不再是“可选项”而是第一公民。很多从 Boot 2 时代过来的同学可能只知道 WebFlux但 Spring Boot 4 做了几件很实在的事。第一是把spring-boot-starter-webflux作为默认 Web 启动器之一跟spring-boot-starter-web的优先级彻底分开。以前容易踩的坑是一旦引入了 webflux但类路径里又有旧的 servlet 相关依赖应用启动时就可能报“多个 WebApplicationContext”的错误。Boot 4 对这两套运行时做了隔离启动逻辑清晰很多。第二是Reactor的版本升级现在内置的是 Reactor 2025.x基于 Netty 和 Reactor Core。我们用的Flux和Mono在流式处理上更顺手特别是Flux.merge和Flux.concatMap配合虚拟线程可以把大模型的多个并行工具调用同时拉起来然后按顺序聚合。这在 Agent 场景里太重要了因为有时候需要让 Agent 同时去查天气、查日历、查备忘录最后把结果一起喂回给大模型。用响应式并行 虚拟线程调度可以用很简洁的代码实现真正的并发调用而且不受 CPU 核数和线程池大小限制。第三是 GraalVM native image 的官方支持更好了。Spring Boot 4 针对 GraalVM 做了很多 AOT 优化我们在 CI 里出 native 镜像的速度比 Boot 3 快了将近一倍。对 Agent 这种需要快速冷启动的微服务来说很香但原生镜像有一些动态代理和反射的坑后面会专门讲。如果没有低延迟启动需求其实普通 JVM 模式已经完全够用。从我们的实际选择来说框架核心是完全跑在 WebFlux 上的但为了兼容老项目我们没有强制用户必须使用响应式全家桶。你依然可以用 Spring MVC 写一个 Controller然后调用基于 Reactor 的内部服务因为Mono和Flux可以很容易地在 servlet 环境里通过.block()或者MonoSubscriber来桥接。不过在这里我劝一句既然选了响应式就尽量从头到尾保持非阻塞混用的话调试的时候坑特别多后面有一节专门讲。1.3 AI Agent 为什么必须响应式我见过很多团队做 Agent 服务端用的还是传统的同步 Servlet —— 发一个 HTTP 请求给大模型然后连接挂起用户看着页面转圈等到整个回答完成后一次性返回全部文本。这种模式对短问题还好但遇到长上下文、多工具调用用户等待时间动辄十几秒甚至几十秒。体验上就是“死等”中间没有任何反馈如果网络又抖一下用户直接关掉页面。响应式解决的就是这个问题。我们用 SSEServer-Sent Events把 Agent 的执行过程拆成实时事件流开始思考、正在调用某个工具、工具返回结果、正在生成回答、最终完成这些事件通过 WebFlux 的FluxServerSentEvent...推给前端。用户看到的第一行字可能只要 300 毫秒就出现了后续内容边生成边流式展示。而且在 Agent 等待工具执行的间隙连接并不阻塞任何线程服务端可以同时处理成千上万个这类“挂起”的流连接。另一个关键点是背压Backpressure。大模型的 token 生成速度可能远快于客户端消费速度如果直接全量缓冲到内存一个小小的大模型输出可能就吃掉几百 MB。用 Reactor 的背压机制可以精确控制从底层 HTTP 连接读取数据的速率保证客户端不消费服务端就不拉取。我们内部一个 Agent 实例在压测时稳定运行 8 小时内存占用没有因为流式输出而增长这就是响应式管线的好处。2. 框架整体设计与核心概念拆解2.1 Agent 编排模型从线性执行到有向图市面上很多 Agent 框架把 Agent 的事情定义得太简单了用户提问大模型返回答案。但真实的 Agent 要能做规划、调用多个工具、根据结果反思、甚至重新规划。我们的框架没有采用 LangChain 那种“链式”咬合的方式而是把整个执行过程建模成一张有向图图上的每个节点是一个EventHandler。我们的核心抽象有三个AgentTask一个完整任务的描述包含目标、输入参数、上下文、状态快照。AgentStep单次执行单位可以是一个 LLM 调用、一个工具调用、一次记忆写入或者一个条件判断。AgentContext贯穿整个任务的上下文对象存放当前对话历史、中间结果、Token 用量和自定义的属性。任务的编排方式更接近“有限状态机 运行时路由”。比如一个典型的 ReAct 循环初始节点把用户问题包装成AgentTask。LLM 节点返回一个决策描述下一步需要调用的工具和参数。工具调用节点并行执行多个工具。工具结果回填到上下文。路由节点根据结果判断是继续循环还是终止输出最终答案。这种模型比纯链式灵活得多因为我们可以在任意节点插入“反思”逻辑也可以让多个工具并行跑最后合并结果。框架内部把图编译成一组 Reactor 操作符组成的管线FluxAgentEvent作为统一输出。用户只需要实现EventHandler接口然后声明节点之间的依赖关系就像写一个响应式工作流一样自然。2.2 响应式流式交互SSE、Flux 与事件规范我们提供的第一等交互方式是 SSE。为什么不是 WebSocket因为 Agent 的响应本质上是单向流服务端到客户端每次用户发消息、服务端流式回复不需要频繁的双向通道。SSE 基于 HTTP 协议天然支持断线重连比 WebSocket 简单太多而且可以配合 WebFlux 的FluxServerSentEvent直接返回不用额外写协议转换层。在内部我们定义了统一的AgentEvent事件模型包含四个主要类型事件类型说明前端展示示例START任务开始显示“思考中...”TOOL_CALL正在调用工具显示“正在查询天气...”TOOL_RESULT工具返回结果显示“天气数据已返回”TOKEN大模型流式 token逐字打印文本FINISH任务结束显示完成状态前端只要在 EventSource 里监听这些事件就能做非常丰富的交互。每个事件都带一个traceId方便在日志系统和前端时间线之间做关联。我们框架里已经内置了对 SSE 输出的AgentEventSseEncoder你不需要手动把事件序列化成 JSON。2.3 模块划分与扩展点开源框架的模块化设计会直接影响社区开发者能不能舒服地做二次开发。我们最终把整个项目拆成了这几个 Maven 模块agent-core核心模型、事件体系、编排引擎不依赖 Spring纯 Java 实现方便其他框架复用。agent-spring-boot-starterSpring Boot 自动配置负责把核心引擎包装成 WebFlux 端点。agent-llm-adapters大模型适配器目前内置 OpenAI 兼容协议、本地 Ollama、以及一个泛化 adapter 接口。agent-tools-sdk定义工具注解和工具注册机制方便开发者把自己的方法暴露成 Agent 工具。agent-examples一组可直接运行的 example 项目。这个拆分的动机很朴素核心模块保持纯 Java可以让不打算用 Spring Boot 的开发者也能用比如纯 Vert.x 或者纯裸 Reactor 项目Spring Boot 相关的东西全部放在 starter 里保证不会污染核心 API。工具 SDK 单独抽出来是因为工具注册这一块的扫描和反射逻辑比较重不想跟核心引擎耦合。扩展点主要集中在这几个接口上AgentEventHandler、ToolRegistry、MemoryStore、LLMProvider、ContextEnricher。社区的二次开发基本就是写一个实现类然后在application.yml里声明或者用Bean注入。我们努力做到让 80% 的定制需求在不用读框架源码的前提下就能完成。3. 实操从零搭一个最小可用 Agent3.1 环境准备与依赖导入先说明下开发环境JDK 25我用的是 OpenJDK 25.0.1Maven 3.9.9Spring Boot 4.0.0。如果你的 IDE 不支持 JDK 25建议先升级 IDEA 到最新版否则虚拟线程的 debug 体验会很糟。新建项目后在pom.xml里引入下面这些坐标。由于 Spring Boot 4 的 BOM 管理你不需要写版本号但agent-*相关模块目前还在 0.1.0 阶段建议固定版本parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version4.0.0/version relativePath/ /parent properties java.version25/java.version agent-framework.version0.1.0/agent-framework.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-webflux/artifactId /dependency !-- 框架核心与Spring Boot自动配置 -- dependency groupIdcom.example.agent/groupId artifactIdagent-core/artifactId version${agent-framework.version}/version /dependency dependency groupIdcom.example.agent/groupId artifactIdagent-spring-boot-starter/artifactId version${agent-framework.version}/version /dependency dependency groupIdcom.example.agent/groupId artifactIdagent-llm-adapters/artifactId version${agent-framework.version}/version /dependency dependency groupIdcom.example.agent/groupId artifactIdagent-tools-sdk/artifactId version${agent-framework.version}/version /dependency /dependencies启动类就是一个普通 Spring Boot 应用不要加EnableAsync之类的东西因为我们的默认调度器是 Reactor不是 Spring 的线程池。加了额外异步配置反而容易造成线程污染。SpringBootApplication public class AgentDemoApplication { public static void main(String[] args) { SpringApplication.run(AgentDemoApplication.class, args); } }3.2 定义一个 Tool框架里最常用的扩展就是工具。我们提供了AgentTool注解直接标记一个方法。比如我们做一个“获取当前时间”的小工具Component public class TimeTools { AgentTool( name get_current_time, description 获取当前日期和时间返回 ISO 格式字符串, parameters { AgentToolParam(name timezone, type string, required false, description 时区如 Asia/Shanghai) } ) public String getCurrentTime(String timezone) { ZoneId zoneId timezone null ? ZoneId.systemDefault() : ZoneId.of(timezone); return ZonedDateTime.now(zoneId).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); } }框架启动时会自动扫描所有带AgentTool注解的方法解析参数描述然后把工具签名注册到上下文的ToolRegistry中。这里有个细节我们使用了AgentToolParam上的description来生成给大模型看的 JSON Schema。你写描述时千万不能敷衍大模型能不能正确提取工具参数很大程度依赖这行文本的质量。比如“timezone”的示例要是写成“时区字符串”模型可能传乱七八糟的值写成“时区如 Asia/Shanghai”模型就知道该填 IANA 格式。如果工具需要访问 Spring 管理的其他 Bean直接用 Spring 的注入就行因为工具方法所在的Component本身就在 Spring 容器里。这一点比很多 Python 框架派生成工具的方式要顺滑不需要搞复杂依赖注入容器。3.3 构建 Agent 流程并流式返回下面是最小可运行的一个 Agent 端点。用户输入一个问题框架自动决定是否调用工具然后流式返回答案。我们在AgentController里使用 WebFlux 的FluxServerSentEventAgentEvent直接返回RestController RequestMapping(/api/agent) public class AgentController { private final AgentEngine agentEngine; public AgentController(AgentEngine agentEngine) { this.agentEngine agentEngine; } PostMapping(value /chat, produces MediaType.TEXT_EVENT_STREAM_VALUE) public FluxServerSentEventAgentEvent chat(RequestBody ChatRequest request) { AgentTask task AgentTask.builder() .taskId(UUID.randomUUID().toString()) .userId(request.userId()) .userMessage(request.message()) .conversationId(request.conversationId()) .build(); return agentEngine.stream(task) .map(event - ServerSentEvent.AgentEventbuilder(event) .event(event.type().name()) .id(event.traceId()) .build()); } }AgentEngine.stream()是核心入口。它接收一个AgentTask返回一个FluxAgentEvent。底层执行流程是从内存或持久化存储中加载会话历史。把用户消息和工具定义列表合成系统 Prompt。调用 LLM Provider 获取“意图决策”可能返回一个工具调用指令。执行工具调用把结果回填到上下文。再次调用 LLM Provider 生成最终答案以 token 流形式发射事件。更新会话历史后结束。如果你只接 OpenAI 兼容接口那配置非常简单。在application.yml里这样写agent: llm: provider: openai base-url: https://api.example.com/v1 api-key: ${LLM_API_KEY} model: gpt-4o-mini temperature: 0.7 max-tokens: 1024不需要额外写 Provider 代码。如果你要用本地模型把provider换成ollama再配置model: qwen2.5:7b框架就能连上你本地跑的 Ollama。我们的 adapter 层对这两个协议做了兼容因为 Ollama 的/api/chat流式返回和 OpenAI 的格式不是完全一致内部把它们转换成统一的LlmMessage结构。3.4 集成大模型 API 的响应式注意事项大多数大模型 HTTP 接口都支持流式返回。我们的 LLM Provider 底层用的是 Spring 的WebClient配合 Reactor Netty整个过程不占用任何线程。这里有一个重要注意点不要使用FeignClient或者 RestTemplate 来做流式调用。Feign 基于同步 HTTP拿到流式响应后必须.block()一次把整个响应体读完才返回根本没法做到逐 token 转发。我们之前试过用RestClient的exchange去拿流代码难看不说长文本场景下内存占用也很高。正确姿势是直接注入WebClient用retrieve().bodyToFlux(String.class)逐行解析 SSE。如果你要定制 LLM Provider实现LLMProvider接口里的这一个方法就够了FluxLlmTokenChunk streamCompleteChat(ListChatMessage messages);框架会在内部把模型返回的 tool call 指令和普通 token 分开处理。这里特别提醒很多国内模型接口在 “tool call” 参数格式上并不完全兼容 OpenAI比如tools字段类型差异。碰到这种情况建议先做一层协议转换适配器而不是直接改框架里的 OpenAI adapter。4. 踩坑记录与性能调优4.1 虚拟线程与响应式混用时的经典坑我前面说了前期劝大家别混用虚拟线程和 WebFlux但我们自己还是踩了不少。最典型的一个坑是在Flux的某个操作符里如果用block()去等待一个虚拟线程完成由于 Reactor 的调度线程和虚拟线程是两套体系很容易造成死锁或者线程泄漏。举个例子我们想在一个map操作符里调用一个基于虚拟线程阻塞 API 的工具。刚开始这么写flux.map(msg - { String result virtualThreadTool.call(msg); // 这是个同步阻塞方法 return result; })看起来没问题但map里的代码运行在 Reactor 的 event loop 线程上你的阻塞调用会把整个 event loop 卡住导致其他请求全部超时。正确的方式是使用publishOn切换到自己的虚拟线程调度器或者干脆把工具调用也写成响应式。我们最终的定义是Agent 编排层用 Reactor工具执行器内部可以根据工具类型选择阻塞或非阻塞实现。比如调用一个数据库访问工具可以用Mono.fromCallable(() - jdbcTemplate.query(...)).subscribeOn(Schedulers.boundedElastic())如果是调用远程 REST 工具继续用 WebClient 的非阻塞链。这个边界要划分清楚不然代码最终的调试成本远高于那点性能收益。还有一个经验是如果你用虚拟线程跑阻塞工具不要把它放到 Reactor 的default调度器上而是创建Schedulers.fromExecutorService(Executors.newVirtualThreadPerTaskExecutor())单独给一个虚拟线程调度器。这样既能享受虚拟线程的轻量性又不干扰 Netty 的 event loop。4.2 流式聚合与超时控制AI Agent 的流式输出跨度从几百毫秒到几十秒如果某个工具调用或者 LLM 请求一直不返回用户端就是无限等待。必须做超时和取消。Reactor 自带超时操作符但要注意用法。我们最初用了这样的代码agentEngine.stream(task) .timeout(Duration.ofSeconds(30))结果发现整个流 30 秒后一刀切即使已经有 28 秒的输出也被截断了。这明显不合理。后来改为“事件级别超时控制”只对中间环节加限制LlmProvider provider ...; FluxAgentEvent withTimeout provider.streamCompleteChat(messages) .timeout(Duration.ofSeconds(10)) .onErrorResume(e - Flux.just(AgentEvent.error(模型响应超时)));而整体流的超时设置为“空闲超时”也就是太久没有新事件才断开agentEngine.stream(task) .timeout(Duration.ofSeconds(30), Flux.empty()) // 每30秒必须有新事件否则结束还要注意取消传播。当 HTTP 客户端断开连接时WebFlux 会自动取消Flux但我们内部启动的并行工具调用子任务需要显式监听取消信号。我们给每个AgentContext挂了一个Disposable.Composite在完成和取消时统一释放。4.3 热路径优化MethodHandle 与工具反射工具调用的核心路径上有一个很隐蔽的性能问题Spring 扫描到AgentTool注解后我们使用方法对象来反射调用。Java 的反射在 JIT 优化后其实不慢但在高并发下Method.invoke的调用开销仍比直接接口调用高很多。我们在热身压测时发现method.invoke占了工具执行时间的 15%这对一个高吞吐 Agent 服务来说不可接受。优化方案是改用MethodHandle。在注册工具时就把方法转换成MethodHandle并缓存运行时直接调invokeExact或者invoke。因为在注册阶段我们就解析好了参数类型和顺序所以运行时不需要再检查参数。举个小例子MethodHandles.Lookup lookup MethodHandles.lookup(); MethodHandle handle lookup.findVirtual(TimeTools.class, getCurrentTime, MethodType.methodType(String.class, String.class));然后执行时ListObject args parseArguments(jsonSchemaNode, toolDef, input); Object result methodHandle.invoke(targetBean, args.toArray());MethodHandle.invoke自带类型检查比反射干净很多。我们实测下来工具调用的延迟降了约 40%RPS 从 1800 涨到 2400。如果你的框架不用在超高并发场景也许没必要这么早优化但一旦要做公共 Agent 服务这一步省不得。另外还有参数解析的优化。很多框架用 Jackson 把 JSON 数据转换为工具方法参数这一块也容易成为热点。我们为每种参数类型预建了Converter注册表而不是每次都objectMapper.convertValue。特别是字符串、数字、布尔这些基本类型直接解析 JSON 节点少了反序列化的整链路。4.4 常见问题速查表与避坑技巧为了方便后来人我把社区反馈里最常见的 5 个问题整理成一张表每个都附我们验证过的原因和解决办法。问题现象原因解决方案SSE 连接被 nginx 缓冲前端半天收不到流EventSource 一直 pending最后一下全出nginx 默认缓冲了代理响应在 nginx 里proxy_buffering off;并设置X-Accel-Buffering: no大模型流式返回时 HTTP 400 错误日志里status400但同样的 JSON 用 Postman 请求没问题某些模型接口对tools参数要求格式严格或stream_options字段冲突改为直连模型调试对比请求体差异内存不断增长压测 20 分钟后堆内存持续上升可能没有正确处理Flux背压导致所有 token 先进入内存队列检查是否在订阅时使用了cache操作符尽量流式转发虚拟线程导致线程泄漏容器启动几小时后 GC 异常、线程数暴增虚拟线程在不通过ExecutorService.close()时可能出现线程泄漏使用Executors.newVirtualThreadPerTaskExecutor()并确保每个任务调用close()Spring Boot 4 启动后找不到AgentEngineBean注入空指针没有启用自动配置或者没有引入agent-spring-boot-starter检查启动类是否包扫描到配置类并确认依赖坐标无误这些问题的共同点是先去怀疑你的基础组件再来怀疑框架。我们框架自己的 bug 当然也不少但绝大多数用户反馈的问题都出在部署和配置层面。5. 开源之后的一点心得与常用配置参考经历了从闭源到开源的过程有些体会不吐不快。代码开源之后第一周我们收到了 120 多颗 star但真正有价值的不是 star 数而是社区提交的 issue 和 PR。其中有一个用户帮忙修复了 Ollama 供应商的num_predict参数映射问题那是一个我们完全没测到的边界情况。这让我觉得开源框架的意义确实是汇聚群体的智慧而不是单方面展示代码。如果你也想照着这个思路自己写一套 Agent 框架我建议优先关注三点事件模型是否足够统一、工具注册是否足够简单、以及流式链路是否完全非阻塞。很多 Java 系 Agent 框架就是死在这三个问题上。关于许可证我们最终选择了 Apache 2.0。原因很简单我们希望商业公司也能放心用只要保留版权声明和 License 声明即可。这一点对 Java 生态尤其重要很多公司内部对 GPL 类许可证有很严格的限制Apache 2.0 基本不会引起合规问题。最后给出一份常用的部署配置参考。如果你用 Docker Compose 部署 Agent 服务建议加上健康检查因为响应式服务的线程模型与普通服务不同K8s 存活探针的/actuator/health必须要能正确反映下游 LLM 服务的连通性。我们的application.yml配置了这样一个探针management: endpoints: web: exposure: include: health,info,metrics health: llm: enabled: true url: ${agent.llm.base-url}/models timeout: 3s这种情况下/actuator/health会返回下游大模型服务的健康状态一旦 LLM 挂掉K8s 就不会把流量打进来。这比单纯返回200 OK但实际模型不可用要安全得多。代码库的 examples 目录里还放了一个“AI 简历助手”的小项目包含前端聊天界面可以直接把整个项目 clone 下来体验。启动方式就是标准的mvn spring-boot:run然后打开http://localhost:8080就行。我个人的习惯是每次想了解一个新框架都会先跑通一个最小 demo再去读核心源码。你现在如果正好在调研 Java 生态的 Agent 方案不妨把我们这个框架跑起来再对比一下 Spring AI 或者 LangChain4j 的做法。毕竟技术选型的终极答案从来不是某个框架有多火而是它能否在满足业务需求的前提下把复杂度和维护成本压到你能接受的水平。