文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Java调用ChatGPT(基于SpringBoot和Vue)实现连续对话、流式输出和自定义baseUrl

2023-08-16 15:47

关注

源码及更详细的介绍说明参见Git上的README.md文档 https://github.com/asleepyfish/chatgpt

本文Demo(SpringBoot和Main方法Demo均包括)的Git地址:https://github.com/asleepyfish/chatgpt-demo

流式输出结合Vue前端的Demo的Git地址:https://github.com/asleepyfish/chatgpt-vue

后续使用方法和api版本更新均在Github的README.md文档更新,本博客暂停更新~

2023-05-28更新

制作了一个基于本文sdk包的对话网站,可前往http://chatgpt.alpacos.cn/体验。
[注]: 由于本人的服务器的带宽较低,首次加载页面可能会略有些慢,请耐心等待,详细使用说明请查看另一篇博客~ 👇
ChatGPT网页版(基于SpringBoot和Vue)

2023-07-17更新

所有的测试Demo均包括结合Spring框架的(ChatGPTController类中)和main方法使用(MainTest类中)Demo地址:https://github.com/asleepyfish/chatgpt-demo

版本更新说明

1. 配置阶段

1.1 依赖引入

pom.xml中引入依赖(当前最新版本为1.3.2,可前往Github页面查看当前最新版本)

                    io.github.asleepyfish            chatgpt            1.3.2        

1.2 配置application.yml文件

application.yml文件中配置相关参数(Optional为可选参数)

参数解释
token申请的API KEYS
proxy-host (Optional)代理的ip
proxy-port (Optional)代理的端口
model (Optional)model可填可不填,默认即text-davinci-003
chat-model (Optional)可填可不填,默认即gpt-3.5-turbo (ChatGPT当前最强模型,生成回答使用的就是这个模型)
retries (Optional)指的是当chatgpt第一次请求回答失败时,重新请求的次数(增加该参数的原因是因为大量访问的原因,在某一个时刻,chatgpt服务将处于无法访问的情况,不填的默认值为5)
session-expiration-time (Optional)(单位(min))为这个会话在多久不访问后被销毁,这个值不填的时候,即表示所有问答处于同一个会话之下,相同user的会话永不销毁(增加请求消耗)
base-url (Optional)默认为 https://api.openai.com/ ,可不填

例:

chatgpt:  token: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx #必填#  proxy-host: 127.0.0.1 #需要代理时必填#  proxy-port: 7890 #需要代理时必填#  model: text-davinci-003 #可选#  chat-model: gpt-3.5-turbo #可选#  retries: 10 #可选,默认为5#  session-expiration-time: 30 #可选,不填则会话永不过期#  base-url: https://apps.ichati.cn/1d6f32f8-b59d-46f8-85e9-7d434bxxxxxx/ #可选,默认为https://api.openai.com/,请记住务必以/结尾

其中token、proxy-host、proxy-port是必填的

上面的session-expiration-time参数很重要,是用来表示这个会话在多久不访问后被销毁,从而实现联系上下文的连续对话。

实现方式是通过ChatCompletionRequest中的user来区分某个会话,而session-expiration-time表示这个会话在多久不访问后被销毁。

如果这里看不懂请看2.1节示例

1.3 注解添加

启动类上加入图中的注解则将服务注入到Spring中。
在这里插入图片描述

2. 使用

2.1 生成回答

提供了工具类OpenAiUtils,里面提供了相关方法进行调用。
其中最简单的使用方法是:

OpenAiUtils.createChatCompletion(content);// 不建议使用

入参content即输入的问题的字符串。但是不建议使用。

这里建议使用下面的方式,通过传入user的值,再结合session-expiration-time参数,可以实现指定某次会话,或者某个用户的连续对话。

OpenAiUtils.createChatCompletion(content, user);// 建议使用

还提供一个通用的静态方法是

public static List createChatCompletion(ChatCompletionRequest chatCompletionRequest) {...}

入参ChatCompletionRequest 里包含模型的一些可调参数。

OpenAiUtils类中还提供了多个可供选择的静态方法,可以自行查看。

上述方法的返回参数是一个list,是因为调整参数返回答案n可以一次性返回多条不同的解答(nChatCompletionRequest类中一个参数)。

2.1.1 测试

测试代码:

@PostMapping("/chatTest")public List chatTest(String content) {    return OpenAiUtils.createChatCompletion(content, "testUser");}

