注册

保姆级JAVA对接ChatGPT教程,实现自己的AI对话助手

1.前言


大家好,我是王老狮,近期OpenAI开放了chatGPT的最新gpt-3.5-turbo模型,据介绍该模型是和当前官网使用的相同的模型,如果你还没体验过ChatGPT,那么今天就教大家如何打破网络壁垒,打造一个属于自己的智能助手把。本文包括API Key的申请以及网络代理的搭建,那么事不宜迟,我们现在开始。


2.对接流程


2.1.API-Key的获取


首先第一步要获取OpenAI接口的API Key,该Key是你用来调用接口的token,主要用于接口鉴权。获取该key首先要注册OpenAi的账号,具体可以见我的另外一篇文章,ChatGPT保姆级注册教程



  1. 打开platform.openai.com/网站,点击view API Key,

image.png



  1. 点击创建key

image.png



  1. 弹窗显示生成的key,记得把key复制,不然等会就找不到这个key了,只能重新创建。

image.png


将API Key保存好以备用


2.2.API用量的查看


这里可以查看API的使用情况,新账号注册默认有5美元的试用额度,之前都是18美元,API成本降了之后试用额度也狠狠地砍了一刀啊,哈哈。


image.png


2.3.核心代码实现


2.3.1.pom依赖


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0modelVersion>
<groupId>com.webtapgroupId>
<artifactId>webtapartifactId>
<version>0.0.1version>
<packaging>jarpackaging>

<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.2.RELEASEversion>
parent>

<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>nz.net.ultraq.thymeleafgroupId>
<artifactId>thymeleaf-layout-dialectartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-mailartifactId>
dependency>

<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
<version>3.4version>
dependency>
<dependency>
<groupId>commons-codecgroupId>
<artifactId>commons-codecartifactId>
dependency>
<dependency>
<groupId>org.jsoupgroupId>
<artifactId>jsoupartifactId>
<version>1.9.2version>
dependency>

<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.56version>
dependency>
<dependency>
<groupId>net.sourceforge.nekohtmlgroupId>
<artifactId>nekohtmlartifactId>
<version>1.9.22version>
dependency>
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelper-spring-boot-starterartifactId>
<version>1.4.1version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpasyncclientartifactId>
<version>4.0.2version>
dependency>
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpcore-nioartifactId>
<version>4.3.2version>
dependency>

<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpclientartifactId>
<version>4.3.5version>
<exclusions>
<exclusion>
<artifactId>commons-codecartifactId>
<groupId>commons-codecgroupId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>commons-httpclientgroupId>
<artifactId>commons-httpclientartifactId>
<version>3.1version>
<exclusions>
<exclusion>
<artifactId>commons-codecartifactId>
<groupId>commons-codecgroupId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>1.3.1version>
dependency>
<dependency>
<groupId>com.github.ulisesbocchiogroupId>
<artifactId>jasypt-spring-boot-starterartifactId>
<version>2.0.0version>
dependency>

dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>

project>

2.3.2.实体类ChatMessage.java


用于存放发送的消息信息,注解使用了lombok,如果没有使用lombok可以自动生成构造方法以及get和set方法


@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChatMessage {
//消息角色
String role;
//消息内容
String content;
}

2.3.3.实体类ChatCompletionRequest.java


用于发送的请求的参数实体类,参数释义如下:


model:选择使用的模型,如gpt-3.5-turbo


messages :发送的消息列表


temperature :温度,参数从0-2,越低表示越精准,越高表示越广发,回答的内容重复率越低


n :回复条数,一次对话回复的条数


stream :是否流式处理,就像ChatGPT一样的处理方式,会增量的发送信息。


max_tokens :生成的答案允许的最大token数


user :对话用户


@Data
@Builder
public class ChatCompletionRequest {

String model;

List<ChatMessage> messages;

Double temperature;

Integer n;

Boolean stream;

List<String> stop;

Integer max_tokens;

String user;
}

2.3.4.实体类ExecuteRet .java


