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