|
@@ -0,0 +1,152 @@
|
|
|
|
|
+
|
|
|
|
|
+package com.tzld.supply.api.google;
|
|
|
|
|
+
|
|
|
|
|
+import com.alibaba.fastjson.JSONArray;
|
|
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import okhttp3.*;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
+
|
|
|
|
|
+import javax.annotation.PostConstruct;
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.util.Objects;
|
|
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
+
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+@Service
|
|
|
|
|
+public class GeminiApiService {
|
|
|
|
|
+
|
|
|
|
|
+ private OkHttpClient client;
|
|
|
|
|
+ @Value("${airouter.gemini.apikey:}")
|
|
|
|
|
+ private String aiRouterApiKey;
|
|
|
|
|
+ /**
|
|
|
|
|
+ * AIRouter url
|
|
|
|
|
+ */
|
|
|
|
|
+ private static final String URL = "https://airouter.piaoquantv.com/v1beta/models/%s:generateContent?key=%s";
|
|
|
|
|
+
|
|
|
|
|
+ private static final MediaType JSON_MEDIA_TYPE = MediaType.get("application/json; charset=utf-8");
|
|
|
|
|
+
|
|
|
|
|
+ @PostConstruct
|
|
|
|
|
+ public void init() {
|
|
|
|
|
+ // 初始化 OkHttpClient (设置超时时间为 60 秒)
|
|
|
|
|
+ OkHttpClient.Builder builder = new OkHttpClient.Builder()
|
|
|
|
|
+ .connectTimeout(60, TimeUnit.SECONDS)
|
|
|
|
|
+ .readTimeout(600, TimeUnit.SECONDS)
|
|
|
|
|
+ .writeTimeout(600, TimeUnit.SECONDS);
|
|
|
|
|
+// Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 7890));
|
|
|
|
|
+// builder.proxy(proxy);
|
|
|
|
|
+ client = builder.build();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 调用 Gemini
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param systemPrompt 你的 Prompt(定义角色、规则、输出格式)
|
|
|
|
|
+ * @param input 输入内容
|
|
|
|
|
+ * @return 解析后的 JSONObject 结果,解析失败返回 null
|
|
|
|
|
+ */
|
|
|
|
|
+ public JSONObject callGemini(ModelEnum model, String systemPrompt, String input) throws IOException {
|
|
|
|
|
+ if (Objects.isNull(model)) {
|
|
|
|
|
+ model = ModelEnum.GEMINI_2_FLASH;
|
|
|
|
|
+ }
|
|
|
|
|
+ JSONObject requestBodyJson = new JSONObject();
|
|
|
|
|
+
|
|
|
|
|
+ // ==========================================
|
|
|
|
|
+ // 使用 System Instruction 隔离规则与输入
|
|
|
|
|
+ // 这会让大模型更严格地遵守你设定的“红灯标准”和“评分流程”
|
|
|
|
|
+ // ==========================================
|
|
|
|
|
+ JSONObject systemInstruction = new JSONObject();
|
|
|
|
|
+ JSONArray sysParts = new JSONArray();
|
|
|
|
|
+ JSONObject sysText = new JSONObject();
|
|
|
|
|
+ sysText.put("text", systemPrompt);
|
|
|
|
|
+ sysParts.add(sysText);
|
|
|
|
|
+ systemInstruction.put("parts", sysParts);
|
|
|
|
|
+ requestBodyJson.put("system_instruction", systemInstruction);
|
|
|
|
|
+
|
|
|
|
|
+ // ==========================================
|
|
|
|
|
+ // User Content 仅放入待处理的目标内容
|
|
|
|
|
+ // ==========================================
|
|
|
|
|
+ JSONArray contentsArray = new JSONArray();
|
|
|
|
|
+ JSONObject userContent = new JSONObject();
|
|
|
|
|
+ userContent.put("role", "user");
|
|
|
|
|
+ JSONArray userParts = new JSONArray();
|
|
|
|
|
+ JSONObject userText = new JSONObject();
|
|
|
|
|
+ userText.put("text", input != null ? input : "");
|
|
|
|
|
+ userParts.add(userText);
|
|
|
|
|
+ userContent.put("parts", userParts);
|
|
|
|
|
+ contentsArray.add(userContent);
|
|
|
|
|
+ requestBodyJson.put("contents", contentsArray);
|
|
|
|
|
+
|
|
|
|
|
+ // ==========================================
|
|
|
|
|
+ // 强制开启 JSON 模式 (JSON Mode)
|
|
|
|
|
+ // Gemini 官方特性,强制返回标准的 JSON 格式
|
|
|
|
|
+ // ==========================================
|
|
|
|
|
+ JSONObject generationConfig = new JSONObject();
|
|
|
|
|
+ generationConfig.put("responseMimeType", "application/json");
|
|
|
|
|
+ requestBodyJson.put("generationConfig", generationConfig);
|
|
|
|
|
+
|
|
|
|
|
+ log.info(requestBodyJson.toJSONString());
|
|
|
|
|
+ // 构建并发送 HTTP 请求
|
|
|
|
|
+ String url = String.format(URL, model.getModel(), aiRouterApiKey);
|
|
|
|
|
+ RequestBody body = RequestBody.create(JSON_MEDIA_TYPE, requestBodyJson.toJSONString());
|
|
|
|
|
+ Request request = new Request.Builder()
|
|
|
|
|
+ .url(url)
|
|
|
|
|
+ .post(body)
|
|
|
|
|
+ .build();
|
|
|
|
|
+
|
|
|
|
|
+ try (Response response = client.newCall(request).execute()) {
|
|
|
|
|
+ if (!response.isSuccessful()) {
|
|
|
|
|
+ String errorBody = response.body() != null ? response.body().string() : "无";
|
|
|
|
|
+ throw new IOException("请求失败!HTTP: " + response.code() + " | 详情: " + errorBody);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String responseBodyString = response.body().string();
|
|
|
|
|
+ log.info(responseBodyString);
|
|
|
|
|
+ // 提取大模型的回复文本
|
|
|
|
|
+ JSONObject responseJson = JSONObject.parseObject(responseBodyString);
|
|
|
|
|
+ JSONArray candidates = responseJson.getJSONArray("candidates");
|
|
|
|
|
+
|
|
|
|
|
+ if (candidates != null && !candidates.isEmpty()) {
|
|
|
|
|
+ JSONObject firstCandidate = candidates.getJSONObject(0);
|
|
|
|
|
+ JSONObject content = firstCandidate.getJSONObject("content");
|
|
|
|
|
+ JSONArray parts = content.getJSONArray("parts");
|
|
|
|
|
+
|
|
|
|
|
+ if (parts != null && !parts.isEmpty()) {
|
|
|
|
|
+ String rawLlmOutput = parts.getJSONObject(0).getString("text");
|
|
|
|
|
+ // ==========================================
|
|
|
|
|
+ // 防御性清洗并转化为 JSONObject
|
|
|
|
|
+ // 应对极少数情况下大模型仍然返回 Markdown 格式的情况
|
|
|
|
|
+ // ==========================================
|
|
|
|
|
+ String cleanJsonStr = cleanMarkdown(rawLlmOutput);
|
|
|
|
|
+ try {
|
|
|
|
|
+ return JSONObject.parseObject(cleanJsonStr);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ System.err.println("大模型返回的内容不是有效的 JSON: " + rawLlmOutput);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ throw new IOException("未在响应中找到有效的内容 (candidates)");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 兜底工具:移除大模型喜欢附带的 ```json 和 ``` 标记
|
|
|
|
|
+ */
|
|
|
|
|
+ private String cleanMarkdown(String raw) {
|
|
|
|
|
+ if (raw == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ String cleaned = raw.trim();
|
|
|
|
|
+ if (cleaned.startsWith("```json")) {
|
|
|
|
|
+ cleaned = cleaned.substring(7);
|
|
|
|
|
+ } else if (cleaned.startsWith("```")) {
|
|
|
|
|
+ cleaned = cleaned.substring(3);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (cleaned.endsWith("```")) {
|
|
|
|
|
+ cleaned = cleaned.substring(0, cleaned.length() - 3);
|
|
|
|
|
+ }
|
|
|
|
|
+ return cleaned.trim();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|