Post请求

入参输入:Java序列化的方式

返回结果:

[  "\n\nJava序列化是将Java对象转换为字节序列的过程,以便在网络上传输或将其保存到磁盘上。Java提供了两种序列化方式:\n\n1. 基于Serializable接口的序列化\n\nSerializable接口是Java提供的一个标记接口,用于标记一个类可以被序列化。如果一个类实现了Serializable接口,那么它的所有非瞬态字段都会被序列化。序列化的过程可以通过ObjectOutputStream类来实现,反序列化的过程可以通过ObjectInputStream类来实现。\n\n2. 基于Externalizable接口的序列化\n\nExternalizable接口也是Java提供的一个标记接口,用于标记一个类可以被序列化。与Serializable接口不同的是,Externalizable接口需要实现writeExternal和readExternal方法,这两个方法分别用于序列化和反序列化。在序列化的过程中,只有被writeExternal方法显式写入的字段才会被序列化,而在反序列化的过程中,只有被readExternal方法显式读取的字段才会被反序列化。\n\n总的来说,基于Serializable接口的序列化更加简单,但是它会序列化所有非瞬态字段,包括一些不需要序列化的字段,而基于Externalizable接口的序列化可以更加灵活地控制序列化的过程。"]

再次输入:有没有更加高效的序列化框架

返回结果:

[  "是的,Java中有很多高效的序列化框架,以下是一些常用的序列化框架:\n\n1. Protobuf\n\nProtobuf是Google开发的一种高效的序列化框架,它可以将结构化数据序列化为二进制格式,支持多种编程语言。相比于Java自带的序列化方式,Protobuf序列化后的数据更小,解析速度更快。\n\n2. Kryo\n\nKryo是一个快速、高效的Java序列化框架,它可以将Java对象序列化为字节数组,支持多种数据类型。Kryo序列化的速度比Java自带的序列化方式快很多,序列化后的数据也更小。\n\n3. FST\n\nFST是一个高性能的Java序列化框架,它可以将Java对象序列化为字节数组,支持多种数据类型。FST序列化的速度比Java自带的序列化方式快很多,序列化后的数据也更小。\n\n4. Avro\n\nAvro是一个高效的数据序列化系统,它可以将结构化数据序列化为二进制格式,支持多种编程语言。Avro序列化后的数据比Java自带的序列化方式更小,解析速度也更快。\n\n总的来说,这些高效的序列化框架都比Java自带的序列化方式更快、更小、更灵活,可以根据具体的需求选择合适的框架。"]

可以看出上述两次问答是在一次会话中的,而前面所说的参数session-expiration-time即这个user所代表的会话多久没被继续访问时的销毁时间。单位(min)

2.2 生成图片

最简单的使用方式是

OpenAiUtils.createImage(prompt);

入参表示生成图片的描述文字,还提供了一个通用的静态方法

public static List createImage(CreateImageRequest createImageRequest) {...}

入参CreateImageRequest中有一些可以使用的参数,其中n表示生成图片的数量,responseFormat表示生成图片的格式,格式中分为urlb64_json两种,如果希望返回的是url,则返回的url会在生成一个小时后消失,默认值是url

2.2.1 测试

测试代码

    @Test    public void testGenerateImg() {        OpenAiUtils.createImage("英短").forEach(System.out::println);    }

结果
默认情况下会生成一个url,点击去就可以看到图片。
在这里插入图片描述

2.3 下载图片

在3.2的基础上做了优化,直接使用responseFormatb64_json然后解析成图片返回。简单使用方式如下:

OpenAiUtils.downloadImage(prompt, response);

通用方式如下:

public static void downloadImage(CreateImageRequest createImageRequest, HttpServletResponse response) {...}

CreateImageRequest对象中设置的返回参数n大于1时,会将图片打包成一个zip包返回,当n等于1时直接返回图片。

2.3.1 测试

测试代码

@RestControllerpublic class ChatGPTController {    @GetMapping("/downloadImage")    public void downloadImage(String prompt, HttpServletResponse response) {        OpenAiUtils.downloadImage(prompt, response);    }}

发送get请求,然后选择Send and Download
在这里插入图片描述

我用的get 工具是idea里面下载的插件Fast Request的,用Postman也是可以的,但是要选择 Send and Download,上图中绿色的箭头是Send,蓝色的是Send and Download。

