Skip to content

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

This content is not available in your language yet.

完成第一次 Feat AI 调用 中,你已经跑通了最基本的模型调用。本文将系统讲解 ChatModel 的配置项、两种调用模式的完整用法、多轮对话的实现方式,以及如何处理异常。

FeatAI.chatModel(...) 接受一个 Consumer<ChatOptions>,所有配置都通过链式调用完成:

ChatModel chatModel = FeatAI.chatModel(opts -> opts
.model("Qwen2.5-72B-Instruct")
.system("你是一个专业的 Java 工程师。")
.debug(false)
);
配置项说明默认值
model(...)模型名称必填
system(...)系统提示词,定义角色和输出风格null
baseUrl(...)服务地址从环境变量 FEAT_AI_BASE_URL 读取,或默认 Gitee AI
apiKey(...)API 密钥从环境变量 FEAT_AI_API_KEY 读取
debug(...)是否打印请求/响应详情false
extraBody(...)模型特有的额外参数null
specProvider(...)自定义 API 规范适配器OpenAI 规范

system(...) 是最值得优先使用的配置。它定义了模型的整体角色和输出边界,往往比反复修改用户提示词更有效:

ChatModel chatModel = FeatAI.chatModel(opts -> opts
.model("Qwen3-06B")
.system("你是一个擅长生成藏头诗的诗人,只输出诗句,不解释。")
);

某些模型或厂商支持标准参数之外的自定义字段,通过 extraBody(...) 传入:

ChatModel chatModel = FeatAI.chatModel(opts -> opts
.model("qwen2.5:7b")
.extraBody(body -> {
body.put("seed", 42);
body.put("temperature", 0.7);
})
);

chat(...) 系列方法返回 CompletableFuture<ChatResponse>,适合需要完整响应后再处理的场景。

chatModel.chat("你好,请自我介绍一下。").thenAccept(rsp -> {
System.out.println("内容: " + rsp.getContent());
System.out.println("Token 消耗: " + rsp.getUsage().getTotalTokens());
});

ChatResponse 包含响应状态和错误信息:

chatModel.chat("你好").thenAccept(rsp -> {
if (rsp.isSuccess()) {
System.out.println(rsp.getContent());
} else {
System.err.println("请求失败: " + rsp.getError());
}
});

你可以在单次调用中动态传入工具函数,无需在模型初始化时预先配置:

List<Tool> tools = Collections.singletonList(myTool);
chatModel.chat("查一下北京天气", tools).thenAccept(rsp -> {
if (rsp.getToolCalls() != null) {
// 模型决定调用工具
rsp.getToolCalls().forEach(toolCall -> {
System.out.println("模型请求调用: " + toolCall.getFunction().getName());
});
} else {
System.out.println(rsp.getContent());
}
});

chatStream(...) 通过 ChatStreamListener 回调实时接收模型生成的片段,适合需要逐字展示的场景。

chatModel.chatStream("根据以下关键词生成一首藏头诗:情,人,节,快,乐", new ChatStreamListener() {
@Override
public void onStreamResponse(String content) {
System.out.print(content);
}
});

完整回调:接收完成事件和推理内容

Section titled “完整回调:接收完成事件和推理内容”

ChatStreamListener 提供四个回调方法,按需重写:

chatModel.chatStream("介绍一下 Feat 框架", new ChatStreamListener() {
@Override
public void onStreamResponse(String content) {
// 每收到一个文本片段触发
System.out.print(content);
}
@Override
public void onReasoning(String content) {
// 某些模型的推理过程(如 DeepSeek-R1 的思考链)
System.err.print("[思考] " + content);
}
@Override
public void onCompletion(ChatResponse chatResponse) {
// 流完全结束时触发,可获取完整 usage
System.out.println("\n\n流结束,总 tokens: " + chatResponse.getUsage().getTotalTokens());
}
@Override
public void onError(Throwable throwable) {
// 网络异常或流式传输错误
System.err.println("出错了: " + throwable.getMessage());
}
});
chatModel.chatStream("查一下北京天气", tools, new ChatStreamListener() {
@Override
public void onStreamResponse(String content) {
System.out.print(content);
}
@Override
public void onCompletion(ChatResponse chatResponse) {
if (chatResponse.getToolCalls() != null) {
chatResponse.getToolCalls().forEach(tc ->
System.out.println("需要调用: " + tc.getFunction().getName())
);
}
}
});

