chat - 模型的响应与计费
2026年9月7日 上午8:33:28
在 Solon AI 中,模型调用有两种返回方式:call() 返回一个 ChatResponse,适用于同步调用;stream() 返回 Flux<ChatEvent>,适用于流式订阅。流式事件中的 RESPONSE_END 是一次响应的正常终态,调用 getResponse() 可取得完整的最终 ChatResponse(其 getMessage() 等结果访问器对应完整聚合结果)。ChatResponse 还负责追踪 Token 的消耗情况。
1、响应接口 (ChatResponse)
ChatResponse 统一了承载同步调用结果和流式终态结果的结构。针对目前流行的“推理模型”(如 DeepSeek-R1, OpenAI o1),它提供了专门的方法来区分思维链(Thinking)和最终答案。
提示: 对于支持深度思考的模型,推荐使用 getText() 获取最终文本,getContent() 可用于获取消息原始内容或日志记录。
以下接口代码与当前 solon-ai-core/src/main/java/org/noear/solon/ai/chat/ChatResponse.java 一致:
package org.noear.solon.ai.chat;
import org.noear.solon.ai.AiUsage;
import org.noear.solon.ai.chat.content.ContentBlock;
import org.noear.solon.ai.chat.event.ChatEvent;
import org.noear.solon.ai.chat.event.ChatEventType;
import org.noear.solon.ai.chat.message.AssistantMessage;
import org.noear.solon.ai.chat.tool.ToolCall;
import org.noear.solon.lang.Nullable;
import org.noear.solon.lang.Preview;
import java.util.List;
/**
* 聊天响应(模型调用的结果,不可变)
*/
@Preview("3.1")
public interface ChatResponse {
/**
* 获取原始响应数据
*/
@Nullable
String getFrameRaw();
/**
* 是否为终态
*/
boolean isTerminal();
/**
* 获取模型
*/
String getModel();
/**
* 获取错误
*/
@Nullable
ChatException getError();
/**
* 获取消息(终态;流式下即完整聚合结果)
*/
@Nullable
AssistantMessage getMessage();
/**
* 是否为空(没有内容,也没有工具调用)
*/
boolean isEmpty();
/**
* 是否有消息内容
*/
boolean hasContent();
/**
* 获取消息原始内容
*/
String getContent();
/**
* 获取文本
*/
String getText();
/**
* 获取思考
*/
String getThinking();
/**
* 获取工具调用(没有时为空集合,不为 null)
*
* @since 4.1
*/
List<ToolCall> getToolCalls();
/**
* 获取内容块(多模态;没有时为空集合,不为 null)
*
* @since 4.1
*/
List<ContentBlock> getBlocks();
/**
* 获取完成原因(已归一化:工具调用为 {@code "tool"}、正常结束为 {@code "stop"},
* 其余如 {@code "length"} / {@code "content_filter"} 原样透传)
*
* @since 4.1
*/
String getFinishReason();
/**
* 获取使用情况(完成时,才会有使用情况)
*/
@Nullable
AiUsage getUsage();
/**
* 本次响应的语义事件(仅非流式)
*
* <p>非流式 {@code call()} 没有事件流可供投递,但方言同样会解析出引用、服务端工具结果、
* 思考签名、拒答等语义——这些信息并非流式独有,不能静默丢弃。因此非流式路径改为把事件
* 收集到结果上。</p>
*
* <p>流式路径下事件由 {@code stream()} 直接投递,此处为空列表(不会为 null)。
* 自动工具调用产生多轮时,与聚合消息一致——只携带末轮。</p>
*
* @since 4.1
*/
List<ChatEvent> getEvents();
}
调用与流式订阅示例:
// 同步调用:直接取得最终响应
ChatResponse response = chatModel.prompt("hello").call();
System.out.println(response.getText());
// 流式调用:RESPONSE_END 的 getResponse() 是完整终态
chatModel.prompt("hello").stream().subscribe(event -> {
if (event.is(ChatEventType.RESPONSE_END)) {
ChatResponse terminal = event.getResponse();
System.out.println(terminal.getText());
}
});
2、计费与使用统计 (AiUsage)
AiUsage 用于记录单次对话消耗的 Token 资源。不同的模型服务商提供的原始 JSON 结构差异很大,Solon AI 将其标准化为三个核心指标。
package org.noear.solon.ai;
import org.noear.snack4.ONode;
public class AiUsage {
private final long promptTokens;
private final long thinkTokens;
private final long completionTokens;
private final long totalTokens;
private final long cacheCreationInputTokens;
private final long cacheReadInputTokens;
private final ONode source;
public AiUsage(long promptTokens, long thinkTokens, long completionTokens, long totalTokens, ONode source) {
this(promptTokens, thinkTokens, completionTokens, totalTokens, 0L, 0L, source);
}
public AiUsage(long promptTokens, long thinkTokens, long completionTokens, long totalTokens,
long cacheCreationInputTokens, long cacheReadInputTokens, ONode source) {
this.promptTokens = promptTokens;
this.thinkTokens = thinkTokens;
this.completionTokens = completionTokens;
this.totalTokens = totalTokens;
this.cacheCreationInputTokens = cacheCreationInputTokens;
this.cacheReadInputTokens = cacheReadInputTokens;
this.source = source;
}
/**
* 获取提示语消耗词元数
*/
public long promptTokens() {
return promptTokens;
}
/**
* 获取思考消耗词元数
*/
public long thinkTokens() {
return thinkTokens;
}
/**
* 获取完成消耗词元数
*/
public long completionTokens() {
return completionTokens;
}
/**
* 获取总消耗词元数
*/
public long totalTokens() {
return totalTokens;
}
/**
* 获取缓存创建输入词元数 (Claude Prompt Caching)
*/
public long cacheCreationInputTokens() {
return cacheCreationInputTokens;
}
/**
* 获取缓存读取输入词元数 (Claude Prompt Caching)
*/
public long cacheReadInputTokens() {
return cacheReadInputTokens;
}
/**
* 源数据
*/
public ONode getSource() {
return source;
}
}
3、代码示例
获取清理后的内容
ChatResponse resp = chatModel.call(prompt);
// 自动处理思考过程,只打印结果
System.out.println("Result: " + resp.getText());
// 打印计费信息
AiUsage usage = resp.getUsage();
if (usage != null) {
System.out.println("Cost Tokens: " + usage.totalTokens());
}
处理原始数据 如果你需要获取厂商特有的计费细节(例如 DeepSeek 的 prompt_cache_hit_tokens):
long cacheHit = resp.getUsage().getSource()
.get("usage")
.get("prompt_cache_hit_tokens").getLong();