用于接收请求返回的信息以及执行结果



/**
* 调用返回
*/

public class ExecuteRet {

/**
* 操作是否成功
*/

private final boolean success;

/**
* 返回的内容
*/

private final String respStr;

/**
* 请求的地址
*/

private final HttpMethod method;

/**
* statusCode
*/

private final int statusCode;

public ExecuteRet(booleansuccess, StringrespStr, HttpMethodmethod, intstatusCode) {
this.success =success;
this.respStr =respStr;
this.method =method;
this.statusCode =statusCode;
}

@Override
public String toString()
{
return String.format("[success:%s,respStr:%s,statusCode:%s]", success, respStr, statusCode);
}

/**
*@returnthe isSuccess
*/

public boolean isSuccess() {
return success;
}

/**
*@returnthe !isSuccess
*/

public boolean isNotSuccess() {
return !success;
}

/**
*@returnthe respStr
*/

public String getRespStr() {
return respStr;
}

/**
*@returnthe statusCode
*/

public int getStatusCode() {
return statusCode;
}

/**
*@returnthe method
*/

public HttpMethod getMethod() {
return method;
}
}

2.3.5.实体类ChatCompletionChoice .java


用于接收ChatGPT返回的数据


@Data
public class ChatCompletionChoice {

Integer index;

ChatMessage message;

String finishReason;
}

2.3.6.接口调用核心类OpenAiApi .java


使用httpclient用于进行api接口的调用,支持post和get方法请求。


url为配置文件open.ai.url的值,表示调用api的地址:https://api.openai.com/ ,token为获取的api-key。
执行post或者get方法时增加头部信息headers.put("Authorization", "Bearer " + token); 用于通过接口鉴权。



@Slf4j
@Component
public class OpenAiApi {

@Value("${open.ai.url}")
private String url;
@Value("${open.ai.token}")
private String token;

private static final MultiThreadedHttpConnectionManagerCONNECTION_MANAGER= new MultiThreadedHttpConnectionManager();

static {
// 默认单个host最大链接数
CONNECTION_MANAGER.getParams().setDefaultMaxConnectionsPerHost(
Integer.valueOf(20));
// 最大总连接数,默认20
CONNECTION_MANAGER.getParams()
.setMaxTotalConnections(20);
// 连接超时时间
CONNECTION_MANAGER.getParams()
.setConnectionTimeout(60000);
// 读取超时时间
CONNECTION_MANAGER.getParams().setSoTimeout(60000);
}

public ExecuteRet get(Stringpath, Map headers) {
GetMethod method = new GetMethod(url +path);
if (headers== null) {
headers = new HashMap<>();
}
headers.put("Authorization", "Bearer " + token);
for (Map.Entry h : headers.entrySet()) {
method.setRequestHeader(h.getKey(), h.getValue());
}
return execute(method);
}

public ExecuteRet post(Stringpath, Stringjson, Map headers) {
try {
PostMethod method = new PostMethod(url +path);
//log.info("POST Url is {} ", url + path);
// 输出传入参数
log.info(String.format("POST JSON HttpMethod's Params = %s",json));
StringRequestEntity entity = new StringRequestEntity(json, "application/json", "UTF-8");
method.setRequestEntity(entity);
if (headers== null) {
headers = new HashMap<>();
}
headers.put("Authorization", "Bearer " + token);
for (Map.Entry h : headers.entrySet()) {
method.setRequestHeader(h.getKey(), h.getValue());
}
return execute(method);
} catch (UnsupportedEncodingExceptionex) {
log.error(ex.getMessage(),ex);
}
return new ExecuteRet(false, "", null, -1);
}

public ExecuteRet execute(HttpMethodmethod) {
HttpClient client = new HttpClient(CONNECTION_MANAGER);
int statusCode = -1;
String respStr = null;
boolean isSuccess = false;
try {
client.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "UTF8");
statusCode = client.executeMethod(method);
method.getRequestHeaders();

// log.info("执行结果statusCode = " + statusCode);
InputStreamReader inputStreamReader = new InputStreamReader(method.getResponseBodyAsStream(), "UTF-8");
BufferedReader reader = new BufferedReader(inputStreamReader);
StringBuilder stringBuffer = new StringBuilder(100);
String str;
while ((str = reader.readLine()) != null) {
log.debug("逐行读取String = " + str);
stringBuffer.append(str.trim());
}
respStr = stringBuffer.toString();
if (respStr != null) {
log.info(String.format("执行结果String = %s, Length = %d", respStr, respStr.length()));
}
inputStreamReader.close();
reader.close();
// 返回200,接口调用成功
isSuccess = (statusCode == HttpStatus.SC_OK);
} catch (IOExceptionex) {
} finally {
method.releaseConnection();
}
return new ExecuteRet(isSuccess, respStr,method, statusCode);
}

}

