From 51d2016988fbf6743f302bf7f93468a8f6f46c90 Mon Sep 17 00:00:00 2001 From: scottpan <43121650@qq.com> Date: Tue, 3 Mar 2026 00:25:52 +0800 Subject: [PATCH] =?UTF-8?q?fix(kieai):=20Gemini=20=E8=AF=BB=E8=B6=85?= =?UTF-8?q?=E6=97=B6=2090s=20+=20=E5=93=8D=E5=BA=94=20UTF-8=20=E8=A7=A3?= =?UTF-8?q?=E7=A0=81=EF=BC=8C=E8=A7=A3=E5=86=B3=20Read=20timed=20out=20?= =?UTF-8?q?=E4=B8=8E=E4=B8=AD=E6=96=87=E4=B9=B1=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made-with: Cursor --- .../impl/tool/ToolKieAIServiceImpl.java | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) diff --git a/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolKieAIServiceImpl.java b/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolKieAIServiceImpl.java index 3fbecf7..2aab069 100644 --- a/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolKieAIServiceImpl.java +++ b/msh_crmeb_22/crmeb-service/src/main/java/com/zbkj/service/service/impl/tool/ToolKieAIServiceImpl.java @@ -7,15 +7,28 @@ import com.zbkj.common.model.article.Article; import com.zbkj.common.request.kieai.*; import com.zbkj.common.response.kieai.KieAICreateTaskResponse; import com.zbkj.common.response.kieai.KieAIQueryTaskResponse; +import com.zbkj.common.utils.SseEmitterUtil; import com.zbkj.service.dao.ArticleDao; import com.zbkj.service.helper.KieAIHelper; import com.zbkj.service.service.tool.ToolKieAIService; +import okhttp3.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.*; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import javax.annotation.Resource; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.TimeUnit; /** * KieAI 服务实现类(新版 API v1) @@ -29,6 +42,10 @@ public class ToolKieAIServiceImpl implements ToolKieAIService { private static final Logger logger = LoggerFactory.getLogger(ToolKieAIServiceImpl.class); + private static final String GEMINI_CHAT_PATH = "/gemini-2.5-flash/v1/chat/completions"; + private static final String BROWSER_USER_AGENT = + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"; + @Autowired private KieAIConfig config; @@ -38,6 +55,9 @@ public class ToolKieAIServiceImpl implements ToolKieAIService { @Resource private ArticleDao articleDao; + @Autowired(required = false) + private org.springframework.web.client.RestTemplate restTemplate; + @Override public KieAICreateTaskResponse createTextToImageTask(KieAINanoBananaRequest request) { logger.info("创建KieAI文本生成图像任务,提示词: {}", request.getInput().getPrompt()); @@ -271,4 +291,155 @@ public class ToolKieAIServiceImpl implements ToolKieAIService { return null; } } + + /** Gemini 非流式对话:读超时 90 秒,避免 KieAI 大模型响应慢导致 Read timed out */ + private static final int GEMINI_CHAT_READ_TIMEOUT_MS = 90_000; + private static final int GEMINI_CHAT_CONNECT_TIMEOUT_MS = 15_000; + + @Override + public Map geminiChat(KieAIGeminiChatRequest request) { + if (!helper.isApiTokenConfigured()) { + throw new RuntimeException("KieAI API Token 未配置"); + } + String url = config.getBaseUrl() + GEMINI_CHAT_PATH; + Map body = buildGeminiRequestBody(request, false); + HttpHeaders headers = helper.createHeaders(); + HttpEntity entity = new HttpEntity<>(helper.toJsonString(body), headers); + RestTemplate client = restTemplateWithLongTimeout(); + ResponseEntity response = client.exchange(url, HttpMethod.POST, entity, String.class); + if (response.getStatusCode() != HttpStatus.OK || response.getBody() == null) { + throw new RuntimeException("Gemini Chat 请求失败: " + response.getStatusCode()); + } + Map result = com.alibaba.fastjson.JSON.parseObject(response.getBody()); + if (result == null) { + throw new RuntimeException("Gemini Chat 响应解析失败"); + } + return result; + } + + private RestTemplate restTemplateWithLongTimeout() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setReadTimeout(GEMINI_CHAT_READ_TIMEOUT_MS); + factory.setConnectTimeout(GEMINI_CHAT_CONNECT_TIMEOUT_MS); + RestTemplate rt = new RestTemplate(factory); + rt.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); + return rt; + } + + @Override + public SseEmitter geminiChatStream(KieAIGeminiChatRequest request) { + if (!helper.isApiTokenConfigured()) { + throw new RuntimeException("KieAI API Token 未配置"); + } + SseEmitter emitter = new SseEmitter(120000L); + Map body = buildGeminiRequestBody(request, true); + String url = config.getBaseUrl() + GEMINI_CHAT_PATH; + OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(120, TimeUnit.SECONDS) + .writeTimeout(30, TimeUnit.SECONDS) + .build(); + RequestBody requestBody = RequestBody.create( + okhttp3.MediaType.parse("application/json; charset=utf-8"), + helper.toJsonString(body)); + Request okRequest = new Request.Builder() + .url(url) + .post(requestBody) + .addHeader("Authorization", "Bearer " + config.getApiToken()) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "text/event-stream") + .addHeader("User-Agent", BROWSER_USER_AGENT) + .build(); + try { + client.newCall(okRequest).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + logger.error("Gemini chat stream request failed", e); + SseEmitterUtil.completeWithError(emitter, e); + } + + @Override + public void onResponse(Call call, okhttp3.Response response) throws IOException { + if (!response.isSuccessful() || response.body() == null) { + SseEmitterUtil.completeWithError(emitter, new RuntimeException("Gemini stream failed: " + response.code())); + return; + } + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(response.body().byteStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + if (line.startsWith("data: ")) { + String json = line.substring(6).trim(); + if ("[DONE]".equals(json) || json.isEmpty()) { + break; + } + SseEmitterUtil.send(emitter, json); + } + } + SseEmitterUtil.complete(emitter); + } catch (Exception e) { + logger.error("Gemini chat stream read error", e); + SseEmitterUtil.completeWithError(emitter, e); + } + } + }); + } catch (Exception e) { + logger.error("Gemini chat stream error", e); + SseEmitterUtil.completeWithError(emitter, e); + } + return emitter; + } + + private Map buildGeminiRequestBody(KieAIGeminiChatRequest request, boolean stream) { + Map body = new HashMap<>(); + List> messagesOut = new ArrayList<>(); + for (KieAIGeminiChatRequest.Message msg : request.getMessages()) { + Map m = new HashMap<>(); + m.put("role", msg.getRole()); + Object content = msg.getContent(); + if (content instanceof String) { + List> parts = new ArrayList<>(); + Map textPart = new HashMap<>(); + textPart.put("type", "text"); + textPart.put("text", content); + parts.add(textPart); + m.put("content", parts); + } else if (content instanceof List) { + @SuppressWarnings("unchecked") + List list = (List) content; + List> parts = new ArrayList<>(); + for (Object item : list) { + if (item instanceof KieAIGeminiChatRequest.ContentItem) { + KieAIGeminiChatRequest.ContentItem ci = (KieAIGeminiChatRequest.ContentItem) item; + Map part = new HashMap<>(); + part.put("type", ci.getType()); + if ("text".equals(ci.getType())) { + part.put("text", ci.getText()); + } else if ("image_url".equals(ci.getType()) && ci.getImageUrl() != null) { + Map iu = new HashMap<>(); + iu.put("url", ci.getImageUrl().getUrl()); + part.put("image_url", iu); + } + parts.add(part); + } + } + m.put("content", parts); + } else { + m.put("content", content); + } + messagesOut.add(m); + } + body.put("messages", messagesOut); + body.put("stream", stream); + if (request.getIncludeThoughts() != null) { + body.put("include_thoughts", request.getIncludeThoughts()); + } + if (request.getTools() != null && !request.getTools().isEmpty()) { + body.put("tools", request.getTools()); + } + if (request.getResponseFormat() != null && !request.getResponseFormat().isEmpty()) { + body.put("response_format", request.getResponseFormat()); + } + return body; + } }