From 1dcfa3702505f0c431757312b5304531029f90f6 Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期四, 09 四月 2026 18:57:38 +0800
Subject: [PATCH] #getter$摘出entity
---
rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiParamValidationSupport.java | 320 +++++++++++++++++++++++++++++++++++++++++++++-------
1 files changed, 273 insertions(+), 47 deletions(-)
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiParamValidationSupport.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiParamValidationSupport.java
index 9312751..d5b3c1a 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiParamValidationSupport.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiParamValidationSupport.java
@@ -1,106 +1,332 @@
package com.vincent.rsf.server.ai.service.impl;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.vincent.rsf.framework.exception.CoolException;
import com.vincent.rsf.server.ai.config.AiDefaults;
import com.vincent.rsf.server.ai.dto.AiParamValidateResultDto;
import com.vincent.rsf.server.ai.entity.AiParam;
-import io.micrometer.observation.ObservationRegistry;
+import lombok.extern.slf4j.Slf4j;
import lombok.RequiredArgsConstructor;
-import org.springframework.ai.chat.messages.UserMessage;
-import org.springframework.ai.chat.model.ChatResponse;
-import org.springframework.ai.chat.prompt.Prompt;
-import org.springframework.ai.model.tool.DefaultToolCallingManager;
-import org.springframework.ai.model.tool.ToolCallingManager;
-import org.springframework.ai.openai.OpenAiChatModel;
-import org.springframework.ai.openai.OpenAiChatOptions;
-import org.springframework.ai.openai.api.OpenAiApi;
-import org.springframework.ai.tool.execution.DefaultToolExecutionExceptionProcessor;
-import org.springframework.ai.tool.resolution.SpringBeanToolCallbackResolver;
-import org.springframework.ai.util.json.schema.SchemaType;
-import org.springframework.context.support.GenericApplicationContext;
-import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Component;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClient;
-import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.client.RestClientResponseException;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Date;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
@Component
+@Slf4j
@RequiredArgsConstructor
public class AiParamValidationSupport {
- private final GenericApplicationContext applicationContext;
- private final ObservationRegistry observationRegistry;
+ private static final String PROBE_MESSAGE = "璇峰洖澶� OK";
+ private static final int MAX_DETAIL_TEXT_LENGTH = 8000;
+ private final ObjectMapper objectMapper;
+
+ /**
+ * 瀵逛竴浠� AI 鍙傛暟鑽夌鍋氱湡瀹炶繛閫氭�ф牎楠屻��
+ * 鏍¢獙鏂瑰紡涓嶆槸绠�鍗曞垽鏂瓧娈甸潪绌猴紝鑰屾槸鐩存帴鏋勯�犺亰澶╂ā鍨嬪苟鍙戣捣涓�娆℃渶灏忔帰娴嬭皟鐢紝
+ * 鐢ㄨ繑鍥炵粨鏋滃拰鑰楁椂鐢熸垚鍓嶇鍙睍绀虹殑鏍¢獙鎶ュ憡銆�
+ */
public AiParamValidateResultDto validate(AiParam aiParam) {
long startedAt = System.currentTimeMillis();
+ ValidationRequestContext context = buildValidationRequestContext(aiParam);
try {
- OpenAiChatModel chatModel = createChatModel(aiParam);
- ChatResponse response = chatModel.call(new Prompt(List.of(new UserMessage("璇峰洖澶� OK"))));
- if (response == null || response.getResult() == null || response.getResult().getOutput() == null
- || !StringUtils.hasText(response.getResult().getOutput().getText())) {
+ ValidationResponseSnapshot responseSnapshot = executeValidation(context);
+ String assistantText = extractAssistantText(responseSnapshot.responseBody());
+ if (!StringUtils.hasText(assistantText)) {
throw new CoolException("妯″瀷宸茶繛鎺ワ紝浣嗘湭杩斿洖鏈夋晥鍝嶅簲");
}
long elapsedMs = System.currentTimeMillis() - startedAt;
+ String detail = buildValidationDetail(
+ context.requestUrl(),
+ context.requestBody(),
+ responseSnapshot.statusCode(),
+ responseSnapshot.responseBody(),
+ assistantText,
+ null
+ );
+ log.info("AI 鍙傛暟鑽夌鏍¢獙鎴愬姛, model={}, requestUrl={}, responseStatus={}",
+ aiParam.getModel(), context.requestUrl(), responseSnapshot.statusCode());
return AiParamValidateResultDto.builder()
.status(AiDefaults.PARAM_VALIDATE_VALID)
.message("妯″瀷杩為�氭垚鍔�")
+ .detail(detail)
.model(aiParam.getModel())
.elapsedMs(elapsedMs)
.validatedAt(formatDate(new Date()))
+ .requestUrl(context.requestUrl())
+ .requestBody(context.requestBody())
+ .responseStatus(responseSnapshot.statusCode())
+ .responseBody(abbreviate(responseSnapshot.responseBody()))
.build();
} catch (Exception e) {
long elapsedMs = System.currentTimeMillis() - startedAt;
- String message = e instanceof CoolException ? e.getMessage() : "妯″瀷楠岃瘉澶辫触: " + e.getMessage();
+ ValidationErrorSnapshot errorSnapshot = extractErrorSnapshot(e);
+ String message = e instanceof CoolException
+ ? e.getMessage()
+ : "妯″瀷楠岃瘉澶辫触: " + firstNonBlank(
+ extractResponseErrorMessage(errorSnapshot.responseBody()),
+ errorSnapshot.errorMessage(),
+ e.getMessage());
+ String detail = buildValidationDetail(
+ context.requestUrl(),
+ context.requestBody(),
+ errorSnapshot.statusCode(),
+ errorSnapshot.responseBody(),
+ null,
+ e
+ );
+ log.warn("AI 鍙傛暟鑽夌鏍¢獙澶辫触, model={}, requestUrl={}, responseStatus={}, error={}",
+ aiParam.getModel(), context.requestUrl(), errorSnapshot.statusCode(), errorSnapshot.errorMessage(), e);
return AiParamValidateResultDto.builder()
.status(AiDefaults.PARAM_VALIDATE_INVALID)
.message(message)
+ .detail(detail)
.model(aiParam.getModel())
.elapsedMs(elapsedMs)
.validatedAt(formatDate(new Date()))
+ .requestUrl(context.requestUrl())
+ .requestBody(context.requestBody())
+ .responseStatus(errorSnapshot.statusCode())
+ .responseBody(abbreviate(errorSnapshot.responseBody()))
.build();
}
}
- private OpenAiChatModel createChatModel(AiParam aiParam) {
- OpenAiApi openAiApi = buildOpenAiApi(aiParam);
- ToolCallingManager toolCallingManager = DefaultToolCallingManager.builder()
- .observationRegistry(observationRegistry)
- .toolCallbackResolver(new SpringBeanToolCallbackResolver(applicationContext, SchemaType.OPEN_API_SCHEMA))
- .toolExecutionExceptionProcessor(new DefaultToolExecutionExceptionProcessor(false))
- .build();
- return new OpenAiChatModel(
- openAiApi,
- OpenAiChatOptions.builder()
- .model(aiParam.getModel())
- .temperature(aiParam.getTemperature())
- .topP(aiParam.getTopP())
- .maxTokens(aiParam.getMaxTokens())
- .streamUsage(true)
- .build(),
- toolCallingManager,
- org.springframework.retry.support.RetryTemplate.builder().maxAttempts(1).build(),
- observationRegistry
+ private ValidationRequestContext buildValidationRequestContext(AiParam aiParam) {
+ AiOpenAiApiSupport.EndpointConfig endpointConfig = AiOpenAiApiSupport.resolveEndpointConfig(aiParam.getBaseUrl());
+ String requestUrl = endpointConfig.baseUrl() + endpointConfig.completionsPath();
+ Map<String, Object> requestBody = new LinkedHashMap<>();
+ requestBody.put("model", aiParam.getModel());
+ requestBody.put("messages", List.of(Map.of("role", "user", "content", PROBE_MESSAGE)));
+ requestBody.put("stream", false);
+ if (aiParam.getTemperature() != null) {
+ requestBody.put("temperature", aiParam.getTemperature());
+ }
+ if (aiParam.getTopP() != null) {
+ requestBody.put("top_p", aiParam.getTopP());
+ }
+ if (aiParam.getMaxTokens() != null) {
+ requestBody.put("max_tokens", aiParam.getMaxTokens());
+ }
+ return new ValidationRequestContext(
+ buildRestClient(aiParam),
+ requestUrl,
+ requestBody,
+ toPrettyJson(requestBody)
);
}
- private OpenAiApi buildOpenAiApi(AiParam aiParam) {
+ private RestClient buildRestClient(AiParam aiParam) {
int timeoutMs = aiParam.getTimeoutMs() == null ? AiDefaults.DEFAULT_TIMEOUT_MS : aiParam.getTimeoutMs();
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(timeoutMs);
requestFactory.setReadTimeout(timeoutMs);
- return OpenAiApi.builder()
- .baseUrl(aiParam.getBaseUrl())
- .apiKey(aiParam.getApiKey())
- .restClientBuilder(RestClient.builder().requestFactory(requestFactory))
- .webClientBuilder(WebClient.builder())
+ return RestClient.builder()
+ .requestFactory(requestFactory)
+ .defaultHeader("Authorization", "Bearer " + aiParam.getApiKey())
.build();
}
+ private ValidationResponseSnapshot executeValidation(ValidationRequestContext context) {
+ ResponseEntity<String> responseEntity = context.restClient().post()
+ .uri(context.requestUrl())
+ .contentType(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON)
+ .body(context.requestBodyMap())
+ .retrieve()
+ .toEntity(String.class);
+ return new ValidationResponseSnapshot(
+ responseEntity.getStatusCode().value(),
+ responseEntity.getBody()
+ );
+ }
+
+ private String extractAssistantText(String responseBody) {
+ if (!StringUtils.hasText(responseBody)) {
+ return "";
+ }
+ try {
+ JsonNode root = objectMapper.readTree(responseBody);
+ List<JsonNode> candidates = List.of(
+ root.at("/choices/0/message/content"),
+ root.at("/choices/0/text"),
+ root.at("/output_text"),
+ root.at("/output/0/content/0/text")
+ );
+ for (JsonNode candidate : candidates) {
+ String text = flattenText(candidate);
+ if (StringUtils.hasText(text)) {
+ return text;
+ }
+ }
+ } catch (Exception e) {
+ log.debug("瑙f瀽 AI 鍙傛暟鑽夌鏍¢獙鍝嶅簲澶辫触: {}", e.getMessage());
+ }
+ return "";
+ }
+
+ private String flattenText(JsonNode node) {
+ if (node == null || node.isMissingNode() || node.isNull()) {
+ return "";
+ }
+ if (node.isTextual()) {
+ return node.asText();
+ }
+ if (node.isArray()) {
+ List<String> parts = new ArrayList<>();
+ for (JsonNode item : node) {
+ String text = flattenText(item);
+ if (StringUtils.hasText(text)) {
+ parts.add(text);
+ }
+ }
+ return String.join("\n", parts).trim();
+ }
+ if (node.isObject()) {
+ String text = flattenText(node.get("text"));
+ if (StringUtils.hasText(text)) {
+ return text;
+ }
+ text = flattenText(node.get("content"));
+ if (StringUtils.hasText(text)) {
+ return text;
+ }
+ }
+ return "";
+ }
+
+ private ValidationErrorSnapshot extractErrorSnapshot(Throwable throwable) {
+ RestClientResponseException responseException = findResponseException(throwable);
+ if (responseException != null) {
+ return new ValidationErrorSnapshot(
+ responseException.getStatusCode().value(),
+ responseException.getResponseBodyAsString(),
+ rootMessage(throwable)
+ );
+ }
+ return new ValidationErrorSnapshot(null, null, rootMessage(throwable));
+ }
+
+ private RestClientResponseException findResponseException(Throwable throwable) {
+ Throwable current = throwable;
+ while (current != null) {
+ if (current instanceof RestClientResponseException responseException) {
+ return responseException;
+ }
+ current = current.getCause();
+ }
+ return null;
+ }
+
+ private String extractResponseErrorMessage(String responseBody) {
+ if (!StringUtils.hasText(responseBody)) {
+ return "";
+ }
+ try {
+ JsonNode root = objectMapper.readTree(responseBody);
+ List<JsonNode> candidates = List.of(
+ root.at("/error/message"),
+ root.at("/message"),
+ root.at("/detail")
+ );
+ for (JsonNode candidate : candidates) {
+ String text = flattenText(candidate);
+ if (StringUtils.hasText(text)) {
+ return text;
+ }
+ }
+ } catch (Exception e) {
+ log.debug("瑙f瀽 AI 鍙傛暟鑽夌鏍¢獙閿欒鍝嶅簲澶辫触: {}", e.getMessage());
+ }
+ return "";
+ }
+
+ private String buildValidationDetail(String requestUrl,
+ String requestBody,
+ Integer responseStatus,
+ String responseBody,
+ String assistantText,
+ Throwable throwable) {
+ List<String> sections = new ArrayList<>();
+ sections.add("璇锋眰 URL:\n" + defaultText(requestUrl));
+ sections.add("璇锋眰浣�:\n" + defaultText(requestBody));
+ sections.add("HTTP 鐘舵��:\n" + (responseStatus == null ? "--" : responseStatus));
+ if (StringUtils.hasText(assistantText)) {
+ sections.add("瑙f瀽缁撴灉:\n" + abbreviate(assistantText));
+ }
+ sections.add("鍝嶅簲缁撴灉:\n" + defaultText(abbreviate(responseBody)));
+ if (throwable != null) {
+ sections.add("寮傚父绫诲瀷:\n" + throwable.getClass().getName());
+ sections.add("寮傚父淇℃伅:\n" + defaultText(rootMessage(throwable)));
+ }
+ return String.join("\n\n", sections);
+ }
+
+ private String toPrettyJson(Object value) {
+ try {
+ return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(value);
+ } catch (Exception e) {
+ return String.valueOf(value);
+ }
+ }
+
+ private String rootMessage(Throwable throwable) {
+ Throwable current = throwable;
+ while (current != null && current.getCause() != null) {
+ current = current.getCause();
+ }
+ return current == null ? "" : current.getMessage();
+ }
+
+ private String defaultText(String value) {
+ return StringUtils.hasText(value) ? value : "--";
+ }
+
+ private String firstNonBlank(String... values) {
+ for (String value : values) {
+ if (StringUtils.hasText(value)) {
+ return value;
+ }
+ }
+ return "";
+ }
+
+ private String abbreviate(String value) {
+ if (!StringUtils.hasText(value)) {
+ return value;
+ }
+ if (value.length() <= MAX_DETAIL_TEXT_LENGTH) {
+ return value;
+ }
+ return value.substring(0, MAX_DETAIL_TEXT_LENGTH) + "\n...锛堝凡鎴柇锛�";
+ }
+
private String formatDate(Date date) {
+ /** 缁熶竴杈撳嚭缁欏墠绔殑鏍¢獙鏃堕棿鏍煎紡銆� */
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
}
+
+ private record ValidationRequestContext(RestClient restClient,
+ String requestUrl,
+ Map<String, Object> requestBodyMap,
+ String requestBody) {
+ }
+
+ private record ValidationResponseSnapshot(int statusCode, String responseBody) {
+ }
+
+ private record ValidationErrorSnapshot(Integer statusCode, String responseBody, String errorMessage) {
+ }
}
--
Gitblit v1.9.1