2.3.7.定义接口常量类PathConstant.class


用于维护支持的api接口列表


public class PathConstant {
public static class MODEL {
//获取模型列表
public static String MODEL_LIST = "/v1/models";
}

public static class COMPLETIONS {
public static String CREATE_COMPLETION = "/v1/completions";
//创建对话
public static String CREATE_CHAT_COMPLETION = "/v1/chat/completions";

}
}

2.3.8.接口调用调试单元测试类OpenAiApplicationTests.class


核心代码都已经准备完毕,接下来写个单元测试测试下接口调用情况。



@SpringBootTest
@RunWith(SpringRunner.class)
public class OpenAiApplicationTests {

@Autowired
private OpenAiApi openAiApi;
@Test
public void createChatCompletion2() {
Scanner in = new Scanner(System.in);
String input = in.next();
ChatMessage systemMessage = new ChatMessage('user', input);
messages.add(systemMessage);
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
.model("gpt-3.5-turbo-0301")
.messages(messages)
.user("testing")
.max_tokens(500)
.temperature(1.0)
.build();
ExecuteRet executeRet = openAiApi.post(PathConstant.COMPLETIONS.CREATE_CHAT_COMPLETION, JSONObject.toJSONString(chatCompletionRequest),
null);
JSONObject result = JSONObject.parseObject(executeRet.getRespStr());
List choices = result.getJSONArray("choices").toJavaList(ChatCompletionChoice.class);
System.out.println(choices.get(0).getMessage().getContent());
ChatMessage context = new ChatMessage(choices.get(0).getMessage().getRole(), choices.get(0).getMessage().getContent());
System.out.println(context.getContent());
}

}


  • 使用Scanner 用于控制台输入信息,如果单元测试时控制台不能输入,那么进入IDEA的安装目录,修改以下文件。增加最后一行增加-Deditable.java.test.console=true即可。

image.png
image.png




  • 创建ChatMessage对象,用于存放参数,role有user,system,assistant,一般接口返回的响应为assistant角色,我们一般使用user就好。




  • 定义请求参数ChatCompletionRequest,这里我们使用3.1日发布的最新模型gpt-3.5-turbo-0301。具体都有哪些模型大家可以调用v1/model接口查看支持的模型。




  • 之后调用openAiApi.post进行接口的请求,并将请求结果转为JSON对象。取其中的choices字段转为ChatCompletionChoice对象,该对象是存放api返回的具体信息。


    接口返回信息格式如下:


    {
    "id": "chatcmpl-6rNPw1hqm5xMVMsyf6PXClRHtNQAI",
    "object": "chat.completion",
    "created": 1678179420,
    "model": "gpt-3.5-turbo-0301",
    "usage": {
    "prompt_tokens": 16,
    "completion_tokens": 339,
    "total_tokens": 355
    },
    "choices": [{
    "message": {
    "role": "assistant",
    "content": "\n\nI. 介绍数字孪生的概念和背景\n A. 数字孪生的定义和意义\n B. 数字孪生的发展历程\n C. 数字孪生在现代工业的应用\n\nII. 数字孪生的构建方法\n A. 数字孪生的数据采集和处理\n B. 数字孪生的建模和仿真\n C. 数字孪生的验证和测试\n\nIII. 数字孪生的应用领域和案例分析\n A. 制造业领域中的数字孪生应用\n B. 建筑和城市领域中的数字孪生应用\n C. 医疗和健康领域中的数字孪生应用\n\nIV. 数字孪生的挑战和发展趋势\n A. 数字孪生的技术挑战\n B. 数字孪生的实践难点\n C. 数字孪生的未来发展趋势\n\nV. 结论和展望\n A. 总结数字孪生的意义和价值\n B. 展望数字孪生的未来发展趋势和研究方向"
    },
    "finish_reason": "stop",
    "index": 0
    }]
    }



  • 输出对应的信息。




