配置对话模型并处理流式输出
在 完成第一次 Feat AI 调用 中,你已经跑通了最基本的模型调用。本文将系统讲解 ChatModel 的配置项、两种调用模式的完整用法、多轮对话的实现方式,以及如何处理异常。
创建 ChatModel
Section titled “创建 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 规范 |
配置系统提示词
Section titled “配置系统提示词”system(...) 是最值得优先使用的配置。它定义了模型的整体角色和输出边界,往往比反复修改用户提示词更有效:
ChatModel chatModel = FeatAI.chatModel(opts -> opts .model("Qwen3-06B") .system("你是一个擅长生成藏头诗的诗人,只输出诗句,不解释。"));传递模型特有参数
Section titled “传递模型特有参数”某些模型或厂商支持标准参数之外的自定义字段,通过 extraBody(...) 传入:
ChatModel chatModel = FeatAI.chatModel(opts -> opts .model("qwen2.5:7b") .extraBody(body -> { body.put("seed", 42); body.put("temperature", 0.7); }));同步调用:chat(…)
Section titled “同步调用:chat(…)”chat(...) 系列方法返回 CompletableFuture<ChatResponse>,适合需要完整响应后再处理的场景。
最简单的单轮对话
Section titled “最简单的单轮对话”chatModel.chat("你好,请自我介绍一下。").thenAccept(rsp -> { System.out.println("内容: " + rsp.getContent()); System.out.println("Token 消耗: " + rsp.getUsage().getTotalTokens());});检查响应状态
Section titled “检查响应状态”ChatResponse 包含响应状态和错误信息:
chatModel.chat("你好").thenAccept(rsp -> { if (rsp.isSuccess()) { System.out.println(rsp.getContent()); } else { System.err.println("请求失败: " + rsp.getError()); }});携带工具函数
Section titled “携带工具函数”你可以在单次调用中动态传入工具函数,无需在模型初始化时预先配置:
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(…)
Section titled “流式调用:chatStream(…)”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()); }});流式调用携带工具函数
Section titled “流式调用携带工具函数”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 不替你维护对话状态,你需要自己管理消息列表。
使用消息列表
Section titled “使用消息列表”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()));});一个完整的多轮示例
Section titled “一个完整的多轮示例”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()); }); }); }}同步调用的错误
Section titled “同步调用的错误”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()); }});流式调用的错误
Section titled “流式调用的错误”流式错误通过 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); }});HTTP 状态码异常
Section titled “HTTP 状态码异常”Feat AI 内部已经处理了常见的 HTTP 错误场景:
- 401/403:API Key 无效或权限不足
- 429:请求频率过高
- 5xx:服务端错误
这些错误会触发 onError 回调,或在同步调用中导致 CompletableFuture 异常完成。
流式输出为什么是碎片化的
Section titled “流式输出为什么是碎片化的”这是预期行为。大语言模型按 token 生成内容,流式传输每次推送一个或几个 token,不会等待完整句子。
为什么模型似乎”不记得”之前的对话
Section titled “为什么模型似乎”不记得”之前的对话”因为每个 chat(...) / chatStream(...) 调用都是独立的 HTTP 请求。模型本身是无状态的,上下文需要通过消息列表显式传递。
onReasoning 一直没有触发
Section titled “onReasoning 一直没有触发”并非所有模型都输出推理内容。目前 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));- 让模型具备规划、推理和调用工具的能力:使用 Agent
- 文本向量化与语义检索:Embedding 与向量