├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── README.md ├── common ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── cyy │ └── common │ ├── enums │ └── ErrorCode.java │ ├── exception │ ├── ClientGlobalException.java │ ├── GlobalException.java │ └── SystemGlobalException.java │ └── utils │ └── R.java ├── generator ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── cyy │ └── generator │ └── MPCodeGenerator.java ├── mvnw ├── mvnw.cmd ├── pic └── chat_demo.png ├── pom.xml └── web ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── cyy │ │ └── chat │ │ ├── CustomizedChatApplication.java │ │ ├── DocParser │ │ ├── AbstractParser.java │ │ └── PdfParse.java │ │ ├── config │ │ ├── DefaultConfig.java │ │ ├── GlobalExceptionHandler.java │ │ └── SwaggerConfig.java │ │ ├── controller │ │ └── ChatController.java │ │ ├── model │ │ ├── ChatGptModel.java │ │ ├── EmbeddingModel.java │ │ ├── parama │ │ │ ├── ChatGptApiParam.java │ │ │ ├── EmbeddingsApiParam.java │ │ │ └── Message.java │ │ └── result │ │ │ ├── ChatGptApiResult.java │ │ │ ├── Choices.java │ │ │ ├── EmbeddingObj.java │ │ │ └── EmbeddingsApiResult.java │ │ ├── pojo │ │ └── PDFData.java │ │ └── service │ │ ├── ChatServiceImpl.java │ │ └── IChatService.java └── resources │ └── application.yml └── test └── java └── com └── cyy └── chat └── CustomizedChatApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigcyy/customized-chat/87b5c4c1ccb2dbb84c0ef3e80c33fc8c8833d534/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## customized chat 2 | 3 | ### 简介 4 | 5 | 基于ChatGpt,Java,SpringBoot,Vue,Milvus向量数据库的定制化聊天Web demo 6 | 7 | - 可开发成个人知识库 8 | - 针对某品牌的智能客服 9 | - 私人助理 10 | - more 11 | ### 在线体验 12 | 13 | TODO 14 | 15 | ![ui](pic/chat_demo.png) 16 | 17 | ### 前端项目地址 18 | https://github.com/bigcyy/customized-chat-vue 19 | 20 | ### 原理 21 | 22 | 上传PDF,让ChtGpt基于PDF的内容回答问题,原理很简单:将内容分割然后embedding存入向量数据库,当用户询问时将问题embedding,拿embedding结果去向量数据库查询相似度最高的几段话丢给ChatGpt让他组织语言并结合自己丰富的知识进行润色。 23 | 24 | ### 本地使用 25 | 26 | > 注意,你需要有OpenAI账号并且创建一个apiKey,由于国内无法使用连接OpenAi服务所以你需要配置代理 27 | 28 | 1. 安装Milvus向量数据库 29 | 30 | ``` 31 | wget https://github.com/milvus-io/milvus/releases/download/v2.2.2/milvus-standalone-docker-compose.yml -O docker-compose.yml 32 | sudo docker-compose up -d 33 | ``` 34 | 35 | 2. 克隆后端项目 36 | 37 | ``` 38 | git clone git@github.com:bigcyy/customized-chat.git 39 | ``` 40 | 41 | 3. 用idea打开项目 42 | 43 | 4. 初始化Milvus向量数据库表结构以及配置代理 44 | 45 | * application.yml中配置向量数据库连接地址和端口(本地不需要修改),然后配置你的代理ip和端口 46 | 47 | * 找到项目test文件夹下的CustomizedChatApplicationTests.java,运行prepare函数创建表结构 48 | 49 | 5. 找到项目主函数运行后端 50 | 51 | 6. 克隆并运行前端项目 52 | 53 | ``` 54 | git clone git@github.com:bigcyy/customized-chat-vue.git 55 | cd customized-chat-vue 56 | npm install 57 | npm run serve 58 | ``` 59 | 60 | 7. 访问控制台输出的地址 61 | 62 | 8. 界面左下角配置你的apiKey 63 | 64 | 9. 界面左下角上传你需要定制聊天的PDF文件 65 | 10. enjoy it 66 | 67 | ### todo 68 | 69 | - 支持更多的文件格式 70 | - token计数 71 | - 优化文本的分割 72 | - 抽离embedding模型,使其能更优雅的切换为自己训练的模型或者其他公开的模型 73 | 74 | ### 参考资料 75 | 76 | https://twitter.com/chuangbo/status/1631461656151887873 作者详细的描述了该方案的运行流程,本项目也是参考自该文章 77 | 78 | ### 相关项目 79 | 80 | - https://github.com/GanymedeNil/document.ai 81 | - https://github.com/mckaywrigley/paul-graham-gpt -------------------------------------------------------------------------------- /common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.cyy 8 | customized-chat 9 | 0.0.1-SNAPSHOT 10 | 11 | 12 | common 13 | 0.0.1-SNAPSHOT 14 | 15 | 16 | 21 17 | 21 18 | UTF-8 19 | 20 | 21 | 22 | 23 | com.github.xiaoymin 24 | knife4j-openapi3-jakarta-spring-boot-starter 25 | 26 | 27 | -------------------------------------------------------------------------------- /common/src/main/java/com/cyy/common/enums/ErrorCode.java: -------------------------------------------------------------------------------- 1 | package com.cyy.common.enums; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * 错误码的规范,采用 ABCDE 五位数错误码 7 | * 默认 00000 为成功 8 | * 9 | * A 位: 错误等级 10 | * - A: 表示客户端错误 11 | * - B: 表示服务端错误 12 | * BC 位:业务模块 13 | * - 00:通用错误(未分类) 14 | * - 01:用户相关 15 | * - 02:设置相关 16 | * - 03:知识库相关 17 | * - 04:应用相关 18 | * - 05:对话相关 19 | * 20 | * DE 位:具体错误类型 21 | * - 00:未知错误 22 | * - 01:缺少必要参数 23 | * - 02:参数格式错误 24 | * - 03:未授权访问 25 | * - 04:资源不存在 26 | * - 05:操作失败(具体原因待补充) 27 | * - 06:请求超时 28 | * - 07:资源已存在 29 | * - 08:数据校验失败 30 | * 31 | * @author winter 32 | * @author CYY 33 | */ 34 | @Getter 35 | public enum ErrorCode { 36 | 37 | SUCCESS("00000", "success"), 38 | CLIENT_ERROR("A0000","客户端错误"), 39 | SYSTEM_ERROR("B0000", "系统内部异常"); 40 | 41 | /** 42 | * 状态码 43 | */ 44 | private final String code; 45 | 46 | /** 47 | * 信息 48 | */ 49 | private final String message; 50 | 51 | ErrorCode(String code, String message) { 52 | this.code = code; 53 | this.message = message; 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /common/src/main/java/com/cyy/common/exception/ClientGlobalException.java: -------------------------------------------------------------------------------- 1 | package com.cyy.common.exception; 2 | 3 | import com.cyy.common.enums.ErrorCode; 4 | 5 | public class ClientGlobalException extends GlobalException{ 6 | public ClientGlobalException() { 7 | super(ErrorCode.CLIENT_ERROR); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /common/src/main/java/com/cyy/common/exception/GlobalException.java: -------------------------------------------------------------------------------- 1 | package com.cyy.common.exception; 2 | 3 | 4 | import com.cyy.common.enums.ErrorCode; 5 | 6 | /** 7 | * 全局通用异常类,实在不知道该抛出什么异常时可抛出该异常,尽量抛出模块对应的异常 8 | * @author winter 9 | * @author CYY 10 | */ 11 | 12 | public class GlobalException extends RuntimeException{ 13 | /** 14 | * 错误码 default: ErrorCode.SYSTEM_ERROR 15 | */ 16 | private String code = ErrorCode.SYSTEM_ERROR.getCode(); 17 | 18 | /** 19 | * 错误提示 20 | */ 21 | private String message; 22 | 23 | public GlobalException(String message) 24 | { 25 | this.message = message; 26 | } 27 | 28 | public GlobalException(ErrorCode errorCode) 29 | { 30 | this.code = errorCode.getCode(); 31 | this.message = errorCode.getMessage(); 32 | } 33 | 34 | public GlobalException(ErrorCode errorCode, String message) 35 | { 36 | this.code = errorCode.getCode(); 37 | this.message = message; 38 | } 39 | 40 | 41 | public GlobalException(String message, String code) 42 | { 43 | this.message = message; 44 | this.code = code; 45 | } 46 | 47 | public String getMessage() 48 | { 49 | return message; 50 | } 51 | 52 | public String getCode() 53 | { 54 | return code; 55 | } 56 | 57 | public GlobalException setMessage(String message) 58 | { 59 | this.message = message; 60 | return this; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /common/src/main/java/com/cyy/common/exception/SystemGlobalException.java: -------------------------------------------------------------------------------- 1 | package com.cyy.common.exception; 2 | 3 | import com.cyy.common.enums.ErrorCode; 4 | 5 | public class SystemGlobalException extends GlobalException{ 6 | public SystemGlobalException() { 7 | super(ErrorCode.SYSTEM_ERROR); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /common/src/main/java/com/cyy/common/utils/R.java: -------------------------------------------------------------------------------- 1 | 2 | package com.cyy.common.utils; 3 | 4 | 5 | import com.cyy.common.enums.ErrorCode; 6 | import io.swagger.v3.oas.annotations.media.Schema; 7 | import lombok.Data; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | /** 13 | * @author winter 14 | * @author CYY 15 | * R 类的使用简介: 16 | * 不允许通过直接实例化 R 类 , 建议使用 R.ok() , R.error().data().... 来链式编程 17 | * 比如在我们 18 | * 成功(不出异常)返回 数据(json)时,示例: return R.ok().data("data", json). 19 | * 失败(全局异常处理) : return R.error().message("服务器内部异常") 20 | */ 21 | @Data 22 | @Schema(description = "通用返回类") 23 | public class R { 24 | @Schema(description = "请求是否成功") 25 | private boolean success; 26 | @Schema(description = "返回码") 27 | private String code; 28 | @Schema(description = "返回信息") 29 | private String message; 30 | @Schema(description = "返回的 JSON 数据") 31 | private Map data = new HashMap<>(); 32 | 33 | // 不允许通过直接实例化 R 类 , 建议使用 R.ok() , R.error().data().... 来链式编程 34 | private R() {} 35 | 36 | //成功静态方法 37 | public static R ok(){ 38 | R r = new R(); 39 | r.setSuccess(true); 40 | r.setCode(ErrorCode.SUCCESS.getCode()); 41 | r.setMessage("成功"); 42 | return r; 43 | } 44 | 45 | public static R systemError(){ 46 | R r = new R(); 47 | r.setSuccess(false); 48 | r.setCode(ErrorCode.SYSTEM_ERROR.getCode()); 49 | r.setMessage("失败"); 50 | return r; 51 | } 52 | 53 | public static R clientError(){ 54 | R r = new R(); 55 | r.setSuccess(false); 56 | r.setCode(ErrorCode.CLIENT_ERROR.getCode()); 57 | r.setMessage("失败"); 58 | return r; 59 | } 60 | 61 | public static R error(ErrorCode errorCode) { 62 | R r = new R(); 63 | r.setSuccess(false); 64 | r.setCode(errorCode.getCode()); 65 | r.setMessage(errorCode.getMessage()); 66 | return r; 67 | } 68 | 69 | public static R error(String code, String message) { 70 | R r = new R(); 71 | r.setSuccess(false); 72 | r.setCode(code); 73 | r.setMessage(message); 74 | return r; 75 | } 76 | 77 | 78 | // 通过return this 来使用达到链式编程的目的hh 79 | public R success(Boolean success){ 80 | this.setSuccess(success); 81 | return this; 82 | } 83 | public R message(String message){ 84 | this.setMessage(message); 85 | return this; 86 | } 87 | public R code(String code){ 88 | this.setCode(code); 89 | return this; 90 | } 91 | //链式编程在放map的Data里似乎特别好用 , 使用 map 的好处在于 一些原本用 多种场景的dto类,现在只需要在map里设置不同的 key来实现 92 | public R data(String key, Object value){ 93 | this.data.put(key, value); 94 | return this; 95 | } 96 | public R data(Map map){ 97 | this.setData(map); 98 | return this; 99 | } 100 | 101 | } -------------------------------------------------------------------------------- /generator/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.cyy 8 | customized-chat 9 | 0.0.1-SNAPSHOT 10 | 11 | 12 | generator 13 | 14 | 15 | 21 16 | 21 17 | UTF-8 18 | 19 | 20 | 21 | 22 | com.baomidou 23 | mybatis-plus-spring-boot3-starter 24 | 25 | 26 | 27 | com.baomidou 28 | mybatis-plus-generator 29 | 3.5.10.1 30 | 31 | 32 | 33 | org.freemarker 34 | freemarker 35 | 2.3.30 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /generator/src/main/java/com/cyy/generator/MPCodeGenerator.java: -------------------------------------------------------------------------------- 1 | package com.cyy.generator; 2 | 3 | import com.baomidou.mybatisplus.annotation.FieldFill; 4 | import com.baomidou.mybatisplus.generator.FastAutoGenerator; 5 | import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; 6 | import com.baomidou.mybatisplus.generator.fill.Column; 7 | 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | /** 13 | * 代码生成器,用于生成指定表的 entity, dao, xxxMapper.xml, service, controller 14 | */ 15 | public class MPCodeGenerator { 16 | /** 17 | * 交互式生成 18 | * @return 控制台的输入 19 | */ 20 | public static void main(String[] args) { 21 | FastAutoGenerator.create("url", "root", "root") 22 | // 全局配置 23 | .globalConfig(builder -> builder.author("CYY")) 24 | // 包配置 25 | .packageConfig(builder -> 26 | builder.parent("com.cyy.chat") 27 | .entity("model") 28 | .mapper("dao") 29 | .service("service") 30 | .serviceImpl("service.impl") 31 | .xml("mappers") 32 | .controller("controller")) 33 | // 策略配置 34 | .strategyConfig((scanner, builder) -> builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all"))) 35 | .entityBuilder() 36 | .enableLombok() 37 | .addTableFills( 38 | new Column("create_time", FieldFill.INSERT) 39 | ) 40 | .build()) 41 | // 使用Freemarker引擎模板,默认的是Velocity引擎模板 42 | .templateEngine(new FreemarkerTemplateEngine()) 43 | .execute(); 44 | } 45 | 46 | // 处理 all 情况 47 | protected static List getTables(String tables) { 48 | return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(",")); 49 | } 50 | } -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /usr/local/etc/mavenrc ] ; then 40 | . /usr/local/etc/mavenrc 41 | fi 42 | 43 | if [ -f /etc/mavenrc ] ; then 44 | . /etc/mavenrc 45 | fi 46 | 47 | if [ -f "$HOME/.mavenrc" ] ; then 48 | . "$HOME/.mavenrc" 49 | fi 50 | 51 | fi 52 | 53 | # OS specific support. $var _must_ be set to either true or false. 54 | cygwin=false; 55 | darwin=false; 56 | mingw=false 57 | case "`uname`" in 58 | CYGWIN*) cygwin=true ;; 59 | MINGW*) mingw=true;; 60 | Darwin*) darwin=true 61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 63 | if [ -z "$JAVA_HOME" ]; then 64 | if [ -x "/usr/libexec/java_home" ]; then 65 | export JAVA_HOME="`/usr/libexec/java_home`" 66 | else 67 | export JAVA_HOME="/Library/Java/Home" 68 | fi 69 | fi 70 | ;; 71 | esac 72 | 73 | if [ -z "$JAVA_HOME" ] ; then 74 | if [ -r /etc/gentoo-release ] ; then 75 | JAVA_HOME=`java-config --jre-home` 76 | fi 77 | fi 78 | 79 | if [ -z "$M2_HOME" ] ; then 80 | ## resolve links - $0 may be a link to maven's home 81 | PRG="$0" 82 | 83 | # need this for relative symlinks 84 | while [ -h "$PRG" ] ; do 85 | ls=`ls -ld "$PRG"` 86 | link=`expr "$ls" : '.*-> \(.*\)$'` 87 | if expr "$link" : '/.*' > /dev/null; then 88 | PRG="$link" 89 | else 90 | PRG="`dirname "$PRG"`/$link" 91 | fi 92 | done 93 | 94 | saveddir=`pwd` 95 | 96 | M2_HOME=`dirname "$PRG"`/.. 97 | 98 | # make it fully qualified 99 | M2_HOME=`cd "$M2_HOME" && pwd` 100 | 101 | cd "$saveddir" 102 | # echo Using m2 at $M2_HOME 103 | fi 104 | 105 | # For Cygwin, ensure paths are in UNIX format before anything is touched 106 | if $cygwin ; then 107 | [ -n "$M2_HOME" ] && 108 | M2_HOME=`cygpath --unix "$M2_HOME"` 109 | [ -n "$JAVA_HOME" ] && 110 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 111 | [ -n "$CLASSPATH" ] && 112 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 113 | fi 114 | 115 | # For Mingw, ensure paths are in UNIX format before anything is touched 116 | if $mingw ; then 117 | [ -n "$M2_HOME" ] && 118 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 119 | [ -n "$JAVA_HOME" ] && 120 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 121 | fi 122 | 123 | if [ -z "$JAVA_HOME" ]; then 124 | javaExecutable="`which javac`" 125 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 126 | # readlink(1) is not available as standard on Solaris 10. 127 | readLink=`which readlink` 128 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 129 | if $darwin ; then 130 | javaHome="`dirname \"$javaExecutable\"`" 131 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 132 | else 133 | javaExecutable="`readlink -f \"$javaExecutable\"`" 134 | fi 135 | javaHome="`dirname \"$javaExecutable\"`" 136 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 137 | JAVA_HOME="$javaHome" 138 | export JAVA_HOME 139 | fi 140 | fi 141 | fi 142 | 143 | if [ -z "$JAVACMD" ] ; then 144 | if [ -n "$JAVA_HOME" ] ; then 145 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 146 | # IBM's JDK on AIX uses strange locations for the executables 147 | JAVACMD="$JAVA_HOME/jre/sh/java" 148 | else 149 | JAVACMD="$JAVA_HOME/bin/java" 150 | fi 151 | else 152 | JAVACMD="`\\unset -f command; \\command -v java`" 153 | fi 154 | fi 155 | 156 | if [ ! -x "$JAVACMD" ] ; then 157 | echo "Error: JAVA_HOME is not defined correctly." >&2 158 | echo " We cannot execute $JAVACMD" >&2 159 | exit 1 160 | fi 161 | 162 | if [ -z "$JAVA_HOME" ] ; then 163 | echo "Warning: JAVA_HOME environment variable is not set." 164 | fi 165 | 166 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 167 | 168 | # traverses directory structure from process work directory to filesystem root 169 | # first directory with .mvn subdirectory is considered project base directory 170 | find_maven_basedir() { 171 | 172 | if [ -z "$1" ] 173 | then 174 | echo "Path not specified to find_maven_basedir" 175 | return 1 176 | fi 177 | 178 | basedir="$1" 179 | wdir="$1" 180 | while [ "$wdir" != '/' ] ; do 181 | if [ -d "$wdir"/.mvn ] ; then 182 | basedir=$wdir 183 | break 184 | fi 185 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 186 | if [ -d "${wdir}" ]; then 187 | wdir=`cd "$wdir/.."; pwd` 188 | fi 189 | # end of workaround 190 | done 191 | echo "${basedir}" 192 | } 193 | 194 | # concatenates all lines of a file 195 | concat_lines() { 196 | if [ -f "$1" ]; then 197 | echo "$(tr -s '\n' ' ' < "$1")" 198 | fi 199 | } 200 | 201 | BASE_DIR=`find_maven_basedir "$(pwd)"` 202 | if [ -z "$BASE_DIR" ]; then 203 | exit 1; 204 | fi 205 | 206 | ########################################################################################## 207 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 208 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 209 | ########################################################################################## 210 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Found .mvn/wrapper/maven-wrapper.jar" 213 | fi 214 | else 215 | if [ "$MVNW_VERBOSE" = true ]; then 216 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 217 | fi 218 | if [ -n "$MVNW_REPOURL" ]; then 219 | jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 220 | else 221 | jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 222 | fi 223 | while IFS="=" read key value; do 224 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 225 | esac 226 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 227 | if [ "$MVNW_VERBOSE" = true ]; then 228 | echo "Downloading from: $jarUrl" 229 | fi 230 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 231 | if $cygwin; then 232 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 233 | fi 234 | 235 | if command -v wget > /dev/null; then 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Found wget ... using wget" 238 | fi 239 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 240 | wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 241 | else 242 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 243 | fi 244 | elif command -v curl > /dev/null; then 245 | if [ "$MVNW_VERBOSE" = true ]; then 246 | echo "Found curl ... using curl" 247 | fi 248 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 249 | curl -o "$wrapperJarPath" "$jarUrl" -f 250 | else 251 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 252 | fi 253 | 254 | else 255 | if [ "$MVNW_VERBOSE" = true ]; then 256 | echo "Falling back to using Java to download" 257 | fi 258 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 259 | # For Cygwin, switch paths to Windows format before running javac 260 | if $cygwin; then 261 | javaClass=`cygpath --path --windows "$javaClass"` 262 | fi 263 | if [ -e "$javaClass" ]; then 264 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 265 | if [ "$MVNW_VERBOSE" = true ]; then 266 | echo " - Compiling MavenWrapperDownloader.java ..." 267 | fi 268 | # Compiling the Java class 269 | ("$JAVA_HOME/bin/javac" "$javaClass") 270 | fi 271 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 272 | # Running the downloader 273 | if [ "$MVNW_VERBOSE" = true ]; then 274 | echo " - Running MavenWrapperDownloader.java ..." 275 | fi 276 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 277 | fi 278 | fi 279 | fi 280 | fi 281 | ########################################################################################## 282 | # End of extension 283 | ########################################################################################## 284 | 285 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 286 | if [ "$MVNW_VERBOSE" = true ]; then 287 | echo $MAVEN_PROJECTBASEDIR 288 | fi 289 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 290 | 291 | # For Cygwin, switch paths to Windows format before running java 292 | if $cygwin; then 293 | [ -n "$M2_HOME" ] && 294 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 295 | [ -n "$JAVA_HOME" ] && 296 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 297 | [ -n "$CLASSPATH" ] && 298 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 299 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 300 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 301 | fi 302 | 303 | # Provide a "standardized" way to retrieve the CLI args that will 304 | # work with both Windows and non-Windows executions. 305 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 306 | export MAVEN_CMD_LINE_ARGS 307 | 308 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 309 | 310 | exec "$JAVACMD" \ 311 | $MAVEN_OPTS \ 312 | $MAVEN_DEBUG_OPTS \ 313 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 314 | "-Dmaven.home=${M2_HOME}" \ 315 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 316 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 317 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /pic/chat_demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigcyy/customized-chat/87b5c4c1ccb2dbb84c0ef3e80c33fc8c8833d534/pic/chat_demo.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.3.0 9 | 10 | 11 | 12 | com.cyy 13 | customized-chat 14 | 0.0.1-SNAPSHOT 15 | customized_chat 16 | customized_chat 17 | pom 18 | 19 | 20 | 21 21 | 21 22 | UTF-8 23 | 24 | 25 | 26 | web 27 | common 28 | generator 29 | 30 | 31 | 32 | 33 | 34 | org.springframework.ai 35 | spring-ai-bom 36 | 1.0.0-SNAPSHOT 37 | pom 38 | import 39 | 40 | 41 | 42 | com.github.xiaoymin 43 | knife4j-openapi3-jakarta-spring-boot-starter 44 | 4.4.0 45 | 46 | 47 | 48 | com.baomidou 49 | mybatis-plus-spring-boot3-starter 50 | 3.5.10.1 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | org.projectlombok 59 | lombok 60 | provided 61 | 62 | 63 | 64 | 65 | 66 | spring-milestones 67 | Spring Milestones 68 | https://repo.spring.io/milestone 69 | 70 | false 71 | 72 | 73 | 74 | spring-snapshots 75 | Spring Snapshots 76 | https://repo.spring.io/snapshot 77 | 78 | false 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /web/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.cyy 8 | customized-chat 9 | 0.0.1-SNAPSHOT 10 | 11 | 12 | web 13 | 14 | 15 | 16 | 17 | 18 | com.cyy 19 | common 20 | 0.0.1-SNAPSHOT 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-test 29 | test 30 | 31 | 32 | io.milvus 33 | milvus-sdk-java 34 | 2.2.1 35 | 36 | 37 | org.slf4j 38 | slf4j-api 39 | 40 | 41 | org.apache.logging.log4j 42 | log4j-slf4j-impl 43 | 44 | 45 | 46 | 47 | 48 | com.squareup.okhttp3 49 | okhttp 50 | 4.10.0 51 | 52 | 53 | 54 | com.google.code.gson 55 | gson 56 | 2.10.1 57 | 58 | 59 | 60 | org.jsoup 61 | jsoup 62 | 1.15.3 63 | 64 | 65 | 66 | org.apache.pdfbox 67 | pdfbox 68 | 2.0.24 69 | 70 | 71 | 72 | org.springframework.ai 73 | spring-ai-openai-spring-boot-starter 74 | 75 | 76 | 77 | 78 | 79 | 80 | org.springframework.boot 81 | spring-boot-maven-plugin 82 | 83 | 84 | 85 | org.projectlombok 86 | lombok 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /web/src/main/java/com/cyy/chat/CustomizedChatApplication.java: -------------------------------------------------------------------------------- 1 | package com.cyy.chat; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class CustomizedChatApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(CustomizedChatApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /web/src/main/java/com/cyy/chat/DocParser/AbstractParser.java: -------------------------------------------------------------------------------- 1 | package com.cyy.chat.DocParser; 2 | 3 | import java.io.InputStream; 4 | import java.util.List; 5 | 6 | /** 7 | * @author CYY 8 | * @date 2023年03月20日 上午8:13 9 | * @description 10 | */ 11 | public abstract class AbstractParser { 12 | public abstract List parse(InputStream inputStream) throws Exception; 13 | } 14 | -------------------------------------------------------------------------------- /web/src/main/java/com/cyy/chat/DocParser/PdfParse.java: -------------------------------------------------------------------------------- 1 | package com.cyy.chat.DocParser; 2 | 3 | import com.cyy.chat.DocParser.AbstractParser; 4 | import org.apache.pdfbox.pdmodel.PDDocument; 5 | import org.apache.pdfbox.text.PDFTextStripper; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.net.URISyntaxException; 11 | import java.net.URL; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | /** 16 | * @author CYY 17 | * @date 2023年03月11日 下午3:23 18 | * @description 19 | */ 20 | public class PdfParse extends AbstractParser { 21 | private static final int MAX_LENGTH = 200; 22 | 23 | @Override 24 | public List parse(InputStream inputStream) throws IOException { 25 | // 打开 PDF 文件 26 | PDDocument document = PDDocument.load(inputStream); 27 | // 创建 PDFTextStripper 对象 28 | PDFTextStripper stripper = new PDFTextStripper(); 29 | // 获取文本内容 30 | String text = stripper.getText(document); 31 | //过滤字符 32 | text = text.replaceAll("\\s", " ").replaceAll("(\\r\\n|\\r|\\n|\\n\\r)"," "); 33 | String[] sentence = text.split("。"); 34 | List ans = new ArrayList<>(); 35 | for (String s : sentence) { 36 | if (s.length() > MAX_LENGTH) { 37 | for (int index = 0; index < sentence.length; index = (index + 1) * MAX_LENGTH) { 38 | String substring = s.substring(index, MAX_LENGTH); 39 | if(substring.length() < 5) continue; 40 | ans.add(substring); 41 | } 42 | } else { 43 | ans.add(s); 44 | } 45 | } 46 | // 关闭文档 47 | document.close(); 48 | return ans; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /web/src/main/java/com/cyy/chat/config/DefaultConfig.java: -------------------------------------------------------------------------------- 1 | package com.cyy.chat.config; 2 | 3 | import io.milvus.client.MilvusServiceClient; 4 | import io.milvus.param.ConnectParam; 5 | import okhttp3.OkHttpClient; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | import java.net.InetSocketAddress; 11 | import java.net.Proxy; 12 | import java.net.http.HttpClient; 13 | 14 | /** 15 | * @author CYY 16 | * @date 2023年03月11日 下午9:30 17 | * @description 18 | */ 19 | @Configuration 20 | public class DefaultConfig { 21 | @Value("${proxy.ip}") 22 | private String proxyIp; 23 | @Value("${proxy.port}") 24 | private int proxyPort; 25 | @Value("${milvus.ip}") 26 | private String milvusIp; 27 | @Value("${milvus.port}") 28 | private int milvusPort; 29 | @Bean 30 | public OkHttpClient openAIHttpClient(){ 31 | return new OkHttpClient.Builder().proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyIp,proxyPort))).build(); 32 | } 33 | @Bean 34 | public MilvusServiceClient milvusClient(){ 35 | return new MilvusServiceClient( 36 | ConnectParam.newBuilder() 37 | .withHost(milvusIp) 38 | .withPort(milvusPort) 39 | .build() 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /web/src/main/java/com/cyy/chat/config/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.cyy.chat.config; 2 | 3 | 4 | 5 | import com.cyy.common.enums.ErrorCode; 6 | import com.cyy.common.exception.GlobalException; 7 | import com.cyy.common.utils.R; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.web.bind.annotation.ExceptionHandler; 10 | import org.springframework.web.bind.annotation.RestControllerAdvice; 11 | 12 | 13 | /** 14 | * 全局异常处理 15 | * @author winter 16 | * @author CYY 17 | * @create 2022-04-03 下午8:33 18 | */ 19 | 20 | @RestControllerAdvice 21 | @Slf4j 22 | public class GlobalExceptionHandler { 23 | 24 | @ExceptionHandler(GlobalException.class) 25 | public R error(GlobalException e) { 26 | log.error("全局异常捕获: {}", e.getMessage(), e); 27 | return R.error(e.getCode(),e.getMessage()); 28 | } 29 | 30 | /** 31 | * 拦截统一的运行时异常 32 | */ 33 | @ExceptionHandler(RuntimeException.class) 34 | public R error(RuntimeException e) { 35 | log.error("全局异常捕获: {}", e.getMessage(), e); 36 | return R.error(ErrorCode.SYSTEM_ERROR); 37 | } 38 | 39 | /** 40 | * 拦截所有未被捕捉到的异常 41 | */ 42 | @ExceptionHandler(Exception.class) 43 | public R error(Exception e) { 44 | log.error("全局异常捕获: {}", e.getMessage(), e); 45 | return R.error(ErrorCode.SYSTEM_ERROR); 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /web/src/main/java/com/cyy/chat/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | 2 | package com.cyy.chat.config; 3 | 4 | import io.swagger.v3.oas.models.OpenAPI; 5 | import io.swagger.v3.oas.models.info.Info; 6 | import io.swagger.v3.oas.models.info.License; 7 | import org.springdoc.core.customizers.GlobalOpenApiCustomizer; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.util.concurrent.ThreadLocalRandom; 14 | 15 | /*** 16 | * 创建Swagger配置 17 | */ 18 | @Configuration 19 | public class SwaggerConfig { 20 | /** 21 | * 根据@Tag 上的排序,写入x-order 22 | * 23 | * @return the global open api customizer 24 | */ 25 | @Bean 26 | public GlobalOpenApiCustomizer orderGlobalOpenApiCustomizer() { 27 | return openApi -> { 28 | if (openApi.getTags()!=null){ 29 | openApi.getTags().forEach(tag -> { 30 | Map map=new HashMap<>(); 31 | map.put("x-order", ThreadLocalRandom.current().nextInt(0,100)); 32 | tag.setExtensions(map); 33 | }); 34 | } 35 | if(openApi.getPaths()!=null){ 36 | openApi.addExtension("x-test123","333"); 37 | openApi.getPaths().addExtension("x-abb",ThreadLocalRandom.current().nextInt(1,100)); 38 | } 39 | 40 | }; 41 | } 42 | 43 | @Bean 44 | public OpenAPI customOpenAPI() { 45 | return new OpenAPI() 46 | .info(new Info() 47 | .title("知识库管理系统API") 48 | .version("0.0.1-SNAPSHOT") 49 | .description("利用 AI 管理你的知识库") 50 | .termsOfService("http://chat.cyy.com") 51 | .license(new License().name("Apache 2.0") 52 | .url("http://chat.cyy.com"))); 53 | } 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /web/src/main/java/com/cyy/chat/controller/ChatController.java: -------------------------------------------------------------------------------- 1 | package com.cyy.chat.controller; 2 | 3 | import com.cyy.chat.DocParser.AbstractParser; 4 | import com.cyy.chat.service.IChatService; 5 | import com.cyy.chat.DocParser.PdfParse; 6 | import com.cyy.common.utils.R; 7 | import io.swagger.v3.oas.annotations.Operation; 8 | import io.swagger.v3.oas.annotations.Parameter; 9 | import io.swagger.v3.oas.annotations.tags.Tag; 10 | import jakarta.annotation.Resource; 11 | import org.springframework.ai.openai.OpenAiChatModel; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.web.bind.annotation.*; 14 | import org.springframework.web.multipart.MultipartFile; 15 | 16 | import jakarta.servlet.http.HttpServletRequest; 17 | import java.util.List; 18 | 19 | /** 20 | * @author CYY 21 | * @date 2023年03月11日 下午7:22 22 | * @description 23 | */ 24 | @RestController 25 | @RequestMapping("/api") 26 | @Tag(name = "对话引擎", description = "管理对话相关的内容") 27 | public class ChatController { 28 | @Autowired 29 | private IChatService chatService; 30 | 31 | @Resource 32 | private OpenAiChatModel chatModel; 33 | 34 | private final String openAIkey = "openAI_key"; 35 | @PutMapping("/save") 36 | public void saveKey(@RequestParam String key, HttpServletRequest request){ 37 | request.getSession().setAttribute(openAIkey,key); 38 | } 39 | @GetMapping("/chat") 40 | @Operation( 41 | summary = "对话接口", 42 | description = "根据用户输入的问题返回对应的回答" 43 | ) 44 | public R chat( 45 | @Parameter(description = "用户的问题",required = true,example = "你能做什么?") 46 | @RequestParam String question){ 47 | return R.ok().data("msg",chatModel.call(question)); 48 | } 49 | @PostMapping("/upload") 50 | public void upload(MultipartFile file, HttpServletRequest request) throws Exception { 51 | AbstractParser pdfParse = new PdfParse(); 52 | String key = (String) request.getSession().getAttribute(openAIkey); 53 | List sentenceList = pdfParse.parse(file.getInputStream()); 54 | chatService.save(key,sentenceList); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /web/src/main/java/com/cyy/chat/model/ChatGptModel.java: -------------------------------------------------------------------------------- 1 | package com.cyy.chat.model; 2 | 3 | import com.cyy.chat.model.parama.ChatGptApiParam; 4 | import com.cyy.chat.model.result.ChatGptApiResult; 5 | import com.cyy.chat.model.parama.Message; 6 | import com.google.gson.Gson; 7 | import okhttp3.*; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Component; 10 | 11 | import java.io.IOException; 12 | import java.net.SocketTimeoutException; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * @author CYY 18 | * @date 2023年02月11日 下午5:49 19 | * @description 20 | */ 21 | @Component 22 | public class ChatGptModel { 23 | private final Message systemMsg = new Message("system", 24 | "Use the text provided to form your answer, but avoid copying the text verbatim and avoid mentioning that you referenced the text in your answer. " + 25 | "Try to use your own words when possible. Keep your answer under 5 sentences." + 26 | "If the question is not relevant to the text provided you just need to reply in Chinese: \"Sorry, I can't provide you with the answer to {question}.\" " + 27 | "If not ask please chat normally." + 28 | "Be accurate, helpful, concise, and clear."); 29 | @Autowired 30 | private OkHttpClient openAiHttpClient; 31 | private final ChatGptApiParam chatGptApiParam = new ChatGptApiParam(); 32 | 33 | /** 34 | * 向openAi发送请求获取问题答案 35 | * @param question 问题 36 | * @return 答案,超时或者其他异常返回默认信息 37 | */ 38 | public String doChat(String apiKey,String question,List passages){ 39 | /* 40 | 将查找出来的文章集合处理成字符串 41 | */ 42 | StringBuilder builder = new StringBuilder(); 43 | for(int i = 1;i<= passages.size();i++){ 44 | builder.append(i).append(passages.get(i-1)).append("\n"); 45 | } 46 | /* 47 | 处理需要请求的信息 48 | */ 49 | List messageList = new ArrayList<>(); 50 | messageList.add(this.systemMsg); 51 | String msg = "Refer to the text below to answer in simplified Chinese:\"{%s}\"\n\"text:{%s}\""; 52 | String format = String.format(msg, question, builder); 53 | Message userMessage = new Message("user", format); 54 | messageList.add(userMessage); 55 | this.chatGptApiParam.setMessages(messageList); //加入请求体中 56 | Gson gson = new Gson(); 57 | String json = gson.toJson(this.chatGptApiParam); 58 | RequestBody requestBody = RequestBody.create(json, MediaType.get("application/json; charset=utf-8")); 59 | Request request = new Request.Builder() 60 | .url("https://api.openai.com/v1/chat/completions") 61 | .addHeader("Content-Type","application/json") 62 | .addHeader("Authorization",apiKey) 63 | .post(requestBody).build(); 64 | Message gptMessage; 65 | try (Response response = this.openAiHttpClient.newCall(request).execute()) { 66 | if(response.code() == 200){ 67 | ChatGptApiResult chatGptApiResult = gson.fromJson(response.body().string(), ChatGptApiResult.class); 68 | //取出gpt回应信息 69 | gptMessage = chatGptApiResult.getChoices().get(0).getMessage(); 70 | }else { 71 | return "chatGpt出错了,错误码:"+response.code(); 72 | //todo 频繁问答对应策略 73 | } 74 | } catch (SocketTimeoutException e){ 75 | e.printStackTrace(); 76 | return "网络超时了,稍后再向我提问吧。"; 77 | }catch (IOException e) { 78 | e.printStackTrace(); 79 | return "chatGpt出错了,try again......"; 80 | } 81 | return gptMessage.getContent(); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /web/src/main/java/com/cyy/chat/model/EmbeddingModel.java: -------------------------------------------------------------------------------- 1 | package com.cyy.chat.model; 2 | 3 | import com.cyy.chat.model.parama.EmbeddingsApiParam; 4 | import com.cyy.chat.model.result.EmbeddingsApiResult; 5 | import com.google.gson.Gson; 6 | import okhttp3.*; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Component; 9 | 10 | 11 | /** 12 | * @author CYY 13 | * @date 2023年03月11日 下午9:00 14 | * @description 15 | */ 16 | @Component 17 | public class EmbeddingModel { 18 | @Autowired 19 | private OkHttpClient openAiHttpClient; 20 | private EmbeddingsApiParam embeddingsApiParam = new EmbeddingsApiParam(); 21 | 22 | /** 23 | * 请求获取Embeddings,请求出错返回null 24 | * @param apiKey openAIKey 25 | * @param msg 需要Embeddings的信息 26 | * @return 为null则请求失败,反之放回正确结果 27 | */ 28 | public EmbeddingsApiResult doEmbedding(String apiKey, String msg){ 29 | this.embeddingsApiParam.setInput(msg); 30 | Gson gson = new Gson(); 31 | String json = gson.toJson(this.embeddingsApiParam); 32 | RequestBody requestBody = RequestBody.create(json, MediaType.get("application/json; charset=utf-8")); 33 | Request request = new Request.Builder() 34 | .url("https://api.openai.com/v1/embeddings") 35 | .addHeader("Content-Type","application/json") 36 | .addHeader("Authorization", apiKey) 37 | .post(requestBody).build(); 38 | EmbeddingsApiResult embeddingsApiResult = null; 39 | try(Response response = openAiHttpClient.newCall(request).execute()){ 40 | if(response.code() == 200){ 41 | embeddingsApiResult = gson.fromJson(response.body().string(), EmbeddingsApiResult.class); 42 | } 43 | }catch (Exception e){ 44 | e.printStackTrace(); 45 | } 46 | return embeddingsApiResult; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /web/src/main/java/com/cyy/chat/model/parama/ChatGptApiParam.java: -------------------------------------------------------------------------------- 1 | package com.cyy.chat.model.parama; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author CYY 7 | * @date 2023年02月11日 下午8:36 8 | * @description chat模式的api参数,聊天模式官方推荐参数 9 | */ 10 | public class ChatGptApiParam { 11 | private String model = "gpt-3.5-turbo"; 12 | private List messages; 13 | private double temperature = 1; 14 | private double top_p = 1; 15 | private int n = 1; 16 | private boolean stream = false; 17 | private String[] stop = null; 18 | private int max_tokens = 3072; 19 | private double presence_penalty = 0; 20 | private double frequency_penalty = 0; 21 | 22 | public ChatGptApiParam() { 23 | } 24 | 25 | public String getModel() { 26 | return model; 27 | } 28 | 29 | public void setModel(String model) { 30 | this.model = model; 31 | } 32 | 33 | public List getMessages() { 34 | return messages; 35 | } 36 | 37 | public void setMessages(List messages) { 38 | this.messages = messages; 39 | } 40 | 41 | public double getTemperature() { 42 | return temperature; 43 | } 44 | 45 | public void setTemperature(double temperature) { 46 | this.temperature = temperature; 47 | } 48 | 49 | public double getTop_p() { 50 | return top_p; 51 | } 52 | 53 | public void setTop_p(double top_p) { 54 | this.top_p = top_p; 55 | } 56 | 57 | public int getN() { 58 | return n; 59 | } 60 | 61 | public void setN(int n) { 62 | this.n = n; 63 | } 64 | 65 | public boolean isStream() { 66 | return stream; 67 | } 68 | 69 | public void setStream(boolean stream) { 70 | this.stream = stream; 71 | } 72 | 73 | public String[] getStop() { 74 | return stop; 75 | } 76 | 77 | public void setStop(String[] stop) { 78 | this.stop = stop; 79 | } 80 | 81 | public int getMax_tokens() { 82 | return max_tokens; 83 | } 84 | 85 | public void setMax_tokens(int max_tokens) { 86 | this.max_tokens = max_tokens; 87 | } 88 | 89 | public double getPresence_penalty() { 90 | return presence_penalty; 91 | } 92 | 93 | public void setPresence_penalty(double presence_penalty) { 94 | this.presence_penalty = presence_penalty; 95 | } 96 | 97 | public double getFrequency_penalty() { 98 | return frequency_penalty; 99 | } 100 | 101 | public void setFrequency_penalty(double frequency_penalty) { 102 | this.frequency_penalty = frequency_penalty; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /web/src/main/java/com/cyy/chat/model/parama/EmbeddingsApiParam.java: -------------------------------------------------------------------------------- 1 | package com.cyy.chat.model.parama; 2 | 3 | /** 4 | * @author CYY 5 | * @date 2023年03月10日 下午9:49 6 | * @description 7 | */ 8 | public class EmbeddingsApiParam { 9 | private String model = "text-embedding-ada-002"; 10 | private String input; 11 | 12 | public String getModel() { 13 | return model; 14 | } 15 | 16 | public void setModel(String model) { 17 | this.model = model; 18 | } 19 | 20 | public String getInput() { 21 | return input; 22 | } 23 | 24 | public void setInput(String input) { 25 | this.input = input; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /web/src/main/java/com/cyy/chat/model/parama/Message.java: -------------------------------------------------------------------------------- 1 | package com.cyy.chat.model.parama; 2 | 3 | /** 4 | * @author CYY 5 | * @date 2023年03月02日 上午11:42 6 | * @description 7 | */ 8 | public class Message { 9 | private String role; 10 | private String content; 11 | 12 | public Message(String role, String content) { 13 | this.role = role; 14 | this.content = content; 15 | } 16 | 17 | public String getRole() { 18 | return role; 19 | } 20 | 21 | public void setRole(String role) { 22 | this.role = role; 23 | } 24 | 25 | public String getContent() { 26 | return content; 27 | } 28 | 29 | public void setContent(String content) { 30 | this.content = content; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /web/src/main/java/com/cyy/chat/model/result/ChatGptApiResult.java: -------------------------------------------------------------------------------- 1 | package com.cyy.chat.model.result; 2 | 3 | import com.google.api.Usage; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author CYY 9 | * @date 2023年02月11日 下午6:47 10 | * @description chatGpt官方api返回结果封装 11 | */ 12 | public class ChatGptApiResult { 13 | private String id; 14 | private String object; 15 | private Long created; 16 | private String model; 17 | private Usage usage; 18 | 19 | private List choices; 20 | 21 | public String getId() { 22 | return id; 23 | } 24 | 25 | public void setId(String id) { 26 | this.id = id; 27 | } 28 | 29 | public String getObject() { 30 | return object; 31 | } 32 | 33 | public void setObject(String object) { 34 | this.object = object; 35 | } 36 | 37 | public Long getCreated() { 38 | return created; 39 | } 40 | 41 | public void setCreated(Long created) { 42 | this.created = created; 43 | } 44 | 45 | public String getModel() { 46 | return model; 47 | } 48 | 49 | public void setModel(String model) { 50 | this.model = model; 51 | } 52 | 53 | public List getChoices() { 54 | return choices; 55 | } 56 | 57 | public void setChoices(List choices) { 58 | this.choices = choices; 59 | } 60 | 61 | public Usage getUsage() { 62 | return usage; 63 | } 64 | 65 | public void setUsage(Usage usage) { 66 | this.usage = usage; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /web/src/main/java/com/cyy/chat/model/result/Choices.java: -------------------------------------------------------------------------------- 1 | package com.cyy.chat.model.result; 2 | 3 | import com.cyy.chat.model.parama.Message; 4 | 5 | /** 6 | * @author CYY 7 | * @date 2023年02月11日 下午6:52 8 | * @description chatGpt官方api返回结果封装 9 | */ 10 | public class Choices { 11 | private Message message; 12 | private int index; 13 | private String finish_reason; 14 | 15 | public Message getMessage() { 16 | return message; 17 | } 18 | 19 | public void setMessage(Message message) { 20 | this.message = message; 21 | } 22 | 23 | public int getIndex() { 24 | return index; 25 | } 26 | 27 | public void setIndex(int index) { 28 | this.index = index; 29 | } 30 | public String getFinish_reason() { 31 | return finish_reason; 32 | } 33 | 34 | public void setFinish_reason(String finish_reason) { 35 | this.finish_reason = finish_reason; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /web/src/main/java/com/cyy/chat/model/result/EmbeddingObj.java: -------------------------------------------------------------------------------- 1 | package com.cyy.chat.model.result; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author CYY 7 | * @date 2023年03月10日 下午10:58 8 | * @description 9 | */ 10 | public class EmbeddingObj { 11 | private String object; 12 | private Integer index; 13 | private List embedding; 14 | 15 | public String getObject() { 16 | return object; 17 | } 18 | 19 | public void setObject(String object) { 20 | this.object = object; 21 | } 22 | 23 | public Integer getIndex() { 24 | return index; 25 | } 26 | 27 | public void setIndex(Integer index) { 28 | this.index = index; 29 | } 30 | 31 | public List getEmbedding() { 32 | return embedding; 33 | } 34 | 35 | public void setEmbedding(List embedding) { 36 | this.embedding = embedding; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /web/src/main/java/com/cyy/chat/model/result/EmbeddingsApiResult.java: -------------------------------------------------------------------------------- 1 | package com.cyy.chat.model.result; 2 | 3 | import com.google.api.Usage; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author CYY 9 | * @date 2023年03月10日 下午10:54 10 | * @description 11 | */ 12 | public class EmbeddingsApiResult { 13 | private String object; 14 | private List data; 15 | private String model; 16 | private Usage usage; 17 | 18 | public String getObject() { 19 | return object; 20 | } 21 | 22 | public void setObject(String object) { 23 | this.object = object; 24 | } 25 | 26 | public List getData() { 27 | return data; 28 | } 29 | 30 | public void setData(List data) { 31 | this.data = data; 32 | } 33 | 34 | public String getModel() { 35 | return model; 36 | } 37 | 38 | public void setModel(String model) { 39 | this.model = model; 40 | } 41 | 42 | public Usage getUsage() { 43 | return usage; 44 | } 45 | 46 | public void setUsage(Usage usage) { 47 | this.usage = usage; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /web/src/main/java/com/cyy/chat/pojo/PDFData.java: -------------------------------------------------------------------------------- 1 | package com.cyy.chat.pojo; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author CYY 7 | * @date 2023年03月11日 下午10:08 8 | * @description 9 | */ 10 | public class PDFData { 11 | private Long id; 12 | private String content; 13 | private Integer contentWordCount; 14 | 15 | public PDFData(Long id, String content, Integer contentWordCount) { 16 | this.id = id; 17 | this.content = content; 18 | this.contentWordCount = contentWordCount; 19 | } 20 | 21 | public Long getId() { 22 | return id; 23 | } 24 | 25 | public void setId(Long id) { 26 | this.id = id; 27 | } 28 | 29 | public String getContent() { 30 | return content; 31 | } 32 | 33 | public void setContent(String content) { 34 | this.content = content; 35 | } 36 | 37 | public Integer getContentWordCount() { 38 | return contentWordCount; 39 | } 40 | 41 | public void setContentWordCount(Integer contentWordCount) { 42 | this.contentWordCount = contentWordCount; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /web/src/main/java/com/cyy/chat/service/ChatServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.cyy.chat.service; 2 | 3 | import com.cyy.chat.model.ChatGptModel; 4 | import com.cyy.chat.model.EmbeddingModel; 5 | import com.cyy.chat.model.result.EmbeddingsApiResult; 6 | import com.cyy.chat.pojo.PDFData; 7 | import io.milvus.client.MilvusClient; 8 | import io.milvus.common.clientenum.ConsistencyLevelEnum; 9 | import io.milvus.grpc.SearchResults; 10 | import io.milvus.param.R; 11 | import io.milvus.param.collection.LoadCollectionParam; 12 | import io.milvus.param.collection.ReleaseCollectionParam; 13 | import io.milvus.param.dml.InsertParam; 14 | import io.milvus.param.dml.SearchParam; 15 | import io.milvus.response.SearchResultsWrapper; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.stereotype.Service; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Arrays; 21 | import java.util.List; 22 | import java.util.Objects; 23 | 24 | /** 25 | * @author CYY 26 | * @date 2023年03月11日 下午7:31 27 | * @description 28 | */ 29 | @Service 30 | public class ChatServiceImpl implements IChatService{ 31 | @Autowired 32 | private MilvusClient milvusClient; 33 | @Autowired 34 | private ChatGptModel chatGptModel; 35 | @Autowired 36 | private EmbeddingModel embeddingModel; 37 | public String toChat(String key,String question){ 38 | EmbeddingsApiResult embedding = embeddingModel.doEmbedding(key,question); 39 | if(embedding == null) return "请求失败!"; 40 | List vector = embedding.getData().get(0).getEmbedding(); 41 | List searchResult = search(Arrays.asList(vector)); 42 | List contents = new ArrayList<>(); 43 | for(PDFData data:searchResult){ 44 | contents.add(data.getContent()); 45 | } 46 | String ans = chatGptModel.doChat(key,question, contents); 47 | return ans; 48 | } 49 | public List search(List> search_vectors){ 50 | milvusClient.loadCollection( 51 | LoadCollectionParam.newBuilder() 52 | .withCollectionName("pdf_data") 53 | .build() 54 | ); 55 | final Integer SEARCH_K = 4; // TopK 56 | final String SEARCH_PARAM = "{\"nprobe\":10}"; // Params 57 | List ids = Arrays.asList("id"); 58 | List contents = Arrays.asList("content"); 59 | List contentWordCounts = Arrays.asList("content_word_count"); 60 | SearchParam searchParam = SearchParam.newBuilder() 61 | .withCollectionName("pdf_data") 62 | .withConsistencyLevel(ConsistencyLevelEnum.STRONG) 63 | .withOutFields(ids) 64 | .withOutFields(contents) 65 | .withOutFields(contentWordCounts) 66 | .withTopK(SEARCH_K) 67 | .withVectors(search_vectors) 68 | .withVectorFieldName("content_vector") 69 | .withParams(SEARCH_PARAM) 70 | .build(); 71 | R respSearch = milvusClient.search(searchParam); 72 | List pdfDataList = new ArrayList<>(); 73 | if(respSearch.getStatus() == R.Status.Success.getCode()){ 74 | //respSearch.getData().getStatus() == R.Status.Success 75 | SearchResults resp = respSearch.getData(); 76 | if(!resp.hasResults()){ //判断是否查到结果 77 | return new ArrayList<>(); 78 | } 79 | for (int i = 0; i < search_vectors.size(); ++i) { 80 | SearchResultsWrapper wrapperSearch = new SearchResultsWrapper(resp.getResults()); 81 | List id = (List) wrapperSearch.getFieldData("id", 0); 82 | List content = (List) wrapperSearch.getFieldData("content", 0); 83 | List contentWordCount = (List) wrapperSearch.getFieldData("content_word_count", 0); 84 | PDFData pdfData = new PDFData(id.get(0),content.get(0),contentWordCount.get(0)); 85 | pdfDataList.add(pdfData); 86 | 87 | } 88 | 89 | } 90 | milvusClient.releaseCollection( 91 | ReleaseCollectionParam.newBuilder() 92 | .withCollectionName("pdf_data") 93 | .build()); 94 | return pdfDataList; 95 | } 96 | public void save(String key,List sentenceList){ 97 | List contentWordCount = new ArrayList<>(); 98 | List> contentVector = new ArrayList<>(); 99 | for(String str : sentenceList){ 100 | contentWordCount.add(str.length()); 101 | EmbeddingsApiResult embedding = embeddingModel.doEmbedding(key,str); 102 | try { 103 | Thread.sleep(3000); 104 | } catch (InterruptedException e) { 105 | throw new RuntimeException(e); 106 | } 107 | if(embedding == null){ 108 | return; 109 | } 110 | contentVector.add(embedding.getData().get(0).getEmbedding()); 111 | } 112 | 113 | List fields = new ArrayList<>(); 114 | fields.add(new InsertParam.Field("content", sentenceList)); 115 | fields.add(new InsertParam.Field("content_word_count", contentWordCount)); 116 | fields.add(new InsertParam.Field("content_vector", contentVector)); 117 | 118 | InsertParam insertParam = InsertParam.newBuilder() 119 | .withCollectionName("pdf_data") 120 | .withFields(fields) 121 | .build(); 122 | //插入数据 123 | milvusClient.insert(insertParam); 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /web/src/main/java/com/cyy/chat/service/IChatService.java: -------------------------------------------------------------------------------- 1 | package com.cyy.chat.service; 2 | 3 | import com.cyy.chat.pojo.PDFData; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author CYY 9 | * @date 2023年03月11日 下午7:30 10 | * @description 11 | */ 12 | public interface IChatService { 13 | String toChat(String key, String question); 14 | void save(String key, List sentenceList); 15 | List search(List> search_vectors); 16 | } 17 | -------------------------------------------------------------------------------- /web/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | 2 | proxy: 3 | ip: 127.0.0.1 4 | port: 7890 5 | milvus: 6 | ip: localhost 7 | port: 19530 8 | server: 9 | port: 8081 10 | 11 | spring: 12 | ai: 13 | openai: 14 | base-url: https://openrouter.ai/api/ 15 | api-key: sk-xxxxxxdsfsfsdfsdfsdfsdfsdf 16 | 17 | springdoc: 18 | swagger-ui: 19 | path: /swagger-ui.html 20 | tags-sorter: alpha 21 | operations-sorter: alpha 22 | api-docs: 23 | path: /v3/api-docs 24 | group-configs: 25 | - group: 'default' 26 | paths-to-match: '/**' 27 | packages-to-scan: com.cyy.chat.controller 28 | -------------------------------------------------------------------------------- /web/src/test/java/com/cyy/chat/CustomizedChatApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.cyy.chat; 2 | 3 | import io.milvus.client.MilvusServiceClient; 4 | import io.milvus.grpc.DataType; 5 | import io.milvus.param.IndexType; 6 | import io.milvus.param.MetricType; 7 | import io.milvus.param.collection.CreateCollectionParam; 8 | import io.milvus.param.collection.DropCollectionParam; 9 | import io.milvus.param.collection.FieldType; 10 | import io.milvus.param.index.CreateIndexParam; 11 | import org.junit.jupiter.api.Test; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | 15 | @SpringBootTest 16 | class CustomizedChatApplicationTests { 17 | @Autowired 18 | MilvusServiceClient milvusClient; 19 | 20 | @Test 21 | void prepare() { 22 | dropCollection(milvusClient); 23 | createCollection(milvusClient); 24 | buildIndex(milvusClient); 25 | } 26 | void buildIndex(MilvusServiceClient client){ 27 | final String INDEX_PARAM = "{\"nlist\":1024}"; 28 | client.createIndex( 29 | CreateIndexParam.newBuilder() 30 | .withCollectionName("pdf_data") 31 | .withFieldName("content_vector") 32 | .withIndexType(IndexType.IVF_FLAT) 33 | .withMetricType(MetricType.L2) 34 | .withExtraParam(INDEX_PARAM) 35 | .withSyncMode(Boolean.FALSE) 36 | .build() 37 | ); 38 | } 39 | void dropCollection(MilvusServiceClient client){ 40 | client.dropCollection( 41 | DropCollectionParam.newBuilder() 42 | .withCollectionName("pdf_data") 43 | .build() 44 | ); 45 | } 46 | void createCollection(MilvusServiceClient client){ 47 | FieldType fieldType1 = FieldType.newBuilder() 48 | .withName("id") 49 | .withDataType(DataType.Int64) 50 | .withPrimaryKey(true) 51 | .withAutoID(true) 52 | .build(); 53 | FieldType fieldType2 = FieldType.newBuilder() 54 | .withName("content_word_count") 55 | .withDataType(DataType.Int32) 56 | .build(); 57 | FieldType fieldType3 = FieldType.newBuilder() 58 | .withName("content") 59 | .withDataType(DataType.VarChar) 60 | .withMaxLength(1024) 61 | .build(); 62 | FieldType fieldType4 = FieldType.newBuilder() 63 | .withName("content_vector") 64 | .withDataType(DataType.FloatVector) 65 | .withDimension(1536) 66 | .build(); 67 | CreateCollectionParam createCollectionReq = CreateCollectionParam.newBuilder() 68 | .withCollectionName("pdf_data") 69 | .withShardsNum(4) 70 | .addFieldType(fieldType1) 71 | .addFieldType(fieldType2) 72 | .addFieldType(fieldType3) 73 | .addFieldType(fieldType4) 74 | .build(); 75 | client.createCollection(createCollectionReq); 76 | } 77 | } 78 | --------------------------------------------------------------------------------