|
@@ -3,20 +3,24 @@ package com.tzld.piaoquan.sde.integration;
|
|
|
import com.alibaba.fastjson.JSON;
|
|
import com.alibaba.fastjson.JSON;
|
|
|
import com.alibaba.fastjson.JSONArray;
|
|
import com.alibaba.fastjson.JSONArray;
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
|
|
+import lombok.Builder;
|
|
|
|
|
+import lombok.Data;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import okhttp3.*;
|
|
import okhttp3.*;
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
+import org.springframework.util.StringUtils;
|
|
|
|
|
|
|
|
import javax.annotation.PostConstruct;
|
|
import javax.annotation.PostConstruct;
|
|
|
import java.io.IOException;
|
|
import java.io.IOException;
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* OpenRouter API Client
|
|
* OpenRouter API Client
|
|
|
- * 支持通过 OpenRouter 调用 Gemini 和 ChatGPT 模型
|
|
|
|
|
|
|
+ * 支持通过 OpenRouter 调用 Gemini 和 ChatGPT 等多种模型
|
|
|
*
|
|
*
|
|
|
* @author supeng
|
|
* @author supeng
|
|
|
*/
|
|
*/
|
|
@@ -39,6 +43,12 @@ public class OpenRouterClient {
|
|
|
@Value("${openrouter.timeout:60}")
|
|
@Value("${openrouter.timeout:60}")
|
|
|
private int timeout;
|
|
private int timeout;
|
|
|
|
|
|
|
|
|
|
+ @Value("${openrouter.default.gemini.model:google/gemini-2.0-flash-exp}")
|
|
|
|
|
+ private String defaultGeminiModel;
|
|
|
|
|
+
|
|
|
|
|
+ @Value("${openrouter.default.gpt.model:openai/gpt-4o-mini}")
|
|
|
|
|
+ private String defaultGptModel;
|
|
|
|
|
+
|
|
|
private OkHttpClient httpClient;
|
|
private OkHttpClient httpClient;
|
|
|
|
|
|
|
|
@PostConstruct
|
|
@PostConstruct
|
|
@@ -51,127 +61,447 @@ public class OpenRouterClient {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 调用 OpenRouter API
|
|
|
|
|
|
|
+ * 使用 ChatRequest 对象调用 API
|
|
|
*
|
|
*
|
|
|
- * @param model 模型名称,例如: "google/gemini-pro", "openai/gpt-4", "openai/gpt-3.5-turbo"
|
|
|
|
|
- * @param messages 消息列表,每个消息包含 role 和 content
|
|
|
|
|
- * @param temperature 温度参数,控制随机性 (0.0-2.0)
|
|
|
|
|
- * @param maxTokens 最大生成 token 数
|
|
|
|
|
- * @return API 响应内容
|
|
|
|
|
|
|
+ * @param request 聊天请求对象
|
|
|
|
|
+ * @return 聊天响应对象
|
|
|
* @throws IOException 网络请求异常
|
|
* @throws IOException 网络请求异常
|
|
|
*/
|
|
*/
|
|
|
- public String chat(String model, List<Map<String, String>> messages, Double temperature, Integer maxTokens) throws IOException {
|
|
|
|
|
- JSONObject requestBody = buildRequestBody(model, messages, temperature, maxTokens);
|
|
|
|
|
|
|
+ public ChatResponse chat(ChatRequest request) throws IOException {
|
|
|
|
|
+ validateRequest(request);
|
|
|
|
|
|
|
|
- Request request = new Request.Builder()
|
|
|
|
|
- .url(url)
|
|
|
|
|
- .addHeader("Authorization", "Bearer " + apiKey)
|
|
|
|
|
- .addHeader("Content-Type", "application/json")
|
|
|
|
|
- .addHeader("HTTP-Referer", "https://github.com/tzld")
|
|
|
|
|
- .addHeader("X-Title", "Supply Demand Engine")
|
|
|
|
|
- .post(RequestBody.create(JSON_MEDIA_TYPE, requestBody.toJSONString()))
|
|
|
|
|
- .build();
|
|
|
|
|
|
|
+ JSONObject requestBody = buildRequestBody(request);
|
|
|
|
|
+ Request httpRequest = buildHttpRequest(requestBody);
|
|
|
|
|
+
|
|
|
|
|
+ log.info("Calling OpenRouter API with model: {}", request.getModel());
|
|
|
|
|
+ log.debug("Request body: {}", requestBody.toJSONString());
|
|
|
|
|
|
|
|
- log.info("Calling OpenRouter API with model: {}", model);
|
|
|
|
|
|
|
+ try (Response response = httpClient.newCall(httpRequest).execute()) {
|
|
|
|
|
+ String responseBody = response.body() != null ? response.body().string() : "";
|
|
|
|
|
|
|
|
- try (Response response = httpClient.newCall(request).execute()) {
|
|
|
|
|
if (!response.isSuccessful()) {
|
|
if (!response.isSuccessful()) {
|
|
|
- String errorBody = response.body() != null ? response.body().string() : "No error body";
|
|
|
|
|
- log.error("OpenRouter API call failed: {} - {}", response.code(), errorBody);
|
|
|
|
|
- throw new IOException("OpenRouter API call failed: " + response.code() + " - " + errorBody);
|
|
|
|
|
|
|
+ log.error("OpenRouter API call failed: {} - {}", response.code(), responseBody);
|
|
|
|
|
+ throw new IOException("OpenRouter API call failed: " + response.code() + " - " + responseBody);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- String responseBody = response.body().string();
|
|
|
|
|
log.debug("OpenRouter API response: {}", responseBody);
|
|
log.debug("OpenRouter API response: {}", responseBody);
|
|
|
-
|
|
|
|
|
return parseResponse(responseBody);
|
|
return parseResponse(responseBody);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 简化的调用方法,使用默认参数
|
|
|
|
|
|
|
+ * 简化调用 - 使用简单字符串
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param model 模型名称
|
|
|
|
|
+ * @param content 用户消息内容
|
|
|
|
|
+ * @return 响应内容
|
|
|
|
|
+ * @throws IOException 网络请求异常
|
|
|
|
|
+ */
|
|
|
|
|
+ public String chat(String model, String content) throws IOException {
|
|
|
|
|
+ return chat(ChatRequest.builder()
|
|
|
|
|
+ .model(model)
|
|
|
|
|
+ .addUserMessage(content)
|
|
|
|
|
+ .build()).getContent();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 简化调用 - 使用简单字符串,带系统提示
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param model 模型名称
|
|
|
|
|
+ * @param systemPrompt 系统提示
|
|
|
|
|
+ * @param userContent 用户消息内容
|
|
|
|
|
+ * @return 响应内容
|
|
|
|
|
+ * @throws IOException 网络请求异常
|
|
|
|
|
+ */
|
|
|
|
|
+ public String chat(String model, String systemPrompt, String userContent) throws IOException {
|
|
|
|
|
+ return chat(ChatRequest.builder()
|
|
|
|
|
+ .model(model)
|
|
|
|
|
+ .addSystemMessage(systemPrompt)
|
|
|
|
|
+ .addUserMessage(userContent)
|
|
|
|
|
+ .build()).getContent();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 简化调用 - 使用 Map 格式消息列表
|
|
|
*
|
|
*
|
|
|
* @param model 模型名称
|
|
* @param model 模型名称
|
|
|
* @param messages 消息列表
|
|
* @param messages 消息列表
|
|
|
- * @return API 响应内容
|
|
|
|
|
|
|
+ * @return 响应内容
|
|
|
* @throws IOException 网络请求异常
|
|
* @throws IOException 网络请求异常
|
|
|
*/
|
|
*/
|
|
|
public String chat(String model, List<Map<String, String>> messages) throws IOException {
|
|
public String chat(String model, List<Map<String, String>> messages) throws IOException {
|
|
|
- return chat(model, messages, 0.7, 2000);
|
|
|
|
|
|
|
+ ChatRequest request = ChatRequest.builder()
|
|
|
|
|
+ .model(model)
|
|
|
|
|
+ .messages(convertMessages(messages))
|
|
|
|
|
+ .build();
|
|
|
|
|
+ return chat(request).getContent();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 调用 Gemini 模型
|
|
|
|
|
|
|
+ * 简化调用 - 带参数
|
|
|
*
|
|
*
|
|
|
- * @param messages 消息列表
|
|
|
|
|
- * @return API 响应内容
|
|
|
|
|
|
|
+ * @param model 模型名称
|
|
|
|
|
+ * @param messages 消息列表
|
|
|
|
|
+ * @param temperature 温度参数
|
|
|
|
|
+ * @param maxTokens 最大 token 数
|
|
|
|
|
+ * @return 响应内容
|
|
|
* @throws IOException 网络请求异常
|
|
* @throws IOException 网络请求异常
|
|
|
*/
|
|
*/
|
|
|
- public String chatWithGemini(List<Map<String, String>> messages) throws IOException {
|
|
|
|
|
- return chat("google/gemini-pro", messages);
|
|
|
|
|
|
|
+ public String chat(String model, List<Map<String, String>> messages, Double temperature, Integer maxTokens) throws IOException {
|
|
|
|
|
+ ChatRequest request = ChatRequest.builder()
|
|
|
|
|
+ .model(model)
|
|
|
|
|
+ .messages(convertMessages(messages))
|
|
|
|
|
+ .temperature(temperature)
|
|
|
|
|
+ .maxTokens(maxTokens)
|
|
|
|
|
+ .build();
|
|
|
|
|
+ return chat(request).getContent();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 调用 ChatGPT 模型
|
|
|
|
|
|
|
+ * 调用 Gemini 模型 - 使用配置的默认模型
|
|
|
*
|
|
*
|
|
|
- * @param messages 消息列表
|
|
|
|
|
- * @param useGpt4 是否使用 GPT-4,false 则使用 GPT-3.5-turbo
|
|
|
|
|
- * @return API 响应内容
|
|
|
|
|
|
|
+ * @param content 用户消息内容
|
|
|
|
|
+ * @return 响应内容
|
|
|
* @throws IOException 网络请求异常
|
|
* @throws IOException 网络请求异常
|
|
|
*/
|
|
*/
|
|
|
- public String chatWithGPT(List<Map<String, String>> messages, boolean useGpt4) throws IOException {
|
|
|
|
|
- String model = useGpt4 ? "openai/gpt-4" : "openai/gpt-3.5-turbo";
|
|
|
|
|
- return chat(model, messages);
|
|
|
|
|
|
|
+ public String chatWithGemini(String content) throws IOException {
|
|
|
|
|
+ return chat(defaultGeminiModel, content);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 调用 Gemini 模型 - 指定具体模型
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param model Gemini 模型名称
|
|
|
|
|
+ * @param content 用户消息内容
|
|
|
|
|
+ * @return 响应内容
|
|
|
|
|
+ * @throws IOException 网络请求异常
|
|
|
|
|
+ */
|
|
|
|
|
+ public String chatWithGemini(String model, String content) throws IOException {
|
|
|
|
|
+ return chat(model, content);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 调用 ChatGPT 模型 - 使用配置的默认模型
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param content 用户消息内容
|
|
|
|
|
+ * @return 响应内容
|
|
|
|
|
+ * @throws IOException 网络请求异常
|
|
|
|
|
+ */
|
|
|
|
|
+ public String chatWithGPT(String content) throws IOException {
|
|
|
|
|
+ return chat(defaultGptModel, content);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 调用 ChatGPT 模型 - 指定具体模型
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param model GPT 模型名称
|
|
|
|
|
+ * @param content 用户消息内容
|
|
|
|
|
+ * @return 响应内容
|
|
|
|
|
+ * @throws IOException 网络请求异常
|
|
|
|
|
+ */
|
|
|
|
|
+ public String chatWithGPT(String model, String content) throws IOException {
|
|
|
|
|
+ return chat(model, content);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 创建请求构建器
|
|
|
|
|
+ */
|
|
|
|
|
+ public ChatRequest.ChatRequestBuilder requestBuilder() {
|
|
|
|
|
+ return ChatRequest.builder();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 验证请求参数
|
|
|
|
|
+ */
|
|
|
|
|
+ private void validateRequest(ChatRequest request) {
|
|
|
|
|
+ if (!StringUtils.hasText(request.getModel())) {
|
|
|
|
|
+ throw new IllegalArgumentException("Model cannot be empty");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (request.getMessages() == null || request.getMessages().isEmpty()) {
|
|
|
|
|
+ throw new IllegalArgumentException("Messages cannot be empty");
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 构建请求体
|
|
* 构建请求体
|
|
|
*/
|
|
*/
|
|
|
- private JSONObject buildRequestBody(String model, List<Map<String, String>> messages, Double temperature, Integer maxTokens) {
|
|
|
|
|
|
|
+ private JSONObject buildRequestBody(ChatRequest request) {
|
|
|
JSONObject requestBody = new JSONObject();
|
|
JSONObject requestBody = new JSONObject();
|
|
|
- requestBody.put("model", model);
|
|
|
|
|
|
|
+ requestBody.put("model", request.getModel());
|
|
|
|
|
|
|
|
JSONArray messagesArray = new JSONArray();
|
|
JSONArray messagesArray = new JSONArray();
|
|
|
- for (Map<String, String> message : messages) {
|
|
|
|
|
|
|
+ for (Message message : request.getMessages()) {
|
|
|
JSONObject messageNode = new JSONObject();
|
|
JSONObject messageNode = new JSONObject();
|
|
|
- messageNode.put("role", message.get("role"));
|
|
|
|
|
- messageNode.put("content", message.get("content"));
|
|
|
|
|
|
|
+ messageNode.put("role", message.getRole());
|
|
|
|
|
+ messageNode.put("content", message.getContent());
|
|
|
messagesArray.add(messageNode);
|
|
messagesArray.add(messageNode);
|
|
|
}
|
|
}
|
|
|
requestBody.put("messages", messagesArray);
|
|
requestBody.put("messages", messagesArray);
|
|
|
|
|
|
|
|
- if (temperature != null) {
|
|
|
|
|
- requestBody.put("temperature", temperature);
|
|
|
|
|
|
|
+ if (request.getTemperature() != null) {
|
|
|
|
|
+ requestBody.put("temperature", request.getTemperature());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (request.getMaxTokens() != null) {
|
|
|
|
|
+ requestBody.put("max_tokens", request.getMaxTokens());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (request.getTopP() != null) {
|
|
|
|
|
+ requestBody.put("top_p", request.getTopP());
|
|
|
}
|
|
}
|
|
|
- if (maxTokens != null) {
|
|
|
|
|
- requestBody.put("max_tokens", maxTokens);
|
|
|
|
|
|
|
+ if (request.getTopK() != null) {
|
|
|
|
|
+ requestBody.put("top_k", request.getTopK());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (request.getFrequencyPenalty() != null) {
|
|
|
|
|
+ requestBody.put("frequency_penalty", request.getFrequencyPenalty());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (request.getPresencePenalty() != null) {
|
|
|
|
|
+ requestBody.put("presence_penalty", request.getPresencePenalty());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (request.getStop() != null && !request.getStop().isEmpty()) {
|
|
|
|
|
+ requestBody.put("stop", request.getStop());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return requestBody;
|
|
return requestBody;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 解析响应,提取生成的内容
|
|
|
|
|
|
|
+ * 构建 HTTP 请求
|
|
|
|
|
+ */
|
|
|
|
|
+ private Request buildHttpRequest(JSONObject requestBody) {
|
|
|
|
|
+ return new Request.Builder()
|
|
|
|
|
+ .url(url)
|
|
|
|
|
+ .addHeader("Authorization", "Bearer " + apiKey)
|
|
|
|
|
+ .addHeader("Content-Type", "application/json")
|
|
|
|
|
+ .addHeader("HTTP-Referer", "https://github.com/tzld")
|
|
|
|
|
+ .addHeader("X-Title", "Supply Demand Engine")
|
|
|
|
|
+ .post(RequestBody.create(JSON_MEDIA_TYPE, requestBody.toJSONString()))
|
|
|
|
|
+ .build();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 解析响应
|
|
|
*/
|
|
*/
|
|
|
- private String parseResponse(String responseBody) throws IOException {
|
|
|
|
|
|
|
+ private ChatResponse parseResponse(String responseBody) throws IOException {
|
|
|
try {
|
|
try {
|
|
|
JSONObject root = JSON.parseObject(responseBody);
|
|
JSONObject root = JSON.parseObject(responseBody);
|
|
|
JSONArray choices = root.getJSONArray("choices");
|
|
JSONArray choices = root.getJSONArray("choices");
|
|
|
- if (choices != null && !choices.isEmpty()) {
|
|
|
|
|
- JSONObject firstChoice = choices.getJSONObject(0);
|
|
|
|
|
- JSONObject message = firstChoice.getJSONObject("message");
|
|
|
|
|
- if (message != null) {
|
|
|
|
|
- String content = message.getString("content");
|
|
|
|
|
- if (content != null) {
|
|
|
|
|
- return content;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (choices == null || choices.isEmpty()) {
|
|
|
|
|
+ log.warn("No choices in response, returning empty response");
|
|
|
|
|
+ return ChatResponse.builder()
|
|
|
|
|
+ .content("")
|
|
|
|
|
+ .rawResponse(responseBody)
|
|
|
|
|
+ .build();
|
|
|
}
|
|
}
|
|
|
- log.warn("Unable to parse content from response, returning full response");
|
|
|
|
|
- return responseBody;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ JSONObject firstChoice = choices.getJSONObject(0);
|
|
|
|
|
+ JSONObject message = firstChoice.getJSONObject("message");
|
|
|
|
|
+
|
|
|
|
|
+ String content = "";
|
|
|
|
|
+ String role = "assistant";
|
|
|
|
|
+ if (message != null) {
|
|
|
|
|
+ content = message.getString("content");
|
|
|
|
|
+ role = message.getString("role");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提取使用信息
|
|
|
|
|
+ JSONObject usage = root.getJSONObject("usage");
|
|
|
|
|
+ Integer promptTokens = null;
|
|
|
|
|
+ Integer completionTokens = null;
|
|
|
|
|
+ Integer totalTokens = null;
|
|
|
|
|
+
|
|
|
|
|
+ if (usage != null) {
|
|
|
|
|
+ promptTokens = usage.getInteger("prompt_tokens");
|
|
|
|
|
+ completionTokens = usage.getInteger("completion_tokens");
|
|
|
|
|
+ totalTokens = usage.getInteger("total_tokens");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return ChatResponse.builder()
|
|
|
|
|
+ .content(content)
|
|
|
|
|
+ .role(role)
|
|
|
|
|
+ .promptTokens(promptTokens)
|
|
|
|
|
+ .completionTokens(completionTokens)
|
|
|
|
|
+ .totalTokens(totalTokens)
|
|
|
|
|
+ .rawResponse(responseBody)
|
|
|
|
|
+ .build();
|
|
|
|
|
+
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
- log.error("Error parsing response: {}", e.getMessage());
|
|
|
|
|
- return responseBody;
|
|
|
|
|
|
|
+ log.error("Error parsing response: {}", e.getMessage(), e);
|
|
|
|
|
+ throw new IOException("Failed to parse response: " + e.getMessage(), e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 转换消息格式
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<Message> convertMessages(List<Map<String, String>> messages) {
|
|
|
|
|
+ List<Message> result = new ArrayList<>();
|
|
|
|
|
+ for (Map<String, String> msg : messages) {
|
|
|
|
|
+ result.add(Message.builder()
|
|
|
|
|
+ .role(msg.get("role"))
|
|
|
|
|
+ .content(msg.get("content"))
|
|
|
|
|
+ .build());
|
|
|
}
|
|
}
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 聊天请求对象
|
|
|
|
|
+ */
|
|
|
|
|
+ @Data
|
|
|
|
|
+ @Builder
|
|
|
|
|
+ public static class ChatRequest {
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 模型名称,例如: "google/gemini-pro", "openai/gpt-4", "openai/gpt-3.5-turbo"
|
|
|
|
|
+ */
|
|
|
|
|
+ private String model;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 消息列表
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<Message> messages;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 温度参数,控制随机性 (0.0-2.0)
|
|
|
|
|
+ */
|
|
|
|
|
+ private Double temperature;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 最大生成 token 数
|
|
|
|
|
+ */
|
|
|
|
|
+ private Integer maxTokens;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Top-p 采样参数 (0.0-1.0)
|
|
|
|
|
+ */
|
|
|
|
|
+ private Double topP;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Top-k 采样参数
|
|
|
|
|
+ */
|
|
|
|
|
+ private Integer topK;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 频率惩罚 (-2.0 到 2.0)
|
|
|
|
|
+ */
|
|
|
|
|
+ private Double frequencyPenalty;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 存在惩罚 (-2.0 到 2.0)
|
|
|
|
|
+ */
|
|
|
|
|
+ private Double presencePenalty;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 停止序列
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<String> stop;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 添加消息的便捷方法
|
|
|
|
|
+ */
|
|
|
|
|
+ public static class ChatRequestBuilder {
|
|
|
|
|
+ public ChatRequestBuilder addMessage(String role, String content) {
|
|
|
|
|
+ if (this.messages == null) {
|
|
|
|
|
+ this.messages = new ArrayList<>();
|
|
|
|
|
+ }
|
|
|
|
|
+ this.messages.add(Message.builder().role(role).content(content).build());
|
|
|
|
|
+ return this;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public ChatRequestBuilder addUserMessage(String content) {
|
|
|
|
|
+ return addMessage("user", content);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public ChatRequestBuilder addSystemMessage(String content) {
|
|
|
|
|
+ return addMessage("system", content);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public ChatRequestBuilder addAssistantMessage(String content) {
|
|
|
|
|
+ return addMessage("assistant", content);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 消息对象
|
|
|
|
|
+ */
|
|
|
|
|
+ @Data
|
|
|
|
|
+ @Builder
|
|
|
|
|
+ public static class Message {
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 角色: system, user, assistant
|
|
|
|
|
+ */
|
|
|
|
|
+ private String role;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 消息内容
|
|
|
|
|
+ */
|
|
|
|
|
+ private String content;
|
|
|
|
|
+
|
|
|
|
|
+ public static Message user(String content) {
|
|
|
|
|
+ return Message.builder().role("user").content(content).build();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static Message system(String content) {
|
|
|
|
|
+ return Message.builder().role("system").content(content).build();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static Message assistant(String content) {
|
|
|
|
|
+ return Message.builder().role("assistant").content(content).build();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 聊天响应对象
|
|
|
|
|
+ */
|
|
|
|
|
+ @Data
|
|
|
|
|
+ @Builder
|
|
|
|
|
+ public static class ChatResponse {
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 生成的内容
|
|
|
|
|
+ */
|
|
|
|
|
+ private String content;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 角色
|
|
|
|
|
+ */
|
|
|
|
|
+ private String role;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 提示词使用的 token 数
|
|
|
|
|
+ */
|
|
|
|
|
+ private Integer promptTokens;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 生成内容使用的 token 数
|
|
|
|
|
+ */
|
|
|
|
|
+ private Integer completionTokens;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 总 token 数
|
|
|
|
|
+ */
|
|
|
|
|
+ private Integer totalTokens;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 原始响应
|
|
|
|
|
+ */
|
|
|
|
|
+ private String rawResponse;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 常用模型常量
|
|
|
|
|
+ */
|
|
|
|
|
+ public static class Models {
|
|
|
|
|
+ public static final String GEMINI_3_PRO_PREVIEW = "google/gemini-3-pro-preview";
|
|
|
|
|
+ public static final String GEMINI_25_PRO = "google/gemini-2.5-pro";
|
|
|
|
|
+ public static final String GEMINI_3_FLASH_PREVIEW = "google/gemini-3-flash-preview";
|
|
|
|
|
+ public static final String GEMINI_25_FLASH = "google/gemini-2.5-flash";
|
|
|
|
|
+ public static final String GEMINI_2_FLASH = "google/gemini-2.0-flash-001";
|
|
|
|
|
+ public static final String GPT_4O_MINI = "openai/gpt-4o-mini";
|
|
|
|
|
+ public static final String GPT_5_MINI = "openai/gpt-5-mini";
|
|
|
|
|
+ public static final String GPT_52 = "openai/gpt-5.2";
|
|
|
|
|
+ public static final String GPT_5 = "openai/gpt-5";
|
|
|
|
|
+ public static final String CLAUDE_OPUS_45 = "anthropic/claude-opus-4.5";
|
|
|
|
|
+ public static final String CLAUDE_SONNET_45 = "anthropic/claude-sonnet-4.5";
|
|
|
|
|
+ public static final String CLAUDE_HAIKU_45 = "anthropic/claude-haiku-4.5";
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|