commit 19f147a6e075c8a84bcbfef3c9fdade4b57a1502 Author: yuanjs@qutke.com Date: Mon May 12 16:43:04 2025 +0800 feat: 提交 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6dd35d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +ReadMe.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e79e9fe --- /dev/null +++ b/pom.xml @@ -0,0 +1,152 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.6 + + + cn.ai + Spring-Ai-Gu + 0.0.1-SNAPSHOT + Spring-Ai-Gu + Spring-Ai-Gu + + + + 17 + 17 + UTF-8 + 3.2.6 + 4.3.0 + 1.0.0-beta3 + 3.5.11 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + ${knife4j.version} + + + + + + + + + + + + + + + dev.langchain4j + langchain4j-community-dashscope-spring-boot-starter + + + + + dev.langchain4j + langchain4j-spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + + + com.mysql + mysql-connector-j + + + + com.baomidou + mybatis-plus-spring-boot3-starter + ${mybatis-plus.version} + + + + + dev.langchain4j + langchain4j-document-parser-apache-pdfbox + + + + + dev.langchain4j + langchain4j-easy-rag + + + + dev.langchain4j + langchain4j-pinecone + + + + + org.springframework.boot + spring-boot-starter-webflux + + + dev.langchain4j + langchain4j-reactor + + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + dev.langchain4j + langchain4j-bom + ${langchain4j.version} + pom + import + + + + dev.langchain4j + langchain4j-community-bom + ${langchain4j.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/src/main/java/cn/ai/springaigu/SpringAiGuApplication.java b/src/main/java/cn/ai/springaigu/SpringAiGuApplication.java new file mode 100644 index 0000000..bd4c2df --- /dev/null +++ b/src/main/java/cn/ai/springaigu/SpringAiGuApplication.java @@ -0,0 +1,13 @@ +package cn.ai.springaigu; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringAiGuApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringAiGuApplication.class, args); + } + +} diff --git a/src/main/java/cn/ai/springaigu/asistant/Assistant.java b/src/main/java/cn/ai/springaigu/asistant/Assistant.java new file mode 100644 index 0000000..9192593 --- /dev/null +++ b/src/main/java/cn/ai/springaigu/asistant/Assistant.java @@ -0,0 +1,15 @@ +package cn.ai.springaigu.asistant; + +import dev.langchain4j.service.spring.AiService; + +import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT; + +/** + * 创建接 + */ +//因为我们在配置文件中同时配置了多个大语言模型,所以需要在这里明确指定(EXPLICIT)模型的beanName(qwenChatModel) +@AiService(wiringMode = EXPLICIT, chatModel = "qwenChatModel") +public interface Assistant { + + String chat(String userMessage); +} diff --git a/src/main/java/cn/ai/springaigu/asistant/MemoryChatAssistant.java b/src/main/java/cn/ai/springaigu/asistant/MemoryChatAssistant.java new file mode 100644 index 0000000..462e8a8 --- /dev/null +++ b/src/main/java/cn/ai/springaigu/asistant/MemoryChatAssistant.java @@ -0,0 +1,17 @@ +package cn.ai.springaigu.asistant; + +import dev.langchain4j.service.UserMessage; +import dev.langchain4j.service.spring.AiService; + +import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT; + +// 使用AIService实现聊天记忆 +@AiService( + wiringMode = EXPLICIT, + chatModel = "qwenChatModel", + chatMemory = "chatMemory" +) +public interface MemoryChatAssistant { + @UserMessage("你是我的好朋友,请用上海话回答问题,并且添加一些表情符号。 {{it}}") //{{it}}表示这里唯一的参数的占位符 + String chat(String message); +} \ No newline at end of file diff --git a/src/main/java/cn/ai/springaigu/asistant/SeparateChatAssistant.java b/src/main/java/cn/ai/springaigu/asistant/SeparateChatAssistant.java new file mode 100644 index 0000000..e35bb8b --- /dev/null +++ b/src/main/java/cn/ai/springaigu/asistant/SeparateChatAssistant.java @@ -0,0 +1,44 @@ +package cn.ai.springaigu.asistant; + +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 static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT; + +// 创建记忆隔离对话智能体 +@AiService( + wiringMode = EXPLICIT, + chatModel = "qwenChatModel", + chatMemory = "chatMemory", + chatMemoryProvider = "chatMemoryProvider", + tools = "calculatorTools" //配置tools +) +public interface SeparateChatAssistant { + /** + * 分离聊天记录 + * + * @param memoryId 聊天id + * @param userMessage 用户消息 + */ + // @SystemMessage("你是我的好朋友,请用东北话回答问题。")//系统消息提示词 + //@SystemMessage("你是我的好朋友,请用东北话回答问题。今天是{{current_date}}")//系统消息提示词,在提示词中添加当前日期的占位符{{current_date}} + @SystemMessage(fromResource = "my-prompt-template.txt") + String chat(@MemoryId int memoryId, @UserMessage String userMessage); + + + @UserMessage("你是我的好朋友,请用粤语回答问题。{{message}}") + String chat2(@MemoryId int memoryId, @V("message") String userMessage); + + + @SystemMessage(fromResource = "my-prompt-template3.txt") + String chat3( + @MemoryId int memoryId, + @UserMessage String userMessage, + @V("username") String username, + @V("age") int age + ); + +} \ No newline at end of file diff --git a/src/main/java/cn/ai/springaigu/asistant/XiaozhiAgent.java b/src/main/java/cn/ai/springaigu/asistant/XiaozhiAgent.java new file mode 100644 index 0000000..ba2dbf7 --- /dev/null +++ b/src/main/java/cn/ai/springaigu/asistant/XiaozhiAgent.java @@ -0,0 +1,20 @@ +package cn.ai.springaigu.asistant; + +import dev.langchain4j.service.*; +import dev.langchain4j.service.spring.AiService; +import reactor.core.publisher.Flux; + +import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT; + + +@AiService( + wiringMode = EXPLICIT, + //chatModel = "qwenChatModel", + streamingChatModel = "qwenStreamingChatModel", + chatMemoryProvider = "chatMemoryProviderXiaozhi", + tools = "appointmentTools", //tools配置 + contentRetriever = "contentRetrieverXiaozhiPincone") //配置向量存储 +public interface XiaozhiAgent { + @SystemMessage(fromResource = "zhaozhi-prompt-template.txt") + Flux chat(@MemoryId Long memoryId, @UserMessage String userMessage); +} \ No newline at end of file diff --git a/src/main/java/cn/ai/springaigu/config/EmbeddingStoreConfig.java b/src/main/java/cn/ai/springaigu/config/EmbeddingStoreConfig.java new file mode 100644 index 0000000..4d929c7 --- /dev/null +++ b/src/main/java/cn/ai/springaigu/config/EmbeddingStoreConfig.java @@ -0,0 +1,36 @@ +package cn.ai.springaigu.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.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class EmbeddingStoreConfig { + @Value("${pinecone.api-key}") + private String apiKey; + + @Autowired + private EmbeddingModel embeddingModel; + + @Bean + public EmbeddingStore embeddingStore() { + //创建向量存储 + EmbeddingStore embeddingStore = PineconeEmbeddingStore.builder() + .apiKey(apiKey) + .index("ai-study-index")//如果指定的索引不存在,将创建一个新的索引 + .nameSpace("ai-study-namespace") //如果指定的名称空间不存在,将创建一个新的名称 空间 + .createIndex(PineconeServerlessIndexConfig.builder() + .cloud("AWS") //指定索引部署在 AWS 云服务上。 + .region("us-east-1") //指定索引所在的 AWS 区域为 us-east-1。 + .dimension(embeddingModel.dimension()) //指定索引的向量维度,该维度与 embeddedModel 生成的向量维度相同。 + .build()) + .build(); + return embeddingStore; + } +} diff --git a/src/main/java/cn/ai/springaigu/config/MemoryChatAssistantConfig.java b/src/main/java/cn/ai/springaigu/config/MemoryChatAssistantConfig.java new file mode 100644 index 0000000..c27ff4f --- /dev/null +++ b/src/main/java/cn/ai/springaigu/config/MemoryChatAssistantConfig.java @@ -0,0 +1,16 @@ +package cn.ai.springaigu.config; + +import dev.langchain4j.memory.ChatMemory; +import dev.langchain4j.memory.chat.MessageWindowChatMemory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +// 配置ChatMemory +@Configuration +public class MemoryChatAssistantConfig { + @Bean + ChatMemory chatMemory() { + //设置聊天记忆记录的message数量 + return MessageWindowChatMemory.withMaxMessages(10); + } +} \ No newline at end of file diff --git a/src/main/java/cn/ai/springaigu/config/SeparateChatAssistantConfig.java b/src/main/java/cn/ai/springaigu/config/SeparateChatAssistantConfig.java new file mode 100644 index 0000000..e60f4b4 --- /dev/null +++ b/src/main/java/cn/ai/springaigu/config/SeparateChatAssistantConfig.java @@ -0,0 +1,24 @@ +package cn.ai.springaigu.config; + +import cn.ai.springaigu.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; + +@Configuration +public class SeparateChatAssistantConfig { + + @Autowired + private MongoChatMemoryStore mongoChatMemoryStore; + + @Bean + ChatMemoryProvider chatMemoryProvider() { + return memoryId -> MessageWindowChatMemory.builder() + .id(memoryId) + .maxMessages(10) + .chatMemoryStore(mongoChatMemoryStore)//配置持久化对象 + .build(); + } +} diff --git a/src/main/java/cn/ai/springaigu/config/XiaozhiAgentConfig.java b/src/main/java/cn/ai/springaigu/config/XiaozhiAgentConfig.java new file mode 100644 index 0000000..139e954 --- /dev/null +++ b/src/main/java/cn/ai/springaigu/config/XiaozhiAgentConfig.java @@ -0,0 +1,74 @@ +package cn.ai.springaigu.config; + +import cn.ai.springaigu.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.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Arrays; +import java.util.List; + +@Configuration +public class XiaozhiAgentConfig { + + @Autowired + private MongoChatMemoryStore mongoChatMemoryStore; + + @Bean + ChatMemoryProvider chatMemoryProviderXiaozhi() { + return memoryId -> MessageWindowChatMemory.builder() + .id(memoryId) + .maxMessages(20) + .chatMemoryStore(mongoChatMemoryStore) + .build(); + } + + @Bean + ContentRetriever contentRetrieverXiaozhi() { + //使用FileSystemDocumentLoader读取指定目录下的知识库文档 + //并使用默认的文档解析器对文档进行解析 + Document document1 = FileSystemDocumentLoader.loadDocument("D:/学习/课件/knowledge/医院信息.md"); + Document document2 = FileSystemDocumentLoader.loadDocument("D:/学习/课件/knowledge/科室信息.md"); + Document document3 = FileSystemDocumentLoader.loadDocument("D:/学习/课件/knowledge/神经内科.md"); + List documents = Arrays.asList(document1, document2, document3); + //使用内存向量存储 生产不建议使用 + InMemoryEmbeddingStore embeddingStore = new InMemoryEmbeddingStore<>(); + //使用默认的文档分割器 + EmbeddingStoreIngestor.ingest(documents, embeddingStore); + //从嵌入存储(EmbeddingStore)里检索和查询内容相关的信息 + return EmbeddingStoreContentRetriever.from(embeddingStore); + } + + @Autowired + private EmbeddingStore embeddingStore; + @Autowired + private EmbeddingModel embeddingModel; + + @Bean + ContentRetriever contentRetrieverXiaozhiPincone() { + // 创建一个 EmbeddingStoreContentRetriever 对象,用于从嵌入存储中检索内容 + return EmbeddingStoreContentRetriever + .builder() + // 设置用于生成嵌入向量的嵌入模型 + .embeddingModel(embeddingModel) + // 指定要使用的嵌入存储 + .embeddingStore(embeddingStore) + // 设置最大检索结果数量,这里表示最多返回 1 条匹配结果 + .maxResults(1) + // 设置最小得分阈值,只有得分大于等于 0.8 的结果才会被返回 + .minScore(0.8) + // 构建最终的 EmbeddingStoreContentRetriever 实例 + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/cn/ai/springaigu/controller/XiaozhiController.java b/src/main/java/cn/ai/springaigu/controller/XiaozhiController.java new file mode 100644 index 0000000..96295f6 --- /dev/null +++ b/src/main/java/cn/ai/springaigu/controller/XiaozhiController.java @@ -0,0 +1,49 @@ +package cn.ai.springaigu.controller; + +import cn.ai.springaigu.asistant.XiaozhiAgent; +import cn.ai.springaigu.domain.ChatForm; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Autowired; +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 { + @Autowired + private XiaozhiAgent xiaozhiAgent; +// @Operation(summary = "对话") +// @PostMapping("/chat") +// public String chat(@RequestBody ChatForm chatForm) { +// return xiaozhiAgent.chat(chatForm.getMemoryId(), chatForm.getMessage()); +// } + + @Operation(summary = "对话") + @PostMapping(value = "/chat", produces = "text/stream;charset=utf-8") + public Flux chat(@RequestBody ChatForm chatForm) { + return xiaozhiAgent.chat(chatForm.getMemoryId(), chatForm.getMessage()); + } +} + + +// 测试: +//{ +// "memoryId": 0, +// "message": "我想预约明天上午的神经内科" +//} +// { +// "memoryId": 1, +// "message": "帮我预约一下后天的皮肤科,信息是:翠花,523921199204234816,刘医生" +//} +//{ +// "memoryId": 1, +// "message": "对不起,临时有事,帮我取消一下预约" +//} +// 我头疼应该怎么处理 +// 姓名:姚翠花、230104190000000000、神经内科、明天下午,最好的医生。 +// 医院的位置在哪,神经内科怎么找 \ No newline at end of file diff --git a/src/main/java/cn/ai/springaigu/domain/Appointment.java b/src/main/java/cn/ai/springaigu/domain/Appointment.java new file mode 100644 index 0000000..986d265 --- /dev/null +++ b/src/main/java/cn/ai/springaigu/domain/Appointment.java @@ -0,0 +1,21 @@ +package cn.ai.springaigu.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Appointment { + @TableId(type = IdType.AUTO) + private Long id; + private String username; + private String idCard; + private String department; + private String date; + private String time; + private String doctorName; +} diff --git a/src/main/java/cn/ai/springaigu/domain/ChatForm.java b/src/main/java/cn/ai/springaigu/domain/ChatForm.java new file mode 100644 index 0000000..d62b28d --- /dev/null +++ b/src/main/java/cn/ai/springaigu/domain/ChatForm.java @@ -0,0 +1,9 @@ +package cn.ai.springaigu.domain; + +import lombok.Data; + +@Data +public class ChatForm { + private Long memoryId;//对话id + private String message;//用户问题 +} diff --git a/src/main/java/cn/ai/springaigu/domain/ChatMessages.java b/src/main/java/cn/ai/springaigu/domain/ChatMessages.java new file mode 100644 index 0000000..66dcd64 --- /dev/null +++ b/src/main/java/cn/ai/springaigu/domain/ChatMessages.java @@ -0,0 +1,21 @@ +package cn.ai.springaigu.domain; + +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; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Document("chat_messages") +public class ChatMessages { + + //唯一标识,映射到 MongoDB 文档的 _id 字段 + @Id + private ObjectId id; + private Long messageId; + private String content; //存储当前聊天记录列表的json字符串 +} diff --git a/src/main/java/cn/ai/springaigu/mapper/AppointmentMapper.java b/src/main/java/cn/ai/springaigu/mapper/AppointmentMapper.java new file mode 100644 index 0000000..a6505e7 --- /dev/null +++ b/src/main/java/cn/ai/springaigu/mapper/AppointmentMapper.java @@ -0,0 +1,9 @@ +package cn.ai.springaigu.mapper; + +import cn.ai.springaigu.domain.Appointment; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface AppointmentMapper extends BaseMapper { +} diff --git a/src/main/java/cn/ai/springaigu/service/AppointmentService.java b/src/main/java/cn/ai/springaigu/service/AppointmentService.java new file mode 100644 index 0000000..910d782 --- /dev/null +++ b/src/main/java/cn/ai/springaigu/service/AppointmentService.java @@ -0,0 +1,8 @@ +package cn.ai.springaigu.service; + +import cn.ai.springaigu.domain.Appointment; +import com.baomidou.mybatisplus.extension.service.IService; + +public interface AppointmentService extends IService { + Appointment getOne(Appointment appointment); +} diff --git a/src/main/java/cn/ai/springaigu/service/impl/AppointmentServiceImpl.java b/src/main/java/cn/ai/springaigu/service/impl/AppointmentServiceImpl.java new file mode 100644 index 0000000..36d1fe8 --- /dev/null +++ b/src/main/java/cn/ai/springaigu/service/impl/AppointmentServiceImpl.java @@ -0,0 +1,27 @@ +package cn.ai.springaigu.service.impl; + +import cn.ai.springaigu.domain.Appointment; +import cn.ai.springaigu.mapper.AppointmentMapper; +import cn.ai.springaigu.service.AppointmentService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +@Service +public class AppointmentServiceImpl extends ServiceImpl implements AppointmentService { + /** + * 查询订单是否存在 + * + * @param appointment + */ + @Override + public Appointment getOne(Appointment appointment) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(Appointment::getUsername, appointment.getUsername()); + queryWrapper.eq(Appointment::getIdCard, appointment.getIdCard()); + queryWrapper.eq(Appointment::getDepartment, appointment.getDepartment()); + queryWrapper.eq(Appointment::getDate, appointment.getDate()); + queryWrapper.eq(Appointment::getTime, appointment.getTime()); + return baseMapper.selectOne(queryWrapper); + } +} \ No newline at end of file diff --git a/src/main/java/cn/ai/springaigu/store/MongoChatMemoryStore.java b/src/main/java/cn/ai/springaigu/store/MongoChatMemoryStore.java new file mode 100644 index 0000000..87a4bb1 --- /dev/null +++ b/src/main/java/cn/ai/springaigu/store/MongoChatMemoryStore.java @@ -0,0 +1,46 @@ +package cn.ai.springaigu.store; + +import cn.ai.springaigu.domain.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; + +@Component +public class MongoChatMemoryStore implements ChatMemoryStore { + @Autowired + private MongoTemplate mongoTemplate; + @Override + public List 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 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); + } +} diff --git a/src/main/java/cn/ai/springaigu/tools/AppointmentTools.java b/src/main/java/cn/ai/springaigu/tools/AppointmentTools.java new file mode 100644 index 0000000..d08ff27 --- /dev/null +++ b/src/main/java/cn/ai/springaigu/tools/AppointmentTools.java @@ -0,0 +1,66 @@ +package cn.ai.springaigu.tools; + +import cn.ai.springaigu.domain.Appointment; +import cn.ai.springaigu.service.AppointmentService; +import dev.langchain4j.agent.tool.P; +import dev.langchain4j.agent.tool.Tool; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class AppointmentTools { + + @Autowired + private AppointmentService appointmentService; + + // @Tool(name = "预约挂号", value = "根据参数,先执行工具方法queryDepartment查询是否可预约,并直接给用户回答是否可预约,并让用户确认所有预约信息,用户确认后再进行预约。") + @Tool(name="预约挂号", value = "根据参数,先执行工具方法queryDepartment查询是否可预约,并直接给用户回答是否可预约,并让用户确认所有预约信息,用户确认后再进行预约。如果用户没有提供具体的医生姓名,请从向量存储中找到一位医生。") + public String bookAppointment(Appointment appointment) { + //查找数据库中是否包含对应的预约记录 + Appointment appointmentDB = appointmentService.getOne(appointment); + if (appointmentDB == null) { + appointment.setId(null);//防止大模型幻觉设置了id + if (appointmentService.save(appointment)) { + return "预约成功,并返回预约详情"; + } else { + return "预约失败"; + } + } + return "您在相同的科室和时间已有预约"; + } + + + @Tool(name = "取消预约挂号", value = "根据参数,查询预约是否存在,如果存在则删除预约记录并返回取消预约成功,否则返回取消预约失败") + public String cancelAppointment(Appointment appointment) { + Appointment appointmentDB = appointmentService.getOne(appointment); + if (appointmentDB != null) { + //删除预约记录 + if (appointmentService.removeById(appointmentDB.getId())) { + return "取消预约成功"; + } else { + return "取消预约失败"; + } + } + //取消失败 + return "您没有预约记录,请核对预约科室和时间"; + } + + @Tool(name = "查询是否有号源", value = "根据科室名称,日期,时间和医生查询是否有号源,并返回给用户") + public boolean queryDepartment( + @P(value = "科室名称") String name, + @P(value = "日期") String date, + @P(value = "时间,可选值:上午、下午") String time, + @P(value = "医生名称", required = false) String doctorName + ) { + System.out.println("查询是否有号源"); + System.out.println("科室名称:" + name); + System.out.println("日期:" + date); + System.out.println("时间:" + time); + System.out.println("医生名称:" + doctorName); + //TODO 维护医生的排班信息: + //如果没有指定医生名字,则根据其他条件查询是否有可以预约的医生(有返回true,否则返回false); + //如果指定了医生名字,则判断医生是否有排班(没有排版返回false) + //如果有排班,则判断医生排班时间段是否已约满(约满返回false,有空闲时间返回true) + return true; + } +} \ No newline at end of file diff --git a/src/main/java/cn/ai/springaigu/tools/CalculatorTools.java b/src/main/java/cn/ai/springaigu/tools/CalculatorTools.java new file mode 100644 index 0000000..07622dc --- /dev/null +++ b/src/main/java/cn/ai/springaigu/tools/CalculatorTools.java @@ -0,0 +1,36 @@ +package cn.ai.springaigu.tools; + +import dev.langchain4j.agent.tool.P; +import dev.langchain4j.agent.tool.Tool; +import dev.langchain4j.agent.tool.ToolMemoryId; +import org.springframework.stereotype.Component; + +@Component +public class CalculatorTools { + +// @Tool +// double sum(double a, double b) { +// System.out.println("调用加法运算"); +// return a + b; +// } +// @Tool +// double squareRoot(double x) { +// System.out.println("调用平方根运算"); +// return Math.sqrt(x); +// } + + @Tool(name = "加法", value = "返回两个参数相加之和") + double sum( + @ToolMemoryId int memoryId, + @P(value="加数1", required = true) double a, + @P(value="加数2", required = true) double b) { + System.out.println("调用加法运算 " + memoryId); + return a + b; + } + @Tool(name = "平方根", value = "返回给定参数的平方根") + double squareRoot( + @ToolMemoryId int memoryId, double x) { + System.out.println("调用平方根运算 " + memoryId); + return Math.sqrt(x); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..b6f0989 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,46 @@ +# langchain4j测试模型 +langchain4j: + open-ai: + chat-model: + base-url: https://api.deepseek.com + api-key: sk-d2c693bbb4c64956809a03d7f40e3e70 + # 推理模型 deepseek-reasoner + model-name: deepseek-chat + log-requests: true + log-responses: true + community: + dashscope: + chat-model: + api-key: sk-10a57fb7b31e4806ab5c4ea6ffa02f5a + model-name: qwen-max + embedding-model: + api-key: sk-10a57fb7b31e4806ab5c4ea6ffa02f5a + model-name: text-embedding-v3 + streaming-chat-model: + api-key: sk-10a57fb7b31e4806ab5c4ea6ffa02f5a + model-name: qwen-plus + +spring: + data: + mongodb: + uri: mongodb://localhost:27017/chat_memory_db + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://117.72.43.105:3306/ai_study?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false + username: root + password: Yuan625621105. + +pinecone: + api-key: pcsk_5qjBiY_FrVVSC7NHrMem43SrCJ4CK6EGXduToZKZmJ4dGFDonGJPmVux2mQNiyqypTvkVx + +# 开启 SQL 日志打印 +mybatis-plus: + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + + + +# 启用日志debug级别 +logging: + level: + root: info \ No newline at end of file diff --git a/src/main/resources/mapper/AppointmentMapper.xml b/src/main/resources/mapper/AppointmentMapper.xml new file mode 100644 index 0000000..b94b197 --- /dev/null +++ b/src/main/resources/mapper/AppointmentMapper.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/src/main/resources/my-prompt-template.txt b/src/main/resources/my-prompt-template.txt new file mode 100644 index 0000000..1d3467c --- /dev/null +++ b/src/main/resources/my-prompt-template.txt @@ -0,0 +1,3 @@ +你是我的好朋友,请用东北话回答问题,回答问题的时候适当添加表情符号。 +今天是 {{current_date}}。 + diff --git a/src/main/resources/my-prompt-template3.txt b/src/main/resources/my-prompt-template3.txt new file mode 100644 index 0000000..3041d9a --- /dev/null +++ b/src/main/resources/my-prompt-template3.txt @@ -0,0 +1,3 @@ +你是我的好朋友,我是{{username}},我的年龄是{{age}},请用东北话回答问题,回答问题的时候适当添加表情 +符号。 +今天是 {{current_date}}。 \ No newline at end of file diff --git a/src/main/resources/zhaozhi-prompt-template.txt b/src/main/resources/zhaozhi-prompt-template.txt new file mode 100644 index 0000000..c64b5e5 --- /dev/null +++ b/src/main/resources/zhaozhi-prompt-template.txt @@ -0,0 +1,18 @@ +你的名字是“硅谷小智”,你是一家名为“北京协和医院”的智能客服。 +你是一个训练有素的医疗顾问和医疗伴诊助手。 +你态度友好、礼貌且言辞简洁。 +1、请仅在用户发起第一次会话时,和用户打个招呼,并介绍你是谁。 +2、作为一个训练有素的医疗顾问: +请基于当前临床实践和研究,针对患者提出的特定健康问题,提供详细、准确且实用的医疗建议。请同时考虑可能的病 +因、诊断流程、治疗方案以及预防措施,并给出在不同情境下的应对策略。对于药物治疗,请特别指明适用的药品名 +称、剂量和疗程。如果需要进一步的检查或就医,也请明确指示。 +3、作为医疗伴诊助手,你可以回答用户就医流程中的相关问题,主要包含以下功能: +AI分导诊:根据患者的病情和就医需求,智能推荐最合适的科室。 +AI挂号助手:实现智能查询是否有挂号号源服务;实现智能预约挂号服务;实现智能取消挂号服务。 +4、你必须遵守的规则如下: +在获取挂号预约详情或取消挂号预约之前,你必须确保自己知晓用户的姓名(必选)、身份证号(必选)、预约科室 +(必选)、预约日期(必选,格式举例:2025-04-14)、预约时间(必选,格式:上午 或 下午)、预约医生(可 +选)。 +当被问到其他领域的咨询时,要表示歉意并说明你无法在这方面提供帮助。 +5、请在回答的结果中适当包含一些轻松可爱的图标和表情。 +6、今天是 {{current_date}}。 \ No newline at end of file diff --git a/src/test/java/cn/ai/springaigu/AppointmentServiceTest.java b/src/test/java/cn/ai/springaigu/AppointmentServiceTest.java new file mode 100644 index 0000000..8a5e1e0 --- /dev/null +++ b/src/test/java/cn/ai/springaigu/AppointmentServiceTest.java @@ -0,0 +1,42 @@ +package cn.ai.springaigu; + +import cn.ai.springaigu.domain.Appointment; +import cn.ai.springaigu.service.AppointmentService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class AppointmentServiceTest { + @Autowired + private AppointmentService appointmentService; + + @Test + void testGetOne() { + Appointment appointment = new Appointment(); + appointment.setUsername("张三"); + appointment.setIdCard("123456789012345678"); + appointment.setDepartment("内科"); + appointment.setDate("2025-04-14"); + appointment.setTime("上午"); + Appointment appointmentDB = appointmentService.getOne(appointment); + System.out.println(appointmentDB); + } + + @Test + void testSave() { + Appointment appointment = new Appointment(); + appointment.setUsername("张三"); + appointment.setIdCard("123456789012345678"); + appointment.setDepartment("内科"); + appointment.setDate("2025-04-14"); + appointment.setTime("上午"); + appointment.setDoctorName("张医生"); + appointmentService.save(appointment); + } + + @Test + void testRemoveById() { + appointmentService.removeById(1L); + } +} \ No newline at end of file diff --git a/src/test/java/cn/ai/springaigu/EmbeddingTest.java b/src/test/java/cn/ai/springaigu/EmbeddingTest.java new file mode 100644 index 0000000..4229bb3 --- /dev/null +++ b/src/test/java/cn/ai/springaigu/EmbeddingTest.java @@ -0,0 +1,94 @@ +package cn.ai.springaigu; + +import dev.langchain4j.data.document.Document; +import dev.langchain4j.data.document.loader.FileSystemDocumentLoader; +import dev.langchain4j.data.embedding.Embedding; +import dev.langchain4j.data.segment.TextSegment; +import dev.langchain4j.model.embedding.EmbeddingModel; +import dev.langchain4j.model.output.Response; +import dev.langchain4j.store.embedding.*; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.Arrays; +import java.util.List; + +@SpringBootTest +public class EmbeddingTest { + + @Autowired + private EmbeddingModel embeddingModel; + + @Test + public void testEmbeddingModel() { + Response embed = embeddingModel.embed("你好"); + System.out.println("向量维度:" + embed.content().vector().length); + System.out.println("向量输出:" + embed.toString()); + } + + // 测试向量存储 + @Autowired + private EmbeddingStore embeddingStore; + + + /** + * 将文本转换成向量,然后存储到pinecone中 + *

