基于Quarkus与MCP协议构建Java多智能体LLM Web前端实践

发布时间:2026/5/27 7:44:30

基于Quarkus与MCP协议构建Java多智能体LLM Web前端实践 1. 项目概述一个为LLM设计的Web前端与多智能体通信的Java实践如果你是一名Java开发者同时又对当前大语言模型LLM的应用开发感兴趣那么你很可能面临一个尴尬的局面生态工具链几乎被Python垄断。从OpenAI的官方库到LangChain这样的流行框架再到各种本地模型的服务端Python是当之无愧的“一等公民”。这导致了一个问题当你想要构建一个需要深度定制、高性能、且易于维护的LLM应用前端时用Java从头造轮子的成本高得吓人而直接集成Python组件又常常在部署、并发处理和流式响应上遇到各种水土不服。quarkus-chat-ui这个项目正是为了解决这个痛点而生的。简单来说quarkus-chat-ui是一个用Java具体是Quarkus框架编写的LLM Web聊天界面。但它远不止一个简单的聊天框。它的核心价值在于两点第一它原生支持多智能体Multi-Agent之间的自动化对话每个实例都是一个独立的HTTP MCP服务器智能体可以像人一样互相调用工具、传递消息第二它本身是POJO-actor这个轻量级Actor模型库的一个真实世界用例展示了如何在复杂的异步、流式场景下用清晰的并发模型写出稳健的Java代码。这意味着你不仅得到了一个开箱即用的LLM前端还获得了一个如何在Java中优雅处理LLM长时、流式请求的绝佳范本。想象一下这个场景你启动了两个quarkus-chat-ui实例一个叫“架构师”一个叫“开发者”。你可以给“架构师”下达一个指令“设计一个微服务API”然后“架构师”可以自主地通过MCP协议调用“开发者”实例的工具将具体实现任务分配出去两者在浏览器中实时展开对话和协作而你作为人类可以同时旁观甚至介入两者的对话。这不再是简单的“一问一答”而是构建了一个小型的、可编程的协作网络。对于需要LLM进行复杂任务分解、多角色模拟或自动化工作流的场景这个工具提供了极为简洁的实现路径。2. 核心架构与设计哲学拆解2.1 为什么是MCP模型上下文协议多智能体通信的核心挑战在于标准化。每个LLM、每个工具都有其独特的API让它们直接“对话”需要大量的胶水代码。quarkus-chat-ui选择了MCPModel Context Protocol作为通信基石这是一个明智且前沿的选择。MCP可以理解为LLM世界的“USB协议”。它定义了一套标准让任何实现了MCP服务器的工具如数据库、搜索引擎、代码库都能以一种LLM能理解的方式暴露其功能即“工具”。quarkus-chat-ui的每个实例在启动时都会在/mcp路径下暴露一个HTTP MCP服务器。这个服务器提供了几个关键工具最主要的是submitPrompt。当一个实例例如实例A想要和另一个实例实例B对话时它并不需要知道B的内部实现只需要知道B的MCP服务器地址然后调用submitPrompt这个标准工具即可。这种设计带来了巨大的灵活性解耦智能体之间仅通过标准的MCP工具调用进行交互内部LLM后端Claude、本地模型等的更换不影响通信逻辑。互操作性理论上任何兼容MCP的客户端如Claude Code CLI或服务器都能接入这个网络不局限于quarkus-chat-ui自身。可观测性由于通信基于HTTP你可以使用任何网络调试工具如curl、Wireshark来观察和分析智能体间的消息流这对于调试复杂交互至关重要。注意MCP是一个正在快速发展的协议。quarkus-chat-ui的实现聚焦于HTTP传输和核心工具集这保证了与当前主流MCP生态如Claude Code的良好兼容性。在选择此方案时需关注MCP协议本身的演进。2.2 Quarkus框架的选择为流式与原生编译而生用Java写Web服务不稀奇但用Java优雅地处理LLM典型的数十秒长时请求和Server-Sent Events (SSE) 流式响应则是对框架的严峻考验。quarkus-chat-ui选择Quarkus绝非偶然。传统Spring Boot应用在处理大量并发长连接时线程模型可能成为瓶颈。每个SSE连接都可能长时间占用一个线程。Quarkus从设计之初就是响应式Reactive优先的它基于Vert.x和Mutiny等库提供了高效的异步非阻塞I/O处理能力。在quarkus-chat-ui中你看不到复杂的线程池配置或手动的flush()操作。一个SSE端点简洁到令人惊讶GET Path(/events/{sessionId}) Produces(MediaType.SERVER_SENT_EVENTS) RestStreamElementType(MediaType.APPLICATION_JSON) public MultiChatEvent events(PathParam(sessionId) String sessionId) { return chatService.getEventStream(sessionId); }这段代码返回一个MultiChatEvent流。Quarkus和底层的Mutiny库会负责一切背压Backpressure管理、连接保活、客户端重连、以及高效的数据块编码和发送。开发者只需要关心如何生成事件流而不必陷入网络层的细节。这对于需要稳定、长时间向浏览器推送LLM生成内容的场景来说是基础且关键的优势。此外Quarkus对GraalVM原生编译Native Image的一等公民支持是另一个杀手级特性。它允许你将整个Java应用编译成一个独立的、启动极快的原生可执行文件。这对于需要快速部署、伸缩的LLM辅助工具场景非常有用。项目直接提供了各平台的原生二进制包用户下载后无需安装JDK即可运行极大地降低了使用门槛。2.3 POJO-actor管理复杂并发的“定海神针”多智能体、HTTP MCP服务器、SSE流、提示词队列、取消请求……这些功能交织在一起会引发恐怖的并发问题竞态条件、死锁、状态不一致。quarkus-chat-ui的解决方案是引入POJO-actor一个轻量级的Actor模型库。Actor模型的核心思想是“一切皆Actor”。每个Actor都是一个独立的计算实体拥有自己的私有状态并且只通过异步消息tell或ask与其他Actor通信。这种模型天然避免了共享内存和锁让并发逻辑变得清晰。在quarkus-chat-ui中不同的关注点被隔离到不同的Actor中聊天会话Actor管理一个对话会话的状态和历史。队列管理Actor负责处理用户和MCP传入的提示词排队、执行顺序。失速检测Actor监控长时间未响应的请求。当LLM后端可能是一个阻塞的HTTP调用需要执行时这个阻塞操作被提交给一个虚拟线程Virtual Thread执行。虚拟线程是Java 21引入的轻量级线程可以高效地处理阻塞I/O。当操作完成时虚拟线程会向对应的Actor发送一条消息告知结果。这样主业务逻辑Actor始终保持非阻塞、响应迅速而阻塞操作被安全地卸载。这种设计带来的好处是整个应用代码中没有出现一个synchronized关键字或显式的锁。并发复杂性被Actor的邮箱和消息处理机制所封装。这对于需要高可靠性的多智能体系统来说是架构上的重要保障。代码的可读性和可维护性也大大提升因为每个Actor的职责都非常单一和明确。3. 功能深度解析与实操要点3.1 多智能体对话机制详解多智能体对话是quarkus-chat-ui最吸引人的功能。其工作流程是一个精巧的“上下文注入”与“工具回调”的循环。假设实例A运行在http://localhost:28010想要询问实例B运行在http://localhost:28020。用户在A的浏览器中输入指令要求调用B的submitPrompt工具。A的LLM如Claude在收到用户指令后会识别出这是一个MCP工具调用并向B的/mcp端点发起HTTP请求。关键在于B收到请求后的处理。B的MCP服务器不会直接把原始提示词丢给自己的LLM。相反它会自动对提示词进行富化Enrich。它会插入一段系统上下文明确告诉自己的LLM“你正在哪个实例上运行”、“这个消息是谁发来的”、以及最重要的——“你应该如何回复”。富化后的提示词模板大致如下[Context] You are running on: http://localhost:28020 Received via MCP from: localhost:28010 [Message] What should we work on today? [How to Reply] Use callMcpServer tool: - serverUrl: http://localhost:28010 - toolName: submitPrompt - arguments: {prompt: your reply, _caller: http://localhost:28020}这样B的LLM在生成回复时就完全清楚了对话的上下文和回复的路径。它会生成类似“让我们从API层开始设计”的回复内容并在同一个思考过程中构造出调用A的submitPrompt工具的指令。于是回复内容又作为新的提示词传回了A。一个自主的对话循环就此建立。实操心得这个“富化”步骤是智能体间有效协作的关键。在实际定制中你可以修改这个富化模板例如加入更多角色设定“你是一个严谨的测试工程师”、对话历史摘要或者特定的协作规则从而引导智能体表现出更专业、更符合预期的行为。3.2 提示词队列应对LLM的异步本质LLM生成响应速度不确定可能很快也可能需要几十秒。用户在等待一个回答时可能又想到了新的问题。一个粗糙的UI会禁用输入框或者新输入覆盖旧问题体验很差。quarkus-chat-ui的持久化提示词队列优雅地解决了这个问题。它的行为符合直觉当LLM正忙于处理“解释这段代码”时用户可以在输入框中继续键入“现在为它编写测试”和“并生成文档”。这两个新提示词会被自动加入队列在UI中清晰展示。当“解释这段代码”的响应流结束后系统会自动从队列中取出“现在为它编写测试”并发送给LLM。该请求完成后继续处理“并生成文档”。这个队列不仅是内存中的它通过浏览器本地存储实现了跨页面刷新的持久化。即使你关闭了浏览器标签下次打开未执行的队列任务依然存在。更强大的是队列是可编辑的。你可以拖拽调整任务顺序或者直接删除某个尚未执行的任务。这对于规划复杂的多步思考过程非常有用。在多智能体场景下队列的取消逻辑也得到了精心处理。当你点击“取消”时系统不仅要停止当前实例的LLM生成还需要通过MCP协议将取消信号传递到可能正在为当前任务工作的其他智能体实例并清理由此产生的连锁排队任务。这个功能体现了设计者对真实使用场景的深入思考。3.3 后端适配器设计高度的可扩展性项目没有将后端LLM服务写死而是通过一个简洁的LlmProvider接口抽象了不同LLM服务的差异public interface LlmProvider { String id(); void sendPrompt(String prompt, String model, ConsumerChatEvent emitter, ProviderContext ctx); void cancel(); // ... }要集成一个新的LLM服务比如直接调用OpenAI的API或一个新的本地模型服务你只需要实现这个接口。sendPrompt方法接收提示词、模型参数和一个事件发射器ConsumerChatEvent。你的实现里只需要处理与特定后端的通信并将返回的流无论是SSE还是普通响应转换为标准的ChatEvent如START,CHUNK,COMPLETE回调给发射器即可。队列管理、会话状态、MCP服务器等所有上层功能都自动可用。目前官方支持三种Providerclaude: 包装Claude Code CLI需要ANTHROPIC_API_KEY。codex: 包装OpenAI Codex CLI需要OPENAI_API_KEY。openai-compat: 包装任何提供OpenAI兼容API的服务如本地运行的vLLM或Ollama。这是连接强大本地模型的桥梁。这种设计意味着quarkus-chat-ui作为一个“前端”其能力边界实际上由你所能连接的后端LLM决定。你可以用免费的本地模型搭建一个私人多智能体系统也可以使用顶级的商业API。4. 从零开始完整搭建与配置实战4.1 环境准备与本地模型部署让我们从最实用的场景开始使用本地模型运行多智能体。这里以Ollama为例因为它最简单。如果你有GPUvLLM会获得极快的推理速度。步骤一部署本地LLM服务对于OllamaCPU/GPU通用# 1. 安装Ollama请参考官网 # 2. 拉取一个适合编程对话的模型例如Qwen2.5-Coder ollama pull qwen2.5-coder:7b # 3. 运行模型服务。Ollama默认在 http://localhost:11434 提供API ollama run qwen2.5-coder:7b # 注意ollama run会启动聊天对于API服务通常模型拉取后Ollama服务已就绪。对于vLLM推荐GPU环境# 1. 安装vLLM: pip install vllm # 2. 启动服务指定模型和端口 vllm serve Qwen/Qwen2.5-Coder-7B-Instruct --port 8000 --api-key token-abc123 # 现在vLLM在 http://localhost:8000 提供了OpenAI兼容的API步骤二获取并运行quarkus-chat-ui你有两种方式运行quarkus-chat-ui直接使用预编译的原生可执行文件最快或者从源码构建适合开发定制。方案A使用原生可执行文件无需JDK前往项目的GitHub Release页面。根据你的系统下载对应的二进制文件如quarkus-chat-ui-linux-amd64。在终端中赋予执行权限并运行chmod x quarkus-chat-ui-linux-amd64 ./quarkus-chat-ui-linux-amd64 -Dchat-ui.provideropenai-compat -Dchat-ui.openai-compat.base-urlhttp://localhost:11434/v1 -Dquarkus.http.port28010如果你的模型服务是vLLM则将base-url改为http://localhost:8000。方案B从源码构建需要JDK 21和Mavengit clone https://github.com/scivicslab/quarkus-chat-ui cd quarkus-chat-ui mvn install -DskipTests # 运行 java -Dchat-ui.provideropenai-compat \ -Dchat-ui.openai-compat.base-urlhttp://localhost:11434/v1 \ -Dquarkus.http.port28010 \ -jar app/target/quarkus-app/quarkus-run.jar运行成功后打开浏览器访问http://localhost:28010你应该能看到简洁的聊天界面。尝试发送一条消息确认本地模型正常工作。注意事项首次使用本地模型时回复可能会比较慢这取决于你的硬件。7B参数量的模型在CPU上可能需要数十秒。确保你的Ollama或vLLM服务日志显示模型已成功加载并正在处理请求。如果遇到连接错误检查base-url是否正确Ollama是/v1后缀vLLM通常不是。4.2 配置双智能体对话以Claude Code为例要体验智能体间对话你需要使用支持MCP客户端的后端。这里以Claude Code CLI为例因为它内置了MCP管理功能。前提条件拥有Anthropic API Key。已从源码构建quarkus-chat-ui参考4.1方案B。准备三个终端窗口和两个浏览器标签页。详细步骤安装并配置Claude Code CLInpm install -g anthropic-ai/claude-code export ANTHROPIC_API_KEY你的API密钥 claude --version # 验证安装启动智能体AAlice端口28010终端1在项目根目录执行java -Dchat-ui.providerclaude -Dquarkus.http.port28010 -jar app/target/quarkus-app/quarkus-run.jar浏览器打开http://localhost:28010。启动智能体BBob端口28020终端2同样先设置API密钥然后启动export ANTHROPIC_API_KEY你的API密钥 java -Dchat-ui.providerclaude -Dquarkus.http.port28020 -jar app/target/quarkus-app/quarkus-run.jar浏览器打开http://localhost:28020。配置MCP服务发现关键步骤终端3执行以下命令告诉Claude Code CLI这两个MCP服务器的位置# 让Alice知道Bob claude mcp add bob --transport http http://localhost:28020/mcp # 让Bob知道Alice claude mcp add alice --transport http http://localhost:28010/mcp # 查看注册列表 claude mcp list此时应看到alice和bob的条目。重启智能体实例MCP配置在Claude Code CLI启动时加载。因此需要重启终端1和终端2中的quarkus-chat-ui进程CtrlC后重新运行启动命令。测试对话在Alice的浏览器28010中输入Use mcp__bob__submitPrompt to send Hello Bob! Whats your opinion on using microservices? to Bob. Set _caller to http://localhost:28010.点击发送。Claude会识别出mcp__bob__submitPrompt工具并调用它。切换到Bob的浏览器28020你会看到消息出现[MCP from localhost:28010] Hello Bob! ...。Bob的Claude会自动生成回复并调用工具将回复发回给Alice。回到Alice的浏览器查看回复是否出现。一个自动的对话循环就此开始。清理测试完成后移除MCP注册以避免冲突claude mcp remove alice claude mcp remove bob4.3 核心配置项解析quarkus-chat-ui通过Java系统属性-D参数进行配置非常灵活。配置项描述示例值必需chat-ui.provider指定LLM后端提供商。claude,codex,openai-compat是quarkus.http.port应用服务的HTTP端口。28010,8080否默认8080chat-ui.openai-compat.base-url当provider为openai-compat时本地模型服务的API地址。http://localhost:11434/v1(Ollama)http://localhost:8000(vLLM)是仅openai-compatchat-ui.openai-compat.api-key如果本地服务需要API密钥如vLLM设置了--api-key。token-abc123否chat-ui.openai-compat.model指定请求的模型名称如果与默认值不同。Qwen2.5-Coder-7B-Instruct否组合配置示例使用Claude API端口设为28080-Dchat-ui.providerclaude -Dquarkus.http.port28080使用本地Ollama的CodeLlama模型-Dchat-ui.provideropenai-compat -Dchat-ui.openai-compat.base-urlhttp://localhost:11434/v1 -Dchat-ui.openai-compat.modelcodellama:7b使用vLLM服务并带API密钥-Dchat-ui.provideropenai-compat -Dchat-ui.openai-compat.base-urlhttp://localhost:8000 -Dchat-ui.openai-compat.api-keyyour-token-here5. 常见问题排查与进阶技巧5.1 启动与连接问题速查表在搭建过程中你可能会遇到以下常见问题。这里提供一个快速排查指南。问题现象可能原因解决方案启动失败提示端口占用默认端口8080或其他指定端口已被占用。使用-Dquarkus.http.port新端口指定另一个端口。用lsof -i :端口号或netstat -ano | findstr :端口号(Windows) 查看占用进程。浏览器访问localhost:28010无响应1. 应用未成功启动。2. 防火墙阻止了端口访问。1. 检查终端日志是否有错误。确保使用正确的JAR包路径和配置。2. 检查本地防火墙设置暂时禁用或添加规则。发送消息后长时间无反应使用openai-compat1. 本地模型服务未运行或地址错误。2. 模型加载失败或首次推理极慢。3. API密钥或模型名称错误。1. 确认Ollama/vLLM服务已启动且base-url正确Ollama注意/v1后缀。2. 查看模型服务日志确认是否有请求进来和错误信息。CPU运行大模型请耐心等待。3. 检查vLLM是否设置了--api-key并在配置中提供。使用Claude时提示ANTHROPIC_API_KEY not found环境变量未正确设置。在每个启动quarkus-chat-ui的终端中都执行export ANTHROPIC_API_KEY你的key。对于Windows PowerShell使用$env:ANTHROPIC_API_KEY你的key。MCP工具调用失败提示mcp__xxx__submitPrompt not found1. MCP服务未在Claude Code CLI中注册。2. 注册后未重启quarkus-chat-ui实例。3. 目标实例未运行。1. 用claude mcp add正确注册对方地址。2.重要注册后必须重启对应的quarkus-chat-ui实例Claude Code CLI才会加载新配置。3. 用curl http://localhost:28020/mcp测试目标MCP端点是否可达。智能体B收到消息但不回复1. B实例的MCP配置中缺少A的地址。2. B的LLM在生成回复时未能正确构造调用A的工具指令。1. 确保在B端也用claude mcp add alice ...注册了A。2. 检查B的浏览器开发者工具网络面板看B的LLM是否向A的/mcp端点发出了submitPrompt请求。可能是提示词富化模板或LLM理解问题。原生可执行文件在运行时崩溃或报错1. 平台不兼容如ARM芯片运行x86二进制。2. 缺少必要的原生依赖某些Linux发行版。1. 下载对应你系统架构的二进制文件如Apple Silicon用macos-arm64。2. 尝试从源码构建JAR包运行这兼容性最好。对于Linux确保有glibc等基础库。5.2 性能调优与监控建议当系统稳定运行后你可能需要关注性能和资源使用情况。虚拟线程监控项目利用Java虚拟线程处理阻塞IO。你可以通过JVM参数开启监控java -Dquarkus.thread-pool.virtual-threads.enabledtrue \ -Dquarkus.thread-pool.virtual-threads.logging.enabledtrue \ ...其他配置...在日志中观察虚拟线程的创建和销毁情况确保没有线程泄漏长时间不释放。HTTP连接池智能体间频繁的MCP调用会创建大量HTTP连接。考虑调整Quarkus的HTTP客户端配置如果你直接修改了代码进行调用# 在application.properties中 quarkus.rest-client.max-pool-size100 # 连接池大小 quarkus.rest-client.keep-alive-enabledtrue # 保持连接对于quarkus-chat-ui内置的MCP客户端需要查看其是否使用了可配置的客户端。流式响应缓冲区SSE流式传输默认有缓冲区。如果发现前端接收消息有延迟可以检查Quarkus的响应式流配置但通常默认值已足够。在极高并发下可考虑调整Multi的发布策略。本地模型推理优化vLLM使用--tensor-parallel-size和--pipeline-parallel-size利用多GPU。调整--max-num-seqs提高吞吐量。Ollama在Modelfile中指定num_gpu层数以启用GPU加速。对于CPU调整num_thread参数。通用在quarkus-chat-ui的请求中合理设置max_tokens等参数避免生成过长内容拖慢整体流程。5.3 自定义与扩展指南quarkus-chat-ui作为一个开源项目提供了良好的扩展点。实现新的LlmProvider这是集成新后端的主要方式。创建一个类实现LlmProvider接口。在sendPrompt方法中使用你喜欢的HTTP客户端如Quarkus的RestClient调用后端API。将后端返回的流无论是SSE还是JSON解析并转换为ChatEvent对象通过ConsumerChatEvent emitter回调发出。ChatEvent类型包括START、CHUNK、COMPLETE、ERROR等。在cancel方法中实现中断请求的逻辑。最后通过CDIApplicationScoped将你的Provider注册为BeanQuarkus会自动发现它。修改提示词富化模板智能体间对话的“上下文”和“回复指南”由McpServerService类中的逻辑控制。你可以找到构建提示词字符串的代码部分修改其中的模板文本来改变智能体的自我认知、行为规则或回复格式。例如你可以让智能体在回复时遵循特定的JSON格式。集成quarkus-mcp-gateway进行扩展当智能体数量超过两个时两两之间配置MCP会变得繁琐。此时可以引入quarkus-mcp-gateway作为中心化的路由和发现服务。所有智能体向网关注册网关负责按名称路由请求。这更接近生产环境的微服务架构。你需要部署一个网关实例并将所有quarkus-chat-ui实例的chat-ui.mcp.gateway-url指向它。UI定制前端资源位于src/main/resources/META-INF/resources目录下。你可以修改HTML、CSS和JavaScript来改变界面布局、颜色主题或添加新的交互功能。由于这是一个Quarkus项目前端修改后重新打包即可生效。5.4 生产环境部署考量如果你计划将quarkus-chat-ui用于更严肃的场景需要考虑以下几点安全性API密钥管理切勿将API密钥硬编码在命令行或代码中。使用环境变量、Kubernetes Secrets或HashiCorp Vault等秘密管理工具。网络隔离确保MCP服务器端点/mcp不直接暴露在公网。应在内部网络或通过API网关带有认证进行访问。输入验证虽然MCP调用在智能体间进行但仍需警惕提示词注入攻击。对传入的提示词进行基本的清理和长度限制是明智的。可观测性启用Quarkus的Micrometer指标并集成到Prometheus和Grafana中监控请求延迟、错误率、活跃连接数等。配置结构化日志JSON格式便于通过ELK或Loki进行日志聚合和分析。为重要的MCP工具调用添加分布式追踪通过OpenTelemetry以可视化智能体间的调用链。高可用与伸缩无状态设计quarkus-chat-ui实例本身是无状态的会话状态可通过外部存储如Redis实现持久化。这便于水平扩展。使用原生镜像编译为原生可执行文件启动速度极快毫秒级更适合容器化部署和快速伸缩。负载均衡在多个实例前部署负载均衡器如Nginx并确保SSE长连接在会话期间粘滞到同一后端。资源限制设置JVM内存限制如果使用JAR运行或容器资源限制。为每个quarkus-chat-ui实例配置合理的并发请求上限避免本地模型后端被压垮。这可以通过Actor的邮箱容量或HTTP限流中间件来实现。通过以上这些实践quarkus-chat-ui从一个演示项目可以逐步演进为一个支撑内部团队协作、自动化流程或复杂AI应用的核心组件。它的价值不仅在于功能更在于其采用Quarkus和POJO-actor所展示的、在现代Java中构建复杂异步应用的清晰架构模式。

相关新闻