| | |
| | | import lombok.RequiredArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.StringUtils; |
| | | import org.springframework.web.reactive.function.client.WebClientResponseException; |
| | | import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; |
| | | |
| | | import java.time.Instant; |
| | |
| | | private final AiStreamStateStore aiStreamStateStore; |
| | | |
| | | public AiChatException buildAiException(String code, AiErrorCategory category, String stage, String message, Throwable cause) { |
| | | return new AiChatException(code, category, stage, message, cause); |
| | | return new AiChatException(code, category, stage, resolveExceptionMessage(message, cause), cause); |
| | | } |
| | | |
| | | public void handleStreamFailure(SseEmitter emitter, String requestId, Long sessionId, String model, long startedAt, |
| | |
| | | toolFailureCount |
| | | ); |
| | | aiStreamStateStore.markStreamState(requestId, tenantId, userId, sessionId, promptCode, "ABORTED", exception.getMessage()); |
| | | emitter.completeWithError(exception); |
| | | emitter.complete(); |
| | | return; |
| | | } |
| | | log.error("AI chat failed, requestId={}, sessionId={}, category={}, stage={}, message={}", |
| | |
| | | toolFailureCount |
| | | ); |
| | | aiStreamStateStore.markStreamState(requestId, tenantId, userId, sessionId, promptCode, "FAILED", exception.getMessage()); |
| | | emitter.completeWithError(exception); |
| | | emitter.complete(); |
| | | } |
| | | |
| | | private String resolveExceptionMessage(String message, Throwable cause) { |
| | | String upstreamMessage = extractUpstreamResponseBody(cause); |
| | | if (StringUtils.hasText(upstreamMessage)) { |
| | | return truncateMessage(upstreamMessage); |
| | | } |
| | | return truncateMessage(message); |
| | | } |
| | | |
| | | private String extractUpstreamResponseBody(Throwable throwable) { |
| | | Throwable current = throwable; |
| | | while (current != null) { |
| | | if (current instanceof WebClientResponseException webClientResponseException) { |
| | | String responseBody = webClientResponseException.getResponseBodyAsString(); |
| | | if (StringUtils.hasText(responseBody)) { |
| | | return responseBody.replace('\n', ' ').replace('\r', ' ').trim(); |
| | | } |
| | | } |
| | | current = current.getCause(); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private String truncateMessage(String message) { |
| | | if (!StringUtils.hasText(message)) { |
| | | return message; |
| | | } |
| | | return message.length() > 900 ? message.substring(0, 900) : message; |
| | | } |
| | | |
| | | private boolean isClientAbortException(Throwable throwable) { |