fix(kieai): Gemini 读超时 90s + 响应 UTF-8 解码,解决 Read timed out 与中文乱码
Made-with: Cursor
This commit is contained in:
@@ -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<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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user