Solon v4.1.0

chat - 模型的同步与流式响应(call, stream)

</> markdown
2026年9月7日 上午10:09:06

聊天模型接口(ChatModel)支持:

  • 同步调用(call),一次性返回结果
  • 流式调用(stream,基于 reactivestreams 规范)。通过 ssex-ndjson 流式返回结果。
  • Tool Call(或 Function Call) 与本地数据互动(需要 llm 支持)
  • 提示语多消息输入输出(记忆体)
  • 带图片消息
  • 与 solon-flow 结合使用

1、聊天模型的构建

  • 配置方式构建
solon.ai.chat:
  demo:
    apiUrl: "http://127.0.0.1:11434/api/chat" # 使用完整地址(而不是 api_base)
    standard: "ollama" # 使用 ollama 服务时,需要配置 provider 或 standard
    model: "llama3.2"
    headers:
      x-demo: "demo1"
import 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 DemoConfig {
    @Bean
    public ChatModel build(@Inject("${solon.ai.chat.demo}") ChatConfig config) {
        return ChatModel.of(config).build();
    }
}
  • 手动方式构建
@Configuration
public class DemoConfig {
    @Bean
    public ChatModel build() {
        return ChatModel.of("http://127.0.0.1:11434/api/chat") //使用完整地址(而不是 api_base)
                .headerSet("x-demo", "demo1")
                .standard("ollama") //接口标准
                .model("llama3.2")
                .modelOptions(o->o.optionSet("stream_options", Utils.asMap("include_usage", true))) //v3.8.4 之后
                //.defaultOptionAdd("stream_options", Utils.asMap("include_usage", true)) //v3.8.4 之前
                .build();
    }
}

2、同步调用(call)

public void case1() throws IOException {
    ChatResponse resp = chatModel.prompt("hello").call();

    //打印消息
    log.info("{}", resp.getMessage());
}

3、异步流式或响应式调用(stream)

流式返回为 reactor.core.publisher.Flux<ChatEvent>(reactor 规范)。stream() 是事件流;正文增量应按事件类型过滤,完整终态响应从 RESPONSE_END 事件取得。

import org.noear.solon.ai.chat.event.ChatEvent;
import org.noear.solon.ai.chat.event.ChatEventType;
import reactor.core.publisher.Flux;

public void case2() {
    Flux<ChatEvent> publisher = chatModel.prompt("hello").stream();

    publisher.doOnNext(event -> {
                if (event.is(ChatEventType.TEXT_DELTA) && event.hasText()) {
                    log.info("{}", event.getText());
                } else if (event.is(ChatEventType.RESPONSE_END)) {
                    log.info("终态:{}", event.getResponse());
                }
            }).doOnComplete(() -> {
                log.debug("::完成!");
            }).doOnError(err -> {
                log.error("{}", err);
            })
            .subscribe();
}

可以直接订阅消费(如上)。也可对接各种流行的响应式框架,比如 mutiny、rxjava 或 reactor:

@Produces(MimeType.TEXT_EVENT_STREAM_UTF8_VALUE)
@Mapping("case2")
public Flux<SseEvent> case2(String prompt) {
    return chatModel.prompt(prompt).stream()
                .filter(e -> e.is(ChatEventType.TEXT_DELTA) && e.hasText())
                .map(ChatEvent::getText)
                .map(text -> new SseEvent().data(text))
                .doOnError(err -> {
                    log.error("{}", err);
                });
}

4、模型日志

内部默认会打印 llm 请求与响应的日志,分别以 llm-request:llm-response: 开头。日志级别为:DEBUG。