Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/chuanshuozho_aiexample.json
T

1 line
46 KiB
JSON

{"repo": "chuanshuozho/aiexample", "files": {"Ai_example/AI_example/src/main/resources/application.yml": {"286793f3eba1813d4833e209932777a80c02e1b3": {"t": 1785745259.0493386, "n": 2812, "h": "2d31a22d27a95345", "c": "spring:\n application:\n name: AI_example\n datasource:\n url: ${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/ai_chat?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true}\n username: ${SPRING_DATASOURCE_USERNAME:root}\n password: ${SPRING_DATASOURCE_PASSWORD:121506}\n driver-class-name: com.mysql.cj.jdbc.Driver\n jpa:\n hibernate:\n ddl-auto: update\n show-sql: true\n properties:\n hibernate:\n format_sql: true\n servlet:\n multipart:\n max-file-size: 10MB\n max-request-size: 15MB\n\n # Spring AI Configuration (using OpenAI-compatible API with DeepSeek)\n ai:\n openai:\n api-key: ${SPRING_AI_OPENAI_API_KEY:${DEEPSEEK_API_KEY:sk-your-api-key}}\n base-url: ${SPRING_AI_OPENAI_BASE_URL:https://api.deepseek.com}\n chat:\n enabled: true\n options:\n model: deepseek-chat\n temperature: 0.7\n embedding:\n enabled: true\n options:\n model: deepseek-embedding\n\nserver:\n port: 8081\n\n# JVM Memory Settings (add to startup command: -Xmx512m -Xms256m)\n# For large documents, consider: -Xmx1g -Xms512m\n\n# Actuator Configuration for health checks\nmanagement:\n endpoints:\n web:\n exposure:\n include: health,info\n endpoint:\n health:\n show-details: when-authorized\n\n# Knowledge Base Configuration\napp:\n knowledge:\n max-file-size: ${APP_KNOWLEDGE_MAX_FILE_SIZE:5242880} # 5MB,限制文件大小防止内存溢出\n chunk-size: ${APP_KNOWLEDGE_CHUNK_SIZE:500}\n chunk-overlap: ${APP_KNOWLEDGE_CHUNK_OVERLAP:50}\n batch-size: ${APP_KNOWLEDGE_BATCH_SIZE:10} # 分批处理大小\n embedding-cache-size: 1000\n retrieval:\n top-k: ${APP_KNOWLEDGE_RETRIEVAL_TOP_K:3}\n similarity-threshold: ${APP_KNOWLEDGE_RETRIEVAL_SIMILARITY_THRESHOLD:0.5}\n max-context-tokens: ${APP_KNOWLEDGE_MAX_CONTEXT_TOKENS:1000} # 最大上下文 Token 数(降低以减少消耗)\n storage-path: ${APP_KNOWLEDGE_STORAGE_PATH:${user.home}/ai-knowledge/uploads}\n # OCR 配置\n ocr:\n enabled: ${APP_KNOWLEDGE_OCR_ENABLED:true} # 启用 OCR\n datapath: ${APP_KNOWLEDGE_OCR_DATAPATH:} # Tesseract 数据路径,留空则自动检测\n language: ${APP_KNOWLEDGE_OCR_LANGUAGE:chi_sim+eng} # 中文+英文\n\n# Conversation Distillation Configuration\n distillation:\n enabled: ${APP_DISTILLATION_ENABLED:true}\n token-threshold: ${APP_DISTILLATION_TOKEN_THRESHOLD:4000} # Token threshold to trigger distillation\n preserve-recent-pairs: ${APP_DISTILLATION_PRESERVE_RECENT_PAIRS:3} # Number of recent Q&A pairs to preserve\n max-summary-tokens: ${APP_DISTILLATION_MAX_SUMMARY_TOKENS:500} # Max tokens for generated summary\n summary-model: ${APP_DISTILLATION_SUMMARY_MODEL:deepseek-chat} # Model to use for summarization\n\n# JVM Memory Optimization (add to startup command)\n# java -Xms512m -Xmx2048m -XX:+UseG1GC -jar your-application.jar\n"}, "cddb06f263690731bacdf28922dcd2e840309c0b": {"t": 1785749339.5280268, "n": 2812, "h": "2d31a22d27a95345", "c": "spring:\n application:\n name: AI_example\n datasource:\n url: ${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/ai_chat?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true}\n username: ${SPRING_DATASOURCE_USERNAME:root}\n password: ${SPRING_DATASOURCE_PASSWORD:121506}\n driver-class-name: com.mysql.cj.jdbc.Driver\n jpa:\n hibernate:\n ddl-auto: update\n show-sql: true\n properties:\n hibernate:\n format_sql: true\n servlet:\n multipart:\n max-file-size: 10MB\n max-request-size: 15MB\n\n # Spring AI Configuration (using OpenAI-compatible API with DeepSeek)\n ai:\n openai:\n api-key: ${SPRING_AI_OPENAI_API_KEY:${DEEPSEEK_API_KEY:sk-your-api-key}}\n base-url: ${SPRING_AI_OPENAI_BASE_URL:https://api.deepseek.com}\n chat:\n enabled: true\n options:\n model: deepseek-chat\n temperature: 0.7\n embedding:\n enabled: true\n options:\n model: deepseek-embedding\n\nserver:\n port: 8081\n\n# JVM Memory Settings (add to startup command: -Xmx512m -Xms256m)\n# For large documents, consider: -Xmx1g -Xms512m\n\n# Actuator Configuration for health checks\nmanagement:\n endpoints:\n web:\n exposure:\n include: health,info\n endpoint:\n health:\n show-details: when-authorized\n\n# Knowledge Base Configuration\napp:\n knowledge:\n max-file-size: ${APP_KNOWLEDGE_MAX_FILE_SIZE:5242880} # 5MB,限制文件大小防止内存溢出\n chunk-size: ${APP_KNOWLEDGE_CHUNK_SIZE:500}\n chunk-overlap: ${APP_KNOWLEDGE_CHUNK_OVERLAP:50}\n batch-size: ${APP_KNOWLEDGE_BATCH_SIZE:10} # 分批处理大小\n embedding-cache-size: 1000\n retrieval:\n top-k: ${APP_KNOWLEDGE_RETRIEVAL_TOP_K:3}\n similarity-threshold: ${APP_KNOWLEDGE_RETRIEVAL_SIMILARITY_THRESHOLD:0.5}\n max-context-tokens: ${APP_KNOWLEDGE_MAX_CONTEXT_TOKENS:1000} # 最大上下文 Token 数(降低以减少消耗)\n storage-path: ${APP_KNOWLEDGE_STORAGE_PATH:${user.home}/ai-knowledge/uploads}\n # OCR 配置\n ocr:\n enabled: ${APP_KNOWLEDGE_OCR_ENABLED:true} # 启用 OCR\n datapath: ${APP_KNOWLEDGE_OCR_DATAPATH:} # Tesseract 数据路径,留空则自动检测\n language: ${APP_KNOWLEDGE_OCR_LANGUAGE:chi_sim+eng} # 中文+英文\n\n# Conversation Distillation Configuration\n distillation:\n enabled: ${APP_DISTILLATION_ENABLED:true}\n token-threshold: ${APP_DISTILLATION_TOKEN_THRESHOLD:4000} # Token threshold to trigger distillation\n preserve-recent-pairs: ${APP_DISTILLATION_PRESERVE_RECENT_PAIRS:3} # Number of recent Q&A pairs to preserve\n max-summary-tokens: ${APP_DISTILLATION_MAX_SUMMARY_TOKENS:500} # Max tokens for generated summary\n summary-model: ${APP_DISTILLATION_SUMMARY_MODEL:deepseek-chat} # Model to use for summarization\n\n# JVM Memory Optimization (add to startup command)\n# java -Xms512m -Xmx2048m -XX:+UseG1GC -jar your-application.jar\n"}, "f375b7749f9725b7aed3d3da7777eabb0f9ad46c": {"t": 1785749340.5785015, "n": 2285, "h": "c4b6e4c2991fa575", "c": "spring:\n application:\n name: AI_example\n datasource:\n url: ${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/ai_chat?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true}\n username: ${SPRING_DATASOURCE_USERNAME:root}\n password: ${SPRING_DATASOURCE_PASSWORD:121506}\n driver-class-name: com.mysql.cj.jdbc.Driver\n jpa:\n hibernate:\n ddl-auto: update\n show-sql: true\n properties:\n hibernate:\n format_sql: true\n servlet:\n multipart:\n max-file-size: 10MB\n max-request-size: 15MB\n\n # Spring AI Configuration (using OpenAI-compatible API with DeepSeek)\n ai:\n openai:\n api-key: ${SPRING_AI_OPENAI_API_KEY:${DEEPSEEK_API_KEY:sk-your-api-key}}\n base-url: ${SPRING_AI_OPENAI_BASE_URL:https://api.deepseek.com}\n chat:\n enabled: true\n options:\n model: deepseek-chat\n temperature: 0.7\n embedding:\n enabled: true\n options:\n model: deepseek-embedding\n\nserver:\n port: 8081\n\n# JVM Memory Settings (add to startup command: -Xmx512m -Xms256m)\n# For large documents, consider: -Xmx1g -Xms512m\n\n# Actuator Configuration for health checks\nmanagement:\n endpoints:\n web:\n exposure:\n include: health,info\n endpoint:\n health:\n show-details: when-authorized\n\n# Knowledge Base Configuration\napp:\n knowledge:\n max-file-size: ${APP_KNOWLEDGE_MAX_FILE_SIZE:5242880} # 5MB,限制文件大小防止内存溢出\n chunk-size: ${APP_KNOWLEDGE_CHUNK_SIZE:500}\n chunk-overlap: ${APP_KNOWLEDGE_CHUNK_OVERLAP:50}\n batch-size: ${APP_KNOWLEDGE_BATCH_SIZE:10} # 分批处理大小\n embedding-cache-size: 1000\n retrieval:\n top-k: ${APP_KNOWLEDGE_RETRIEVAL_TOP_K:3}\n similarity-threshold: ${APP_KNOWLEDGE_RETRIEVAL_SIMILARITY_THRESHOLD:0.5}\n max-context-tokens: ${APP_KNOWLEDGE_MAX_CONTEXT_TOKENS:1000} # 最大上下文 Token 数(降低以减少消耗)\n storage-path: ${APP_KNOWLEDGE_STORAGE_PATH:${user.home}/ai-knowledge/uploads}\n # OCR 配置\n ocr:\n enabled: ${APP_KNOWLEDGE_OCR_ENABLED:true} # 启用 OCR\n datapath: ${APP_KNOWLEDGE_OCR_DATAPATH:} # Tesseract 数据路径,留空则自动检测\n language: ${APP_KNOWLEDGE_OCR_LANGUAGE:chi_sim+eng} # 中文+英文\n\n# JVM Memory Optimization (add to startup command)\n# java -Xms512m -Xmx2048m -XX:+UseG1GC -jar your-application.jar\n"}, "949951ca50b7aae12d46d519c78ab9298af72870": {"t": 1785749341.6413527, "n": 2276, "h": "a120cde33a5bb292", "c": "spring:\n application:\n name: AI_example\n datasource:\n url: ${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/ai_chat?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true}\n username: ${SPRING_DATASOURCE_USERNAME:root}\n password: ${SPRING_DATASOURCE_PASSWORD:121506}\n driver-class-name: com.mysql.cj.jdbc.Driver\n jpa:\n hibernate:\n ddl-auto: update\n show-sql: true\n properties:\n hibernate:\n format_sql: true\n servlet:\n multipart:\n max-file-size: 10MB\n max-request-size: 15MB\n\n # Spring AI Configuration (using OpenAI-compatible API with DeepSeek)\n ai:\n openai:\n api-key: ${SPRING_AI_OPENAI_API_KEY:${DEEPSEEK_API_KEY:sk-your-api-key}}\n base-url: ${SPRING_AI_OPENAI_BASE_URL:https://api.deepseek.com}\n chat:\n enabled: true\n options:\n model: deepseek-chat\n temperature: 0.7\n embedding:\n enabled: true\n options:\n model: deepseek-embedding\n\nserver:\n port: 8081\n\n# JVM Memory Settings (add to startup command: -Xmx512m -Xms256m)\n# For large documents, consider: -Xmx1g -Xms512m\n\n# Actuator Configuration for health checks\nmanagement:\n endpoints:\n web:\n exposure:\n include: health,info\n endpoint:\n health:\n show-details: when-authorized\n\n# Knowledge Base Configuration\napp:\n knowledge:\n max-file-size: ${APP_KNOWLEDGE_MAX_FILE_SIZE:5242880} # 5MB,限制文件大小防止内存溢出\n chunk-size: ${APP_KNOWLEDGE_CHUNK_SIZE:500}\n chunk-overlap: ${APP_KNOWLEDGE_CHUNK_OVERLAP:50}\n batch-size: ${APP_KNOWLEDGE_BATCH_SIZE:10} # 分批处理大小\n embedding-cache-size: 1000\n retrieval:\n top-k: ${APP_KNOWLEDGE_RETRIEVAL_TOP_K:3}\n similarity-threshold: ${APP_KNOWLEDGE_RETRIEVAL_SIMILARITY_THRESHOLD:0.5}\n max-context-tokens: ${APP_KNOWLEDGE_MAX_CONTEXT_TOKENS:2000} # 最大上下文 Token 数\n storage-path: ${APP_KNOWLEDGE_STORAGE_PATH:${user.home}/ai-knowledge/uploads}\n # OCR 配置\n ocr:\n enabled: ${APP_KNOWLEDGE_OCR_ENABLED:true} # 启用 OCR\n datapath: ${APP_KNOWLEDGE_OCR_DATAPATH:} # Tesseract 数据路径,留空则自动检测\n language: ${APP_KNOWLEDGE_OCR_LANGUAGE:chi_sim+eng} # 中文+英文\n\n# JVM Memory Optimization (add to startup command)\n# java -Xms512m -Xmx2048m -XX:+UseG1GC -jar your-application.jar\n"}}, "Ai_example/AI_example/src/main/java/com/example/ai/service/impl/RAGServiceImpl.java": {"286793f3eba1813d4833e209932777a80c02e1b3": {"t": 1785746130.8062017, "n": 8727, "h": "5e4e2ef35008d657", "c": "package com.example.ai.service.impl;\n\nimport com.example.ai.dto.RAGResponse;\nimport com.example.ai.dto.RetrievalResult;\nimport com.example.ai.dto.SourceReference;\nimport com.example.ai.entity.ChatMessage;\nimport com.example.ai.service.ChatMemoryService;\nimport com.example.ai.service.ConversationDistillationService;\nimport com.example.ai.service.RAGService;\nimport com.example.ai.service.RetrievalService;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ArrayNode;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport okhttp3.*;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\n@Service\npublic class RAGServiceImpl implements RAGService {\n \n private final ChatMemoryService chatMemoryService;\n private final RetrievalService retrievalService;\n private final ConversationDistillationService distillationService;\n private final OkHttpClient httpClient;\n private final ObjectMapper objectMapper;\n \n @Value(\"${spring.ai.openai.base-url:https://api.deepseek.com}\")\n private String baseUrl;\n \n @Value(\"${spring.ai.openai.api-key}\")\n private String apiKey;\n \n @Value(\"${spring.ai.openai.chat.options.model:deepseek-chat}\")\n private String chatModel;\n \n @Value(\"${spring.ai.openai.chat.options.temperature:0.7}\")\n private double temperature;\n \n @Value(\"${app.knowledge.retrieval.top-k:3}\")\n private int maxContextChunks;\n \n @Value(\"${app.knowledge.retrieval.max-context-tokens:2000}\")\n private int maxContextTokens;\n \n private static final String SYSTEM_PROMPT = \"你是AI助手,请基于知识库内容回答问题,没有相关信息请说明。回答简洁专业。\";\n \n @Autowired\n public RAGServiceImpl(ChatMemoryService chatMemoryService, \n RetrievalService retrievalService,\n ConversationDistillationService distillationService) {\n this.chatMemoryService = chatMemoryService;\n this.retrievalService = retrievalService;\n this.distillationService = distillationService;\n this.httpClient = new OkHttpClient.Builder()\n .connectTimeout(30, TimeUnit.SECONDS)\n .readTimeout(60, TimeUnit.SECONDS)\n .writeTimeout(30, TimeUnit.SECONDS)\n .build();\n this.objectMapper = new ObjectMapper();\n }\n \n @Override\n public RAGResponse chatWithRAG(Long sessionId, String question) {\n // 使用蒸馏后的历史(如果启用)\n List<ChatMessage> history = distillationService.getHistoryWithDistillation(sessionId);\n \n // 搜索知识库(限制结果数量)\n List<RetrievalResult> retrievalResults = retrievalService.search(question, maxContextChunks, 0.5);\n \n // 构建上下文(限制 Token 数量)\n String context = buildContext(retrievalResults);\n \n // 调用 API(使用正确的消息格式,包含历史)\n String reply = callChatApiWithContext(question, context, history);\n \n // 保存消息\n if (sessionId != null) {\n chatMemoryService.saveMessage(sessionId, question, reply);\n }\n \n // 构建来源引用\n List<SourceReference> sources = retrievalResults.stream()\n .map(r -> new SourceReference(r.getDocumentId(), r.getDocumentName(), \n r.getContent(), r.getSimilarityScore()))\n .collect(Collectors.toList());\n \n return new RAGResponse(reply, sources, !retrievalResults.isEmpty());\n }\n \n @Override\n public String buildPrompt(String systemPrompt, String context, List<ChatMessage> history, String question) {\n StringBuilder prompt = new StringBuilder();\n prompt.append(systemPrompt).append(\"\\n\\n\");\n if (context != null && !context.isEmpty()) {\n prompt.append(\"知识库内容:\\n\").append(context).append(\"\\n\\n\");\n }\n prompt.append(\"问题:\").append(question);\n return prompt.toString();\n }\n \n private String buildContext(List<RetrievalResult> results) {\n if (results == null || results.isEmpty()) {\n return \"\";\n }\n \n StringBuilder context = new StringBuilder();\n int estimatedTokens = 0;\n \n for (RetrievalResult r : results) {\n String chunk = String.format(\"【%s】%s\", r.getDocumentName(), r.getContent());\n // 估算 Token 数量(粗略:1 Token ≈ 1.5 字符)\n int chunkTokens = chunk.length() / 2;\n \n if (estimatedTokens + chunkTokens > maxContextTokens) {\n break; // 超过限制,停止添加\n }\n \n context.append(chunk).append(\"\\n\\n\");\n estimatedTokens += chunkTokens;\n }\n \n return context.toString();\n }\n \n /**\n * 使用正确的 API 格式调用,减少 Token 消耗\n */\n private String callChatApiWithContext(String question, String context, List<ChatMessage> history) {\n String url = baseUrl + \"/v1/chat/completions\";\n \n try {\n // 使用 ObjectMapper 构建 JSON\n ObjectNode requestBody = objectMapper.createObjectNode();\n requestBody.put(\"model\", chatModel);\n requestBody.put(\"temperature\", temperature);\n requestBody.put(\"max_tokens\", 1500); // 限制输出长度\n \n ArrayNode messages = requestBody.putArray(\"messages\");\n \n // 1. 系统消息 - 合并知识库上下文到一条消息\n ObjectNode systemMsg = messages.addObject();\n systemMsg.put(\"role\", \"system\");\n String systemContent = SYSTEM_PROMPT;\n if (context != null && !context.isEmpty()) {\n systemContent += \"\\n\\n相关知识库内容:\\n\" + context;\n }\n systemMsg.put(\"content\", systemContent);\n \n // 2. 添加历史消息(蒸馏后的历史,可能包含摘要)\n if (history != null && !history.isEmpty()) {\n for (ChatMessage msg : history) {\n // 检查是否是摘要消息\n if (\"[历史对话摘要]\".equals(msg.getUserMessage())) {\n // 摘要作为系统消息的一部分\n ObjectNode summaryMsg = messages.addObject();\n summaryMsg.put(\"role\", \"system\");\n summaryMsg.put(\"content\", \"历史对话摘要:\" + msg.getAssistantReply());\n } else {\n // 常规历史消息\n ObjectNode historyUserMsg = messages.addObject();\n historyUserMsg.put(\"role\", \"user\");\n historyUserMsg.put(\"content\", msg.getUserMessage());\n \n ObjectNode historyAssistantMsg = messages.addObject();\n historyAssistantMsg.put(\"role\", \"assistant\");\n historyAssistantMsg.put(\"content\", msg.getAssistantReply());\n }\n }\n }\n \n // 3. 用户问题\n ObjectNode userMsg = messages.addObject();\n userMsg.put(\"role\", \"user\");\n userMsg.put(\"content\", question);\n \n String jsonBody = objectMapper.writeValueAsString(requestBody);\n \n Request request = new Request.Builder()\n .url(url)\n .addHeader(\"Authorization\", \"Bearer \" + apiKey)\n .addHeader(\"Content-Type\", \"application/json\")\n .post(RequestBody.create(jsonBody, MediaType.parse(\"application/json\")))\n .build();\n \n try (Response response = httpClient.newCall(request).execute()) {\n if (!response.isSuccessful()) {\n String errorBody = response.body() != null ? response.body().string() : \"Unknown error\";\n throw new RuntimeException(\"Chat API call failed: \" + response.code() + \" - \" + errorBody);\n }\n \n String responseBody = response.body().string();\n return parseChatResponse(responseBody);\n }\n } catch (IOException e) {\n throw new RuntimeException(\"Failed to call chat API: \" + e.getMessage(), e);\n }\n }\n \n private String parseChatResponse(String responseBody) throws IOException {\n JsonNode root = objectMapper.readTree(responseBody);\n JsonNode choices = root.path(\"choices\");\n \n if (choices.isArray() && choices.size() > 0) {\n return choices.get(0).path(\"message\").path(\"content\").asText();\n }\n \n throw new IOException(\"Invalid chat response format\");\n }\n}\n"}, "cddb06f263690731bacdf28922dcd2e840309c0b": {"t": 1785749339.6375866, "n": 8727, "h": "5e4e2ef35008d657", "c": "package com.example.ai.service.impl;\n\nimport com.example.ai.dto.RAGResponse;\nimport com.example.ai.dto.RetrievalResult;\nimport com.example.ai.dto.SourceReference;\nimport com.example.ai.entity.ChatMessage;\nimport com.example.ai.service.ChatMemoryService;\nimport com.example.ai.service.ConversationDistillationService;\nimport com.example.ai.service.RAGService;\nimport com.example.ai.service.RetrievalService;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ArrayNode;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport okhttp3.*;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\n@Service\npublic class RAGServiceImpl implements RAGService {\n \n private final ChatMemoryService chatMemoryService;\n private final RetrievalService retrievalService;\n private final ConversationDistillationService distillationService;\n private final OkHttpClient httpClient;\n private final ObjectMapper objectMapper;\n \n @Value(\"${spring.ai.openai.base-url:https://api.deepseek.com}\")\n private String baseUrl;\n \n @Value(\"${spring.ai.openai.api-key}\")\n private String apiKey;\n \n @Value(\"${spring.ai.openai.chat.options.model:deepseek-chat}\")\n private String chatModel;\n \n @Value(\"${spring.ai.openai.chat.options.temperature:0.7}\")\n private double temperature;\n \n @Value(\"${app.knowledge.retrieval.top-k:3}\")\n private int maxContextChunks;\n \n @Value(\"${app.knowledge.retrieval.max-context-tokens:2000}\")\n private int maxContextTokens;\n \n private static final String SYSTEM_PROMPT = \"你是AI助手,请基于知识库内容回答问题,没有相关信息请说明。回答简洁专业。\";\n \n @Autowired\n public RAGServiceImpl(ChatMemoryService chatMemoryService, \n RetrievalService retrievalService,\n ConversationDistillationService distillationService) {\n this.chatMemoryService = chatMemoryService;\n this.retrievalService = retrievalService;\n this.distillationService = distillationService;\n this.httpClient = new OkHttpClient.Builder()\n .connectTimeout(30, TimeUnit.SECONDS)\n .readTimeout(60, TimeUnit.SECONDS)\n .writeTimeout(30, TimeUnit.SECONDS)\n .build();\n this.objectMapper = new ObjectMapper();\n }\n \n @Override\n public RAGResponse chatWithRAG(Long sessionId, String question) {\n // 使用蒸馏后的历史(如果启用)\n List<ChatMessage> history = distillationService.getHistoryWithDistillation(sessionId);\n \n // 搜索知识库(限制结果数量)\n List<RetrievalResult> retrievalResults = retrievalService.search(question, maxContextChunks, 0.5);\n \n // 构建上下文(限制 Token 数量)\n String context = buildContext(retrievalResults);\n \n // 调用 API(使用正确的消息格式,包含历史)\n String reply = callChatApiWithContext(question, context, history);\n \n // 保存消息\n if (sessionId != null) {\n chatMemoryService.saveMessage(sessionId, question, reply);\n }\n \n // 构建来源引用\n List<SourceReference> sources = retrievalResults.stream()\n .map(r -> new SourceReference(r.getDocumentId(), r.getDocumentName(), \n r.getContent(), r.getSimilarityScore()))\n .collect(Collectors.toList());\n \n return new RAGResponse(reply, sources, !retrievalResults.isEmpty());\n }\n \n @Override\n public String buildPrompt(String systemPrompt, String context, List<ChatMessage> history, String question) {\n StringBuilder prompt = new StringBuilder();\n prompt.append(systemPrompt).append(\"\\n\\n\");\n if (context != null && !context.isEmpty()) {\n prompt.append(\"知识库内容:\\n\").append(context).append(\"\\n\\n\");\n }\n prompt.append(\"问题:\").append(question);\n return prompt.toString();\n }\n \n private String buildContext(List<RetrievalResult> results) {\n if (results == null || results.isEmpty()) {\n return \"\";\n }\n \n StringBuilder context = new StringBuilder();\n int estimatedTokens = 0;\n \n for (RetrievalResult r : results) {\n String chunk = String.format(\"【%s】%s\", r.getDocumentName(), r.getContent());\n // 估算 Token 数量(粗略:1 Token ≈ 1.5 字符)\n int chunkTokens = chunk.length() / 2;\n \n if (estimatedTokens + chunkTokens > maxContextTokens) {\n break; // 超过限制,停止添加\n }\n \n context.append(chunk).append(\"\\n\\n\");\n estimatedTokens += chunkTokens;\n }\n \n return context.toString();\n }\n \n /**\n * 使用正确的 API 格式调用,减少 Token 消耗\n */\n private String callChatApiWithContext(String question, String context, List<ChatMessage> history) {\n String url = baseUrl + \"/v1/chat/completions\";\n \n try {\n // 使用 ObjectMapper 构建 JSON\n ObjectNode requestBody = objectMapper.createObjectNode();\n requestBody.put(\"model\", chatModel);\n requestBody.put(\"temperature\", temperature);\n requestBody.put(\"max_tokens\", 1500); // 限制输出长度\n \n ArrayNode messages = requestBody.putArray(\"messages\");\n \n // 1. 系统消息 - 合并知识库上下文到一条消息\n ObjectNode systemMsg = messages.addObject();\n systemMsg.put(\"role\", \"system\");\n String systemContent = SYSTEM_PROMPT;\n if (context != null && !context.isEmpty()) {\n systemContent += \"\\n\\n相关知识库内容:\\n\" + context;\n }\n systemMsg.put(\"content\", systemContent);\n \n // 2. 添加历史消息(蒸馏后的历史,可能包含摘要)\n if (history != null && !history.isEmpty()) {\n for (ChatMessage msg : history) {\n // 检查是否是摘要消息\n if (\"[历史对话摘要]\".equals(msg.getUserMessage())) {\n // 摘要作为系统消息的一部分\n ObjectNode summaryMsg = messages.addObject();\n summaryMsg.put(\"role\", \"system\");\n summaryMsg.put(\"content\", \"历史对话摘要:\" + msg.getAssistantReply());\n } else {\n // 常规历史消息\n ObjectNode historyUserMsg = messages.addObject();\n historyUserMsg.put(\"role\", \"user\");\n historyUserMsg.put(\"content\", msg.getUserMessage());\n \n ObjectNode historyAssistantMsg = messages.addObject();\n historyAssistantMsg.put(\"role\", \"assistant\");\n historyAssistantMsg.put(\"content\", msg.getAssistantReply());\n }\n }\n }\n \n // 3. 用户问题\n ObjectNode userMsg = messages.addObject();\n userMsg.put(\"role\", \"user\");\n userMsg.put(\"content\", question);\n \n String jsonBody = objectMapper.writeValueAsString(requestBody);\n \n Request request = new Request.Builder()\n .url(url)\n .addHeader(\"Authorization\", \"Bearer \" + apiKey)\n .addHeader(\"Content-Type\", \"application/json\")\n .post(RequestBody.create(jsonBody, MediaType.parse(\"application/json\")))\n .build();\n \n try (Response response = httpClient.newCall(request).execute()) {\n if (!response.isSuccessful()) {\n String errorBody = response.body() != null ? response.body().string() : \"Unknown error\";\n throw new RuntimeException(\"Chat API call failed: \" + response.code() + \" - \" + errorBody);\n }\n \n String responseBody = response.body().string();\n return parseChatResponse(responseBody);\n }\n } catch (IOException e) {\n throw new RuntimeException(\"Failed to call chat API: \" + e.getMessage(), e);\n }\n }\n \n private String parseChatResponse(String responseBody) throws IOException {\n JsonNode root = objectMapper.readTree(responseBody);\n JsonNode choices = root.path(\"choices\");\n \n if (choices.isArray() && choices.size() > 0) {\n return choices.get(0).path(\"message\").path(\"content\").asText();\n }\n \n throw new IOException(\"Invalid chat response format\");\n }\n}\n"}, "949951ca50b7aae12d46d519c78ab9298af72870": {"t": 1785749340.723896, "n": 7157, "h": "886b9d1d56af2023", "c": "package com.example.ai.service.impl;\n\nimport com.example.ai.dto.RAGResponse;\nimport com.example.ai.dto.RetrievalResult;\nimport com.example.ai.dto.SourceReference;\nimport com.example.ai.entity.ChatMessage;\nimport com.example.ai.service.ChatMemoryService;\nimport com.example.ai.service.RAGService;\nimport com.example.ai.service.RetrievalService;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ArrayNode;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport okhttp3.*;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\n@Service\npublic class RAGServiceImpl implements RAGService {\n \n private final ChatMemoryService chatMemoryService;\n private final RetrievalService retrievalService;\n private final OkHttpClient httpClient;\n private final ObjectMapper objectMapper;\n \n @Value(\"${spring.ai.openai.base-url:https://api.deepseek.com}\")\n private String baseUrl;\n \n @Value(\"${spring.ai.openai.api-key}\")\n private String apiKey;\n \n @Value(\"${spring.ai.openai.chat.options.model:deepseek-chat}\")\n private String chatModel;\n \n @Value(\"${spring.ai.openai.chat.options.temperature:0.7}\")\n private double temperature;\n \n @Value(\"${app.knowledge.retrieval.top-k:3}\")\n private int maxContextChunks;\n \n @Value(\"${app.knowledge.retrieval.max-context-tokens:2000}\")\n private int maxContextTokens;\n \n private static final String SYSTEM_PROMPT = \"你是AI助手,请基于知识库内容回答问题,没有相关信息请说明。回答简洁专业。\";\n \n @Autowired\n public RAGServiceImpl(ChatMemoryService chatMemoryService, RetrievalService retrievalService) {\n this.chatMemoryService = chatMemoryService;\n this.retrievalService = retrievalService;\n this.httpClient = new OkHttpClient.Builder()\n .connectTimeout(30, TimeUnit.SECONDS)\n .readTimeout(60, TimeUnit.SECONDS)\n .writeTimeout(30, TimeUnit.SECONDS)\n .build();\n this.objectMapper = new ObjectMapper();\n }\n \n @Override\n public RAGResponse chatWithRAG(Long sessionId, String question) {\n // 搜索知识库(限制结果数量)\n List<RetrievalResult> retrievalResults = retrievalService.search(question, maxContextChunks, 0.5);\n \n // 构建上下文(限制 Token 数量)\n String context = buildContext(retrievalResults);\n \n // 调用 API(使用正确的消息格式)\n String reply = callChatApiWithContext(question, context);\n \n // 保存消息\n if (sessionId != null) {\n chatMemoryService.saveMessage(sessionId, question, reply);\n }\n \n // 构建来源引用\n List<SourceReference> sources = retrievalResults.stream()\n .map(r -> new SourceReference(r.getDocumentId(), r.getDocumentName(), \n r.getContent(), r.getSimilarityScore()))\n .collect(Collectors.toList());\n \n return new RAGResponse(reply, sources, !retrievalResults.isEmpty());\n }\n \n @Override\n public String buildPrompt(String systemPrompt, String context, List<ChatMessage> history, String question) {\n StringBuilder prompt = new StringBuilder();\n prompt.append(systemPrompt).append(\"\\n\\n\");\n if (context != null && !context.isEmpty()) {\n prompt.append(\"知识库内容:\\n\").append(context).append(\"\\n\\n\");\n }\n prompt.append(\"问题:\").append(question);\n return prompt.toString();\n }\n \n private String buildContext(List<RetrievalResult> results) {\n if (results == null || results.isEmpty()) {\n return \"\";\n }\n \n StringBuilder context = new StringBuilder();\n int estimatedTokens = 0;\n \n for (RetrievalResult r : results) {\n String chunk = String.format(\"【%s】%s\", r.getDocumentName(), r.getContent());\n // 估算 Token 数量(粗略:1 Token ≈ 1.5 字符)\n int chunkTokens = chunk.length() / 2;\n \n if (estimatedTokens + chunkTokens > maxContextTokens) {\n break; // 超过限制,停止添加\n }\n \n context.append(chunk).append(\"\\n\\n\");\n estimatedTokens += chunkTokens;\n }\n \n return context.toString();\n }\n \n /**\n * 使用正确的 API 格式调用,减少 Token 消耗\n */\n private String callChatApiWithContext(String question, String context) {\n String url = baseUrl + \"/v1/chat/completions\";\n \n try {\n // 使用 ObjectMapper 构建 JSON\n ObjectNode requestBody = objectMapper.createObjectNode();\n requestBody.put(\"model\", chatModel);\n requestBody.put(\"temperature\", temperature);\n requestBody.put(\"max_tokens\", 1500); // 限制输出长度\n \n ArrayNode messages = requestBody.putArray(\"messages\");\n \n // 1. 系统消息 - 合并知识库上下文到一条消息\n ObjectNode systemMsg = messages.addObject();\n systemMsg.put(\"role\", \"system\");\n String systemContent = SYSTEM_PROMPT;\n if (context != null && !context.isEmpty()) {\n systemContent += \"\\n\\n相关知识库内容:\\n\" + context;\n }\n systemMsg.put(\"content\", systemContent);\n \n // 2. 用户问题\n ObjectNode userMsg = messages.addObject();\n userMsg.put(\"role\", \"user\");\n userMsg.put(\"content\", question);\n \n String jsonBody = objectMapper.writeValueAsString(requestBody);\n \n Request request = new Request.Builder()\n .url(url)\n .addHeader(\"Authorization\", \"Bearer \" + apiKey)\n .addHeader(\"Content-Type\", \"application/json\")\n .post(RequestBody.create(jsonBody, MediaType.parse(\"application/json\")))\n .build();\n \n try (Response response = httpClient.newCall(request).execute()) {\n if (!response.isSuccessful()) {\n String errorBody = response.body() != null ? response.body().string() : \"Unknown error\";\n throw new RuntimeException(\"Chat API call failed: \" + response.code() + \" - \" + errorBody);\n }\n \n String responseBody = response.body().string();\n return parseChatResponse(responseBody);\n }\n } catch (IOException e) {\n throw new RuntimeException(\"Failed to call chat API: \" + e.getMessage(), e);\n }\n }\n \n private String parseChatResponse(String responseBody) throws IOException {\n JsonNode root = objectMapper.readTree(responseBody);\n JsonNode choices = root.path(\"choices\");\n \n if (choices.isArray() && choices.size() > 0) {\n return choices.get(0).path(\"message\").path(\"content\").asText();\n }\n \n throw new IOException(\"Invalid chat response format\");\n }\n}\n"}}, "Ai_example/AI_example/target/classes/application.yml": {"286793f3eba1813d4833e209932777a80c02e1b3": {"t": 1785746952.2472062, "n": 2812, "h": "2d31a22d27a95345", "c": "spring:\n application:\n name: AI_example\n datasource:\n url: ${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/ai_chat?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true}\n username: ${SPRING_DATASOURCE_USERNAME:root}\n password: ${SPRING_DATASOURCE_PASSWORD:121506}\n driver-class-name: com.mysql.cj.jdbc.Driver\n jpa:\n hibernate:\n ddl-auto: update\n show-sql: true\n properties:\n hibernate:\n format_sql: true\n servlet:\n multipart:\n max-file-size: 10MB\n max-request-size: 15MB\n\n # Spring AI Configuration (using OpenAI-compatible API with DeepSeek)\n ai:\n openai:\n api-key: ${SPRING_AI_OPENAI_API_KEY:${DEEPSEEK_API_KEY:sk-your-api-key}}\n base-url: ${SPRING_AI_OPENAI_BASE_URL:https://api.deepseek.com}\n chat:\n enabled: true\n options:\n model: deepseek-chat\n temperature: 0.7\n embedding:\n enabled: true\n options:\n model: deepseek-embedding\n\nserver:\n port: 8081\n\n# JVM Memory Settings (add to startup command: -Xmx512m -Xms256m)\n# For large documents, consider: -Xmx1g -Xms512m\n\n# Actuator Configuration for health checks\nmanagement:\n endpoints:\n web:\n exposure:\n include: health,info\n endpoint:\n health:\n show-details: when-authorized\n\n# Knowledge Base Configuration\napp:\n knowledge:\n max-file-size: ${APP_KNOWLEDGE_MAX_FILE_SIZE:5242880} # 5MB,限制文件大小防止内存溢出\n chunk-size: ${APP_KNOWLEDGE_CHUNK_SIZE:500}\n chunk-overlap: ${APP_KNOWLEDGE_CHUNK_OVERLAP:50}\n batch-size: ${APP_KNOWLEDGE_BATCH_SIZE:10} # 分批处理大小\n embedding-cache-size: 1000\n retrieval:\n top-k: ${APP_KNOWLEDGE_RETRIEVAL_TOP_K:3}\n similarity-threshold: ${APP_KNOWLEDGE_RETRIEVAL_SIMILARITY_THRESHOLD:0.5}\n max-context-tokens: ${APP_KNOWLEDGE_MAX_CONTEXT_TOKENS:1000} # 最大上下文 Token 数(降低以减少消耗)\n storage-path: ${APP_KNOWLEDGE_STORAGE_PATH:${user.home}/ai-knowledge/uploads}\n # OCR 配置\n ocr:\n enabled: ${APP_KNOWLEDGE_OCR_ENABLED:true} # 启用 OCR\n datapath: ${APP_KNOWLEDGE_OCR_DATAPATH:} # Tesseract 数据路径,留空则自动检测\n language: ${APP_KNOWLEDGE_OCR_LANGUAGE:chi_sim+eng} # 中文+英文\n\n# Conversation Distillation Configuration\n distillation:\n enabled: ${APP_DISTILLATION_ENABLED:true}\n token-threshold: ${APP_DISTILLATION_TOKEN_THRESHOLD:4000} # Token threshold to trigger distillation\n preserve-recent-pairs: ${APP_DISTILLATION_PRESERVE_RECENT_PAIRS:3} # Number of recent Q&A pairs to preserve\n max-summary-tokens: ${APP_DISTILLATION_MAX_SUMMARY_TOKENS:500} # Max tokens for generated summary\n summary-model: ${APP_DISTILLATION_SUMMARY_MODEL:deepseek-chat} # Model to use for summarization\n\n# JVM Memory Optimization (add to startup command)\n# java -Xms512m -Xmx2048m -XX:+UseG1GC -jar your-application.jar\n"}, "cddb06f263690731bacdf28922dcd2e840309c0b": {"t": 1785749339.6278317, "n": 2812, "h": "2d31a22d27a95345", "c": "spring:\n application:\n name: AI_example\n datasource:\n url: ${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/ai_chat?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true}\n username: ${SPRING_DATASOURCE_USERNAME:root}\n password: ${SPRING_DATASOURCE_PASSWORD:121506}\n driver-class-name: com.mysql.cj.jdbc.Driver\n jpa:\n hibernate:\n ddl-auto: update\n show-sql: true\n properties:\n hibernate:\n format_sql: true\n servlet:\n multipart:\n max-file-size: 10MB\n max-request-size: 15MB\n\n # Spring AI Configuration (using OpenAI-compatible API with DeepSeek)\n ai:\n openai:\n api-key: ${SPRING_AI_OPENAI_API_KEY:${DEEPSEEK_API_KEY:sk-your-api-key}}\n base-url: ${SPRING_AI_OPENAI_BASE_URL:https://api.deepseek.com}\n chat:\n enabled: true\n options:\n model: deepseek-chat\n temperature: 0.7\n embedding:\n enabled: true\n options:\n model: deepseek-embedding\n\nserver:\n port: 8081\n\n# JVM Memory Settings (add to startup command: -Xmx512m -Xms256m)\n# For large documents, consider: -Xmx1g -Xms512m\n\n# Actuator Configuration for health checks\nmanagement:\n endpoints:\n web:\n exposure:\n include: health,info\n endpoint:\n health:\n show-details: when-authorized\n\n# Knowledge Base Configuration\napp:\n knowledge:\n max-file-size: ${APP_KNOWLEDGE_MAX_FILE_SIZE:5242880} # 5MB,限制文件大小防止内存溢出\n chunk-size: ${APP_KNOWLEDGE_CHUNK_SIZE:500}\n chunk-overlap: ${APP_KNOWLEDGE_CHUNK_OVERLAP:50}\n batch-size: ${APP_KNOWLEDGE_BATCH_SIZE:10} # 分批处理大小\n embedding-cache-size: 1000\n retrieval:\n top-k: ${APP_KNOWLEDGE_RETRIEVAL_TOP_K:3}\n similarity-threshold: ${APP_KNOWLEDGE_RETRIEVAL_SIMILARITY_THRESHOLD:0.5}\n max-context-tokens: ${APP_KNOWLEDGE_MAX_CONTEXT_TOKENS:1000} # 最大上下文 Token 数(降低以减少消耗)\n storage-path: ${APP_KNOWLEDGE_STORAGE_PATH:${user.home}/ai-knowledge/uploads}\n # OCR 配置\n ocr:\n enabled: ${APP_KNOWLEDGE_OCR_ENABLED:true} # 启用 OCR\n datapath: ${APP_KNOWLEDGE_OCR_DATAPATH:} # Tesseract 数据路径,留空则自动检测\n language: ${APP_KNOWLEDGE_OCR_LANGUAGE:chi_sim+eng} # 中文+英文\n\n# Conversation Distillation Configuration\n distillation:\n enabled: ${APP_DISTILLATION_ENABLED:true}\n token-threshold: ${APP_DISTILLATION_TOKEN_THRESHOLD:4000} # Token threshold to trigger distillation\n preserve-recent-pairs: ${APP_DISTILLATION_PRESERVE_RECENT_PAIRS:3} # Number of recent Q&A pairs to preserve\n max-summary-tokens: ${APP_DISTILLATION_MAX_SUMMARY_TOKENS:500} # Max tokens for generated summary\n summary-model: ${APP_DISTILLATION_SUMMARY_MODEL:deepseek-chat} # Model to use for summarization\n\n# JVM Memory Optimization (add to startup command)\n# java -Xms512m -Xmx2048m -XX:+UseG1GC -jar your-application.jar\n"}, "f375b7749f9725b7aed3d3da7777eabb0f9ad46c": {"t": 1785749340.6870353, "n": 2192, "h": "3729abc7f9a5f3a4", "c": "spring:\n application:\n name: AI_example\n datasource:\n url: ${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/ai_chat?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true}\n username: ${SPRING_DATASOURCE_USERNAME:root}\n password: ${SPRING_DATASOURCE_PASSWORD:121506}\n driver-class-name: com.mysql.cj.jdbc.Driver\n jpa:\n hibernate:\n ddl-auto: update\n show-sql: true\n properties:\n hibernate:\n format_sql: true\n servlet:\n multipart:\n max-file-size: 10MB\n max-request-size: 15MB\n\n # Spring AI Configuration (using OpenAI-compatible API with DeepSeek)\n ai:\n openai:\n api-key: ${SPRING_AI_OPENAI_API_KEY:${DEEPSEEK_API_KEY:sk-your-api-key}}\n base-url: ${SPRING_AI_OPENAI_BASE_URL:https://api.deepseek.com}\n chat:\n enabled: true\n options:\n model: deepseek-chat\n temperature: 0.7\n embedding:\n enabled: true\n options:\n model: deepseek-embedding\n\nserver:\n port: 8081\n\n# JVM Memory Settings (add to startup command: -Xmx512m -Xms256m)\n# For large documents, consider: -Xmx1g -Xms512m\n\n# Actuator Configuration for health checks\nmanagement:\n endpoints:\n web:\n exposure:\n include: health,info\n endpoint:\n health:\n show-details: when-authorized\n\n# Knowledge Base Configuration\napp:\n knowledge:\n max-file-size: ${APP_KNOWLEDGE_MAX_FILE_SIZE:5242880} # 5MB,限制文件大小防止内存溢出\n chunk-size: ${APP_KNOWLEDGE_CHUNK_SIZE:500}\n chunk-overlap: ${APP_KNOWLEDGE_CHUNK_OVERLAP:50}\n batch-size: ${APP_KNOWLEDGE_BATCH_SIZE:10} # 分批处理大小\n embedding-cache-size: 1000\n retrieval:\n top-k: ${APP_KNOWLEDGE_RETRIEVAL_TOP_K:3}\n similarity-threshold: ${APP_KNOWLEDGE_RETRIEVAL_SIMILARITY_THRESHOLD:0.7}\n storage-path: ${APP_KNOWLEDGE_STORAGE_PATH:${user.home}/ai-knowledge/uploads}\n # OCR 配置\n ocr:\n enabled: ${APP_KNOWLEDGE_OCR_ENABLED:true} # 启用 OCR\n datapath: ${APP_KNOWLEDGE_OCR_DATAPATH:} # Tesseract 数据路径,留空则自动检测\n language: ${APP_KNOWLEDGE_OCR_LANGUAGE:chi_sim+eng} # 中文+英文\n\n# JVM Memory Optimization (add to startup command)\n# java -Xms512m -Xmx2048m -XX:+UseG1GC -jar your-application.jar\n"}}}}