001/* 002 * Copyright (c) 2023-2026, Agents-Flex (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.agentsflex.core.model.chat; 017 018import com.agentsflex.core.util.Maps; 019import com.agentsflex.core.util.Metadata; 020import com.agentsflex.core.util.StringUtil; 021 022import java.util.List; 023import java.util.Map; 024 025/** 026 * 聊天选项配置类,用于控制大语言模型(LLM)的生成行为。 027 * 支持 Builder 模式,便于链式调用。 028 * 注意:不同模型厂商对参数的支持和默认值可能不同。 029 */ 030public class ChatOptions extends Metadata { 031 032 033 /** 034 * 指定使用的大模型名称。 035 * 例如:"gpt-4", "qwen-max", "claude-3-sonnet" 等。 036 * 如果未设置,将使用客户端默认模型。 037 */ 038 private String model; 039 040 /** 041 * 随机种子(Seed),用于控制生成结果的可重复性。 042 * 当 seed 相同时,相同输入将产生相同输出(前提是其他参数也一致)。 043 * 注意:并非所有模型都支持 seed 参数。 044 */ 045 private String seed; 046 047 /** 048 * 温度(Temperature)控制输出的随机性。 049 * <ul> 050 * <li>值越低(如 0.1~0.3):输出更确定、稳定、可重复,适合事实性任务(如 RAG、结构化输出)</li> 051 * <li>值越高(如 0.7~1.0):输出更多样、有创意,但可能不稳定或偏离事实</li> 052 * </ul> 053 * 推荐值: 054 * <ul> 055 * <li>文档处理、路由、工具调用:0.1 ~ 0.3</li> 056 * <li>问答、摘要:0.2 ~ 0.5</li> 057 * <li>创意写作:0.7 ~ 1.0</li> 058 * </ul> 059 * 默认值:0.5f 060 */ 061 private Float temperature = 0.5f; 062 063 /** 064 * Top-p(也称 nucleus sampling)控制生成时考虑的概率质量。 065 * 模型从累积概率不超过 p 的最小词集中采样。 066 * - 值为 1.0 表示考虑所有词(等同于无 top-p 限制) 067 * - 值为 0.9 表示只考虑累积概率达 90% 的词 068 * 注意:temperature 和 top_p 不应同时调整,通常只用其一。 069 */ 070 private Float topP; 071 072 /** 073 * Top-k 控制生成时考虑的最高概率词的数量。 074 * 模型仅从 top-k 个最可能的词中采样。 075 * - 值为 50 表示只考虑概率最高的 50 个词 076 * - 值越小,输出越确定;值越大,输出越多样 077 * 注意:与 top_p 类似,通常不与 temperature 同时使用。 078 */ 079 private Integer topK; 080 081 /** 082 * 生成内容的最大 token 数量(不包括输入 prompt)。 083 * 用于限制响应长度,防止生成过长内容。 084 * 注意:不同模型有不同上限,超过将被截断或报错。 085 */ 086 private Integer maxTokens; 087 088 /** 089 * 停止序列(Stop Sequences),当生成内容包含这些字符串时立即停止。 090 * 例如:设置为 ["\n", "。"] 可在句末或换行时停止。 091 * 适用于需要精确控制输出长度的场景。 092 */ 093 private List<String> stop; 094 095 /** 096 * 是否启用“思考模式”(Thinking Mode)。 097 * 适用于支持该特性的模型(如 Qwen3),开启后模型会显式输出推理过程。 098 * 默认为 null(由模型决定)。 099 */ 100 private Boolean thinkingEnabled; 101 102 /** 103 * 是否返回 Usage 信息, 仅在 stream 模式下有效。 104 * 适用于支持该特性的模型(如 Qwen3),开启后模型会返回 Usage 信息。 105 * 默认为 true。 106 */ 107 private Boolean includeUsage; 108 109 /** 110 * 额外的模型参数,用于传递模型特有或未明确暴露的配置。 111 * 例如:{"response_format": "json", "presence_penalty": 0.5} 112 * 使用 addExtraBody() 方法可方便地添加单个参数。 113 */ 114 private Map<String, Object> extraBody; 115 116 117 protected Boolean retryEnabled; // 默认开启错误重试 118 protected Integer retryCount; 119 protected Integer retryInitialDelayMs; 120 121 122 private Map<String, Object> responseFormat; 123 124 /** 125 * 是否为流式请求。 126 * 这个不允许用户设置,由 Framework 自动设置(用户设置也可能被修改)。 127 * 用户调用 chat 或者 chatStream 方法时,会自动设置这个字段。 128 */ 129 private boolean streaming; 130 131 // ===== 构造函数 ===== 132 public ChatOptions() { 133 } 134 135 private ChatOptions(Builder builder) { 136 this.model = builder.model; 137 this.seed = builder.seed; 138 this.temperature = builder.temperature; 139 this.topP = builder.topP; 140 this.topK = builder.topK; 141 this.maxTokens = builder.maxTokens; 142 this.stop = builder.stop; 143 this.thinkingEnabled = builder.thinkingEnabled; 144 this.includeUsage = builder.includeUsage; 145 this.extraBody = builder.extraBody; 146 this.retryEnabled = builder.retryEnabled; 147 this.retryCount = builder.retryCount; 148 this.retryInitialDelayMs = builder.retryInitialDelayMs; 149 this.responseFormat = builder.responseFormat; 150 151 if (builder.metadata != null && !builder.metadata.isEmpty()){ 152 this.putMetadata(builder.metadata); 153 } 154 } 155 156 // ===== Getter / Setter ===== 157 158 public String getModel() { 159 return model; 160 } 161 162 public String getModelOrDefault(String defaultModel) { 163 return StringUtil.hasText(model) ? model : defaultModel; 164 } 165 166 public void setModel(String model) { 167 this.model = model; 168 } 169 170 public String getSeed() { 171 return seed; 172 } 173 174 public void setSeed(String seed) { 175 this.seed = seed; 176 } 177 178 public Float getTemperature() { 179 return temperature; 180 } 181 182 public void setTemperature(Float temperature) { 183 if (temperature != null && temperature < 0) { 184 throw new IllegalArgumentException("temperature must be greater than 0"); 185 } 186 this.temperature = temperature; 187 } 188 189 public Float getTopP() { 190 return topP; 191 } 192 193 public void setTopP(Float topP) { 194 if (topP != null && (topP < 0 || topP > 1)) { 195 throw new IllegalArgumentException("topP must be between 0 and 1"); 196 } 197 this.topP = topP; 198 } 199 200 public Integer getTopK() { 201 return topK; 202 } 203 204 public void setTopK(Integer topK) { 205 if (topK != null && topK < 0) { 206 throw new IllegalArgumentException("topK must be greater than 0"); 207 } 208 this.topK = topK; 209 } 210 211 public Integer getMaxTokens() { 212 return maxTokens; 213 } 214 215 public void setMaxTokens(Integer maxTokens) { 216 if (maxTokens != null && maxTokens < 0) { 217 throw new IllegalArgumentException("maxTokens must be greater than 0"); 218 } 219 this.maxTokens = maxTokens; 220 } 221 222 public List<String> getStop() { 223 return stop; 224 } 225 226 public void setStop(List<String> stop) { 227 this.stop = stop; 228 } 229 230 public Boolean getThinkingEnabled() { 231 return thinkingEnabled; 232 } 233 234 public Boolean getThinkingEnabledOrDefault(Boolean defaultValue) { 235 return thinkingEnabled != null ? thinkingEnabled : defaultValue; 236 } 237 238 public void setThinkingEnabled(Boolean thinkingEnabled) { 239 this.thinkingEnabled = thinkingEnabled; 240 } 241 242 public Boolean getIncludeUsage() { 243 return includeUsage; 244 } 245 246 public Boolean getIncludeUsageOrDefault(Boolean defaultValue) { 247 return includeUsage != null ? includeUsage : defaultValue; 248 } 249 250 public void setIncludeUsage(Boolean includeUsage) { 251 this.includeUsage = includeUsage; 252 } 253 254 255 public Map<String, Object> getExtraBody() { 256 return extraBody; 257 } 258 259 public void setExtraBody(Map<String, Object> extraBody) { 260 this.extraBody = extraBody; 261 } 262 263 /** 264 * 添加一个额外参数到 extra 映射中。 265 * 266 * @param key 参数名 267 * @param value 参数值 268 */ 269 public void addExtraBody(String key, Object value) { 270 if (extraBody == null) { 271 extraBody = Maps.of(key, value); 272 } else { 273 extraBody.put(key, value); 274 } 275 } 276 277 public Boolean getRetryEnabled() { 278 return retryEnabled; 279 } 280 281 public boolean getRetryEnabledOrDefault(boolean defaultValue) { 282 return retryEnabled != null ? retryEnabled : defaultValue; 283 } 284 285 public void setRetryEnabled(Boolean retryEnabled) { 286 this.retryEnabled = retryEnabled; 287 } 288 289 public Integer getRetryCount() { 290 return retryCount; 291 } 292 293 public int getRetryCountOrDefault(int defaultValue) { 294 return retryCount != null ? retryCount : defaultValue; 295 } 296 297 298 public void setRetryCount(Integer retryCount) { 299 this.retryCount = retryCount; 300 } 301 302 public Integer getRetryInitialDelayMs() { 303 return retryInitialDelayMs; 304 } 305 306 public int getRetryInitialDelayMsOrDefault(int defaultValue) { 307 return retryInitialDelayMs != null ? retryInitialDelayMs : defaultValue; 308 } 309 310 public void setRetryInitialDelayMs(Integer retryInitialDelayMs) { 311 this.retryInitialDelayMs = retryInitialDelayMs; 312 } 313 314 public Map<String, Object> getResponseFormat() { 315 return responseFormat; 316 } 317 318 public void setResponseFormat(Map<String, Object> responseFormat) { 319 this.responseFormat = responseFormat; 320 } 321 322 public boolean isStreaming() { 323 return streaming; 324 } 325 326 public void setStreaming(boolean streaming) { 327 this.streaming = streaming; 328 } 329 330 331 /** 332 * 创建 ChatOptions 的 Builder 实例。 333 * 334 * @return 新的 Builder 对象 335 */ 336 public static Builder builder() { 337 return new Builder(); 338 } 339 340 /** 341 * ChatOptions 的构建器类,支持链式调用。 342 */ 343 public static final class Builder { 344 345 private String model; 346 private String seed; 347 private Float temperature = 0.5f; 348 private Float topP; 349 private Integer topK; 350 private Integer maxTokens; 351 private List<String> stop; 352 private Boolean thinkingEnabled; 353 private Boolean includeUsage; 354 private Map<String, Object> extraBody; 355 private Boolean retryEnabled; 356 private int retryCount = 3; 357 private int retryInitialDelayMs = 1000; 358 public Map<String, Object> responseFormat; 359 public Map<String, Object> metadata; 360 361 public Builder model(String model) { 362 this.model = model; 363 return this; 364 } 365 366 public Builder seed(String seed) { 367 this.seed = seed; 368 return this; 369 } 370 371 public Builder temperature(Float temperature) { 372 this.temperature = temperature; 373 return this; 374 } 375 376 public Builder topP(Float topP) { 377 this.topP = topP; 378 return this; 379 } 380 381 public Builder topK(Integer topK) { 382 this.topK = topK; 383 return this; 384 } 385 386 public Builder maxTokens(Integer maxTokens) { 387 this.maxTokens = maxTokens; 388 return this; 389 } 390 391 public Builder stop(List<String> stop) { 392 this.stop = stop; 393 return this; 394 } 395 396 public Builder thinkingEnabled(Boolean thinkingEnabled) { 397 this.thinkingEnabled = thinkingEnabled; 398 return this; 399 } 400 401 public Builder includeUsage(Boolean includeUsage) { 402 this.includeUsage = includeUsage; 403 return this; 404 } 405 406 public Builder extraBody(Map<String, Object> extra) { 407 this.extraBody = extra; 408 return this; 409 } 410 411 public Builder addExtraBody(String key, Object value) { 412 if (this.extraBody == null) { 413 this.extraBody = Maps.of(key, value); 414 } else { 415 this.extraBody.put(key, value); 416 } 417 return this; 418 } 419 420 public Builder retryEnabled(Boolean retryEnabled) { 421 this.retryEnabled = retryEnabled; 422 return this; 423 } 424 425 public Builder retryCount(int retryCount) { 426 this.retryCount = retryCount; 427 return this; 428 } 429 430 public Builder retryInitialDelayMs(int retryInitialDelayMs) { 431 this.retryInitialDelayMs = retryInitialDelayMs; 432 return this; 433 } 434 435 public Builder responseFormat(Map<String, Object> responseFormat) { 436 this.responseFormat = responseFormat; 437 return this; 438 } 439 440 public Builder responseFormatToJsonObject() { 441 this.responseFormat = Maps.of("type", "json_object"); 442 return this; 443 } 444 445 public Builder responseFormatToJsonSchema(Map<String, Object> json_schema) { 446 this.responseFormat = Maps.of("type", "json_schema").set("json_schema", json_schema); 447 return this; 448 } 449 450 public Builder metadata(Map<String, Object> metadata) { 451 this.metadata = metadata; 452 return this; 453 } 454 455 public Builder addMetadata(String key, Object value) { 456 if (this.metadata == null) { 457 this.metadata = Maps.of(key, value); 458 } else { 459 this.metadata.put(key, value); 460 } 461 return this; 462 } 463 464 /** 465 * 构建并返回 ChatOptions 实例。 466 * 467 * @return 配置完成的 ChatOptions 对象 468 */ 469 public ChatOptions build() { 470 return new ChatOptions(this); 471 } 472 } 473}