fix(kieai): Gemini 读超时 90s + 响应 UTF-8 解码,解决 Read timed out 与中文乱码

Made-with: Cursor
This commit is contained in:
2026-03-03 00:25:52 +08:00
parent 8ba027b194
commit 51d2016988

View File

@@ -7,15 +7,28 @@ import com.zbkj.common.model.article.Article;
import com.zbkj.common.request.kieai.*; import com.zbkj.common.request.kieai.*;
import com.zbkj.common.response.kieai.KieAICreateTaskResponse; import com.zbkj.common.response.kieai.KieAICreateTaskResponse;
import com.zbkj.common.response.kieai.KieAIQueryTaskResponse; import com.zbkj.common.response.kieai.KieAIQueryTaskResponse;
import com.zbkj.common.utils.SseEmitterUtil;
import com.zbkj.service.dao.ArticleDao; import com.zbkj.service.dao.ArticleDao;
import com.zbkj.service.helper.KieAIHelper; import com.zbkj.service.helper.KieAIHelper;
import com.zbkj.service.service.tool.ToolKieAIService; import com.zbkj.service.service.tool.ToolKieAIService;
import okhttp3.*;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; 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.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.annotation.Resource; 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 * KieAI 服务实现类(新版 API v1
@@ -29,6 +42,10 @@ public class ToolKieAIServiceImpl implements ToolKieAIService {
private static final Logger logger = LoggerFactory.getLogger(ToolKieAIServiceImpl.class); 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 @Autowired
private KieAIConfig config; private KieAIConfig config;
@@ -38,6 +55,9 @@ public class ToolKieAIServiceImpl implements ToolKieAIService {
@Resource @Resource
private ArticleDao articleDao; private ArticleDao articleDao;
@Autowired(required = false)
private org.springframework.web.client.RestTemplate restTemplate;
@Override @Override
public KieAICreateTaskResponse createTextToImageTask(KieAINanoBananaRequest request) { public KieAICreateTaskResponse createTextToImageTask(KieAINanoBananaRequest request) {
logger.info("创建KieAI文本生成图像任务提示词: {}", request.getInput().getPrompt()); logger.info("创建KieAI文本生成图像任务提示词: {}", request.getInput().getPrompt());
@@ -271,4 +291,155 @@ public class ToolKieAIServiceImpl implements ToolKieAIService {
return null; 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<String, Object> geminiChat(KieAIGeminiChatRequest request) {
if (!helper.isApiTokenConfigured()) {
throw new RuntimeException("KieAI API Token 未配置");
}
String url = config.getBaseUrl() + GEMINI_CHAT_PATH;
Map<String, Object> body = buildGeminiRequestBody(request, false);
HttpHeaders headers = helper.createHeaders();
HttpEntity<String> entity = new HttpEntity<>(helper.toJsonString(body), headers);
RestTemplate client = restTemplateWithLongTimeout();
ResponseEntity<String> 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<String, Object> 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<String, Object> 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<String, Object> buildGeminiRequestBody(KieAIGeminiChatRequest request, boolean stream) {
Map<String, Object> body = new HashMap<>();
List<Map<String, Object>> messagesOut = new ArrayList<>();
for (KieAIGeminiChatRequest.Message msg : request.getMessages()) {
Map<String, Object> m = new HashMap<>();
m.put("role", msg.getRole());
Object content = msg.getContent();
if (content instanceof String) {
List<Map<String, Object>> parts = new ArrayList<>();
Map<String, Object> 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<Map<String, Object>> parts = new ArrayList<>();
for (Object item : list) {
if (item instanceof KieAIGeminiChatRequest.ContentItem) {
KieAIGeminiChatRequest.ContentItem ci = (KieAIGeminiChatRequest.ContentItem) item;
Map<String, Object> 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<String, Object> 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;
}
} }