保姆級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保姆級註冊教程。
- 開啟https://platform.openai.com/網站,點選view API Key,
- 點選建立key
- 彈窗顯示生成的key,記得把key複製,不然等會就找不到這個key了,只能重新建立。
將API Key儲存好以備用
2.2.API用量的檢視
這裡可以檢視API的使用情況,新賬號註冊預設有5美元的試用額度,之前都是18美元,API成本降了之後試用額度也狠狠地砍了一刀啊,哈哈。
2.3.核心程式碼實現
2.3.1.pom依賴
```
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.9.2</version>
</dependency>
<!-- alibaba.fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.56</version>
</dependency>
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
<version>1.9.22</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>
<version>4.0.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore-nio</artifactId>
<version>4.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3.5</version>
<exclusions>
<exclusion>
<artifactId>commons-codec</artifactId>
<groupId>commons-codec</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
<exclusions>
<exclusion>
<artifactId>commons-codec</artifactId>
<groupId>commons-codec</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
```
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<String, String> headers) {
GetMethod method = new GetMethod(url +path);
if (headers== null) {
headers = new HashMap<>();
}
headers.put("Authorization", "Bearer " + token);
for (Map.Entry<String, String> h : headers.entrySet()) {
method.setRequestHeader(h.getKey(), h.getValue());
}
return execute(method);
}
public ExecuteRet post(Stringpath, Stringjson, Map<String, String> 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
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<ChatCompletionChoice> 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即可。
-
建立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.結果演示
2.4.連續對話實現
2.4.1連續對話的功能實現
基本介面調通之後,發現一次會話之後,沒有返回完,輸入繼續又重新發起了新的會話。那麼那麼我們該如何實現聯絡上下文呢?其實只要做一些簡單地改動,將每次對話的資訊都儲存到一個訊息列表中,這樣問答就支援上下文了,程式碼如下:
List<ChatMessage> messages = 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());
List<ChatCompletionChoice> 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());
messages.add(context);
in = new Scanner(System.in);
input = in.next();
}
}
因為OpenAi的/v1/chat/completions介面訊息引數是個list,這個是用來儲存我們的上下文的,因此我們只要將每次對話的內容用list進行儲存即可。
2.4.2結果如下:
4.常見問題
4.1.OpenAi介面呼叫不通
因為https://api.openai.com/
地址也被限制了,但是介面沒有對地區做校驗,因此可以自己搭建一個香港代理,也可以走科學上網。
我採用的是香港代理的模式,一勞永逸,具體代理配置流程如下:
- 購買一臺香港的虛擬機器,反正以後都會用得到,作為開發者建議搞一個。搞活動的時候新人很便宜,基本3年的才200塊錢。
- 訪問http://nginx.org/download/nginx-1.23.3.tar.gz 下載最新版nginx
- 部署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 <https://api.openai.com>;
}
}
```
- 啟動nginx
- 將介面訪問地址改為nginx的機器出口IP+埠即可
如果代理配置大家還不瞭解,可以留下評論我單獨出一期教程。
4.2.介面返回401
檢查請求方法是否增加token欄位以及key是否正確
5.總結
至此JAVA對OpenAI對接就已經完成了,並且也支援連續對話,大家可以在此基礎上不斷地完善和橋接到web服務,定製自己的ChatGPT助手了。我自己也搭建了個平臺,不斷地在完善中,具體可見下圖,後續會開源出來,想要體驗的可以私信我獲取地址和賬號哈
本文正在參加「金石計劃」
- 保姆級JAVA對接ChatGPT教程,實現自己的AI對話助手
- ChatGPT保姆級註冊教學
- 公司產品太多了,怎麼實現一次登入產品互通?
- 前一陣鬧得沸沸揚揚的IP歸屬地,到底是怎麼實現的?
- 漫談資料安全-老闆擔心敏感資料洩露,該如何建設安全的資料體系?
- 【JAVA祕籍功法篇-分散式事務】事務的實現原理
- 必知必會JVM三-面試必備,JVM堆記憶體詳解
- Spring Cloud Zuul閘道器修改為短連線方法
- Jar包問題查詢指令碼
- Myabtis原始碼分析五-Mybatis配置載入完全圖解,建造者模式的使用
- Myabtis原始碼分析四-快取模組分析 ,裝飾模式的使用
- Mybatis開發要點-resultType和resultMap有什麼區別?
- 程式碼review神器Upsource,讓你快樂的進行CodeReview