用 ChatModel 构建 LLM 驱动的 Java 应用
如果你尝试过在 Java 应用中集成大语言模型LLM大概率写过不少样板代码HTTP 客户端、JSON 解析、流式处理、会话管理……Solon AI 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独立工作dependencygroupIdorg.noear/groupIdartifactIdsolon-ai/artifactIdversion${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非 baseUrlstandard:ollama# 接口规范方言标识model:llama3.2# 模型名称headers:x-demo:demo1然后通过Bean注入一个可以直接使用的ChatModelimportorg.noear.solon.ai.chat.ChatConfig;importorg.noear.solon.ai.chat.ChatModel;importorg.noear.solon.annotation.Bean;importorg.noear.solon.annotation.Configuration;importorg.noear.solon.annotation.Inject;ConfigurationpublicclassAiConfig{BeanpublicChatModelchatModel(Inject(${solon.ai.chat.demo})ChatModelmodel){returnmodel;}}3.2 编程式 BuilderBeanpublicChatModelchatModel(){returnChatModel.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. 同步调用最简单的方式最基本的用法——发送提示词获取完整响应importorg.noear.solon.ai.chat.ChatModel;importorg.noear.solon.ai.chat.ChatResponse;importorg.noear.solon.annotation.Inject;importorg.noear.solon.annotation.Component;ComponentpublicclassChatService{InjectChatModelchatModel;publicStringask(Stringquestion)throwsIOException{ChatResponserespchatModel.prompt(question).call();returnresp.getMessage().getContent();}}仅三行业务代码搞定。5. 流式调用实时响应对于聊天机器人和助手类应用流式响应是刚需。ChatModel返回 Reactor 的FluxChatResponseimportreactor.core.publisher.Flux;publicFluxStringaskStream(Stringquestion)throwsIOException{returnchatModel.prompt(question).stream().filter(resp-resp.hasContent())// 跳过空块.map(resp-resp.getContent());}如果你使用 Solon Web Reactive可以直接把Flux返回给 SSE 端点importorg.noear.solon.web.sse.SseEvent;importorg.noear.solon.annotation.Mapping;importreactor.core.publisher.Flux;Mapping(/chat/stream)publicFluxSseEventchatStream(Stringprompt)throwsIOException{returnchatModel.prompt(prompt).stream().filter(resp-resp.hasContent()).map(resp-newSseEvent().data(resp.getContent()));}流式协议根据提供商不同使用标准 SSE 或x-ndjson。6. 对话记忆ChatSessionLLM 本身是无状态的每次请求都需要传入历史上下文。ChatSession自动帮你完成这件事。6.1 基本用法importorg.noear.solon.ai.chat.ChatSession;importorg.noear.solon.ai.chat.session.InMemoryChatSession;ChatSessionsessionInMemoryChatSession.builder().sessionId(user-123).maxMessages(10)// 保留最近 10 轮.build();// 第一轮ChatResponseresp1chatModel.prompt(你好).session(session).call();// 第二轮——模型记得刚才的对话ChatResponseresp2chatModel.prompt(我刚才说了什么).session(session).call();6.2 Web 应用中的用户级会话在实际的 Web 应用中每个用户需要一个独立的会话importorg.noear.solon.annotation.Controller;importorg.noear.solon.web.sse.SseEvent;importreactor.core.publisher.Flux;importjava.util.Map;importjava.util.concurrent.ConcurrentHashMap;ControllerpublicclassChatController{InjectChatModelchatModel;finalMapString,ChatSessionsessionMapnewConcurrentHashMap();Mapping(/chat)publicFluxSseEventchat(StringsessionId,Stringprompt)throwsIOException{ChatSessionsessionsessionMap.computeIfAbsent(sessionId,k-InMemoryChatSession.builder().sessionId(k).build());returnchatModel.prompt(prompt).session(session).options(o-o.systemPrompt(你是一个友好、乐于助人的助手。)).stream().filter(ChatResponse::hasContent).map(resp-newSseEvent().data(resp.getContent()));}}6.3 内置会话实现实现类存储方式适用场景InMemoryChatSession本地 Map开发、单节点FileChatSession文件系统CLI 工具、桌面应用RedisChatSessionRedis生产环境、分布式部署7. 调优ChatOptions通过ChatOptions可以在每次请求中控制模型行为chatModel.prompt(写一首关于 Java 的诗).options(o-o.temperature(0.8).max_tokens(500).top_p(0.9).systemPrompt(你是一位富有创造力的诗人。)).call();部分常用参数方法说明temperature(val)采样温度0.0–2.0max_tokens(val)最大输出 Token 数top_p(val)核采样参数top_k(val)Top-K 采样frequency_penalty(val)降低重复presence_penalty(val)鼓励新话题tool_choice(val)强制工具调用none、auto、required或工具名role(val)Agent 角色role instruction 可自动生成 systemPromptinstruction(val)Agent 指令role instruction 可自动生成 systemPromptsystemPrompt(val)本次请求的系统提示词完全定制8. 多消息 Prompt有时候你需要的不只是一条消息。可以用Prompt和ChatMessage构建更复杂的上下文importorg.noear.solon.ai.chat.Prompt;importorg.noear.solon.ai.chat.message.ChatMessage;PromptpromptPrompt.of(ChatMessage.ofUser(Hello, how are you?),ChatMessage.ofAssistant(Bonjour, comment allez-vous?),ChatMessage.ofUser(What is your name?));ChatResponserespchatModel.prompt(prompt).options(o-o.systemPrompt(你是一名中英翻译专家。)).call();9. 完整实战知识感知聊天机器人下面是一个轻量级的 RAG 模式示例——用ChatMessage.ofUserAugment()把上下文注入到 Prompt 中importorg.noear.solon.ai.chat.ChatModel;importorg.noear.solon.ai.chat.ChatResponse;importorg.noear.solon.ai.chat.message.ChatMessage;importorg.noear.solon.annotation.Component;importorg.noear.solon.annotation.Inject;ComponentpublicclassKnowledgeChatbot{InjectChatModelchatModel;publicStringanswer(Stringquestion,StringreferenceContext)throwsException{// 将参考上下文与用户问题合并ChatMessageaugmentedChatMessage.ofUserAugment(question,referenceContext);ChatResponserespchatModel.prompt(augmented).options(o-o.temperature(0.3).systemPrompt(你是一个知识渊博的助手。请基于提供的参考资料回答。)).call();returnresp.getMessage().getContent();}}这种模式——用上下文增强用户输入再调用模型——正是 Solon AI 中 RAG检索增强生成的基础。10. 下一步ChatModel只是入口点。Solon AI 还提供工具调用— 用ToolMapping定义 LLM 可调用的 Java 方法Talent 系统— 可复用的能力模块Agent—ReActAgent和TeamAgent实现多步推理RAG 流水线— 完整的文档加载、切分、嵌入、检索流程MCP 协议— 连接 MCP 服务器使用外部工具完整文档参见官方指南 https://solon.noear.org/article/918模型构建 https://solon.noear.org/article/920API 参考你有没有在 Java 中集成过 LLM最大的痛点是什么欢迎在评论区分享可能会在后续文章中专门讨论。