十分钟编码实战springboot对接ChatGPT

Java后端

        pom.xml添加ChatGPT的依赖

<dependency>
    <groupId>com.unfbx</groupId>
    <artifactId>chatgpt-java</artifactId>
    <version>1.0.10</version>
    <!--排除子依赖 slf4j-simple 不然会有冲突 -->
    <exclusions>
       <exclusion>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-simple</artifactId>
       </exclusion>
    </exclusions>
</dependency>

SpringBootApplication启动文件配置

@SpringBootApplication
public class NotesApplication implements CommandLineRunner {
    public static void main(String[] args) {
        SpringApplication.run(NotesApplication.class, args);
    }
    @Override
    public void run(String... args) throws Exception {
    }
    @Value("${chatgpt.apiKey}")
    private List<String> apiKey;
    @Value("${chatgpt.apiHost}")
    private String apiHost;
    @Bean
    public OpenAiStreamClientSub openAiStreamClient() {
        //本地开发调试条件: 1. 配置科学上网   2.配置IP代理
        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 17890));
        HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
        //!!!!!!测试或者发布到服务器千万不要配置Level == BODY!!!!
        httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
        OkHttpClient okHttpClient = new OkHttpClient
                .Builder()
                .proxy(proxy)
                .addInterceptor(httpLoggingInterceptor)
                .connectTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(60, TimeUnit.SECONDS)
                .readTimeout(600, TimeUnit.SECONDS)
                .build();
        System.out.println("使用的key:" + apiKey);
        return OpenAiStreamClientSub
                .builder()
                .apiHost(apiHost)
                .apiKey(apiKey)
                //自定义key使用策略 默认随机策略
                .keyStrategy(new KeyRandomStrategy())
                .okHttpClient(okHttpClient)
                .build();
    }
}

注意本地开发配置条件:.配置IP代理   很关键,缺一不可

控制器Controller

/**
     * AI助手端 SSE 服务端 推送 客户端
     * @param headers
     */
    @RequestMapping("/v1/chat/completions")
    @CrossOrigin
    public SseEmitter sseCompletions(@RequestHeader Map<String, String> headers)  {
        String uuid = headers.get("uuid");
        //解码 内容部分
        String promptContent = headers.get("promptcontent");
        String content = null;
        try {
            content = URLDecoder.decode(promptContent, "utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        log.info("content:{}", content);
        //默认30秒超时,设置为0L则永不超时
        SseEmitter sseEmitter = new SseEmitter(0L);
        long startTime = System.currentTimeMillis();
        customChatGpt.sseCompletions(sseEmitter, uuid,  content);
        long endTime = System.currentTimeMillis();
        log.info("请求耗时:{} ms", (endTime-startTime)/1000);
        return sseEmitter;
}
  1. 需要配置 @CrossOrigin 注解: 全称"跨域资源共享"(Cross-origin resource sharing)

解决跨域问题,实现CORS的关键在于服务器,只要服务器实现CORS接口,就可以实现跨域通信

返回值为SseEmitter ,此处采用SSE推送技术实现,SseEmitter简介:SpringMVC提供的一种技术,可以实现服务端向客户端实时推送数据的功能,用法在Contorller中提供一个接口,返回SseEmitter对象,发送数据可以在另一个接口调用其send方法发送数据,SpringBoot已经集成这个功能因此直接使用

生成ChatGPT喜欢的聊天列表,用于ChatGPT更好的理解与用户聊天的上下文

 public void sseCompletions(SseEmitter sseEmitter, String uuid, String question) {
        String key = String.format(messagesKey, uuid);
        List<Message> messages = redisCache.getCacheObject(key);
        if (StringUtils.isNotNull(messages)) {
            if (messages.size() >= 5) {
                messages = messages.subList(1, 5);
            }
            Message currentMessage = Message.builder().content(question).role(Message.Role.USER).build();
            messages.add(currentMessage);
        } else {
            messages = new ArrayList<>();
            Message currentMessage = Message.builder().content(question).role(Message.Role.USER).build();
            messages.add(currentMessage);
        }
        OpenAISSEEventSourceListener eventSourceListener = new OpenAISSEEventSourceListener(sseEmitter);
        openAiStreamClientSub.streamChatCompletion(messages, eventSourceListener);
        redisCache.setCacheObject(key, messages, 50, TimeUnit.MINUTES );
}

1.3 重载 EventSourceListener 监听器 重写 onEvent 回调接口
@SneakyThrows
    @Override
    public void onEvent(EventSource eventSource, String id, String type, String data) {
        log.info("OpenAI返回数据:{}", data);
        if (data.equals("[DONE]")) {
            log.info("OpenAI返回数据结束了");
            sseEmitter.send(SseEmitter.event()
                    .id("[DONE]")
                    .data("[DONE]")
                    .reconnectTime(3000));
            return;
        }
        ObjectMapper mapper = new ObjectMapper();
        // 读取Json
        ChatCompletionResponse completionResponse = mapper.readValue(data, ChatCompletionResponse.class);
        sseEmitter.send(SseEmitter.event()
                .id(completionResponse.getId())
                .data(completionResponse.getChoices().get(0).getDelta())
                .reconnectTime(3000));
    }

 构造http请求 v1/chat/completions 接口,发起对话

public void streamChatCompletion(ChatCompletion chatCompletion, EventSourceListener eventSourceListener) {
        if (Objects.isNull(eventSourceListener)) {
            log.error("参数异常:EventSourceListener不能为空,可以参考:com.unfbx.chatgpt.sse.ConsoleEventSourceListener");
            throw new BaseException(CommonError.PARAM_ERROR);
        } else {
            if (!chatCompletion.isStream()) {
                chatCompletion.setStream(true);
            }

            try {
                EventSource.Factory factory = EventSources.createFactory(this.okHttpClient);
                ObjectMapper mapper = new ObjectMapper();
                String requestBody = mapper.writeValueAsString(chatCompletion);
                Request request = (new okhttp3.Request.Builder()).url(this.apiHost + "v1/chat/completions").post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)).build();
                factory.newEventSource(request, eventSourceListener);
            } catch (JsonProcessingException var8) {
                log.error("请求参数解析异常:{}", var8);
                var8.printStackTrace();
            } catch (Exception var9) {
                log.error("请求参数解析异常:{}", var9);
                var9.printStackTrace();
            }

        }
    }