多轮对话的本质是:把历史消息一并发送给模型。Feat AI 不替你维护对话状态,你需要自己管理消息列表。

import tech.smartboot.feat.ai.chat.entity.Message;
List<Message> messages = new ArrayList<>();
messages.add(Message.ofSystem("你是一个专业的 Java 工程师"));
messages.add(Message.ofUser("什么是 Feat?"));
chatModel.chat(messages).thenAccept(rsp -> {
System.out.println("AI: " + rsp.getContent());
// 把 AI 的回复也加入历史,供下一轮使用
messages.add(Message.ofAssistant(rsp.getContent()));
});
ChatDemo.java
import tech.smartboot.feat.ai.FeatAI;
import tech.smartboot.feat.ai.chat.ChatModel;
public class ChatDemo {
public static void main(String[] args) {
ChatModel chatModel = FeatAI.chatModel(opts ->
opts.model("Qwen2.5-72B-Instruct")
);
chatModel.chat("你好,请自我介绍一下。").thenAccept(rsp -> {
System.out.println("第一轮: " + rsp.getContent());
System.out.println("usage: " + rsp.getUsage());
chatModel.chat("我对你说的上一句话是什么?").thenAccept(rsp2 -> {
System.out.println("第二轮: " + rsp2.getContent());
});
});
}
}

chat(...) 返回 CompletableFuture,异常会封装在 Future 中:

chatModel.chat("你好").whenComplete((rsp, ex) -> {
if (ex != null) {
System.err.println("请求异常: " + ex.getMessage());
} else if (!rsp.isSuccess()) {
System.err.println("业务错误: " + rsp.getError());
} else {
System.out.println(rsp.getContent());
}
});

流式错误通过 ChatStreamListener.onError(...) 回调处理。默认实现是 printStackTrace,生产环境务必重写:

chatModel.chatStream("你好", new ChatStreamListener() {
@Override
public void onStreamResponse(String content) {
System.out.print(content);
}
@Override
public void onError(Throwable throwable) {
// 自定义错误处理,如记录日志、通知用户
logger.error("流式调用失败", throwable);
}
});

Feat AI 内部已经处理了常见的 HTTP 错误场景:

  • 401/403:API Key 无效或权限不足
  • 429:请求频率过高
  • 5xx:服务端错误

这些错误会触发 onError 回调,或在同步调用中导致 CompletableFuture 异常完成。

这是预期行为。大语言模型按 token 生成内容,流式传输每次推送一个或几个 token,不会等待完整句子。

为什么模型似乎”不记得”之前的对话

Section titled “为什么模型似乎”不记得”之前的对话”

因为每个 chat(...) / chatStream(...) 调用都是独立的 HTTP 请求。模型本身是无状态的,上下文需要通过消息列表显式传递。

并非所有模型都输出推理内容。目前 DeepSeek-R1、Qwen3 等部分模型支持,普通模型不会触发此回调。

如何切换不同的 API 规范(如 Anthropic)

Section titled “如何切换不同的 API 规范(如 Anthropic)”

默认使用 OpenAI 兼容规范。如需切换,通过 specProvider(...) 传入自定义的 Provider 工厂:

ChatModel chatModel = FeatAI.chatModel(opts -> opts
.baseUrl("https://api.anthropic.com/v1")
.apiKey("your-key")
.model("claude-3-sonnet")
.specProvider(AnthropicProvider::new)
);