+ * 参考: + * https://docs.langchain4j.dev/tutorials/embedding-stores + */ + @Test + public void testPineconeEmbeded() { + //将文本转换成向量 + TextSegment segment1 = TextSegment.from("我喜欢羽毛球"); + Embedding embedding1 = embeddingModel.embed(segment1).content(); + //存入向量数据库 + embeddingStore.add(embedding1, segment1); + TextSegment segment2 = TextSegment.from("今天天气很好"); + Embedding embedding2 = embeddingModel.embed(segment2).content(); + embeddingStore.add(embedding2, segment2); + } + + /** + * Pinecone-相似度匹配 + */ + @Test + public void embeddingSearch() { + //提问,并将问题转成向量数据 + Embedding queryEmbedding = embeddingModel.embed("你最喜欢的运动是什么?").content(); + //创建搜索请求对象 + EmbeddingSearchRequest searchRequest = EmbeddingSearchRequest.builder() + .queryEmbedding(queryEmbedding) + .maxResults(1) //匹配最相似的一条记录 + //.minScore(0.8) + .build(); + //根据搜索请求 searchRequest 在向量存储中进行相似度搜索 + EmbeddingSearchResult searchResult = embeddingStore.search(searchRequest); + //searchResult.matches():获取搜索结果中的匹配项列表。 + //.get(0):从匹配项列表中获取第一个匹配项 + EmbeddingMatch embeddingMatch = searchResult.matches().get(0); + //获取匹配项的相似度得分 + System.out.println(embeddingMatch.score()); // 0.8144288515898701 + //返回文本结果 + System.out.println(embeddingMatch.embedded().text()); + } + + // 上传知识库到Pinecone + @Test + public void testUploadKnowledgeLibrary() { + //使用FileSystemDocumentLoader读取指定目录下的知识库文档 + //并使用默认的文档解析器对文档进行解析 + Document document1 = FileSystemDocumentLoader.loadDocument("D:/学习/课件/knowledge/医院信息.md"); + Document document2 = FileSystemDocumentLoader.loadDocument("D:/学习/课件/knowledge/科室信息.md"); + Document document3 = FileSystemDocumentLoader.loadDocument("D:/学习/课件/knowledge/神经内科.md"); + List documents = Arrays.asList(document1, document2, document3); + //文本向量化并存入向量数据库:将每个片段进行向量化,得到一个嵌入向量 + EmbeddingStoreIngestor + .builder() + .embeddingStore(embeddingStore) + .embeddingModel(embeddingModel) + .build() + .ingest(documents); + } +} diff --git a/src/test/java/cn/ai/springaigu/MongoCrudTest.java b/src/test/java/cn/ai/springaigu/MongoCrudTest.java new file mode 100644 index 0000000..bfc7978 --- /dev/null +++ b/src/test/java/cn/ai/springaigu/MongoCrudTest.java @@ -0,0 +1,77 @@ +package cn.ai.springaigu; + +import cn.ai.springaigu.domain.ChatMessages; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +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; + +@SpringBootTest +public class MongoCrudTest { + @Autowired + private MongoTemplate mongoTemplate; + + /** + * 插入文档 + */ + /* + @Test + public void testInsert() { + mongoTemplate.insert(new ChatMessages(1L, "聊天记录")); + }*/ + + /** + * 插入文档 + */ + @Test + public void testInsert2() { + ChatMessages chatMessages = new ChatMessages(); + chatMessages.setContent("聊天记录列表"); + mongoTemplate.insert(chatMessages); + } + + /** + * 根据id查询文档 + */ + @Test + public void testFindById() { + ChatMessages chatMessages = mongoTemplate.findById("6801ead733ba9c4a0d9b6c7b", ChatMessages.class); + System.out.println(chatMessages); + } + /** + * 修改文档 + */ + @Test + public void testUpdate() { + Criteria criteria = Criteria.where("_id").is("6801ead733ba9c4a0d9b6c7b"); + Query query = new Query(criteria); + Update update = new Update(); + update.set("content", "新的聊天记录列表"); + //修改或新增 + mongoTemplate.upsert(query, update, ChatMessages.class); + } + /** + * 新增或修改文档 + */ + @Test + public void testUpdate2() { + Criteria criteria = Criteria.where("_id").is("100"); + Query query = new Query(criteria); + Update update = new Update(); + update.set("content", "新的聊天记录列表"); + //修改或新增 + mongoTemplate.upsert(query, update, ChatMessages.class); + } + /** + * 删除文档 + */ + @Test + public void testDelete() { + Criteria criteria = Criteria.where("_id").is("100"); + Query query = new Query(criteria); + mongoTemplate.remove(query, ChatMessages.class); + } +} \ No newline at end of file diff --git a/src/test/java/cn/ai/springaigu/RAGTest.java b/src/test/java/cn/ai/springaigu/RAGTest.java new file mode 100644 index 0000000..48708d7 --- /dev/null +++ b/src/test/java/cn/ai/springaigu/RAGTest.java @@ -0,0 +1,137 @@ +package cn.ai.springaigu; + +import dev.langchain4j.data.document.Document; +import dev.langchain4j.data.document.loader.FileSystemDocumentLoader; +import dev.langchain4j.data.document.parser.TextDocumentParser; +import dev.langchain4j.data.document.parser.apache.pdfbox.ApachePdfBoxDocumentParser; +import dev.langchain4j.data.document.splitter.DocumentByParagraphSplitter; +import dev.langchain4j.data.embedding.Embedding; +import dev.langchain4j.data.message.UserMessage; +import dev.langchain4j.data.segment.TextSegment; +import dev.langchain4j.model.embedding.EmbeddingModel; +import dev.langchain4j.model.embedding.onnx.HuggingFaceTokenizer; +import dev.langchain4j.store.embedding.*; +import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.nio.file.FileSystems; +import java.nio.file.PathMatcher; +import java.util.Arrays; +import java.util.List; + + +@SpringBootTest +public class RAGTest { + + + // 文档加载 + @Test + public void testReadDocument() { +// // 加载单个文档 +// Document document = FileSystemDocumentLoader.loadDocument("E:/knowledge/file.txt", new TextDocumentParser()); +// // 从一个目录中加载所有文档 +// List documents = FileSystemDocumentLoader.loadDocuments("E:/knowledge", new TextDocumentParser()); +// // 从一个目录中加载所有的.txt文档 +// PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:*.txt"); +// List documents = FileSystemDocumentLoader.loadDocuments("E:/knowledge",pathMatcher, new TextDocumentParser()); +// // 从一个目录及其子目录中加载所有文档 +// List documents = FileSystemDocumentLoader.loadDocumentsRecursively("E:/knowledge", new TextDocumentParser()); + + //使用FileSystemDocumentLoader读取指定目录下的知识库文档 + //并使用默认的文档解析器TextDocumentParser对文档进行解析 + Document document = FileSystemDocumentLoader.loadDocument("D:\\IdeaProjects\\learn\\Spring-Ai-Gu\\src\\main\\resources\\my-prompt-template.txt"); + System.out.println(document.text()); + } + + // 来自 langchain4j 模块的文本文档解析器(TextDocumentParser),它能够解析纯文本格式的文件(例如 TXT、HTML、MD 等)。 + // ApachePdfBoxDocumentParser 解析PDF + // ApachePoiDocumentParser 解析微软办公软件的文件格式(例如 DOC、DOCX、PPT、PPTX、XLS、XLSX 等) + // ApacheTikaDocumentParser 以自动检测并解析几乎所有现有的文件格式 + + /** + * 解析PDF + */ + @Test + public void testParsePDF() { + Document document = FileSystemDocumentLoader.loadDocument("C:\\Users\\admin\\Desktop\\工作\\幼儿园E-portfolio\\Biannual Portfolios\\N1:认知、语言.pdf", new ApachePdfBoxDocumentParser()); + System.out.println(document); + } + + // 文档分割 + // 按段落文档分割器(DocumentByParagraphSplitter) + // 按行文档分割器(DocumentByLineSplitter) + // 按句子文档分割器(DocumentBySentenceSplitter) + // 按单词文档分割器(DocumentByWordSplitter) + // 按字符文档分割器(DocumentByCharacterSplitter) + // 按正则表达式文档分割器(DocumentByRegexSplitter) + // 递归分割:DocumentSplitters.recursive (...) + // 默认情况下每个文本片段最多不能超过300个token + + /** + * 加载文档并存入向量数据库 + */ + @Test + public void testReadDocumentAndStore() { + //使用FileSystemDocumentLoader读取指定目录下的知识库文档 + //并使用默认的文档解析器对文档进行解析(TextDocumentParser) + Document document = FileSystemDocumentLoader.loadDocument("C:\\Users\\admin\\Desktop\\评估报告.md"); + //为了简单起见,我们暂时使用基于内存的向量存储 + InMemoryEmbeddingStore embeddingStore = new InMemoryEmbeddingStore<>(); + //ingest + //1、分割文档:默认使用递归分割器,将文档分割为多个文本片段,每个片段包含不超过 300个token,并且 有 30 个token的重叠部分保证连贯性 + //DocumentByParagraphSplitter(DocumentByLineSplitter(DocumentBySentenceSplitter(DocumentByWordSplitter))) + //2、文本向量化:使用一个LangChain4j内置的轻量化向量模型对每个文本片段进行向量化 + //3、将原始文本和向量存储到向量数据库中(InMemoryEmbeddingStore) + EmbeddingStoreIngestor.ingest(document, embeddingStore); + //查看向量数据库内容 + System.out.println(embeddingStore); + } + + + /** + * 文档分割 + */ + @Test + public void testDocumentSplitter() { + //使用FileSystemDocumentLoader读取指定目录下的知识库文档 + //并使用默认的文档解析器对文档进行解析(TextDocumentParser) + Document document = FileSystemDocumentLoader.loadDocument("C:\\Users\\admin\\Desktop\\评估报告.md"); + //为了简单起见,我们暂时使用基于内存的向量存储 + InMemoryEmbeddingStore embeddingStore = new InMemoryEmbeddingStore<>(); + //自定义文档分割器 + //按段落分割文档:每个片段包含不超过 300个token,并且有 30个token的重叠部分保证连贯性 + //注意:当段落长度总和小于设定的最大长度时,就不会有重叠的必要。 + DocumentByParagraphSplitter documentSplitter = new DocumentByParagraphSplitter( + 300, + 30, + //token分词器:按token计算 + new HuggingFaceTokenizer()); + //按字符计算 + //DocumentByParagraphSplitter documentSplitter = new DocumentByParagraphSplitter(300, 30); + EmbeddingStoreIngestor + .builder() + .embeddingStore(embeddingStore) + .documentSplitter(documentSplitter) + .build() + .ingest(document); + } + + /** + * 计算 token 长度 + */ + @Test + public void testTokenCount() { + String text = "这是一个示例文本,用于测试 token 长度的计算。"; + UserMessage userMessage = UserMessage.userMessage(text); + //计算 token 长度 + //QwenTokenizer tokenizer = new QwenTokenizer(System.getenv("DASH_SCOPE_API_KEY"),"qwen-max"); + HuggingFaceTokenizer tokenizer = new HuggingFaceTokenizer(); + int count = tokenizer.estimateTokenCountInMessage(userMessage); + System.out.println("token长度:" + count); + } + + + +} \ No newline at end of file diff --git a/src/test/java/cn/ai/springaigu/TestGPT.java b/src/test/java/cn/ai/springaigu/TestGPT.java new file mode 100644 index 0000000..c009f48 --- /dev/null +++ b/src/test/java/cn/ai/springaigu/TestGPT.java @@ -0,0 +1,196 @@ +package cn.ai.springaigu; + +import cn.ai.springaigu.asistant.Assistant; +import cn.ai.springaigu.asistant.MemoryChatAssistant; +import cn.ai.springaigu.asistant.SeparateChatAssistant; +import dev.langchain4j.community.model.dashscope.QwenChatModel; +//import dev.langchain4j.model.openai.OpenAiChatModel; +import dev.langchain4j.community.model.dashscope.WanxImageModel; +import dev.langchain4j.data.image.Image; +import dev.langchain4j.data.message.AiMessage; +import dev.langchain4j.data.message.UserMessage; +import dev.langchain4j.memory.chat.MessageWindowChatMemory; +import dev.langchain4j.model.chat.response.ChatResponse; +import dev.langchain4j.model.output.Response; +import dev.langchain4j.service.AiServices; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.Arrays; + + +@SpringBootTest +public class TestGPT { + + // /** +// * 整合SpringBoot +// */ +// @Autowired +// private OpenAiChatModel openAiChatModel; + @Value("${langchain4j.community.dashscope.chat-model.api-key}") + private String apiKey; + /** + * 通义千问大模型 + */ + @Autowired + private QwenChatModel qwenChatModel; + + @Autowired + private Assistant assistant; + + /** + * gpt-4o-mini语言模型接入测试 + * 如果你暂时没有密钥,也可以使用LangChain4j 提供的演示密钥,这个密钥是免费的,有使用配额限制,且仅限于 gpt-4o-mini 模型。 + */ +// @Test +// public void testGPTDemo() { +// //初始化模型 +// OpenAiChatModel model = OpenAiChatModel.builder() +// //LangChain4j提供的代理服务器,该代理服务器会将演示密钥替换成真实密钥, 再将请求转发给OpenAI API +// .baseUrl("http://langchain4j.dev/demo/openai/v1") //设置模型api地址(如果apiKey = "demo",则可省略baseUrl的配置) +// .apiKey("demo") //设置模型apiKey +// .modelName("gpt-4o-mini") //设置模型名称 +// .build(); +// //向模型提问 +// String answer = model.chat("你好"); +// //输出结果 +// System.out.println(answer); +// } +// +// @Test +// public void testSpringBoot() { +// //向模型提问 +// String answer = openAiChatModel.chat("你好"); +// //输出结果 +// System.out.println(answer); +// } + @Test + public void testDashScopeQwen() { + //向模型提问 + String answer = qwenChatModel.chat("你好"); + //输出结果 + System.out.println(answer); + } + + @Test + public void testDashScopeWanx() { + WanxImageModel wanxImageModel = WanxImageModel.builder() + .modelName("wanx2.1-t2i-plus") + .apiKey(apiKey) + .build(); + Response response = wanxImageModel.generate("奇幻森林精灵:在一片弥漫着轻柔薄雾的 古老森林深处,阳光透过茂密枝叶洒下金色光斑。一位身材娇小、长着透明薄翼的精灵少女站在一朵硕大的蘑菇上。她 有着海藻般的绿色长发,发间点缀着蓝色的小花,皮肤泛着珍珠般的微光。身上穿着由翠绿树叶和白色藤蔓编织而成的 连衣裙,手中捧着一颗散发着柔和光芒的水晶球,周围环绕着五彩斑斓的蝴蝶,脚下是铺满苔藓的地面,蘑菇和蕨类植物丛生,营造出神秘而梦幻的氛围。"); + System.out.println(response.content().url()); + } + + @Test + public void testChat() { + //创建AIService + Assistant assistant = AiServices.create(Assistant.class, qwenChatModel); + //调用service的接口 + String answer = assistant.chat("Hello"); + System.out.println(answer); + } + //也可以在 Assistant 接口上添加 @AiService 注解 + @Test + public void testAssistant() { + String answer = assistant.chat("Hello"); + System.out.println(answer); + } + + // 测试对话是否有记忆 + @Test + public void testChatMemory() { + String answer1 = assistant.chat("我是环环"); + System.out.println(answer1); + String answer2 = assistant.chat("我是谁"); + System.out.println(answer2); + } + + + // 聊天记忆的简单实现 + @Test + public void testChatMemory2() { + //第一轮对话 + UserMessage userMessage1 = UserMessage.userMessage("我是环环"); + ChatResponse chatResponse1 = qwenChatModel.chat(userMessage1); + AiMessage aiMessage1 = chatResponse1.aiMessage(); + //输出大语言模型的回复 + System.out.println(aiMessage1.text()); + //第二轮对话 + UserMessage userMessage2 = UserMessage.userMessage("你知道我是谁吗"); + ChatResponse chatResponse2 = qwenChatModel.chat(Arrays.asList(userMessage1, + aiMessage1, userMessage2)); + AiMessage aiMessage2 = chatResponse2.aiMessage(); + //输出大语言模型的回复 + System.out.println(aiMessage2.text()); + } + + // 使用ChatMemory实现聊天记忆 + @Test + public void testChatMemory3() { + //创建chatMemory + MessageWindowChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10); + //创建AIService + Assistant assistant = AiServices + .builder(Assistant.class) + .chatLanguageModel(qwenChatModel) + .chatMemory(chatMemory) + .build(); + //调用service的接口 + String answer1 = assistant.chat("我是环环"); + System.out.println(answer1); + String answer2 = assistant.chat("我是谁"); + System.out.println(answer2); + } + + + @Autowired + private MemoryChatAssistant memoryChatAssistant; + @Test + public void testChatMemory4() { + String answer1 = memoryChatAssistant.chat("我是环环"); + System.out.println(answer1); + String answer2 = memoryChatAssistant.chat("我是谁"); + System.out.println(answer2); + } + + @Autowired + private SeparateChatAssistant separateChatAssistant; + @Test + public void testChatMemory5() { + String answer1 = separateChatAssistant.chat(1,"我是环环"); + System.out.println(answer1); + String answer2 = separateChatAssistant.chat(1,"我是谁"); + System.out.println(answer2); + String answer3 = separateChatAssistant.chat(2,"我是谁"); + System.out.println(answer3); + } + + //系统提示词@SystemMessage + @Test + public void testSystemMessage() { + String answer = separateChatAssistant.chat(3,"今天几号"); + System.out.println(answer); + } + + @Test + public void testUserMessage() { + String answer = memoryChatAssistant.chat("我是环环"); + System.out.println(answer); + } + @Test + public void testV() { + String answer1 = separateChatAssistant.chat2(1, "我是环环"); + System.out.println(answer1); + String answer2 = separateChatAssistant.chat2(1, "我是谁"); + System.out.println(answer2); + } + @Test + public void testUserInfo() { + String answer = separateChatAssistant.chat3(1, "我是谁,我多大了", "翠花", 18); + System.out.println(answer); + } + +} \ No newline at end of file diff --git a/src/test/java/cn/ai/springaigu/ToolsTest.java b/src/test/java/cn/ai/springaigu/ToolsTest.java new file mode 100644 index 0000000..08f120a --- /dev/null +++ b/src/test/java/cn/ai/springaigu/ToolsTest.java @@ -0,0 +1,19 @@ +package cn.ai.springaigu; + +import cn.ai.springaigu.asistant.SeparateChatAssistant; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class ToolsTest { + @Autowired + private SeparateChatAssistant separateChatAssistant; + @Test + public void testCalculatorTools() { + String answer = separateChatAssistant.chat(1, "1+2等于几,475695037565的平方根是多 少?"); + //答案:3,689706.4865 + System.out.println(answer); + + } +}