Skip to content

配置对话模型并处理流式输出

This content is not available in your language yet.

如果说 完成第一次 Feat AI 调用 解决的是“模型有没有真的返回内容”,那么这一页要解决的是下一层问题:

  • 我该怎么配模型
  • 什么时候用 chat(...)
  • 什么时候用 chatStream(...)
  • 调整 systemtemperaturenoThink 这类参数到底意味着什么

一个最常见的下一步写法,是在模型上显式补系统提示词和输出行为:

OllamaDemo.java
import tech.smartboot.feat.ai.FeatAI;
import tech.smartboot.feat.ai.chat.ChatModel;
import tech.smartboot.feat.ai.chat.ChatModelVendor;
public class OllamaDemo {
public static void main(String[] args) {
ChatModel chatModel = FeatAI.chatModel(opts -> {
opts.model(ChatModelVendor.Ollama.Qwen3_06B)
.system("你是一个擅长生成藏头诗的诗人。")
.noThink(true)
.debug(false);
});
chatModel.chat("写一句和 Feat 有关的短句", rsp -> {
System.out.println(rsp.getContent());
});
}
}

这段代码里最值得关心的是三项配置:

  • system(...):定义角色或输出风格
  • noThink(true):在支持的模型上关闭“思考过程”输出
  • debug(...):本地排查请求时打开,平时不必默认开启

chat(...)chatStream(...) 到底怎么选

Section titled “chat(...) 和 chatStream(...) 到底怎么选”

这两个方法不是谁更高级,而是两种不同的交付方式。

chat(...),如果你要的是完整结果

Section titled “用 chat(...),如果你要的是完整结果”
chatModel.chat("你好,请自我介绍一下。", rsp -> {
System.out.println("content: " + rsp.getContent());
System.out.println("usage: " + rsp.getUsage());
});

适合:

  • 后端任务处理
  • 一次性生成结果
  • 你需要在最后统一读取 usage

chatStream(...),如果你要边生成边消费

Section titled “用 chatStream(...),如果你要边生成边消费”
chatModel.chatStream("根据以下关键词生成一首藏头诗:情,人,节,快,乐", content -> {
System.out.print(content);
});

适合:

  • 终端逐步输出
  • 前端打字机效果
  • 较长内容的实时展示

如果你需要在流式结束后再做收尾动作,可以用完整回调版本:

chatModel.chatStream("介绍一下 Feat", new StreamResponseCallback() {
@Override
public void onStreamResponse(String content) {
System.out.print(content);
}
@Override
public void onCompletion(ResponseMessage responseMessage) {
System.out.println("\n完成,总令牌数:" + responseMessage.getUsage().getTotalTokens());
}
});

多轮对话不是一个额外模式,本质上只是“你把历史消息也一起发给模型”。

最简单的做法是维护一组消息:

import tech.smartboot.feat.ai.chat.entity.Message;
import tech.smartboot.feat.ai.chat.entity.MessageRole;
List<Message> messages = new ArrayList<>();
messages.add(new Message(MessageRole.SYSTEM, "你是一个专业的 Java 工程师"));
messages.add(new Message(MessageRole.USER, "什么是 Feat?"));
chatModel.chat(messages, rsp -> {
System.out.println(rsp.getContent());
});

如果你准备做真正的聊天产品,这一步很重要。
如果你当前只是做单次问答,先别急着把对话状态管理复杂化。

最值得最早用。
它往往比你反复修改用户提示词更有效,因为它定义的是整体角色和输出边界。

当你明确要控制“稳定性 vs 创造性”时再动它。

  • 较低:回答更收敛
  • 较高:回答更发散

如果你还不知道自己想要什么风格,先保持默认值通常更稳。

当你需要控制回复长度时再用。
比如做固定格式输出、成本控制或者避免模型生成过长内容。

只有当你已经确认某些模型会输出不希望暴露的“思考过程”时,再显式使用它。

仓库里的 ChatDemo 展示了一个很典型的多轮问题:

ChatDemo.java
import tech.smartboot.feat.ai.FeatAI;
import tech.smartboot.feat.ai.chat.ChatModel;
import tech.smartboot.feat.ai.chat.ChatModelVendor;
public class ChatDemo {
public static void main(String[] args) {
ChatModel chatModel = FeatAI.chatModel(opts ->
opts.model(ChatModelVendor.GiteeAI.Qwen2_5_72B_Instruct)
);
chatModel.chat("你好,请自我介绍一下。", rsp -> {
System.out.println("rsp: " + rsp.getContent());
System.out.println("usage: " + rsp.getUsage());
chatModel.chat("我对你说的上一句话是什么?", rsp2 -> {
System.out.println("rsp2: " + rsp2.getContent());
});
});
}
}

这个例子至少能帮助你理解两件事:

  • chat(...) 可以直接拿到响应对象,不只是拿到文本
  • 你完全可以在一个回调里继续发下一轮请求

第一次调用能成功,为什么一加配置就不对了

Section titled “第一次调用能成功,为什么一加配置就不对了”

优先检查:

  1. 模型本身是否支持你设置的能力
  2. system(...) 是否把角色限制得过死
  3. 你是不是同时改了太多参数,导致很难判断是哪一项生效

chatStream(...) 没有等到完整句子就一直打印碎片

Section titled “chatStream(...) 没有等到完整句子就一直打印碎片”

这是正常的。
流式输出本来就是按片段到达,不保证是完整语义单元。

因为这取决于模型和厂商支持情况。
它不是所有后端都一定有效的通用开关。