等待ChatGPT流式回答,效果如下

H5前端

下载依赖: npm  install event-source-polyfill

创建SSE连接

createSSE(item){
				let that = this
				const itemAss = {
					type: 'chat',
					content: '回答中...',
					my: false,
					loading: true,
				};
				that.messages.push(itemAss)
				that.disableInput()
				that.setScroll()
				
				if(window.EventSource){
					this.eventSource = new EventSourcePolyfill(
					//${config.baseUrl}/sse/openApi/chatGPT/v1/chat/sseCompletions
					  `http://localhost:7091/openApi/chatGPT/v1/chat/completions`, {
					  // 设置重连时间
					  heartbeatTimeout: 60 * 60 * 1000,
					  // 添加token
					  headers: {
						'Authorization': `Bearer `,
						'uuid': that.userName,
						'promptcontent': encodeURIComponent(item.content)
					  },
					});
					this.eventSource.onopen = (e) => {
					  console.log("已建立SSE连接~")
					 
					}
					this.eventSource.onmessage = (e) => {
					  //隐藏加载框
					  // uni.hideLoading();
					  let item = that.messages[that.messages.length - 1]
					  console.log("消息入栈:", e.data)
					  let result = e.data
					  //数据流开始标记
					  if(result === '{"role":"assistant"}'){
						item.content  = ''
					  }
					  //数据流结束标记
					  if(result === '[DONE]'){
					    uni.setStorageSync('isOpen', true)
						that.closeSSE()
					  }
					  //内容
					  if(result.includes('content')){
						let jsonResult = JSON.parse(result)
						//内容进行拼接
						item.content += jsonResult.content
						that.setScroll()
					  }
					
					}
					this.eventSource.onerror = (e) => {
					  if (e.readyState == EventSource.CLOSED) {
						console.log("SSE连接关闭", JSON.stringify(this.eventSource))
					  } else if (this.eventSource.readyState == EventSource.CONNECTING) {
						console.log("SSE正在重连", JSON.stringify(this.eventSource))
						
					  } else {
						console.log('error', e);
					  }
					};
				} else {
					console.log("你的浏览器不支持SSE~")
				}
},

问题及解决办法

配置本地代理

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值