2.3.9.结果演示


image.png


2.4.连续对话实现


2.4.1连续对话的功能实现


基本接口调通之后,发现一次会话之后,没有返回完,输入继续又重新发起了新的会话。那么那么我们该如何实现联系上下文呢?其实只要做一些简单地改动,将每次对话的信息都保存到一个消息列表中,这样问答就支持上下文了,代码如下:


Listmessages = new ArrayList<>();
@Test
public void createChatCompletion() {
Scanner in = new Scanner(System.in);
String input = in.next();
while (!"exit".equals(input)) {
ChatMessage systemMessage = new ChatMessage(ChatMessageRole.USER.value(), input);
messages.add(systemMessage);
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
.model("gpt-3.5-turbo-0301")
.messages(messages)
.user("testing")
.max_tokens(500)
.temperature(1.0)
.build();
ExecuteRet executeRet = openAiApi.post(PathConstant.COMPLETIONS.CREATE_CHAT_COMPLETION, JSONObject.toJSONString(chatCompletionRequest),
null);
JSONObject result = JSONObject.parseObject(executeRet.getRespStr());
Listchoices = result.getJSONArray("choices").toJavaList(ChatCompletionChoice.class);
System.out.println(choices.get(0).getMessage().getContent());
ChatMessage context = new ChatMessage(choices.get(0).getMessage().getRole(), choices.get(0).getMessage().getContent());
messages.add(context);
in = new Scanner(System.in);
input = in.next();
}
}

因为OpenAi的/v1/chat/completions接口消息参数是个list,这个是用来保存我们的上下文的,因此我们只要将每次对话的内容用list进行保存即可。


2.4.2结果如下:


image.png


image.png


4.常见问题


4.1.OpenAi接口调用不通


因为https://api.openai.com/地址也被限制了,但是接口没有对地区做校验,因此可以自己搭建一个香港代理,也可以走科学上网。


我采用的是香港代理的模式,一劳永逸,具体代理配置流程如下:



  1. 购买一台香港的虚拟机,反正以后都会用得到,作为开发者建议搞一个。搞活动的时候新人很便宜,基本3年的才200块钱。
  2. 访问nginx.org/download/ng… 下载最新版nginx
  3. 部署nginx并修改/nginx/config/nginx.conf文件,配置接口代理路径如下

server {
listen 19999;
server_name ai;

ssl_certificate /usr/local/nginx/ssl/server.crt;
ssl_certificate_key /usr/local/nginx/ssl/server.key;

ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;

ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;

#charset koi8-r;

location /v1/ {
proxy_pass ;
}
}


  1. 启动nginx
  2. 将接口访问地址改为nginx的机器出口IP+端口即可

如果代理配置大家还不了解,可以留下评论我单独出一期教程。


4.2.接口返回401


检查请求方法是否增加token字段以及key是否正确


5.总结


至此JAVA对OpenAI对接就已经完成了,并且也支持连续对话,大家可以在此基础上不断地完善和桥接到web服务,定制自己的ChatGPT助手了。我自己也搭建了个平台,不断地在完善中,具体可见下图,后续会开源出来,想要体验的可以私信我获取地址和账号哈


image.png



作者:王老狮
来源:juejin.cn/post/7208907027841171512

0 个评论

要回复文章请先登录注册