pom.xml
@@ -22,11 +22,11 @@ <properties> <maven.compiler.source>25</maven.compiler.source> <maven.compiler.target>25</maven.compiler.target> <maven.compiler.release>25</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>25</java.version> <lombok.version>1.18.44</lombok.version> <maven.compiler.release>25</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>25</java.version> <lombok.version>1.18.44</lombok.version> <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version> <pagehelper.spring.boot.starter.version>2.1.1</pagehelper.spring.boot.starter.version> <fastjson.version>2.0.53</fastjson.version> @@ -35,9 +35,9 @@ <bitwalker.version>1.21</bitwalker.version> <jwt.version>0.13.0</jwt.version> <kaptcha.version>2.3.3</kaptcha.version> <knife4j.version>4.5.0</knife4j.version> <springdoc.version>2.8.17</springdoc.version> <swagger.annotations.version>1.6.15</swagger.annotations.version> <knife4j.version>4.5.0</knife4j.version> <springdoc.version>2.8.17</springdoc.version> <swagger.annotations.version>1.6.15</swagger.annotations.version> <poi.version>5.2.3</poi.version> <oshi.version>6.6.5</oshi.version> <velocity.version>2.3</velocity.version> @@ -53,21 +53,41 @@ <getui-sdk.version>1.0.7.0</getui-sdk.version> <jsqlparser.version>4.9</jsqlparser.version> <thumbnailator.version>0.4.20</thumbnailator.version> <langchain4j.version>1.0.0-beta3</langchain4j.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-bom</artifactId> <version>${langchain4j.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-community-bom</artifactId> <version>${langchain4j.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- ruoyi-springboot2 / swagger knife4j é ç½® --> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> <version>${knife4j.version}</version> </dependency> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>${springdoc.version}</version> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> <version>${knife4j.version}</version> </dependency> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>${springdoc.version}</version> </dependency> <!-- SpringBoot æ ¸å¿å --> <dependency> @@ -113,10 +133,44 @@ <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <!-- pool å¯¹è±¡æ± --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-pinecone</artifactId> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-ollama-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-mcp</artifactId> </dependency> <!-- Mysql驱å¨å --> @@ -246,11 +300,11 @@ </dependency> <!-- Swagger3ä¾èµ --> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> <version>${swagger.annotations.version}</version> </dependency> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> <version>${swagger.annotations.version}</version> </dependency> <!-- 鲿¢è¿å ¥swagger页颿¥ç±»å转æ¢éè¯¯ï¼æé¤3.0.0ä¸çå¼ç¨ï¼æå¨å¢å 1.6.2çæ¬ --> @@ -300,11 +354,11 @@ </exclusion> </exclusions> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </dependency> <!-- minio --> @@ -373,28 +427,28 @@ <build> <finalName>${project.artifactId}</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.14.1</version> <configuration> <release>${maven.compiler.release}</release> <proc>full</proc> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.14.1</version> <configuration> <release>${maven.compiler.release}</release> <proc>full</proc> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> src/main/java/com/ruoyi/ai/assistant/ApproveTodoAgent.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,20 @@ package com.ruoyi.ai.assistant; import dev.langchain4j.service.MemoryId; import dev.langchain4j.service.SystemMessage; import dev.langchain4j.service.UserMessage; import dev.langchain4j.service.spring.AiService; import reactor.core.publisher.Flux; import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT; @AiService( wiringMode = EXPLICIT, streamingChatModel = "qwenStreamingChatModel", chatMemoryProvider = "chatMemoryProviderApproveTodo", tools = "approveTodoTools") public interface ApproveTodoAgent { @SystemMessage(fromResource = "approve-todo-agent-prompt.txt") Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage); } src/main/java/com/ruoyi/ai/assistant/ApproveTodoIntentExecutor.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,208 @@ package com.ruoyi.ai.assistant; import com.alibaba.fastjson2.JSON; import com.ruoyi.ai.tools.ApproveTodoTools; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import java.math.BigDecimal; import java.util.LinkedHashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @Component public class ApproveTodoIntentExecutor { private static final Pattern APPROVE_ID_PATTERN = Pattern.compile("\\b[A-Za-z]*\\d{8,}\\b"); private static final Pattern LIMIT_PATTERN = Pattern.compile("(å|æè¿)?(\\d{1,2})æ¡"); private final ApproveTodoTools approveTodoTools; public ApproveTodoIntentExecutor(ApproveTodoTools approveTodoTools) { this.approveTodoTools = approveTodoTools; } public String tryExecute(String message) { if (!StringUtils.hasText(message)) { return null; } String text = message.trim(); String approveId = extractApproveId(text); if (containsAny(text, "ç»è®¡", "åæ", "å¾è¡¨", "è¶å¿", "å æ¯")) { return approveTodoTools.getTodoStats(); } if (containsAny(text, "æµè½¬", "è¿åº¦", "èç¹", "æ¥å¿")) { return StringUtils.hasText(approveId) ? approveTodoTools.getTodoProgress(approveId) : missingApproveId("todo_progress", "æ¥è¯¢å®¡æ¹è¿åº¦éè¦æä¾æµç¨ç¼å·ã"); } if (containsAny(text, "详æ ", "æç»") && !containsAny(text, "å表")) { return StringUtils.hasText(approveId) ? approveTodoTools.getTodoDetail(approveId) : missingApproveId("todo_detail", "æ¥è¯¢å®¡æ¹è¯¦æ éè¦æä¾æµç¨ç¼å·ã"); } if (containsAny(text, "åæ¶å®¡æ ¸", "æ¤éå®¡æ ¸", "åéå®¡æ ¸")) { return StringUtils.hasText(approveId) ? approveTodoTools.cancelReviewTodo(approveId, extractTail(text, "åå ")) : missingApproveId("cancel_review_action", "åæ¶å®¡æ ¸éè¦æä¾æµç¨ç¼å·ã"); } if (containsAny(text, "å é¤")) { return StringUtils.hasText(approveId) ? approveTodoTools.deleteTodo(approveId) : missingApproveId("delete_action", "å é¤å®¡æ¹åéè¦æä¾æµç¨ç¼å·ã"); } if (containsAny(text, "驳å", "æç»")) { return StringUtils.hasText(approveId) ? approveTodoTools.reviewTodo(approveId, "reject", extractTail(text, "åå ")) : missingApproveId("review_action", "驳å审æ¹éè¦æä¾æµç¨ç¼å·ã"); } if (containsAny(text, "å®¡æ ¸éè¿", "审æ¹éè¿", "éè¿å®¡æ¹", "åæå®¡æ¹", "审æ¹åæ")) { return StringUtils.hasText(approveId) ? approveTodoTools.reviewTodo(approveId, "approve", extractTail(text, "夿³¨")) : missingApproveId("review_action", "审æ¹éè¿éè¦æä¾æµç¨ç¼å·ã"); } if (StringUtils.hasText(approveId) && containsAny(text, "éè¿", "åæ") && !containsAny(text, "æªéè¿", "éè¿ç", "审æ¹éè¿ç", "å®¡æ ¸éè¿ç")) { return approveTodoTools.reviewTodo(approveId, "approve", extractTail(text, "夿³¨")); } if (containsAny(text, "ä¿®æ¹")) { return StringUtils.hasText(approveId) ? approveTodoTools.updateTodo( approveId, extractValue(text, "æ é¢"), extractDateValue(text, "å¼å§æ¥æ"), extractDateValue(text, "ç»ææ¥æ"), extractBigDecimalValue(text, "éé¢"), extractValue(text, "å°ç¹"), extractIntegerValue(text, "ç±»å"), extractValue(text, "夿³¨")) : missingApproveId("update_action", "ä¿®æ¹å®¡æ¹åéè¦æä¾æµç¨ç¼å·ã"); } if (containsAny(text, "å表", "å¾ å", "æ¥è¯¢å®¡æ¹")) { return approveTodoTools.listTodos( extractStatus(text), extractApproveType(text), extractKeyword(text), extractLimit(text)); } return null; } private boolean containsAny(String text, String... keywords) { for (String keyword : keywords) { if (text.contains(keyword)) { return true; } } return false; } private String extractApproveId(String text) { Matcher matcher = APPROVE_ID_PATTERN.matcher(text); return matcher.find() ? matcher.group() : null; } private Integer extractLimit(String text) { Matcher matcher = LIMIT_PATTERN.matcher(text); return matcher.find() ? Integer.parseInt(matcher.group(2)) : 10; } private String extractStatus(String text) { if (containsAny(text, "å¾ å®¡æ ¸", "å¾ å®¡æ¹")) { return "pending"; } if (containsAny(text, "å®¡æ ¸ä¸")) { return "processing"; } if (containsAny(text, "å·²éè¿", "å®¡æ ¸å®æ")) { return "approved"; } if (containsAny(text, "æªéè¿", "驳å")) { return "rejected"; } if (containsAny(text, "éæ°æäº¤")) { return "resubmitted"; } return "all"; } private Integer extractApproveType(String text) { if (text.contains("å ¬åº")) { return 1; } if (text.contains("请å")) { return 2; } if (text.contains("åºå·®")) { return 3; } if (text.contains("æ¥é")) { return 4; } if (text.contains("éè´")) { return 5; } if (text.contains("æ¥ä»·")) { return 6; } if (text.contains("åè´§")) { return 7; } if (text.contains("å±é©ä½ä¸")) { return 8; } return null; } private String extractKeyword(String text) { String cleaned = text .replace("æ¥è¯¢", "") .replace("审æ¹", "") .replace("å¾ å", "") .replace("å表", "") .replace("å10æ¡", "") .replace("å20æ¡", "") .trim(); return cleaned.length() >= 2 ? cleaned : null; } private String extractValue(String text, String fieldName) { Matcher matcher = Pattern.compile(fieldName + "(æ¹ä¸º|ä¿®æ¹ä¸º|为|æ¯)([^ï¼ã,ï¼;\\s]+)").matcher(text); return matcher.find() ? matcher.group(2) : null; } private String extractDateValue(String text, String fieldName) { Matcher matcher = Pattern.compile(fieldName + "(æ¹ä¸º|ä¿®æ¹ä¸º|为|æ¯)(\\d{4}-\\d{2}-\\d{2})").matcher(text); return matcher.find() ? matcher.group(2) : null; } private Integer extractIntegerValue(String text, String fieldName) { Matcher matcher = Pattern.compile(fieldName + "(æ¹ä¸º|ä¿®æ¹ä¸º|为|æ¯)(\\d{1,2})").matcher(text); return matcher.find() ? Integer.parseInt(matcher.group(2)) : null; } private BigDecimal extractBigDecimalValue(String text, String fieldName) { Matcher matcher = Pattern.compile(fieldName + "(æ¹ä¸º|ä¿®æ¹ä¸º|为|æ¯)(\\d+(\\.\\d+)?)").matcher(text); return matcher.find() ? new BigDecimal(matcher.group(2)) : null; } private String extractTail(String text, String key) { Matcher matcher = Pattern.compile(key + "(æ¯|为|ï¼|:)?(.+)").matcher(text); return matcher.find() ? matcher.group(2).trim() : null; } private String missingApproveId(String type, String description) { Map<String, Object> result = new LinkedHashMap<>(); result.put("success", false); result.put("type", type); result.put("description", description); result.put("summary", Map.of()); result.put("data", Map.of()); result.put("charts", Map.of()); return JSON.toJSONString(result); } } src/main/java/com/ruoyi/ai/assistant/Assistant.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,16 @@ package com.ruoyi.ai.assistant; import dev.langchain4j.service.spring.AiService; import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT; /** * @author :yys * @date : 2025/5/2 18:26 */ //å 为æä»¬å¨é ç½®æä»¶ä¸åæ¶é ç½®äºå¤ä¸ªå¤§è¯è¨æ¨¡åï¼æä»¥éè¦å¨è¿éæç¡®æå®ï¼EXPLICITï¼æ¨¡åçbeanName @AiService(wiringMode = EXPLICIT, chatModel = "qwenChatModel") public interface Assistant { String chat(String userMessage); } src/main/java/com/ruoyi/ai/assistant/SeparateChatAssistant.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,35 @@ package com.ruoyi.ai.assistant; import dev.langchain4j.service.MemoryId; import dev.langchain4j.service.SystemMessage; import dev.langchain4j.service.UserMessage; import dev.langchain4j.service.V; import dev.langchain4j.service.spring.AiService; import dev.langchain4j.service.spring.AiServiceWiringMode; /** * @author :yys * @date : 2025/5/2 19:35 */ @AiService( wiringMode = AiServiceWiringMode.EXPLICIT, chatModel = "qwenChatModel", chatMemoryProvider = "chatMemoryProvider" ) public interface SeparateChatAssistant { @SystemMessage(fromResource = "my-prompt-template.txt")//ç³»ç»æ¶æ¯æç¤ºè¯ String chat(@MemoryId String memoryId, @UserMessage String userMessage); @UserMessage("ä½ æ¯æç好æåï¼è¯·ç¨ç²¤è¯åçé®é¢ã{{message}}") String chat2(@MemoryId String memoryId, @V("message") String userMessage); @SystemMessage(fromResource = "my-prompt-template3.txt") String chat3( @MemoryId String memoryId, @UserMessage String userMessage, @V("username") String username, @V("age") int age ); } src/main/java/com/ruoyi/ai/bean/ChatForm.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,15 @@ package com.ruoyi.ai.bean; import lombok.Data; /** * @author :yys * @date : 2025/5/2 20:03 */ @Data public class ChatForm { private String memoryId;//对è¯id private String message;//ç¨æ·é®é¢ } src/main/java/com/ruoyi/ai/config/ApproveTodoAgentConfig.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,20 @@ package com.ruoyi.ai.config; import com.ruoyi.ai.store.MongoChatMemoryStore; import dev.langchain4j.memory.chat.ChatMemoryProvider; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ApproveTodoAgentConfig { @Bean ChatMemoryProvider chatMemoryProviderApproveTodo(MongoChatMemoryStore mongoChatMemoryStore) { return memoryId -> MessageWindowChatMemory.builder() .id(memoryId) .maxMessages(30) .chatMemoryStore(mongoChatMemoryStore) .build(); } } src/main/java/com/ruoyi/ai/config/EmbeddingStoreConfig.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,36 @@ package com.ruoyi.ai.config; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.store.embedding.EmbeddingStore; import dev.langchain4j.store.embedding.pinecone.PineconeEmbeddingStore; import dev.langchain4j.store.embedding.pinecone.PineconeServerlessIndexConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author :yys * @date : 2025/5/2 21:07 */ @Configuration public class EmbeddingStoreConfig { @Autowired private EmbeddingModel embeddingModel; @Bean public EmbeddingStore<TextSegment> embeddingStore() { //å建åéåå¨ return PineconeEmbeddingStore.builder() .apiKey("pcsk_4SJLnh_tNB3wSLJU8tc4E5P28PcXX8eCLdURqZpVhg1FMV8CRYxjneWdzqRdB5Ftqooi9") .index("xiaozhi-index")//妿æå®çç´¢å¼ä¸åå¨ï¼å°å建ä¸ä¸ªæ°çç´¢å¼ .nameSpace("xiaozhi-namespace") //妿æå®çå称空é´ä¸åå¨ï¼å°å建ä¸ä¸ªæ°çåç§° ç©ºé´ .createIndex(PineconeServerlessIndexConfig.builder() .cloud("AWS") //æå®ç´¢å¼é¨ç½²å¨ AWS äºæå¡ä¸ã .region("us-east-1") //æå®ç´¢å¼æå¨ç AWS åºå为 us-east-1ã .dimension(embeddingModel.dimension()) //æå®ç´¢å¼çåé维度ï¼è¯¥ç»´åº¦ä¸ embeddedModel çæçåé维度ç¸åã .build()) .build(); } } src/main/java/com/ruoyi/ai/config/SeparateChatAssistantConfig.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,29 @@ package com.ruoyi.ai.config; import com.ruoyi.ai.store.MongoChatMemoryStore; import dev.langchain4j.memory.chat.ChatMemoryProvider; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author :yys * @date : 2025/5/2 19:20 */ @Configuration public class SeparateChatAssistantConfig { //æ³¨å ¥æä¹ å对象 @Autowired private MongoChatMemoryStore mongoChatMemoryStore; @Bean ChatMemoryProvider chatMemoryProvider() { return memoryId -> MessageWindowChatMemory.builder() .id(memoryId) .maxMessages(10) .chatMemoryStore(mongoChatMemoryStore)//é ç½®æä¹ å对象 .build(); } } src/main/java/com/ruoyi/ai/config/XiaozhiAgentConfig.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,100 @@ package com.ruoyi.ai.config; import com.ruoyi.ai.store.MongoChatMemoryStore; import dev.langchain4j.data.document.Document; import dev.langchain4j.data.document.loader.FileSystemDocumentLoader; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.memory.chat.ChatMemoryProvider; import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.rag.content.retriever.ContentRetriever; import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever; import dev.langchain4j.store.embedding.EmbeddingStore; import dev.langchain4j.store.embedding.EmbeddingStoreIngestor; import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.Collections; import java.util.List; /** * @author :yys * @date : 2025/5/2 20:01 */ @Configuration public class XiaozhiAgentConfig { @Autowired private MongoChatMemoryStore mongoChatMemoryStore; @Autowired private EmbeddingStore embeddingStore; @Autowired private EmbeddingModel embeddingModel; @Value("${knowledge.one}") private String one; // // @Value("${knowledge.two}") // private String two; // // @Value("${knowledge.three}") // private String three; @Bean ChatMemoryProvider chatMemoryProviderXiaozhi() { return memoryId -> MessageWindowChatMemory.builder() .id(memoryId) .maxMessages(20) .chatMemoryStore(mongoChatMemoryStore) .build(); } @Bean ContentRetriever contentRetrieverXiaozhi() { //使ç¨FileSystemDocumentLoader读åæå®ç®å½ä¸çç¥è¯åºææ¡£ //并使ç¨é»è®¤çææ¡£è§£æå¨å¯¹ææ¡£è¿è¡è§£æ Document document1 = FileSystemDocumentLoader.loadDocument(one); // Document document2 = FileSystemDocumentLoader.loadDocument(two); // Document document3 = FileSystemDocumentLoader.loadDocument(three); // List<Document> documents = Arrays.asList(document1, document2, document3); List<Document> documents = Collections.singletonList(document1); // 2. å°æ°æ®åºæ°æ®è½¬ä¸ºLangChain4jçDocument对象 // List<Document> documents = new ArrayList<>(); //使ç¨å ååéåå¨ InMemoryEmbeddingStore<TextSegment> inMemoryEmbeddingStore = new InMemoryEmbeddingStore<>(); //使ç¨é»è®¤çææ¡£åå²å¨ EmbeddingStoreIngestor.builder() .embeddingModel(embeddingModel) .embeddingStore(inMemoryEmbeddingStore) .build() .ingest(documents); //ä»åµå ¥åå¨ï¼EmbeddingStoreï¼éæ£ç´¢åæ¥è¯¢å 容ç¸å ³çä¿¡æ¯ return EmbeddingStoreContentRetriever.builder() .embeddingModel(embeddingModel) .embeddingStore(inMemoryEmbeddingStore) .build(); } @Bean ContentRetriever contentRetrieverXiaozhiPincone() { // å建ä¸ä¸ª EmbeddingStoreContentRetriever 对象ï¼ç¨äºä»åµå ¥åå¨ä¸æ£ç´¢å 容 return EmbeddingStoreContentRetriever .builder() // 设置ç¨äºçæåµå ¥åéçåµå ¥æ¨¡å .embeddingModel(embeddingModel) // æå®è¦ä½¿ç¨çåµå ¥åå¨ .embeddingStore(embeddingStore) // 设置æå¤§æ£ç´¢ç»ææ°éï¼è¿é表示æå¤è¿å 1 æ¡å¹é ç»æ .maxResults(1) // 设置æå°å¾åéå¼ï¼åªæå¾å大äºçäº 0.8 çç»ææä¼è¢«è¿å .minScore(0.8) // æå»ºæç»ç EmbeddingStoreContentRetriever å®ä¾ .build(); } } src/main/java/com/ruoyi/ai/controller/XiaozhiController.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,36 @@ package com.ruoyi.ai.controller; import com.ruoyi.ai.assistant.ApproveTodoAgent; import com.ruoyi.ai.assistant.ApproveTodoIntentExecutor; import com.ruoyi.ai.bean.ChatForm; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; @Tag(name = "åååå ¬å©æ") @RestController @RequestMapping("/xiaozhi") public class XiaozhiController { private final ApproveTodoAgent approveTodoAgent; private final ApproveTodoIntentExecutor approveTodoIntentExecutor; public XiaozhiController(ApproveTodoAgent approveTodoAgent, ApproveTodoIntentExecutor approveTodoIntentExecutor) { this.approveTodoAgent = approveTodoAgent; this.approveTodoIntentExecutor = approveTodoIntentExecutor; } @Operation(summary = "对è¯") @PostMapping(value = "/chat", produces = "text/stream;charset=utf-8") public Flux<String> chat(@RequestBody ChatForm chatForm) { String directResponse = approveTodoIntentExecutor.tryExecute(chatForm.getMessage()); if (directResponse != null) { return Flux.just(directResponse); } return approveTodoAgent.chat(chatForm.getMemoryId(), chatForm.getMessage()); } } src/main/java/com/ruoyi/ai/mongodbBean/ChatMessages.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,27 @@ package com.ruoyi.ai.mongodbBean; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.bson.types.ObjectId; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; /** * @author :yys * @date : 2025/5/2 19:13 */ @Data @AllArgsConstructor @NoArgsConstructor @Document("chat_messages") public class ChatMessages { //å¯ä¸æ è¯ï¼æ å°å° MongoDB ææ¡£ç _id åæ®µ @Id private ObjectId id; private String messageId; private String content; //åå¨å½åè天记å½å表çjsonå符串 } src/main/java/com/ruoyi/ai/store/MongoChatMemoryStore.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,51 @@ package com.ruoyi.ai.store; import com.ruoyi.ai.mongodbBean.ChatMessages; import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.data.message.ChatMessageDeserializer; import dev.langchain4j.data.message.ChatMessageSerializer; import dev.langchain4j.store.memory.chat.ChatMemoryStore; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; import org.springframework.stereotype.Component; import java.util.LinkedList; import java.util.List; /** * @author :yys * @date : 2025/5/2 19:18 */ @Component public class MongoChatMemoryStore implements ChatMemoryStore { @Autowired private MongoTemplate mongoTemplate; @Override public List<ChatMessage> getMessages(Object memoryId) { Criteria criteria = Criteria.where("memoryId").is(memoryId); Query query = new Query(criteria); ChatMessages chatMessages = mongoTemplate.findOne(query, ChatMessages.class); if(chatMessages == null) return new LinkedList<>(); return ChatMessageDeserializer.messagesFromJson(chatMessages.getContent()); } @Override public void updateMessages(Object memoryId, List<ChatMessage> messages) { Criteria criteria = Criteria.where("memoryId").is(memoryId); Query query = new Query(criteria); Update update = new Update(); update.set("content", ChatMessageSerializer.messagesToJson(messages)); //æ ¹æ®queryæ¡ä»¶è½æ¥è¯¢åºææ¡£ï¼åä¿®æ¹ææ¡£ï¼å¦åæ°å¢ææ¡£ mongoTemplate.upsert(query, update, ChatMessages.class); } @Override public void deleteMessages(Object memoryId) { Criteria criteria = Criteria.where("memoryId").is(memoryId); Query query = new Query(criteria); mongoTemplate.remove(query, ChatMessages.class); } } src/main/java/com/ruoyi/ai/tools/ApproveTodoTools.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,751 @@ package com.ruoyi.ai.tools; import com.alibaba.fastjson2.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.ruoyi.approve.mapper.ApproveLogMapper; import com.ruoyi.approve.mapper.ApproveNodeMapper; import com.ruoyi.approve.mapper.ApproveProcessMapper; import com.ruoyi.approve.pojo.ApproveLog; import com.ruoyi.approve.pojo.ApproveNode; import com.ruoyi.approve.pojo.ApproveProcess; import dev.langchain4j.agent.tool.P; import dev.langchain4j.agent.tool.Tool; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import java.math.BigDecimal; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; @Component public class ApproveTodoTools { private static final int DEFAULT_LIMIT = 10; private static final int MAX_LIMIT = 20; private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private final ApproveProcessMapper approveProcessMapper; private final ApproveNodeMapper approveNodeMapper; private final ApproveLogMapper approveLogMapper; public ApproveTodoTools( ApproveProcessMapper approveProcessMapper, ApproveNodeMapper approveNodeMapper, ApproveLogMapper approveLogMapper) { this.approveProcessMapper = approveProcessMapper; this.approveNodeMapper = approveNodeMapper; this.approveLogMapper = approveLogMapper; } @Tool(name = "æ¥è¯¢å®¡æ¹å¾ åå表", value = "æ¥è¯¢å®¡æ¹å¾ åï¼æ¯ææç¶æãç±»åãå ³é®åè¿æ»¤ï¼è¿å Markdown è¡¨æ ¼ã") public String listTodos( @P(value = "审æ¹ç¶æï¼å¯éå¼ï¼allãpendingãprocessingãapprovedãrejectedãresubmitted", required = false) String status, @P(value = "审æ¹ç±»åç¼å·ï¼å¯ä¸ä¼ ", required = false) Integer approveType, @P(value = "å ³é®åï¼å¯å¹é æµç¨ç¼å·ãæ é¢ãç³è¯·äººãå½å审æ¹äºº", required = false) String keyword, @P(value = "è¿åæ¡æ°ï¼é»è®¤10ï¼æå¤§20", required = false) Integer limit) { LambdaQueryWrapper<ApproveProcess> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(ApproveProcess::getApproveDelete, 0); Integer statusCode = parseStatus(status); if (statusCode != null) { wrapper.eq(ApproveProcess::getApproveStatus, statusCode); } if (approveType != null) { wrapper.eq(ApproveProcess::getApproveType, approveType); } if (StringUtils.hasText(keyword)) { wrapper.and(w -> w.like(ApproveProcess::getApproveId, keyword) .or().like(ApproveProcess::getApproveReason, keyword) .or().like(ApproveProcess::getApproveUserName, keyword) .or().like(ApproveProcess::getApproveUserCurrentName, keyword)); } wrapper.orderByDesc(ApproveProcess::getCreateTime); wrapper.last("limit " + normalizeLimit(limit)); List<ApproveProcess> processes = approveProcessMapper.selectList(wrapper); if (processes == null || processes.isEmpty()) { return jsonResponse( true, "todo_list", "æªæ¥è¯¢å°ç¬¦åæ¡ä»¶ç审æ¹å¾ åã", Map.of("count", 0), Map.of( "columns", List.of("approveId", "approveType", "approveUserName", "approveUserCurrentName", "approveReason", "approveStatus", "createTime"), "items", List.of() ), Map.of() ); } List<Map<String, Object>> items = processes.stream().map(process -> { Map<String, Object> item = new LinkedHashMap<>(); item.put("approveId", process.getApproveId()); item.put("approveType", approveTypeName(process.getApproveType())); item.put("approveUserName", process.getApproveUserName()); item.put("approveUserCurrentName", process.getApproveUserCurrentName()); item.put("approveReason", process.getApproveReason()); item.put("approveStatus", approveStatusName(process.getApproveStatus())); item.put("createTime", formatDateTime(process.getCreateTime())); return item; }).collect(Collectors.toList()); return jsonResponse( true, "todo_list", "å·²è¿å审æ¹å¾ åå表ï¼å¯ç´æ¥æ¸²æè¡¨æ ¼æå¡çã", Map.of( "count", items.size(), "statusFilter", status == null ? "all" : status, "approveType", approveType == null ? "" : approveType, "keyword", keyword == null ? "" : keyword ), Map.of( "columns", List.of("approveId", "approveType", "approveUserName", "approveUserCurrentName", "approveReason", "approveStatus", "createTime"), "items", items ), Map.of() ); } @Tool(name = "æ¥è¯¢å®¡æ¹å¾ å详æ ", value = "æ ¹æ®æµç¨ç¼å·æ¥è¯¢åæ¡å®¡æ¹å¾ å详æ ï¼è¿åç»æåææ¬ã") public String getTodoDetail(@P("æµç¨ç¼å· approveId") String approveId) { ApproveProcess process = getProcessByApproveId(approveId); if (process == null) { return "æªæ¾å°å¯¹åºçå®¡æ¹æµç¨ï¼è¯·ç¡®è®¤æµç¨ç¼å·æ¯å¦æ£ç¡®ã"; } StringJoiner detail = new StringJoiner("\n"); detail.add("审æ¹è¯¦æ "); detail.add("æµç¨ç¼å·: " + safe(process.getApproveId())); detail.add("审æ¹ç±»å: " + approveTypeName(process.getApproveType())); detail.add("ç³è¯·äºº: " + safe(process.getApproveUserName())); detail.add("ç³è¯·é¨é¨: " + safe(process.getApproveDeptName())); detail.add("å½å审æ¹äºº: " + safe(process.getApproveUserCurrentName())); detail.add("æ é¢: " + safe(process.getApproveReason())); detail.add("ç¶æ: " + approveStatusName(process.getApproveStatus())); detail.add("ç³è¯·æ¥æ: " + formatDate(process.getApproveTime())); detail.add("å¼å§æ¥æ: " + formatDate(process.getStartDate())); detail.add("ç»ææ¥æ: " + formatDate(process.getEndDate())); detail.add("å°ç¹: " + safe(process.getLocation())); detail.add("éé¢: " + (process.getPrice() == null ? "" : process.getPrice().toPlainString())); detail.add("夿³¨: " + safe(process.getApproveRemark())); detail.add("å建æ¶é´: " + formatDateTime(process.getCreateTime())); return detail.toString(); } @Tool(name = "æ¥è¯¢å®¡æ¹æµè½¬è®°å½", value = "æ ¹æ®æµç¨ç¼å·æ¥è¯¢å®¡æ¹èç¹åå®¡æ¹æ¥å¿ï¼éååçè¿åº¦ãå¡å¨åªä¸ªèç¹ãè°å¤çè¿ã") public String getTodoProgress(@P("æµç¨ç¼å· approveId") String approveId) { ApproveProcess process = getProcessByApproveId(approveId); if (process == null) { return jsonResponse( false, "todo_progress", "æªæ¾å°å¯¹åºçå®¡æ¹æµç¨ï¼è¯·ç¡®è®¤æµç¨ç¼å·æ¯å¦æ£ç¡®ã", Map.of("approveId", approveId == null ? "" : approveId), Map.of(), Map.of() ); } List<ApproveNode> nodes = listNodes(process); List<ApproveLog> logs = listLogs(process.getId()); List<Map<String, Object>> nodeItems = nodes.stream().map(node -> { Map<String, Object> item = new LinkedHashMap<>(); item.put("approveNodeOrder", node.getApproveNodeOrder()); item.put("approveNodeUser", node.getApproveNodeUser()); item.put("approveNodeStatus", approveNodeStatusName(node.getApproveNodeStatus())); item.put("approveNodeTime", formatDate(node.getApproveNodeTime())); item.put("approveNodeReason", node.getApproveNodeReason()); item.put("approveNodeRemark", node.getApproveNodeRemark()); return item; }).collect(Collectors.toList()); List<Map<String, Object>> logItems = logs.stream().map(log -> { Map<String, Object> item = new LinkedHashMap<>(); item.put("approveNodeOrder", log.getApproveNodeOrder()); item.put("approveUser", log.getApproveUser()); item.put("approveStatus", approveStatusName(log.getApproveStatus())); item.put("approveTime", formatDate(log.getApproveTime())); item.put("approveRemark", log.getApproveRemark()); return item; }).collect(Collectors.toList()); return jsonResponse( true, "todo_progress", "å·²è¿åå®¡æ¹æµè½¬è®°å½ï¼å¯æ¸²ææ¶é´çº¿ãæ¥éª¤æ¡åæ¥å¿è¡¨æ ¼ã", Map.of( "approveId", process.getApproveId(), "currentStatus", approveStatusName(process.getApproveStatus()), "currentApprover", safe(process.getApproveUserCurrentName()), "nodeCount", nodeItems.size(), "logCount", logItems.size() ), Map.of( "nodes", nodeItems, "logs", logItems ), Map.of() ); } @Tool(name = "ç»è®¡å®¡æ¹å¾ åæ°æ®", value = "è¿åä¸ä¸åç»è®¡ç»æï¼å å«æè¦ææ åå¯ç´æ¥ç¨äº ECharts çå¾è¡¨ optionã") public String getTodoStats() { List<ApproveProcess> processes = approveProcessMapper.selectList(new LambdaQueryWrapper<ApproveProcess>() .eq(ApproveProcess::getApproveDelete, 0)); if (processes == null || processes.isEmpty()) { return jsonResponse( true, "todo_stats", "å½å没æå®¡æ¹å¾ åæ°æ®ã", Map.of("total", 0), Map.of( "statusDistribution", Map.of(), "typeDistribution", Map.of(), "recent7DayTrend", List.of(), "tips", List.of() ), Map.of() ); } Map<String, Long> statusStats = processes.stream() .collect(Collectors.groupingBy(p -> approveStatusName(p.getApproveStatus()), LinkedHashMap::new, Collectors.counting())); Map<String, Long> typeStats = processes.stream() .collect(Collectors.groupingBy(p -> approveTypeName(p.getApproveType()), LinkedHashMap::new, Collectors.counting())); long pendingCount = countByStatus(processes, 0); long processingCount = countByStatus(processes, 1); long approvedCount = countByStatus(processes, 2); long rejectedCount = countByStatus(processes, 3); long resubmittedCount = countByStatus(processes, 4); List<String> recentDates = buildRecentDates(7); List<Long> trendValues = recentDates.stream() .map(date -> processes.stream() .filter(process -> process.getCreateTime() != null) .filter(process -> process.getCreateTime().toLocalDate().equals(LocalDate.parse(date))) .count()) .collect(Collectors.toList()); Map<String, Object> summary = new LinkedHashMap<>(); summary.put("total", processes.size()); summary.put("pending", pendingCount); summary.put("processing", processingCount); summary.put("approved", approvedCount); summary.put("rejected", rejectedCount); summary.put("resubmitted", resubmittedCount); summary.put("approvalCompletionRate", calculateRate(approvedCount, processes.size())); summary.put("rejectionRate", calculateRate(rejectedCount, processes.size())); Map<String, Object> charts = new LinkedHashMap<>(); charts.put("statusBarOption", buildStatusBarOption(statusStats)); charts.put("typePieOption", buildTypePieOption(typeStats)); charts.put("recentTrendLineOption", buildRecentTrendLineOption(recentDates, trendValues)); return jsonResponse( true, "todo_stats", "å·²è¿å审æ¹ç»è®¡æ¦è§ä¸å¾è¡¨é ç½®ï¼å端å¯ç´æ¥ä½¿ç¨ charts ä¸ç ECharts option 渲æã", summary, Map.of( "statusDistribution", statusStats, "typeDistribution", typeStats, "recent7DayTrend", toTrendItems(recentDates, trendValues), "tips", List.of( "statusBarOption éåå±ç¤ºå审æ¹ç¶ææ°é对æ¯", "typePieOption éåå±ç¤ºå审æ¹ç±»åå æ¯", "recentTrendLineOption éåå±ç¤ºæè¿7天æ°å¢å®¡æ¹è¶å¿" ) ), charts ); } @Transactional @Tool(name = "审æ¹å¾ å", value = "æ§è¡å®¡æ¹å¨ä½ãaction åªæ¯æ approve æ rejectãapprove 表示éè¿ï¼reject 表示驳åã") public String reviewTodo( @P("æµç¨ç¼å· approveId") String approveId, @P("å¨ä½ï¼approve=éè¿ï¼reject=驳å") String action, @P(value = "审æ¹å¤æ³¨ï¼å¯ä¸ä¼ ", required = false) String remark) { ApproveProcess process = getProcessByApproveId(approveId); if (process == null) { return actionResult(false, "review_action", "æªæ¾å°å¯¹åºå®¡æ¹æµç¨ã", approveId, null); } if (process.getApproveDelete() != null && process.getApproveDelete() == 1) { return actionResult(false, "review_action", "è¯¥å®¡æ¹æµç¨å·²å é¤ï¼ä¸è½åå®¡æ ¸ã", approveId, null); } if (process.getApproveStatus() != null && (process.getApproveStatus() == 2 || process.getApproveStatus() == 3)) { return actionResult(false, "review_action", "è¯¥å®¡æ¹æµç¨å·²ç»æï¼ä¸è½éå¤å®¡æ ¸ã", approveId, null); } List<ApproveNode> nodes = listNodes(process); ApproveNode currentNode = findCurrentNode(nodes); if (currentNode == null) { return actionResult(false, "review_action", "æªæ¾å°å¯å®¡æ ¸çå½åèç¹ã", approveId, null); } String normalizedAction = action == null ? "" : action.trim().toLowerCase(); Date now = new Date(); currentNode.setApproveNodeTime(now); currentNode.setUpdateTime(LocalDateTime.now()); ApproveLog log = new ApproveLog(); log.setApproveId(process.getId()); log.setApproveNodeOrder(currentNode.getApproveNodeOrder()); log.setApproveUser(currentNode.getApproveNodeUserId()); log.setApproveTime(now); log.setApproveRemark(remark); if ("approve".equals(normalizedAction)) { currentNode.setApproveNodeStatus(1); currentNode.setApproveNodeReason(null); approveNodeMapper.updateById(currentNode); ApproveNode nextNode = findNextNode(nodes, currentNode.getApproveNodeOrder()); if (nextNode == null) { process.setApproveStatus(2); process.setApproveOverTime(now); process.setApproveUserCurrentId(null); process.setApproveUserCurrentName(null); } else { process.setApproveStatus(1); process.setApproveUserCurrentId(nextNode.getApproveNodeUserId()); process.setApproveUserCurrentName(nextNode.getApproveNodeUser()); } process.setApproveRemark(remark); approveProcessMapper.updateById(process); log.setApproveStatus(nextNode == null ? 2 : 1); approveLogMapper.insert(log); return actionResult(true, "review_action", nextNode == null ? "审æ¹å·²éè¿ï¼ä¸è¯¥æµç¨å·²å ¨é¨å®æã" : "审æ¹å·²éè¿ï¼æµç¨å·²æµè½¬å°ä¸ä¸å®¡æ¹èç¹ã", approveId, Map.of( "action", "approve", "currentStatus", approveStatusName(process.getApproveStatus()), "nextApprover", nextNode == null ? "" : safe(nextNode.getApproveNodeUser()), "remark", safe(remark) )); } if ("reject".equals(normalizedAction)) { currentNode.setApproveNodeStatus(2); currentNode.setApproveNodeReason(remark); currentNode.setApproveNodeRemark(remark); approveNodeMapper.updateById(currentNode); process.setApproveStatus(3); process.setApproveOverTime(now); process.setApproveUserCurrentId(null); process.setApproveUserCurrentName(null); process.setApproveRemark(remark); approveProcessMapper.updateById(process); log.setApproveStatus(3); approveLogMapper.insert(log); return actionResult(true, "review_action", "审æ¹å·²é©³åã", approveId, Map.of( "action", "reject", "currentStatus", approveStatusName(process.getApproveStatus()), "remark", safe(remark) )); } return actionResult(false, "review_action", "action åªæ¯æ approve æ rejectã", approveId, null); } @Transactional @Tool(name = "忶审æ¹å¾ åå®¡æ ¸", value = "æ¤éæè¿ä¸æ¬¡å®¡æ ¸ç»æï¼å°æè¿å®¡æ ¸èç¹æ¢å¤ä¸ºæªå®¡æ ¸ï¼å¹¶åæ»æµç¨ç¶æã") public String cancelReviewTodo( @P("æµç¨ç¼å· approveId") String approveId, @P(value = "åæ¶åå ï¼å¯ä¸ä¼ ", required = false) String reason) { ApproveProcess process = getProcessByApproveId(approveId); if (process == null) { return actionResult(false, "cancel_review_action", "æªæ¾å°å¯¹åºå®¡æ¹æµç¨ã", approveId, null); } List<ApproveNode> nodes = listNodes(process); ApproveNode lastReviewedNode = nodes.stream() .filter(node -> node.getApproveNodeStatus() != null && node.getApproveNodeStatus() != 0) .max(Comparator.comparing(ApproveNode::getApproveNodeOrder)) .orElse(null); if (lastReviewedNode == null) { return actionResult(false, "cancel_review_action", "å½åæµç¨æ²¡æå¯åæ¶çå®¡æ ¸è®°å½ã", approveId, null); } lastReviewedNode.setApproveNodeStatus(0); lastReviewedNode.setApproveNodeTime(null); lastReviewedNode.setApproveNodeReason(null); lastReviewedNode.setApproveNodeRemark(reason); lastReviewedNode.setUpdateTime(LocalDateTime.now()); approveNodeMapper.updateById(lastReviewedNode); List<ApproveLog> logs = listLogs(process.getId()); ApproveLog latestLog = logs.stream() .max(Comparator.comparing(ApproveLog::getApproveNodeOrder) .thenComparing(ApproveLog::getApproveTime, Comparator.nullsLast(Date::compareTo))) .orElse(null); if (latestLog != null) { approveLogMapper.deleteById(latestLog.getId()); } ApproveNode previousReviewedNode = nodes.stream() .filter(node -> !node.getId().equals(lastReviewedNode.getId())) .filter(node -> node.getApproveNodeStatus() != null && node.getApproveNodeStatus() == 1) .max(Comparator.comparing(ApproveNode::getApproveNodeOrder)) .orElse(null); process.setApproveOverTime(null); process.setApproveRemark(reason); process.setApproveStatus(previousReviewedNode == null ? 0 : 1); process.setApproveUserCurrentId(lastReviewedNode.getApproveNodeUserId()); process.setApproveUserCurrentName(lastReviewedNode.getApproveNodeUser()); approveProcessMapper.updateById(process); return actionResult(true, "cancel_review_action", "æè¿ä¸æ¬¡å®¡æ ¸å·²åæ¶ï¼æµç¨å·²åéå°å¯¹åºå®¡æ¹èç¹ã", approveId, Map.of( "rollbackNodeOrder", lastReviewedNode.getApproveNodeOrder(), "currentStatus", approveStatusName(process.getApproveStatus()), "currentApprover", safe(process.getApproveUserCurrentName()), "reason", safe(reason) )); } @Transactional @Tool(name = "ä¿®æ¹å®¡æ¹å¾ å", value = "ä¿®æ¹å®¡æ¹ååºç¡ä¿¡æ¯ãæ¯æä¿®æ¹æ é¢ãå¼å§æ¥æãç»ææ¥æãéé¢ãå°ç¹ãç±»åå夿³¨ãæ¥ææ ¼å¼å¿ é¡»æ¯ yyyy-MM-ddã") public String updateTodo( @P("æµç¨ç¼å· approveId") String approveId, @P(value = "æ°çæ é¢ï¼å¯ä¸ä¼ ", required = false) String approveReason, @P(value = "æ°çå¼å§æ¥æ yyyy-MM-ddï¼å¯ä¸ä¼ ", required = false) String startDate, @P(value = "æ°çç»ææ¥æ yyyy-MM-ddï¼å¯ä¸ä¼ ", required = false) String endDate, @P(value = "æ°çéé¢ï¼å¯ä¸ä¼ ", required = false) BigDecimal price, @P(value = "æ°çå°ç¹ï¼å¯ä¸ä¼ ", required = false) String location, @P(value = "æ°ç审æ¹ç±»åï¼å¯ä¸ä¼ ", required = false) Integer approveType, @P(value = "æ°ç夿³¨ï¼å¯ä¸ä¼ ", required = false) String approveRemark) { ApproveProcess process = getProcessByApproveId(approveId); if (process == null) { return actionResult(false, "update_action", "æªæ¾å°å¯¹åºå®¡æ¹æµç¨ã", approveId, null); } if (!StringUtils.hasText(approveReason) && !StringUtils.hasText(startDate) && !StringUtils.hasText(endDate) && price == null && !StringUtils.hasText(location) && approveType == null && !StringUtils.hasText(approveRemark)) { return actionResult(false, "update_action", "æ²¡ææ£æµå°å¯æ´æ°çåæ®µã", approveId, null); } if (StringUtils.hasText(approveReason)) { process.setApproveReason(approveReason); } if (StringUtils.hasText(startDate)) { process.setStartDate(parseDate(startDate)); } if (StringUtils.hasText(endDate)) { process.setEndDate(parseDate(endDate)); } if (price != null) { process.setPrice(price); } if (StringUtils.hasText(location)) { process.setLocation(location); } if (approveType != null) { process.setApproveType(approveType); } if (StringUtils.hasText(approveRemark)) { process.setApproveRemark(approveRemark); } approveProcessMapper.updateById(process); return actionResult(true, "update_action", "审æ¹åå·²æ´æ°ã", approveId, Map.of( "approveReason", safe(process.getApproveReason()), "startDate", formatDate(process.getStartDate()), "endDate", formatDate(process.getEndDate()), "price", process.getPrice() == null ? "" : process.getPrice(), "location", safe(process.getLocation()), "approveType", approveTypeName(process.getApproveType()), "approveRemark", safe(process.getApproveRemark()) )); } @Transactional @Tool(name = "å é¤å®¡æ¹å¾ å", value = "å é¤å®¡æ¹æµç¨ãé»è¾å 餿µç¨è®°å½ï¼å¹¶åæ¥é»è¾å é¤å®¡æ¹èç¹ã") public String deleteTodo(@P("æµç¨ç¼å· approveId") String approveId) { ApproveProcess process = getProcessByApproveId(approveId); if (process == null) { return actionResult(false, "delete_action", "æªæ¾å°å¯¹åºå®¡æ¹æµç¨ã", approveId, null); } if (process.getApproveDelete() != null && process.getApproveDelete() == 1) { return actionResult(false, "delete_action", "è¯¥å®¡æ¹æµç¨å·²ç»æ¯å é¤ç¶æã", approveId, null); } process.setApproveDelete(1); approveProcessMapper.updateById(process); List<ApproveNode> nodes = listNodes(process); for (ApproveNode node : nodes) { node.setDeleteFlag(1); node.setUpdateTime(LocalDateTime.now()); approveNodeMapper.updateById(node); } return actionResult(true, "delete_action", "å®¡æ¹æµç¨å·²å é¤ã", approveId, Map.of( "deletedNodeCount", nodes.size(), "approveStatus", approveStatusName(process.getApproveStatus()) )); } private ApproveProcess getProcessByApproveId(String approveId) { if (!StringUtils.hasText(approveId)) { return null; } return approveProcessMapper.selectOne(new LambdaQueryWrapper<ApproveProcess>() .eq(ApproveProcess::getApproveId, approveId) .last("limit 1")); } private List<ApproveNode> listNodes(ApproveProcess process) { List<ApproveNode> nodes = approveNodeMapper.selectList(new LambdaQueryWrapper<ApproveNode>() .eq(ApproveNode::getDeleteFlag, 0) .eq(ApproveNode::getApproveProcessId, process.getApproveId()) .orderByAsc(ApproveNode::getApproveNodeOrder)); if (nodes != null && !nodes.isEmpty()) { return nodes; } List<ApproveNode> fallbackNodes = approveNodeMapper.selectList(new LambdaQueryWrapper<ApproveNode>() .eq(ApproveNode::getDeleteFlag, 0) .eq(ApproveNode::getApproveProcessId, String.valueOf(process.getId())) .orderByAsc(ApproveNode::getApproveNodeOrder)); return fallbackNodes == null ? List.of() : fallbackNodes; } private List<ApproveLog> listLogs(Long processId) { List<ApproveLog> logs = approveLogMapper.selectList(new LambdaQueryWrapper<ApproveLog>() .eq(ApproveLog::getApproveId, processId) .orderByAsc(ApproveLog::getApproveNodeOrder, ApproveLog::getApproveTime)); return logs == null ? List.of() : logs; } private ApproveNode findCurrentNode(List<ApproveNode> nodes) { return nodes.stream() .filter(node -> node.getApproveNodeStatus() != null && node.getApproveNodeStatus() == 0) .min(Comparator.comparing(ApproveNode::getApproveNodeOrder)) .orElse(null); } private ApproveNode findNextNode(List<ApproveNode> nodes, Integer currentOrder) { return nodes.stream() .filter(node -> node.getApproveNodeOrder() != null && currentOrder != null) .filter(node -> node.getApproveNodeOrder() > currentOrder) .min(Comparator.comparing(ApproveNode::getApproveNodeOrder)) .orElse(null); } private int normalizeLimit(Integer limit) { if (limit == null || limit <= 0) { return DEFAULT_LIMIT; } return Math.min(limit, MAX_LIMIT); } private Integer parseStatus(String status) { if (!StringUtils.hasText(status) || "all".equalsIgnoreCase(status)) { return null; } return switch (status.trim().toLowerCase()) { case "pending" -> 0; case "processing" -> 1; case "approved" -> 2; case "rejected" -> 3; case "resubmitted" -> 4; default -> null; }; } private String approveStatusName(Integer status) { if (status == null) { return "æªç¥"; } return switch (status) { case 0 -> "å¾ å®¡æ ¸"; case 1 -> "å®¡æ ¸ä¸"; case 2 -> "å®¡æ ¸å®æ"; case 3 -> "å®¡æ ¸æªéè¿"; case 4 -> "已鿰æäº¤"; default -> "æªç¥"; }; } private String approveNodeStatusName(Integer status) { if (status == null) { return "æªç¥"; } return switch (status) { case 0 -> "æªå®¡æ ¸"; case 1 -> "åæ"; case 2 -> "æç»"; default -> "æªç¥"; }; } private String approveTypeName(Integer type) { if (type == null) { return "æªç¥"; } return switch (type) { case 1 -> "å ¬åºç®¡ç"; case 2 -> "请å管ç"; case 3 -> "åºå·®ç®¡ç"; case 4 -> "æ¥é管ç"; case 5 -> "éè´å®¡æ¹"; case 6 -> "æ¥ä»·å®¡æ¹"; case 7 -> "å货审æ¹"; case 8 -> "å±é©ä½ä¸å®¡æ¹"; default -> "ç±»å" + type; }; } private String safe(Object value) { return value == null ? "" : String.valueOf(value).replace('\n', ' ').replace('\r', ' '); } private String formatDateTime(Object value) { if (value == null) { return ""; } if (value instanceof LocalDateTime localDateTime) { return localDateTime.format(DATE_TIME_FORMATTER); } return safe(value); } private String formatDate(Date value) { return value == null ? "" : DATE_FORMAT.format(value); } private long countByStatus(List<ApproveProcess> processes, int status) { return processes.stream() .filter(process -> process.getApproveStatus() != null) .filter(process -> process.getApproveStatus() == status) .count(); } private String calculateRate(long part, int total) { if (total <= 0) { return "0.00%"; } double rate = part * 100.0 / total; return String.format("%.2f%%", rate); } private List<String> buildRecentDates(int days) { List<String> dates = new ArrayList<>(); LocalDate today = LocalDate.now(); for (int i = days - 1; i >= 0; i--) { dates.add(today.minusDays(i).toString()); } return dates; } private List<Map<String, Object>> toTrendItems(List<String> dates, List<Long> values) { List<Map<String, Object>> items = new ArrayList<>(); for (int i = 0; i < dates.size(); i++) { Map<String, Object> item = new LinkedHashMap<>(); item.put("date", dates.get(i)); item.put("count", values.get(i)); items.add(item); } return items; } private Map<String, Object> buildStatusBarOption(Map<String, Long> statusStats) { Map<String, Object> option = new LinkedHashMap<>(); option.put("title", Map.of("text", "审æ¹ç¶æåå¸", "left", "center")); option.put("tooltip", Map.of("trigger", "axis")); option.put("xAxis", Map.of("type", "category", "data", new ArrayList<>(statusStats.keySet()))); option.put("yAxis", Map.of("type", "value")); option.put("series", List.of(Map.of( "name", "æ°é", "type", "bar", "data", new ArrayList<>(statusStats.values()), "barWidth", "40%" ))); return option; } private Map<String, Object> buildTypePieOption(Map<String, Long> typeStats) { List<Map<String, Object>> data = typeStats.entrySet().stream() .map(entry -> { Map<String, Object> item = new LinkedHashMap<>(); item.put("name", entry.getKey()); item.put("value", entry.getValue()); return item; }) .collect(Collectors.toList()); Map<String, Object> option = new LinkedHashMap<>(); option.put("title", Map.of("text", "审æ¹ç±»åå æ¯", "left", "center")); option.put("tooltip", Map.of("trigger", "item")); option.put("legend", Map.of("orient", "vertical", "left", "left")); option.put("series", List.of(Map.of( "name", "审æ¹ç±»å", "type", "pie", "radius", List.of("35%", "65%"), "data", data ))); return option; } private Map<String, Object> buildRecentTrendLineOption(List<String> dates, List<Long> values) { Map<String, Object> option = new LinkedHashMap<>(); option.put("title", Map.of("text", "æè¿7å¤©å®¡æ¹æ°å¢è¶å¿", "left", "center")); option.put("tooltip", Map.of("trigger", "axis")); option.put("xAxis", Map.of("type", "category", "data", dates)); option.put("yAxis", Map.of("type", "value")); option.put("series", List.of(Map.of( "name", "æ°å¢å®¡æ¹", "type", "line", "smooth", true, "data", values, "areaStyle", Map.of() ))); return option; } private Date parseDate(String dateText) { try { return DATE_FORMAT.parse(dateText); } catch (ParseException e) { throw new IllegalArgumentException("æ¥ææ ¼å¼å¿ é¡»æ¯ yyyy-MM-dd"); } } private String actionResult(boolean success, String type, String description, String approveId, Map<String, Object> data) { Map<String, Object> summary = new LinkedHashMap<>(); summary.put("approveId", approveId == null ? "" : approveId); return jsonResponse(success, type, description, summary, data == null ? Map.of() : data, Map.of()); } private String jsonResponse( boolean success, String type, String description, Map<String, Object> summary, Map<String, Object> data, Map<String, Object> charts) { Map<String, Object> result = new LinkedHashMap<>(); result.put("success", success); result.put("type", type); result.put("description", description); result.put("summary", summary == null ? Map.of() : summary); result.put("data", data == null ? Map.of() : data); result.put("charts", charts == null ? Map.of() : charts); return JSON.toJSONString(result); } } src/main/resources/application-dev.yml
@@ -143,31 +143,34 @@ restart: # çé¨ç½²å¼å ³ enabled: false # redis é ç½® redis: # å°å host: 127.0.0.1 # host: 172.17.0.1 # 端å£ï¼é»è®¤ä¸º6379 port: 6379 # æ°æ®åºç´¢å¼ database: 0 # å¯ç # password: root2022! password: data: mongodb: uri: mongodb://114.132.189.42:9028/chat_memory_db # redis é ç½® redis: # å°å host: 127.0.0.1 # host: 172.17.0.1 # 端å£ï¼é»è®¤ä¸º6379 port: 6379 # æ°æ®åºç´¢å¼ database: 0 # å¯ç # password: root2022! password: # è¿æ¥è¶ æ¶æ¶é´ timeout: 10s lettuce: pool: # è¿æ¥æ± ä¸çæå°ç©ºé²è¿æ¥ min-idle: 0 # è¿æ¥æ± ä¸çæå¤§ç©ºé²è¿æ¥ max-idle: 8 # è¿æ¥æ± çæå¤§æ°æ®åºè¿æ¥æ° max-active: 8 # #è¿æ¥æ± æå¤§é»å¡çå¾ æ¶é´ï¼ä½¿ç¨è´å¼è¡¨ç¤ºæ²¡æéå¶ï¼ max-wait: -1ms # è¿æ¥è¶ æ¶æ¶é´ timeout: 10s lettuce: pool: # è¿æ¥æ± ä¸çæå°ç©ºé²è¿æ¥ min-idle: 0 # è¿æ¥æ± ä¸çæå¤§ç©ºé²è¿æ¥ max-idle: 8 # è¿æ¥æ± çæå¤§æ°æ®åºè¿æ¥æ° max-active: 8 # #è¿æ¥æ± æå¤§é»å¡çå¾ æ¶é´ï¼ä½¿ç¨è´å¼è¡¨ç¤ºæ²¡æéå¶ï¼ max-wait: -1ms # Quartz宿¶ä»»å¡é ç½®ï¼æ°å¢é¨åï¼ quartz: job-store-type: jdbc # ä½¿ç¨æ°æ®åºåå¨ src/main/resources/application.yml
@@ -4,3 +4,39 @@ allow-circular-references: true profiles: active: dev langchain4j: mcp: # MCP æå¡ç«¯å°åï¼æ ¹æ®å®é é¨ç½²ç MCP æå¡è°æ´ï¼ server-url: http://114.132.189.42:8093/ocr # 请æ±è¶ æ¶æ¶é´ï¼æ¯«ç§ï¼ timeout: 30000 # å¯éï¼MCP åè®®çæ¬ version: 1.0 community: dashscope: streaming-chat-model: api-key: sk-bd235cc13cd74e2388aa8984c84f691f model-name: "qwen-max" embedding-model: api-key: sk-9748859926b94096b920baaa12c343f5 model-name: "text-embedding-v3" chat-model: api-key: sk-9748859926b94096b920baaa12c343f5 model-name: "qwen-max" open-ai: chat-model: api-key: sk-9748859926b94096b920baaa12c343f5 base-url: "https://dashscope.aliyuncs.com/compatible-mode/v1" model-name: "deepseek-v3" log-requests: true log-responses: true temperature: 0.9 ollama: chat-model: base-url: "http://localhost:11434" model-name: "deepseek-r1:1.5b" log-requests: true log-responses: true knowledge: one: D:\æ°ç大ç½ç´ ä¼ä¸äº§åä½ç³»è¯´æææ¡£.md src/main/resources/approve-todo-agent-prompt.txt
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,13 @@ ä½ æ¯ä¸ä¸ªå®¡æ¹å¾ å婿ï¼è´è´£å®¡æ¹å¾ åçæ¥è¯¢ãå®¡æ ¸ãåæ¶å®¡æ ¸ãä¿®æ¹ãå é¤åç»è®¡åæã å·¥ä½è¦æ±ï¼ 1. ç¨æ·é®å¾ åå表ã审æ¹è¿åº¦ã审æ¹è¯¦æ ãç»è®¡æ°æ®æ¶ï¼ä¼å è°ç¨å·¥å ·ï¼ä¸è¦èé æ°æ®ã 2. ç¨æ·è¦æ±æ§è¡å®¡æ ¸ãåæ¶å®¡æ ¸ãä¿®æ¹ãå 餿¶ï¼å ç¡®è®¤å ³é®åæ°é½å ¨åè°ç¨å·¥å ·ã 3. å®¡æ ¸å¨ä½éï¼`approve` 表示éè¿ï¼`reject` 表示驳åã 4. ä¿®æ¹å®¡æ¹åæ¶ï¼å¦æç¨æ·æ²¡ææç¡®è¦ä¿®æ¹åªäºå段ï¼è¦å 追é®ç¼ºå¤±å段ï¼ä¸è¦çã 5. å é¤ãå®¡æ ¸ãåæ¶å®¡æ ¸è¿ç±»å¨ä½å±äºç¶æåæ´ï¼æ§è¡åè¦æç¡®åé¦ç»æã 6. é¤âæ¥è¯¢å®¡æ¹å¾ å详æ âå¤ï¼å ¶ä»å·¥å ·é»è®¤è¿å JSONã 7. 对äºè¿äº JSON å·¥å ·ï¼ä½ å¿ é¡»ç´æ¥è¾åºåå§ JSON å符串æ¬èº«ï¼ä¸è¦æ¹åï¼ä¸è¦é¢å¤è§£éï¼ä¸è¦å 裹 Markdown 代ç åï¼ä¸è¦å¨ JSON ååå 任使åã 8. åªæâæ¥è¯¢å®¡æ¹å¾ å详æ âè¿ä¸ªå·¥å ·å 许è¾åºèªç¶è¯è¨ææ¬ã 9. å¦æå·¥å ·è¿åçæ¯ç»è®¡ JSONï¼ä¹åæ ·ç´æ¥è¾åºåå§ JSONï¼å ¶ä¸ `description`ã`summary`ã`charts` å·²ç»ä¾å端使ç¨ã 10. åç使ç¨ä¸æï¼ä½å¨ JSON åºæ¯ä¸ï¼æç»è¾åºå¿ é¡»æ¯åæ³ JSON æ¬ä½ã src/main/resources/my-prompt-template.txt
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,2 @@ ä½ æ¯æç好æåï¼è¯·ç¨ä¸åè¯åçé®é¢ï¼åçé®é¢çæ¶åé彿·»å 表æ 符å·ã ä»å¤©æ¯ {{current_date}}ã src/main/resources/my-prompt-template3.txt
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,3 @@ ä½ æ¯æç好æåï¼ææ¯{{username}}ï¼æç年龿¯{{age}}ï¼è¯·ç¨ä¸åè¯åçé®é¢ï¼åçé®é¢çæ¶åé彿·»å 表æ 符å·ã ä»å¤©æ¯ {{current_date}}ã