在这里插入图片描述

2.4 生成流式回答

生成流式回答的方法是OpenAiUtilscreateStreamChatCompletion方法,本工具类重载了同名的多个参数的方法,其中最通用的方法是

public static void createStreamChatCompletion(ChatCompletionRequest chatCompletionRequest, OutputStream os) {...}

最简单的方法是

public static void createStreamChatCompletion(String content) {...}

其中的content即本次对话的问题。

这里需要主义的是,上述第一个方法中的OutputStream os其实是一个必传的对象,上述的最简单的方法实际上是默认传递的System.out这个os对象,也就是将流式问答的结果显示到IDEA的控制台。

如果需要将流式问答的结果显示到其他界面可以自发的传入OutputStream os对象,这里有一个简便的方法是

public static void createStreamChatCompletion(String content, OutputStream os) {...}

只需要输入问题,和输出流对象即可。

下面将举例具体说明。(本文所有Demo的示例地址: https://github.com/asleepyfish/chatgpt-demo

2.4.1 流式回答输出到IDEA控制台

代码如下:

@GetMapping("/streamChat")public void streamChat(String content) {    // OpenAiUtils.createStreamChatCompletion(content, System.out);    // 下面的默认和上面这句代码一样,是输出结果到控制台    OpenAiUtils.createStreamChatCompletion(content);}

然后使用Postman或者其他可以发送Get请求的工具发送请求。

本次测试的结果如下面的Gif图所示

在这里插入图片描述

2.4.2 流式回答输出到浏览器页面

上述的方法中输出流传入的是System.out对象,该对象实际上就是一个PrintStream对象,会把输出结果展示到控制台。

如果需要将输出结果在浏览器展示,可以从前端传入一个HttpServletResponse response对象,拿到这个response以后将response.getOutputStream()这个输出流对象传入createStreamChatCompletion方法的入参中。同时,为了避免结果输出到浏览器产生乱码和支持流式输出,需要ContentTypeCharacterEncoding

具体代码如下:

@GetMapping("/streamChatWithWeb")public void streamChatWithWeb(String content, HttpServletResponse response) throws IOException {    // 需要指定response的ContentType为流式输出,且字符编码为UTF-8    response.setContentType("text/event-stream");    response.setCharacterEncoding("UTF-8");    // 禁用缓存    response.setHeader("Cache-Control", "no-cache");        OpenAiUtils.createStreamChatCompletion(content, response.getOutputStream());}

测试结果过程的Gif图如下所示:

在这里插入图片描述

2.4.3 流式回答结合Vue输出到前端界面

调用的后端方法同2.4.2节方法streamChatWithWeb,前端只需要在界面传入问题,点击提问按钮即可返回结果流式输出到文本框中。

测试结果过程的Gif图如下所示:

在这里插入图片描述
Vue3 Demo的Git地址在文章开头有~

2.5 查询账单和订阅

查询账单提供了两个方法,金额单位均为美元(USD),且均未对小数位截取,可以根据需要自行选择保留结果小数点位数。

第一个是可以传入开始和结束日期,按照指定日期区间查询的方法:

public String billingUsage(String startDate, String endDate) {...}

其中startDateendDate区间范围不超过100天。

第二个方法是一个入参为可变参数的方法,当不传入参时,查询从2023年1月1日距今的账单的方法,如果有人的订阅日早于2023年1月1日可以传入自定义账单起始日期:

public String billingUsage(String... startDate) {...}

查询订阅提供了一个方法,这个方法的出参中包括了订阅到期日,总额度等信息:

public Subscription subscription() {...}

由于查询总额度和查询使用量是两个接口,这里封装了一个方法来将几个比较有用的参数统一返回的方法,方法如下:

public Billing billing(String... startDate) {...}

这个方法的入参也是一个可变入参,不传参时,startDate默认为2023-01-01,如果账单开始日早于该天,可以传入指定的startDate。出参Billing中有四个参数:dueDate(额度到期日),total(额度总量),usage(已使用量),balance(余额)。

2.5.1 测试

测试代码如下:

@GetMapping("/billing")public void billing() {    String monthUsage = OpenAiUtils.billingUsage("2023-04-01", "2023-05-01");    log.info("四月使用:{}美元", monthUsage);    String totalUsage = OpenAiUtils.billingUsage();    log.info("一共使用:{}美元", totalUsage);    String stageUsage = OpenAiUtils.billingUsage("2023-01-31");    log.info("自从2023/01/31使用:{}美元", stageUsage);    Subscription subscription = OpenAiUtils.subscription();    log.info("订阅信息(包含到期日期,账户总额度等信息):{}", subscription);    // dueDate为到期日,total为总额度,usage为使用量,balance为余额    Billing totalBilling = OpenAiUtils.billing();    log.info("历史账单信息:{}", totalBilling);    // 默认不传参的billing方法的使用量usage从2023-01-01开始,如果用户的账单使用早于该日期,可以传入开始日期startDate    Billing posibleStartBilling = OpenAiUtils.billing("2022-01-01");    log.info("可能的历史账单信息:{}", posibleStartBilling);}

测试结果如下:

四月使用:0.9864320000000001美元一共使用:2.2074280000000002美元自从2023/01/31使用:2.2074280000000001美元订阅信息(包含到期日期,账户总额度等信息):Subscription(object=billing_subscription, hasPaymentMethod=false, canceled=false, canceledAt=null, delinquent=null, accessUntil=1688169600, softLimit=66667, hardLimit=83334, systemHardLimit=83334, softLimitUsd=4.00002, hardLimitUsd=5.00004, systemHardLimitUsd=5.00004, plan=Plan(title=Explore, id=free), accountName=Leo Mikey, poNumber=0, billingEmail=null, taxIds=null, billingAddress=null, businessAddress=null)历史账单信息:Billing(dueDate=2023-07-01, total=5.00004, usage=2.2074280000000002, balance=2.7926119999999998)可能的历史账单信息:Billing(dueDate=2023-07-01, total=5.00004, usage=2.2074280000000002, balance=2.7926119999999998)

2.6 自定义baseUrl

由于部分原因,用户可以选择搭建好的代理服务作为baseUrl,而不需要使用官方的https://api.openai.com/

这部分可以和第4节放在一起看

如果用户基于SpringBoot使用自定义baseUrl,可以参考下面的application.yml,配置chatgpt.base-url,如果不配置,则表示默认使用官方的https://api.openai.com/

chatgpt:  token: sk-xxxxxxxxxxxxxxxxxx  proxy-host: 127.0.0.1 #可选  proxy-port: 7890 #可选  model: text-davinci-003 #可选  chat-model: gpt-3.5-turbo #可选  retries: 10 #可选,默认为5  session-expiration-time: 30 #可选,不填则会话永不过期  base-url: https://openai.api2d.net/ #可选,默认为https://api.openai.com/

然后相关调用代码如下:

@PostMapping("/baseUrl")public void baseUrl() {    // 先在application.yml中配置chatgpt.base-url    System.out.println("models列表:" + OpenAiUtils.listModels());}

如果用户不想和Spring集成,则可以使用main方法调用的方式。

下面为了测试方便性,均以main方法做示例,结合SpringBoot使用OpenAiUtils调用的方法请参考github上的demo示例。

main方法中直接使用请参考第3节扩展部分自定义OpenAiProxyService

如下所示:

public static void main(String[] args) {    ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx")        .proxyHost("127.0.0.1")        .proxyPort(7890)        // 自定义baseUrl,若不需要自定义baseUrl,下一行可以去掉        .baseUrl("https://openai.api2d.net/")        .build();    OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties);    System.out.println("models列表:" + openAiProxyService.listModels());}

其中自定义了baseUrl为 https://openai.api2d.net/ ,并调用了listModels方法。

2.7 模型(model)

提供了两个与模型相关的方法:

// 列出全部modelsList listModels() {...}
// 获取指定model信息Model getModel(String model) {...}

测试代码如下:

ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx")    .proxyHost("127.0.0.1")    .proxyPort(7890)    .build();OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties);System.out.println("models列表:" + openAiProxyService.listModels());System.out.println("=============================================");System.out.println("text-davinci-003信息:" + openAiProxyService.getModel("text-davinci-003"));

测试结果(截取如下):

models列表:[Model(id=whisper-1, object=model, ownedBy=openai-internal, permission=[Permission(id=modelperm-KlsZlfft3Gma8pI6A8rTnyjs,.....=============================================text-davinci-003信息:Model(id=text-davinci-003, object=model, ownedBy=openai-internal, permission=[Permission(id=modelperm-jepinXYt59ncUQrjQEIUEDyC, object=model_permission, created=1688551385, allowCreateEngine=false, allowSampling=true, allowLogProbs=false, allowSearchIndices=false, allowView=true, allowFineTuning=false, organization=*, group=null, isBlocking=false)], root=text-davinci-003, parent=null)

2.8 编辑(edit)

edit功能可以用来检查语句的拼写错误,代码的正确性等,api中重载了多种edit相关的方法,这里介绍最简单的一种,其他重载方法,可通过源码查看。

String edit(String input, String instruction) {...}

测试代码如下:

ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx")    .proxyHost("127.0.0.1")    .proxyPort(7890)    .build();OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties);String input = "What day of the wek is it?";String instruction = "Fix the spelling mistakes";System.out.println("编辑前:" + input);// 下面这句和openAiProxyService.edit(input, instruction, EditModelEnum.TEXT_DAVINCI_EDIT_001);是一样的,默认使用模型TEXT_DAVINCI_EDIT_001System.out.println("编辑后:" + openAiProxyService.edit(input, instruction));System.out.println("=============================================");input = "    public static void mian([String] args) {\n" +    "        system.in.println(\"hello world\");\n" +    "    }";instruction = "Fix the code mistakes";System.out.println("修正代码前:\n" + input);System.out.println("修正代码后:\n" + openAiProxyService.edit(input, instruction, EditModelEnum.CODE_DAVINCI_EDIT_001));

测试结果如下:

编辑前:What day of the wek is it?编辑后:What day of the week is it today?=============================================修正代码前:    public static void mian([String] args) {        system.in.println("hello world");    }修正代码后:    public static void main(String[] args) {        System.out.println("hello world");    }

2.9 向量(embeddings)

有时候需要将文本内容转化成计算机可以识别到的向量形式,可以使用这个方法。api中重载了多种embeddings相关的方法,这里介绍最简单的一种,其他重载方法,可通过源码查看。

List embeddings(String text) {...}

测试代码如下:

ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx")    .proxyHost("127.0.0.1")    .proxyPort(7890)    .build();OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties);// 单文本String text = "Once upon a time";System.out.println("文本:" + text);System.out.println("文本的嵌入向量:" + openAiProxyService.embeddings(text));System.out.println("=============================================");// 文本数组String[] texts = {"Once upon a time", "There was a princess"};System.out.println("文本数组:" + Arrays.toString(texts));EmbeddingRequest embeddingRequest = EmbeddingRequest.builder()    .model(EmbeddingModelEnum.TEXT_EMBEDDING_ADA_002.getModelName()).input(Arrays.asList(texts)).build();System.out.println("文本数组的嵌入向量:" + openAiProxyService.embeddings(embeddingRequest));

测试结果截取如下:

文本:Once upon a time文本的嵌入向量:[0.02122687, -0.02013961, -0.004614537, -0.011599556, -0.011213957, 0.0019817224, 0.0023404553, ...文本数组:[Once upon a time, There was a princess]文本数组的嵌入向量:EmbeddingResult(model=text-embedding-ada-002-v2, object=list, data=[Embedding(object=embedding, embedding=[0.02122687, -0.02013961, -0.004614537, -0.011599556, -0.011213957, 0.0019817224...

2.10 转录(transcription)

转录功能可以将诸如mp3mp4等格式的音频文件转化成文字输出。

最简单的一种使用方法如下,其他重载方法可通过代码查看。

String transcription(String filePath, AudioResponseFormatEnum audioResponseFormatEnum) {...}

下面是一首许嵩的《想象之中》的转录测试。

ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx")    .proxyHost("127.0.0.1")    .proxyPort(7890)    .build();OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties);String filePath = "src/main/resources/audio/想象之中-许嵩.mp3";System.out.println("语音文件转录后的json文本是:" + openAiProxyService.transcription(filePath, AudioResponseFormatEnum.JSON));// File file = new File("src/main/resources/audio/想象之中-许嵩.mp3");// System.out.println("语音文件转录后的json文本是:" + openAiProxyService.transcription(file, AudioResponseFormatEnum.JSON));    

测试结果截取如下:

语音文件转录后的json文本是:{"text":"想像之中 想像之中 雨過一道彩虹 抬起了頭 瑟瑟灰色天空 想像之中 付出會有結果 毫無保留 信奉你的承諾 想像之中 這次要愛很久 我領略過 你眼裡的溫柔 熱戀以後 你忽然的冰凍 但若兩人丟給我去承受 想像中 很不同 想像中 一切都和後來不同 我承認 曾經那麼心動 你沒想像中那麼戀舊 回憶換不回你的溫柔 最後也不是故作冷漠 轉過頭 我怎麼又一滴淚落 我沒想像中那麼脆弱 分開後 形容也沒消瘦 一起踏過了幾座春秋 過了愛不是追逐佔有 想像之中 這次要愛很久 我領略過 你眼裡的溫柔 熱戀以後 你忽然的冰凍 但若兩人丟給我去承受 想像中 很不同 想像中 一切都和後來不同 我承認 曾經那麼心動 你沒想像中那麼戀舊 回憶換不回你的溫柔 最後也不是故作冷漠 轉過頭 我怎麼又一滴淚落 我沒想像中那麼脆弱 分開後 形容也沒消瘦 一起踏過了幾座春秋 過了愛不是追逐佔有 你沒想像中那麼戀舊 回憶換不回你的溫柔 最後也不是故作冷漠 轉過頭 我怎麼又一滴淚落 我沒想像中那麼脆弱 分開後 形容也沒消瘦 一起踏過了幾座春秋 領悟了愛不是追逐佔有"}

上述文字输出的时候是繁体,具体使用可以使用重载方法中一个代码TranscriptionRequest入参的方法,其中的language可以指定返回的语言,具体使用可以前往官网查看。

2.11 翻译(translation)

翻译功能可以将任意的音频文件翻译成英文。

最简单的一种使用方法如下,其他重载方法可通过代码查看。

String translation(String filePath, AudioResponseFormatEnum audioResponseFormatEnum) {...}

下面是一首许嵩的《想象之中》的翻译测试。

ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxx")    .proxyHost("127.0.0.1")    .proxyPort(7890)    .build();OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties);String filePath = "src/main/resources/audio/想象之中-许嵩.mp3";System.out.println("语音文件翻译成英文后的json文本是:" + openAiProxyService.translation(filePath, AudioResponseFormatEnum.JSON));// File file = new File("src/main/resources/audio/想象之中-许嵩.mp3");// System.out.println("语音文件翻译成英文后的json文本是:" + openAiProxyService.translation(file, AudioResponseFormatEnum.JSON));    

测试结果截取如下:

语音文件翻译成英文后的json文本是:{"text":"Translated by Hua Chenyu English Subs In my imagination, there will be results Without keeping your promise In my imagination, this time I will love for a long time I have experienced the tenderness in your eyes After falling in love, you suddenly freeze But if two people are thrown to me to bear In my imagination, it's very different In my imagination, everything is different from the later I admit that I used to be so heartbroken You are not as long-lasting as I imagined Memories can't exchange your tenderness In the end, it's not lonely and lonely Why am I crying again? ..."}

3. 扩展

3.1 自定义OpenAiProxyService

由于之前的版本中使用@Bean的方式初始化OpenAiProxyServiceOpenAiUtils,导致一个
SpringBoot中实例是唯一的。

但是有时候需要在项目里自定义多个OpenAiProxyService实例,来装配不同的ChatGPTProperties信息(可以实例化多个Token(sk-xxxxxxxxxxx)使用)。

所以在1.1.6版本中新增了自定义OpenAiProxyService功能。在维持原有SpringBoot项目中全局的一个OpenAiUtils实例的基础上,现在可以自定义不同的OpenAiProxyService实例,并且实例之间的属性是完全隔离的。

下面是一个Demo用来展示使用方法。

ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-002xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")    .proxyHost("127.0.0.1")// 代理ip    .proxyHost("7890")// 代理端口    .build();OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties);// 直接使用new出来的openAiProxyService来调用方法,每个OpenAiProxyService都拥有自己的Token。// 这样在一个SpringBoot项目中,就可以有多个Token,可以有更多的免费额度供使用了openAiProxyService.createStreamChatCompletion("Java的三大特性是什么");

在上述方法中,新new了一个ChatGPTProperties对象,并且set了tokensk-002xxxxxxxxxxxxxxxxxxxxxxxxx(这里不需要设置除了tokenproxyHostproxyPort以外的其他属性,因为ChatGPTProperties的其他属性拥有默认值,如果需要对其他属性做修改,可以自行设置。注意:sessionExpirationTime没有默认值,表示会话没有过期时间,如果需要设置会话过期时间,请set该值。

而在application.yml中设置的tokensk-001xxxxxxxxxxxxxxxxxxxxxxxxx,这个token是给全局唯一的OpenAitils用的,这样就可以通过OpenAiProxyService的构造方法new出来一个新的OpenAiProxyService实例,其中构造方法的第二个参数直接填Duration.ZERO就好,表示Http调用请求没有超时时间,后续版本更新中,我会新增一个只有一个入参的构造方法。

这样直接使用new出来的openAiProxyService来调用方法,每个OpenAiProxyService都拥有自己的Token。

在一个SpringBoot项目中,就可以有多个Token,可以有更多的免费额度供使用了。

4 自定义OpenAi服务端

4.1 介绍

默认使用OpenAi服务的baseUrlhttps://api.openai.com/ ,众所周知的原因,很多人网络无法直连。如果服务是部署在国外服务器,或者拥有代理,配置proxy-hostproxy-port依旧可以直接访问 https://api.openai.com/ ,这个也是比较推荐的方式,毕竟这是直连openai的服务,最安全,提供的服务接口也最全。

但是很多人没用国外的服务器,也没有自己的代理,在这里提供一种解决方法,通过使用这种方法,来实现无需国外服务器,也无需代理即可访问OpenAi服务。

在这里先简单介绍一下反向代理的概念。

反向代理是一种用于隐藏服务端真实地址的技术。比如说真实地址是B,但是我们不希望用户直连B,让B的地址暴露,我们就可以使用反向代理,让A的请求转发到B。

在这里,我们可以将B理解成 https://api.openai.com/ ,由于我们不能直连 https://api.openai.com/ ,但是我们可以在一台可以直连 https://api.openai.com/ 的服务器上反向代理,给我们暴露一个网址A,我们将baseUrl设置为A,向A发送请求,服务器接受到后自动将请求转发到 https://api.openai.com/ ,并且将结果返回。这样的话,我们只需要指定baseUrl,都不需要设置代理proxy-hostproxy-port即可访问OpenAi的服务了。那现在的问题就是怎么简单的获取到这样的一个baseUrl了。

这里我找到了一个可以提供OpenAi代理的服务。访问https://www.ichati.cn/dashboard/products/tools,注册登录后选择白泽,点击立即使用。

image-20230718104126500

然后输入API-Key,点击开始部署(这里我测试只需要长度保持一致,后面几位的api-key可以随便填)。

image-20230718104238571

然后如下图所示生成了我们的baseUrl(注意,这里生成的网页最后没用斜杠/,使用服务的使用baseUrl需要在最后带/,例如 https://apps.ichati.cn/0a7699ac-bc73-4732-9263-55c1c01f56e3/ )。

image-20230718104412550

4.2 测试

在4.1节中已经生成了我们项目的baseUrl,且因为它给我们转发,我们可以任务,他的服务部署在一台可以直连 https://api.openai.com/ 的服务器上,也就自然不需要设置proxy-hostproxy-port了。

4.2.1 基于SpringBoot使用OpenAiUtils

先设置application.yml,只需要设置tokenbaseUrl即可。

image-20230718111150784

启动SpringBoot项目。

使用Post工具调用下面的接口生成图片。

@PostMapping("/createImage")public List createImage(String prompt) {        List imageList = OpenAiUtils.createImage(prompt);        System.out.println(imageList);        return imageList;        }

image-20230718115911134

结果如下:

image-20230718115949339

访问该链接,就可以得到一个生成的图片~

4.2.2 基于Main方法使用OpenAiProxyService

下面是使用Main方法进行调用的具体代码,只需要设置tokenbaseUrl即可。

ChatGPTProperties properties = ChatGPTProperties.builder().token("sk-xxxx")        .baseUrl("https://apps.ichati.cn/0a7699ac-bc73-4732-9263-55c1c01f56e3/") // 这里需要填自己的baseUrl        .build();        OpenAiProxyService openAiProxyService = new OpenAiProxyService(properties);        System.out.println(openAiProxyService.chatCompletion("Go写个Hello World程序"));

输入结果如下:

[package mainimport "fmt"func main() {    fmt.Println("Hello, World!")}]

5 AI助手展示

接入微信公众号,AI助手可自动回复。
在这里插入图片描述

来源地址:https://blog.csdn.net/qq_41821963/article/details/128992539

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