|
@@ -10,9 +10,11 @@ import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.http.HttpEntity;
|
|
import org.springframework.http.HttpEntity;
|
|
|
|
|
+import org.springframework.http.HttpStatus;
|
|
|
import org.springframework.http.ResponseEntity;
|
|
import org.springframework.http.ResponseEntity;
|
|
|
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
|
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
+import org.springframework.web.client.RestClientException;
|
|
|
import org.springframework.web.client.RestTemplate;
|
|
import org.springframework.web.client.RestTemplate;
|
|
|
import java.util.*;
|
|
import java.util.*;
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
@@ -119,6 +121,24 @@ public class AiGateway {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 判断 {@link RestClientException} 是否为上游外部服务拒绝类错误(429/401)。
|
|
|
|
|
+ * 契约 {@code langgraph.intent.errorPropagate}:LangGraph 上游 429/401(配额/凭据类
|
|
|
|
|
+ * 外部服务错误)必须向调用方传播,不得在网关内被吞成 null 伪装成普通失败。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return true=上游返回 429/401 的 {@link org.springframework.web.client.HttpClientErrorException},
|
|
|
|
|
+ * false=其他错误(超时、连接失败、其他 4xx/5xx 等,仍走 null 兜底)
|
|
|
|
|
+ */
|
|
|
|
|
+ private boolean isExternalServiceError(RestClientException e) {
|
|
|
|
|
+ if (!(e instanceof org.springframework.web.client.HttpClientErrorException)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ org.springframework.web.client.HttpClientErrorException httpErr =
|
|
|
|
|
+ (org.springframework.web.client.HttpClientErrorException) e;
|
|
|
|
|
+ return httpErr.getStatusCode() == HttpStatus.TOO_MANY_REQUESTS
|
|
|
|
|
+ || httpErr.getStatusCode() == HttpStatus.UNAUTHORIZED;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 调用 Python 推荐服务, 失败时返回 null (由调用方决定 fallback)
|
|
* 调用 Python 推荐服务, 失败时返回 null (由调用方决定 fallback)
|
|
|
*/
|
|
*/
|
|
@@ -173,7 +193,11 @@ public class AiGateway {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 调用 Python ChatAgent, 失败时返回 null
|
|
|
|
|
|
|
+ * 调用 Python ChatAgent。
|
|
|
|
|
+ * <p>LangGraph 上游 429/401(配额/凭据类外部服务错误)必须向外传播,不得静默吞成
|
|
|
|
|
+ * null——null 在调用方被当作「普通失败/可兜底」,会让配额错误伪装成普通失败。
|
|
|
|
|
+ * 契约见 {@code LangGraphErrorPropagateContractTest}(能力键 langgraph.intent.errorPropagate)。
|
|
|
|
|
+ * 其余异常(网络挂起/熔断/解析失败)仍返回 null,由调用方走 mock 兜底。
|
|
|
*/
|
|
*/
|
|
|
public Map<String, Object> chat(String query, Long userId, String conversationId,
|
|
public Map<String, Object> chat(String query, Long userId, String conversationId,
|
|
|
Map<String, Object> inputs) {
|
|
Map<String, Object> inputs) {
|
|
@@ -223,6 +247,15 @@ public class AiGateway {
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
return null;
|
|
return null;
|
|
|
|
|
+ } catch (RestClientException e) {
|
|
|
|
|
+ if (isExternalServiceError(e)) {
|
|
|
|
|
+ // 429/401 类上游拒绝必须传播,不得静默吞 null
|
|
|
|
|
+ recordFailure();
|
|
|
|
|
+ throw e;
|
|
|
|
|
+ }
|
|
|
|
|
+ log.warn("AiGateway chat 调用失败: {}", e.getMessage());
|
|
|
|
|
+ recordFailure();
|
|
|
|
|
+ return null;
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
log.warn("AiGateway chat 调用失败: {}", e.getMessage());
|
|
log.warn("AiGateway chat 调用失败: {}", e.getMessage());
|
|
|
recordFailure();
|
|
recordFailure();
|
|
@@ -281,6 +314,15 @@ public class AiGateway {
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
return null;
|
|
return null;
|
|
|
|
|
+ } catch (RestClientException e) {
|
|
|
|
|
+ if (isExternalServiceError(e)) {
|
|
|
|
|
+ // 429/401 类上游拒绝必须传播,与 chat() 传播语义一致
|
|
|
|
|
+ recordFailure();
|
|
|
|
|
+ throw e;
|
|
|
|
|
+ }
|
|
|
|
|
+ log.warn("AiGateway chatForAnalysis 调用失败: {}", e.getMessage());
|
|
|
|
|
+ recordFailure();
|
|
|
|
|
+ return null;
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
log.warn("AiGateway chatForAnalysis 调用失败: {}", e.getMessage());
|
|
log.warn("AiGateway chatForAnalysis 调用失败: {}", e.getMessage());
|
|
|
recordFailure();
|
|
recordFailure();
|