ChatModel 构建 LLM 驱动的 Java 应用 如果你尝试过在 Java 应用中集成大语言模型LLM大概率写过不少样板代码HTTP 客户端、JSON 解析、流式处理、会话管理……Solon 4.0 的ChatModel用一套简洁的 Builder API 把这些都封装好了。本文将通过真实的代码示例带你一步步用ChatModel构建 AI 功能——从简单的单次调用到带记忆的流式聊天机器人。1. 什么是 ChatModelChatModel是 Solon AI 生态中的统一 LLM 客户端。你不再需要为不同的模型提供商写不同的 HTTP 调用而是通过一套统一的 API 完成同步调用— 一次请求完整返回流式调用— 基于 Project Reactor 的响应式流FluxChatResponse工具/函数调用— 让 LLM 调用你的 Java 方法聊天会话— 自动维护对话记忆多模态消息— 文本、图片、音频方言适配— 支持 OpenAI、Ollama、Anthropic、Gemini、DashScope 等多种服务商最核心的是它使用了方言模式Dialect Pattern——你只需要指向任意兼容的 LLM 端点它会自动适配协议。2. 环境配置在pom.xml中添加依赖Solon 不需要父 POM独立工作dependency groupIdorg.noear/groupId artifactIdsolon-ai/artifactId version${solon.version}/version /dependency这会引入所有内置的方言适配器OpenAI、Ollama、Gemini、Anthropic、DashScope。3. 配置方式3.1 通过 YAML 配置推荐solon.ai.chat: demo: apiUrl: http://127.0.0.1:11434/api/chat # 完整 URL非 baseUrl standard: ollama # 接口规范方言标识 model: llama3.2 # 模型名称 headers: x-demo: demo1然后通过Bean注入一个可以直接使用的ChatModelimport org.noear.solon.ai.chat.ChatConfig; import org.noear.solon.ai.chat.ChatModel; import org.noear.solon.annotation.Bean; import org.noear.solon.annotation.Configuration; import org.noear.solon.annotation.Inject; Configuration public class AiConfig { Bean public ChatModel chatModel(Inject(${solon.ai.chat.demo}) ChatModel model) { return model; } }3.2 编程式 BuilderBean public ChatModel chatModel() { return ChatModel.of(http://127.0.0.1:11434/api/chat) .standard(ollama) // 或 .provider(ollama) .model(llama3.2) .timeout(Duration.ofSeconds(60)) .build(); }3.3 支持的模型提供商standard或provider字段选择方言方言标识apiUrl 示例模型openai默认https://api.openai.com/v1/chat/completionsGPT、DeepSeek、Qwen、GLM、Kimi 等ollamahttp://127.0.0.1:11434/api/chat本地 Ollama 模型anthropichttps://api.anthropic.com/v1/messagesClaudegeminihttps://generativelanguage.googleapis.com/...Geminidashscope阿里云 DashScope 端点QwenDashScope 原生4. 同步调用最简单的方式最基本的用法——发送提示词获取完整响应import org.noear.solon.ai.chat.ChatModel; import org.noear.solon.ai.chat.ChatResponse; import org.noear.solon.annotation.Inject; import org.noear.solon.annotation.Component; Component public class ChatService { Inject ChatModel chatModel; public String ask(String question) throws IOException { ChatResponse resp chatModel.prompt(question).call(); return resp.getMessage().getContent(); } }仅三行业务代码搞定。5. 流式调用实时响应对于聊天机器人和助手类应用流式响应是刚需。ChatModel返回 Reactor 的FluxChatResponseimport reactor.core.publisher.Flux; public FluxString askStream(String question) throws IOException { return chatModel.prompt(question) .stream() .filter(resp - resp.hasContent()) // 跳过空块 .map(resp - resp.getContent()); }如果你使用 Solon Web Reactive可以直接把Flux返回给 SSE 端点import org.noear.solon.web.sse.SseEvent; import org.noear.solon.annotation.Mapping; import reactor.core.publisher.Flux; Mapping(/chat/stream) public FluxSseEvent chatStream(String prompt) throws IOException { return chatModel.prompt(prompt) .stream() .filter(resp - resp.hasContent()) .map(resp - new SseEvent().data(resp.getContent())); }流式协议根据提供商不同使用标准 SSE 或x-ndjson。6. 对话记忆ChatSessionLLM 本身是无状态的每次请求都需要传入历史上下文。ChatSession自动帮你完成这件事。6.1 基本用法import org.noear.solon.ai.chat.ChatSession; import org.noear.solon.ai.chat.session.InMemoryChatSession; ChatSession session InMemoryChatSession.builder() .sessionId(user-123) .maxMessages(10) // 保留最近 10 轮 .build(); // 第一轮 ChatResponse resp1 chatModel.prompt(你好) .session(session) .call(); // 第二轮——模型记得刚才的对话 ChatResponse resp2 chatModel.prompt(我刚才说了什么) .session(session) .call();6.2 Web 应用中的用户级会话在实际的 Web 应用中每个用户需要一个独立的会话import org.noear.solon.annotation.Controller; import org.noear.solon.web.sse.SseEvent; import reactor.core.publisher.Flux; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; Controller public class ChatController { Inject ChatModel chatModel; final MapString, ChatSession sessionMap new ConcurrentHashMap(); Mapping(/chat) public FluxSseEvent chat(String sessionId, String prompt) throws IOException { ChatSession session sessionMap.computeIfAbsent(sessionId, k - InMemoryChatSession.builder().sessionId(k).build()); return chatModel.prompt(prompt) .session(session) .options(o - o.systemPrompt(你是一个友好、乐于助人的助手。)) .stream() .filter(ChatResponse::hasContent) .map(resp - new SseEvent().data(resp.getContent())); } }6.3 内置会话实现实现类存储方式适用场景InMemoryChatSession本地 Map开发、单节点FileChatSession文件系统CLI 工具、桌面应用RedisChatSessionRedis生产环境、分布式部署