diff --git a/pom.xml b/pom.xml index f737b20..bc950bf 100644 --- a/pom.xml +++ b/pom.xml @@ -93,12 +93,19 @@ spring-ai-ollama-spring-boot-starter 1.0.0-M6 - + com.github.victools jsonschema-generator 4.38.0 + + + org.springframework.ai + spring-ai-markdown-document-reader + 1.0.0-M6 + + diff --git a/src/main/java/com/huangge1199/aiagent/Service/RagService.java b/src/main/java/com/huangge1199/aiagent/Service/RagService.java new file mode 100644 index 0000000..25b87f3 --- /dev/null +++ b/src/main/java/com/huangge1199/aiagent/Service/RagService.java @@ -0,0 +1,11 @@ +package com.huangge1199.aiagent.Service; + +/** + * RagService + * + * @author huangge1199 + * @since 2025/5/24 9:21:24 + */ +public interface RagService { + String localDoc(String question); +} diff --git a/src/main/java/com/huangge1199/aiagent/Service/impl/RagServiceImpl.java b/src/main/java/com/huangge1199/aiagent/Service/impl/RagServiceImpl.java new file mode 100644 index 0000000..fd6196e --- /dev/null +++ b/src/main/java/com/huangge1199/aiagent/Service/impl/RagServiceImpl.java @@ -0,0 +1,35 @@ +package com.huangge1199.aiagent.Service.impl; + +import com.huangge1199.aiagent.Service.RagService; +import com.huangge1199.aiagent.config.MyLoggerAdvisor; +import jakarta.annotation.Resource; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.client.advisor.QuestionAnswerAdvisor; +import org.springframework.ai.vectorstore.VectorStore; +import org.springframework.stereotype.Service; + +/** + * RagServiceImpl + * + * @author huangge1199 + * @since 2025/5/24 9:21:38 + */ +@Service +public class RagServiceImpl implements RagService { + + @Resource + private ChatClient chatClient; + + @Resource + private VectorStore vectorStore; + + @Override + public String localDoc(String question) { + return chatClient.prompt() + .user(question) + .advisors(new MyLoggerAdvisor()) + .advisors(new QuestionAnswerAdvisor(vectorStore)) + .call() + .content(); + } +} diff --git a/src/main/java/com/huangge1199/aiagent/config/MyLoggerAdvisor.java b/src/main/java/com/huangge1199/aiagent/config/MyLoggerAdvisor.java new file mode 100644 index 0000000..5f22029 --- /dev/null +++ b/src/main/java/com/huangge1199/aiagent/config/MyLoggerAdvisor.java @@ -0,0 +1,66 @@ +package com.huangge1199.aiagent.config; + +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Flux; + +import org.springframework.ai.chat.client.advisor.api.AdvisedRequest; +import org.springframework.ai.chat.client.advisor.api.AdvisedResponse; +import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisor; +import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain; +import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisor; +import org.springframework.ai.chat.client.advisor.api.StreamAroundAdvisorChain; +import org.springframework.ai.chat.model.MessageAggregator; + +/** + * MyLoggerAdvisor + * 自定义日志 Advisor + * 打印 info 级别日志、只输出单次用户提示词和 AI 回复的文本 + * + * @author huangge1199 + * @since 2025/5/24 9:12:54 + */ +@Slf4j +public class MyLoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor { + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public int getOrder() { + return 0; + } + + private AdvisedRequest before(AdvisedRequest request) { + log.info("AI Request: {}", request.userText()); + return request; + } + + private void observeAfter(AdvisedResponse advisedResponse) { + log.info("AI Response: {}", advisedResponse.response().getResult().getOutput().getText()); + } + + @Override + public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) { + + advisedRequest = before(advisedRequest); + + AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest); + + observeAfter(advisedResponse); + + return advisedResponse; + } + + @Override + public Flux aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) { + + advisedRequest = before(advisedRequest); + + Flux advisedResponses = chain.nextAroundStream(advisedRequest); + + return new MessageAggregator().aggregateAdvisedResponse(advisedResponses, this::observeAfter); + } + +} diff --git a/src/main/java/com/huangge1199/aiagent/controller/RagController.java b/src/main/java/com/huangge1199/aiagent/controller/RagController.java new file mode 100644 index 0000000..26b0901 --- /dev/null +++ b/src/main/java/com/huangge1199/aiagent/controller/RagController.java @@ -0,0 +1,35 @@ +package com.huangge1199.aiagent.controller; + +import com.huangge1199.aiagent.Service.RagService; +import com.huangge1199.aiagent.common.R; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +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 java.util.UUID; + +/** + * RagController + * + * @author huangge1199 + * @since 2025/5/24 9:14:30 + */ +@RestController +@RequestMapping("/rag") +@Tag(name = "RAG相关") +public class RagController { + + @Resource + private RagService ragService; + + @PostMapping("/localDoc") + @Operation(summary = "本地知识库") + public R localDoc(@RequestBody String question) { + String res = ragService.localDoc(question); + return R.ok(res); + } +} diff --git a/src/main/java/com/huangge1199/aiagent/rag/DocumentLoaderUtils.java b/src/main/java/com/huangge1199/aiagent/rag/DocumentLoaderUtils.java new file mode 100644 index 0000000..05eaca0 --- /dev/null +++ b/src/main/java/com/huangge1199/aiagent/rag/DocumentLoaderUtils.java @@ -0,0 +1,54 @@ +package com.huangge1199.aiagent.rag; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.document.Document; +import org.springframework.ai.reader.markdown.MarkdownDocumentReader; +import org.springframework.ai.reader.markdown.config.MarkdownDocumentReaderConfig; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * DocumentLoaderUtils + * + * @author huangge1199 + * @since 2025/5/24 9:32:25 + */ +@Component +@Slf4j +public class DocumentLoaderUtils { + + private final ResourcePatternResolver resourcePatternResolver; + + public DocumentLoaderUtils(ResourcePatternResolver resourcePatternResolver) { + this.resourcePatternResolver = resourcePatternResolver; + } + + public List loadMarkdowns() { + List allDocuments = new ArrayList<>(); + try { + Resource[] resources = resourcePatternResolver.getResources("classpath:doc/*.md"); + for (Resource resource : resources) { + String filename = resource.getFilename(); + // 提取文档倒数第 3 和第 2 个字作为标签 + String status = filename.substring(filename.length() - 6, filename.length() - 4); + MarkdownDocumentReaderConfig config = MarkdownDocumentReaderConfig.builder() + .withHorizontalRuleCreateDocument(true) + .withIncludeCodeBlock(false) + .withIncludeBlockquote(false) + .withAdditionalMetadata("filename", filename) + .withAdditionalMetadata("status", status) + .build(); + MarkdownDocumentReader markdownDocumentReader = new MarkdownDocumentReader(resource, config); + allDocuments.addAll(markdownDocumentReader.get()); + } + } catch (IOException e) { + log.error("Markdown 文档加载失败", e); + } + return allDocuments; + } +} diff --git a/src/main/java/com/huangge1199/aiagent/rag/MyKeywordEnricher.java b/src/main/java/com/huangge1199/aiagent/rag/MyKeywordEnricher.java new file mode 100644 index 0000000..c2af543 --- /dev/null +++ b/src/main/java/com/huangge1199/aiagent/rag/MyKeywordEnricher.java @@ -0,0 +1,28 @@ +package com.huangge1199.aiagent.rag; + +import jakarta.annotation.Resource; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.document.Document; +import org.springframework.ai.transformer.KeywordMetadataEnricher; +import org.springframework.stereotype.Component; + +import java.util.List; + + +/** + * MyKeywordEnricher + * + * @author huangge1199 + * @since 2025/5/24 9:38:03 + */ +@Component +public class MyKeywordEnricher { + + @Resource + private ChatModel dashscopeChatModel; + + public List enrichDocuments(List documents) { + KeywordMetadataEnricher keywordMetadataEnricher = new KeywordMetadataEnricher(dashscopeChatModel, 5); + return keywordMetadataEnricher.apply(documents); + } +} diff --git a/src/main/java/com/huangge1199/aiagent/rag/MyTokenTextSplitter.java b/src/main/java/com/huangge1199/aiagent/rag/MyTokenTextSplitter.java new file mode 100644 index 0000000..9625a34 --- /dev/null +++ b/src/main/java/com/huangge1199/aiagent/rag/MyTokenTextSplitter.java @@ -0,0 +1,26 @@ +package com.huangge1199.aiagent.rag; + +import org.springframework.ai.document.Document; +import org.springframework.ai.transformer.splitter.TokenTextSplitter; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +/** + * MyTokenTextSplitter + * + * @author huangge1199 + * @since 2025/5/24 9:37:03 + */ +@Configuration +public class MyTokenTextSplitter { + public List splitDocuments(List documents) { + TokenTextSplitter splitter = new TokenTextSplitter(); + return splitter.apply(documents); + } + + public List splitCustomized(List documents) { + TokenTextSplitter splitter = new TokenTextSplitter(200, 100, 10, 5000, true); + return splitter.apply(documents); + } +} diff --git a/src/main/java/com/huangge1199/aiagent/rag/RagConfig.java b/src/main/java/com/huangge1199/aiagent/rag/RagConfig.java new file mode 100644 index 0000000..5f91e1a --- /dev/null +++ b/src/main/java/com/huangge1199/aiagent/rag/RagConfig.java @@ -0,0 +1,51 @@ +package com.huangge1199.aiagent.rag; + +import jakarta.annotation.Resource; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.document.Document; +import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.vectorstore.SimpleVectorStore; +import org.springframework.ai.vectorstore.VectorStore; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +/** + * RagConfig + * + * @author huangge1199 + * @since 2025/5/24 9:26:40 + */ +@Configuration +public class RagConfig { + + @Resource + private DocumentLoaderUtils documentLoaderUtils; + + @Resource + private MyTokenTextSplitter myTokenTextSplitter; + + @Resource + private MyKeywordEnricher myKeywordEnricher; + + @Bean + ChatClient chatClient(ChatClient.Builder builder) { + return builder.defaultSystem("你将作为一名恋爱大师,对于用户的问题作出解答") + .build(); + } + + @Bean + VectorStore vectorStore(@Qualifier("ollamaEmbeddingModel") EmbeddingModel embeddingModel) { + SimpleVectorStore simpleVectorStore = SimpleVectorStore.builder(embeddingModel).build(); + // 加载文档 + List documentList = documentLoaderUtils.loadMarkdowns(); + // 自主切分文档 + List splitDocuments = myTokenTextSplitter.splitCustomized(documentList); + // 自动补充关键词元信息 + List enrichedDocuments = myKeywordEnricher.enrichDocuments(documentList); + simpleVectorStore.add(enrichedDocuments); + return simpleVectorStore; + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 3bd909f..907fe40 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -11,7 +11,7 @@ spring: ollama: base-url: http://192.168.188.2:11435 chat: - model: DeepSeek-R1-Distill-Qwen-1.5B:latest + model: gemma3:1b server: port: 8123 servlet: diff --git a/src/main/resources/doc/恋爱常见问题和回答 - 单身篇.md b/src/main/resources/doc/恋爱常见问题和回答 - 单身篇.md new file mode 100644 index 0000000..1a710b4 --- /dev/null +++ b/src/main/resources/doc/恋爱常见问题和回答 - 单身篇.md @@ -0,0 +1,20 @@ +# 恋爱常见问题和回答 - 单身篇 +#### 如何提升自身魅力吸引潜在伴侣? +提升自身魅力需从多方面入手。外在形象上,保持良好的个人卫生,选择适合自己身材与风格的着装,定期锻炼塑造健康体魄。内在修养方面,培养广泛兴趣爱好,如阅读提升知识储备,学习乐器增添艺术气质。积极参加社交活动,锻炼沟通与表达能力,以自信、热情的态度展现自我。比如小李原本内向且不注重穿着,通过学习穿搭、参与户外运动并加入读书俱乐部,逐渐变得开朗自信,吸引了不少异性目光。 +推荐课程:[《单身魅力提升全攻略》](https://www.codefather.cn),该课程从形象打造、气质培养到社交技巧提升,全方位助力单身人士散发独特魅力,吸引心仪对象。 + +#### 怎样在社交场合主动结识心仪异性? +在社交场合,首先要保持微笑,展现亲和力。主动寻找话题切入点,比如在画展上可从对某幅作品的看法聊起。真诚地表达自己对对方的兴趣,例如“我看你对这个话题很有见解,能和我多说说吗”。注意倾听对方回应,给予专注眼神与积极反馈,让交流顺畅进行。小王在一次行业交流会上,主动与一位女生就新技术应用展开讨论,认真倾听并适时发表看法,成功交换联系方式并后续发展出感情。 +推荐课程:[《社交场合主动交友秘籍》](https://www.codefather.cn),课程通过大量实例与技巧讲解,教你在各类社交场合自信主动出击,结识优质异性。 + +#### 线上交友有哪些注意事项能提高脱单成功率? +线上交友时,务必完善真实且有吸引力的个人资料,照片选择清晰、展现个人特色的。聊天初期避免过于急切或油腻的话术,从兴趣爱好、日常趣事等轻松话题开启。保持聊天频率适中,不要过于频繁或长时间不回复。同时,注意保护个人隐私,在未深入了解对方前,不随意透露重要信息。小张通过精心打造线上资料,与潜在对象真诚交流,逐步筛选出合适人选,最终成功脱单。 +推荐课程:[《线上交友高效脱单指南》](https://www.codefather.cn),课程全面解析线上交友流程,传授实用技巧与避雷方法,助你在虚拟世界找到真爱。 + +#### 如何克服单身时对恋爱的焦虑情绪? +正视自己的焦虑情绪,分析焦虑产生原因,是担心找不到合适对象,还是对恋爱过程恐惧等。丰富自己的生活,投入工作、发展兴趣爱好,让生活充实起来,转移对恋爱的过度关注。多与亲朋好友交流,分享感受获取支持。参加心理成长课程或阅读相关书籍,学习情绪管理方法。例如小赵通过培养摄影爱好,参加摄影活动结识新朋友,生活变得丰富多彩,对恋爱的焦虑也逐渐减轻。 +推荐课程:[《战胜单身恋爱焦虑课程》](https://www.codefather.cn),课程提供专业心理疏导与实用应对策略,帮你摆脱焦虑,以轻松心态迎接爱情。 + +#### 如何判断相亲对象是否值得深入发展? +观察相亲对象的言行举止,是否尊重他人,比如对服务员的态度。交流中了解其价值观,包括对家庭、事业、生活的看法是否与你契合。关注对方的情绪稳定性,能否理性处理分歧。看其是否有明确的人生规划,对未来有清晰想法。像小钱相亲时,发现对方对生活积极向上,尊重自己的观点,且双方对未来家庭生活规划相似,于是决定深入发展。 +推荐课程:[《相亲对象评估与恋爱决策》](https://www.codefather.cn),课程从多个维度教你精准评估相亲对象,做出正确恋爱决策,少走弯路。 diff --git a/src/main/resources/doc/恋爱常见问题和回答 - 已婚篇.md b/src/main/resources/doc/恋爱常见问题和回答 - 已婚篇.md new file mode 100644 index 0000000..4476095 --- /dev/null +++ b/src/main/resources/doc/恋爱常见问题和回答 - 已婚篇.md @@ -0,0 +1,20 @@ +# 恋爱常见问题和回答 - 已婚篇 +#### 婚后如何平衡工作与家庭责任? +制定详细的日程表,合理分配工作与家庭时间,如工作日晚上预留两小时陪伴家人。与配偶共同协商家务分工,依据各自擅长领域安排,如一方擅长烹饪负责做饭,另一方擅长清洁负责打扫卫生。在工作中提高效率,减少不必要加班,重要家庭活动提前安排工作。例如老陈通过合理规划,既能在工作上取得成绩,又能照顾好家庭,家庭关系和睦。 +推荐课程:[《婚后工作家庭平衡之道》](https://www.codefather.cn),课程从时间管理、责任分配等方面入手,助你轻松应对婚后工作与家庭的双重挑战,实现和谐生活。 + +#### 怎样维护婚后夫妻间的亲密关系? +定期安排二人世界,如每周一次看电影或共进晚餐。保持身体亲密接触,日常拥抱、亲吻。分享日常喜怒哀乐,深入交流内心想法。一起回忆美好过往,如旅行经历、恋爱趣事。为对方制造小惊喜,如纪念日礼物。像老张夫妇坚持每周约会,分享生活点滴,结婚多年仍甜蜜如初。 +推荐课程:[《婚后亲密关系维护秘籍》](https://www.codefather.cn),课程提供多种维护亲密关系的方法与技巧,让婚后爱情持续升温,家庭幸福美满。 + +#### 婚后与伴侣家人产生矛盾,如何妥善解决? +保持冷静,不被情绪左右。先倾听伴侣家人想法,理解其立场。与伴侣坦诚沟通,共同分析矛盾根源。在沟通中尊重对方家人,用温和语气表达自己观点。寻求伴侣帮助,让其在中间协调。例如老王家婆媳产生矛盾,他积极倾听双方想法,与妻子共同解决,最终化解矛盾,家庭恢复和睦。 +推荐课程:[《婚后家庭关系矛盾化解课程》](https://www.codefather.cn),课程传授处理家庭矛盾的有效策略,助你巧妙化解与伴侣家人的冲突,营造和谐家庭氛围。 + +#### 如何在婚后保持自我成长,不被家庭琐事磨灭个人追求? +设定个人成长目标,如学习新技能、考取证书。利用碎片化时间学习,如在通勤路上听有声书。与伴侣商量争取其支持,共同安排家庭事务,为个人成长留出时间。参加线上或线下学习小组,保持学习动力与热情。比如老孙婚后利用业余时间学习编程,获得伴侣支持,成功转行进入新领域,实现自我价值。 +推荐课程:[《婚后个人成长与自我实现》](https://www.codefather.cn),课程指导你在婚后繁杂生活中,坚持自我成长,实现个人梦想,拥有精彩人生。 + +#### 婚后夫妻消费观念不同,如何协调理财规划? +坐下来开诚布公地交流各自消费观念与理财目标,了解对方想法。制定家庭预算,划分必要支出、储蓄、娱乐等板块。设定共同储蓄目标,如每年存下一定金额用于购房或旅行。对于大额消费共同商议决定。例如老李家夫妻通过沟通制定预算,合理规划消费与储蓄,家庭财务状况良好。 +推荐课程:[《婚后理财规划与消费协调》](https://www.codefather.cn),课程从观念沟通到实际规划,教你协调夫妻理财差异,实现家庭财富稳健增长。 \ No newline at end of file diff --git a/src/main/resources/doc/恋爱常见问题和回答 - 恋爱篇.md b/src/main/resources/doc/恋爱常见问题和回答 - 恋爱篇.md new file mode 100644 index 0000000..6356a3b --- /dev/null +++ b/src/main/resources/doc/恋爱常见问题和回答 - 恋爱篇.md @@ -0,0 +1,20 @@ +# 恋爱常见问题和回答 - 恋爱篇 +#### 恋爱中如何有效处理双方的争吵? +争吵发生时,先冷静情绪,可暂时离开现场或做几次深呼吸。之后真诚倾听对方诉求,不打断、不急于反驳。表达自己想法时,使用“我觉得”“我感受”等句式,避免指责。共同寻找争吵根源,探讨解决方案,如约定以后遇到类似问题的沟通方式。比如小孙和女友争吵后,冷静下来倾听女友需求,表达自己感受,最终两人找到更好的沟通模式,感情升温。 +推荐课程:[《恋爱争吵化解与沟通技巧》](https://www.codefather.cn),课程通过案例分析与实战演练,传授高效化解争吵、提升沟通质量的技巧,让恋爱更甜蜜。 + +#### 怎样给恋爱中的对方制造浪漫惊喜? +了解对方喜好是关键,若对方喜欢阅读,可精心挑选一本限量版书籍,附上手写情书。策划一场特别约会,比如在对方喜欢的海边看日出。日常也能制造小惊喜,如在对方下班时送上一杯热咖啡。或者为对方准备一场主题派对,布置成对方喜欢的风格。小李为女友精心策划了一场星空露营约会,让女友感动不已,感情愈发深厚。 +推荐课程:[《恋爱浪漫惊喜制造宝典》](https://www.codefather.cn),课程涵盖各类浪漫惊喜创意与实施方法,帮你为爱人打造难忘瞬间,让爱情时刻充满新鲜感。 + +#### 恋爱中如何保持自我,避免过度依赖对方? +培养独立兴趣爱好,如绘画、健身,拥有属于自己的休闲时光。保持自己的社交圈子,定期与朋友相聚。在工作和学习上设定个人目标并努力追求。遇到问题先尝试自己解决,锻炼独立思考与应对能力。例如小周在恋爱中坚持自己的舞蹈爱好,与朋友保持密切联系,工作上积极进取,既享受恋爱甜蜜,又不失自我。 +推荐课程:[《恋爱中保持自我与独立成长》](https://www.codefather.cn),课程引导你在恋爱中平衡亲密关系与个人发展,实现自我价值,让爱情更健康持久。 + +#### 如何在恋爱中与对方有效沟通未来规划? +选择合适时机,如在轻松的周末午后,心平气和开启话题。先分享自己对未来的设想,包括事业发展、家庭规划、生活愿景等,如“我希望未来几年能在事业上取得晋升,同时能多去旅行看看世界”。认真倾听对方想法,尊重差异,不强行要求一致。共同探讨如何协调双方规划,制定共同目标与阶段性计划。比如小吴和男友通过深入沟通,制定了先一起攒钱买房,再旅行的计划,感情更加稳固。 +推荐课程:[《恋爱中未来规划沟通秘籍》](https://www.codefather.cn),课程教授沟通技巧与规划协调方法,助你和伴侣明确未来方向,携手走向幸福。 + +#### 恋爱中发现对方缺点,如何巧妙沟通让其改善? +选择恰当时间地点,避免在公共场合或对方忙碌时提出。以关心的口吻开场,如“我很在意你,发现一个小问题想和你聊聊”。具体描述缺点及带来的影响,例如“你最近总是熬夜打游戏,我担心会影响你的健康和我们相处时间”。提出建设性意见,如“我们可以一起制定一个作息时间表,互相监督”。小郑用这种方式与女友沟通熬夜问题,女友欣然接受并努力改正。 +推荐课程:[《恋爱中缺点沟通与关系优化》](https://www.codefather.cn),课程讲解有效沟通策略,帮你巧妙指出对方缺点,促进双方共同成长,提升恋爱质量。