用 Solon AI 4.0 ChatModel 少写一半 Java LLM 胶水代码

2026-07-04 46 预计阅读时间: 1 分钟
来源: oschina.net AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

把 LLM 接进 Java 应用,难点往往不在“发一个请求”,而在请求之外的一堆细节:HTTP 客户端、鉴权、JSON 编解码、流式输出、上下文记忆、错误处理。Solon AI 4.0 的 ChatModel 把这些常见工作收进一套 Builder API,让 Java 代码更像是在描述“我要怎样对话”,而不是手写一层又一层协议胶水。

ChatModel 解决的是集成层的重复劳动

传统做法里,业务代码很容易被这些东西淹没:

  • 拼接 /chat/completions 请求体;
  • 维护 messages 数组和角色字段;
  • 解析普通响应和流式响应;
  • 给每次请求塞 API Key、模型名、超时参数;
  • 在多轮对话里保存历史消息。

ChatModel 的价值不是“替你设计提示词”,而是把 LLM 调用抽象成一个 Java 侧稳定接口。你可以在 Builder 里集中配置模型、地址、密钥、默认参数,然后在业务代码里调用聊天能力。

这类封装对团队尤其有用:调用方不用关心底层供应商细节,后续切模型、加日志、统一超时和限流,也更容易收口。

从一次性调用开始:让业务代码只关心输入和输出

下面示例按 ChatModel 常见 Builder 风格演示。不同 Solon AI 4.0 版本的包名、方法名可能略有差异,接入时请以你项目中的依赖版本为准。你可以把它当作一个最小可改造项目骨架。

pom.xml 中可以这样放依赖,版本号请替换为你正在使用的 Solon AI 4.0 版本:

<dependencies>
    <dependency>
        <groupId>org.noear</groupId>
        <artifactId>solon-ai</artifactId>
        <version>4.0.0</version>
    </dependency>
</dependencies>

一个单次问答调用可以写成这样:

package demo;

import org.noear.solon.ai.chat.ChatModel;

public class SingleChatDemo {
    public static void main(String[] args) {
        String apiKey = System.getenv("LLM_API_KEY");
        if (apiKey == null || apiKey.isBlank()) {
            throw new IllegalStateException("Please set LLM_API_KEY first");
        }

        ChatModel chatModel = ChatModel.builder()
                .apiUrl("https://api.example.com/v1/chat/completions")
                .apiKey(apiKey)
                .model("gpt-4o-mini")
                .temperature(0.2)
                .build();

        String answer = chatModel.prompt("用三句话解释 Java 里的虚拟线程适合什么场景。")
                .call()
                .getContent();

        System.out.println(answer);
    }
}

运行时把密钥放到环境变量,不要硬编码进仓库:

export LLM_API_KEY="replace-with-your-key"
mvn -q exec:java -Dexec.mainClass=demo.SingleChatDemo

这里的关键点是配置被收拢到了 Builder:API 地址、Key、模型名、温度等参数不再散落在业务方法里。业务逻辑只看到 prompt -> call -> content 这条路径。

流式输出:别等整段生成完再响应用户

聊天机器人、客服助手、代码解释器这类功能,如果等模型完整生成后才返回,用户会明显感到卡顿。摘要里提到 ChatModel 支持流式处理,这正是 LLM 应用体验的分水岭。

可以这样实践一个命令行流式输出版本:

package demo;

import org.noear.solon.ai.chat.ChatModel;

public class StreamChatDemo {
    public static void main(String[] args) {
        ChatModel chatModel = ChatModel.builder()
                .apiUrl("https://api.example.com/v1/chat/completions")
                .apiKey(System.getenv("LLM_API_KEY"))
                .model("gpt-4o-mini")
                .build();

        chatModel.prompt("给我一个 Java 服务接入 LLM 时的上线检查清单。")
                .stream()
                .subscribe(chunk -> {
                    System.out.print(chunk.getContent());
                    System.out.flush();
                });
    }
}

如果你把它接到 Web 接口,常见做法是用 SSE 把模型 token 持续推给前端。下面是一个可改造的服务端形态,重点是“边收到边写出”:

package demo;

import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Get;
import org.noear.solon.core.handle.Context;
import org.noear.solon.ai.chat.ChatModel;

@Controller
public class ChatSseController {
    private final ChatModel chatModel = ChatModel.builder()
            .apiUrl("https://api.example.com/v1/chat/completions")
            .apiKey(System.getenv("LLM_API_KEY"))
            .model("gpt-4o-mini")
            .build();

    @Get("/chat/stream")
    public void stream(Context ctx, String q) throws Exception {
        ctx.contentType("text/event-stream;charset=UTF-8");

        chatModel.prompt(q)
                .stream()
                .subscribe(chunk -> {
                    try {
                        ctx.output("data: " + chunk.getContent().replace("\n", "\\n") + "\n\n");
                        ctx.flush();
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                });
    }
}

调用测试:

curl -N "http://localhost:8080/chat/stream?q=解释一下什么是RAG"

生产环境里要继续补上鉴权、限流、取消请求、异常事件和日志追踪。流式输出不是简单地“打印更快”,它会改变你的连接管理和前端状态机。

带记忆的聊天:上下文要有边界

摘要提到 ChatModel 可以构建“带记忆的流式聊天机器人”。这类能力通常意味着框架帮你维护多轮消息,业务侧只传入当前用户问题。

可以这样组织一个最小会话层:

package demo;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.noear.solon.ai.chat.ChatModel;
import org.noear.solon.ai.chat.ChatSession;

public class MemoryChatService {
    private final ChatModel chatModel;
    private final Map<String, ChatSession> sessions = new ConcurrentHashMap<>();

    public MemoryChatService(ChatModel chatModel) {
        this.chatModel = chatModel;
    }

    public String ask(String userId, String question) {
        ChatSession session = sessions.computeIfAbsent(userId, id ->
                chatModel.newSession()
                        .system("你是一个严谨的 Java 后端助手,回答要短,必要时给代码。")
        );

        return session.prompt(question)
                .call()
                .getContent();
    }
}

这段代码里,userId -> ChatSession 是一个非常直接的内存会话表。它适合演示,不适合直接照搬到生产:

  • 单机内存会在重启后丢失;
  • 多实例部署会话不一致;
  • 历史消息过长会推高 token 成本;
  • 用户输入可能包含敏感信息,不能无边界保存。

更稳妥的做法是给记忆加上 TTL、最大轮数、摘要压缩和敏感字段过滤。会话存储可以落 Redis 或数据库,但不要把“长期记忆”默认打开给所有场景。

落地时别只看 Demo 是否跑通

ChatModel 降低了 Java 接入 LLM 的门槛,但它不会替你处理所有工程问题。上线前建议检查这些点:

  • 密钥只走环境变量、配置中心或密钥管理服务;
  • 为模型调用设置超时、重试和熔断,避免拖垮业务线程;
  • 流式接口要支持客户端断开后的取消;
  • 记录请求 ID、模型名、耗时、token 用量和错误码;
  • 对用户输入做长度限制和内容审计;
  • 多轮记忆设置最大窗口,必要时做摘要;
  • 把供应商地址、模型名、温度等参数放入配置,而不是写死。

适合优先用 ChatModel 的场景,是你已经有 Java/Solon 服务,并且想快速把问答、总结、分类、辅助生成等能力嵌进去。它把样板代码压下去,让团队把精力放在提示词、上下文、权限和产品体验上。真正的边界也很清楚:模型质量、成本、数据治理和故障隔离,仍然需要你用工程手段兜住。


相关推荐