pom.xml中引入依赖(当前最新版本为1.3.2,可前往Github页面查看当前最新版本)
例:
上面的session-expiration-time参数很重要,是用来表示这个会话在多久不访问后被销毁,从而实现联系上下文的连续对话。
实现方式是通过ChatCompletionRequest中的user来区分某个会话,而session-expiration-time表示这个会话在多久不访问后被销毁。
如果这里看不懂请看2.1节示例
OpenAiUtils.createChatCompletion(content);//不建议使用1入参content即输入的问题的字符串。但是不建议使用。
这里建议使用下面的方式,通过传入user的值,再结合session-expiration-time参数,可以实现指定某次会话,或者某个用户的连续对话。
OpenAiUtils.createChatCompletion(content,user);//建议使用1还提供一个通用的静态方法是
OpenAiUtils类中还提供了多个可供选择的静态方法,可以自行查看。
上述方法的返回参数是一个list,是因为调整参数返回答案n可以一次性返回多条不同的解答(n为ChatCompletionRequest类中一个参数)。
测试代码:
入参输入: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接口的序列化可以更加灵活地控制序列化的过程。"]123再次输入:有没有更加高效的序列化框架
最简单的使用方式是
OpenAiUtils.createImage(prompt);1入参表示生成图片的描述文字,还提供了一个通用的静态方法
测试代码
在3.2的基础上做了优化,直接使用responseFormat为b64_json然后解析成图片返回。简单使用方式如下:
OpenAiUtils.downloadImage(prompt,response);1通用方式如下:
publicstaticvoiddownloadImage(CreateImageRequestcreateImageRequest,HttpServletResponseresponse){...}1当CreateImageRequest对象中设置的返回参数n大于1时,会将图片打包成一个zip包返回,当n等于1时直接返回图片。
我用的get工具是idea里面下载的插件FastRequest的,用Postman也是可以的,但是要选择SendandDownload,上图中绿色的箭头是Send,蓝色的是SendandDownload。
生成流式回答的方法是OpenAiUtils的createStreamChatCompletion方法,本工具类重载了同名的多个参数的方法,其中最通用的方法是
publicstaticvoidcreateStreamChatCompletion(ChatCompletionRequestchatCompletionRequest,OutputStreamos){...}1最简单的方法是
publicstaticvoidcreateStreamChatCompletion(Stringcontent){...}1其中的content即本次对话的问题。
这里需要主义的是,上述第一个方法中的OutputStreamos其实是一个必传的对象,上述的最简单的方法实际上是默认传递的System.out这个os对象,也就是将流式问答的结果显示到IDEA的控制台。
如果需要将流式问答的结果显示到其他界面可以自发的传入OutputStreamos对象,这里有一个简便的方法是
publicstaticvoidcreateStreamChatCompletion(Stringcontent,OutputStreamos){...}1只需要输入问题,和输出流对象即可。
代码如下:
@GetMapping("/streamChat")publicvoidstreamChat(Stringcontent){//OpenAiUtils.createStreamChatCompletion(content,System.out);//下面的默认和上面这句代码一样,是输出结果到控制台OpenAiUtils.createStreamChatCompletion(content);}123456然后使用Postman或者其他可以发送Get请求的工具发送请求。
本次测试的结果如下面的Gif图所示
上述的方法中输出流传入的是System.out对象,该对象实际上就是一个PrintStream对象,会把输出结果展示到控制台。
如果需要将输出结果在浏览器展示,可以从前端传入一个HttpServletResponseresponse对象,拿到这个response以后将response.getOutputStream()这个输出流对象传入createStreamChatCompletion方法的入参中。同时,为了避免结果输出到浏览器产生乱码和支持流式输出,需要ContentType和CharacterEncoding。
具体代码如下:
@GetMapping("/streamChatWithWeb")publicvoidstreamChatWithWeb(Stringcontent,HttpServletResponseresponse)throwsIOException{//需要指定response的ContentType为流式输出,且字符编码为UTF-8response.setContentType("text/event-stream");response.setCharacterEncoding("UTF-8");//禁用缓存response.setHeader("Cache-Control","no-cache");OpenAiUtils.createStreamChatCompletion(content,response.getOutputStream());}123456789测试结果过程的Gif图如下所示:
调用的后端方法同2.4.2节方法streamChatWithWeb,前端只需要在界面传入问题,点击提问按钮即可返回结果流式输出到文本框中。
测试结果过程的Gif图如下所示:
查询账单提供了两个方法,金额单位均为美元(USD),且均未对小数位截取,可以根据需要自行选择保留结果小数点位数。
第一个是可以传入开始和结束日期,按照指定日期区间查询的方法:
publicStringbillingUsage(StringstartDate,StringendDate){...}1其中startDate和endDate区间范围不超过100天。
第二个方法是一个入参为可变参数的方法,当不传入参时,查询从2023年1月1日距今的账单的方法,如果有人的订阅日早于2023年1月1日可以传入自定义账单起始日期:
publicStringbillingUsage(String...startDate){...}1查询订阅提供了一个方法,这个方法的出参中包括了订阅到期日,总额度等信息:
publicSubscriptionsubscription(){...}1由于查询总额度和查询使用量是两个接口,这里封装了一个方法来将几个比较有用的参数统一返回的方法,方法如下:
publicBillingbilling(String...startDate){...}1这个方法的入参也是一个可变入参,不传参时,startDate默认为2023-01-01,如果账单开始日早于该天,可以传入指定的startDate。出参Billing中有四个参数:dueDate(额度到期日),total(额度总量),usage(已使用量),balance(余额)。
测试代码如下:
@GetMapping("/billing")publicvoidbilling(){StringmonthUsage=OpenAiUtils.billingUsage("2023-04-01","2023-05-01");log.info("四月使用:{}美元",monthUsage);StringtotalUsage=OpenAiUtils.billingUsage();log.info("一共使用:{}美元",totalUsage);StringstageUsage=OpenAiUtils.billingUsage("2023-01-31");log.info("自从2023/01/31使用:{}美元",stageUsage);Subscriptionsubscription=OpenAiUtils.subscription();log.info("订阅信息(包含到期日期,账户总额度等信息):{}",subscription);//dueDate为到期日,total为总额度,usage为使用量,balance为余额BillingtotalBilling=OpenAiUtils.billing();log.info("历史账单信息:{}",totalBilling);//默认不传参的billing方法的使用量usage从2023-01-01开始,如果用户的账单使用早于该日期,可以传入开始日期startDateBillingposibleStartBilling=OpenAiUtils.billing("2022-01-01");log.info("可能的历史账单信息:{}",posibleStartBilling);}1234567891011121314151617测试结果如下:
这部分可以和第4节放在一起看
@PostMapping("/baseUrl")publicvoidbaseUrl(){//先在application.yml中配置chatgpt.base-urlSystem.out.println("models列表:"+OpenAiUtils.listModels());}12345如果用户不想和Spring集成,则可以使用main方法调用的方式。
下面为了测试方便性,均以main方法做示例,结合SpringBoot使用OpenAiUtils调用的方法请参考github上的demo示例。
在main方法中直接使用请参考第3节扩展部分自定义OpenAiProxyService。
如下所示:
ChatGPTPropertiesproperties=ChatGPTProperties.builder().token("sk-xxx").proxyHost("127.0.0.1").proxyPort(7890).build();OpenAiProxyServiceopenAiProxyService=newOpenAiProxyService(properties);System.out.println("models列表:"+openAiProxyService.listModels());System.out.println("=============================================");System.out.println("text-davinci-003信息:"+openAiProxyService.getModel("text-davinci-003"));12345678测试结果(截取如下):
Stringedit(Stringinput,Stringinstruction){...}1测试代码如下:
ChatGPTPropertiesproperties=ChatGPTProperties.builder().token("sk-xxx").proxyHost("127.0.0.1").proxyPort(7890).build();OpenAiProxyServiceopenAiProxyService=newOpenAiProxyService(properties);Stringinput="Whatdayofthewekisit";Stringinstruction="Fixthespellingmistakes";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="publicstaticvoidmian([String]args){\n"+"system.in.println("helloworld");\n"+"}";instruction="Fixthecodemistakes";System.out.println("修正代码前:\n"+input);System.out.println("修正代码后:\n"+openAiProxyService.edit(input,instruction,EditModelEnum.CODE_DAVINCI_EDIT_001));1234567891011121314151617测试结果如下:
ChatGPTPropertiesproperties=ChatGPTProperties.builder().token("sk-xxx").proxyHost("127.0.0.1").proxyPort(7890).build();OpenAiProxyServiceopenAiProxyService=newOpenAiProxyService(properties);//单文本Stringtext="Onceuponatime";System.out.println("文本:"+text);System.out.println("文本的嵌入向量:"+openAiProxyService.embeddings(text));System.out.println("=============================================");//文本数组String[]texts={"Onceuponatime","Therewasaprincess"};System.out.println("文本数组:"+Arrays.toString(texts));EmbeddingRequestembeddingRequest=EmbeddingRequest.builder().model(EmbeddingModelEnum.TEXT_EMBEDDING_ADA_002.getModelName()).input(Arrays.asList(texts)).build();System.out.println("文本数组的嵌入向量:"+openAiProxyService.embeddings(embeddingRequest));12345678910111213141516测试结果截取如下:
最简单的一种使用方法如下,其他重载方法可通过代码查看。
Stringtranscription(StringfilePath,AudioResponseFormatEnumaudioResponseFormatEnum){...}1下面是一首许嵩的《想象之中》的转录测试。
ChatGPTPropertiesproperties=ChatGPTProperties.builder().token("sk-xxx").proxyHost("127.0.0.1").proxyPort(7890).build();OpenAiProxyServiceopenAiProxyService=newOpenAiProxyService(properties);StringfilePath="src/main/resources/audio/想象之中-许嵩.mp3";System.out.println("语音文件转录后的json文本是:"+openAiProxyService.transcription(filePath,AudioResponseFormatEnum.JSON));//Filefile=newFile("src/main/resources/audio/想象之中-许嵩.mp3");//System.out.println("语音文件转录后的json文本是:"+openAiProxyService.transcription(file,AudioResponseFormatEnum.JSON));12345678910测试结果截取如下:
语音文件转录后的json文本是:{"text":"想像之中想像之中雨過一道彩虹抬起了頭瑟瑟灰色天空想像之中付出會有結果毫無保留信奉你的承諾想像之中這次要愛很久我領略過你眼裡的溫柔熱戀以後你忽然的冰凍但若兩人丟給我去承受想像中很不同想像中一切都和後來不同我承認曾經那麼心動你沒想像中那麼戀舊回憶換不回你的溫柔最後也不是故作冷漠轉過頭我怎麼又一滴淚落我沒想像中那麼脆弱分開後形容也沒消瘦一起踏過了幾座春秋過了愛不是追逐佔有想像之中這次要愛很久我領略過你眼裡的溫柔熱戀以後你忽然的冰凍但若兩人丟給我去承受想像中很不同想像中一切都和後來不同我承認曾經那麼心動你沒想像中那麼戀舊回憶換不回你的溫柔最後也不是故作冷漠轉過頭我怎麼又一滴淚落我沒想像中那麼脆弱分開後形容也沒消瘦一起踏過了幾座春秋過了愛不是追逐佔有你沒想像中那麼戀舊回憶換不回你的溫柔最後也不是故作冷漠轉過頭我怎麼又一滴淚落我沒想像中那麼脆弱分開後形容也沒消瘦一起踏過了幾座春秋領悟了愛不是追逐佔有"}1上述文字输出的时候是繁体,具体使用可以使用重载方法中一个代码TranscriptionRequest入参的方法,其中的language可以指定返回的语言,具体使用可以前往官网查看。
翻译功能可以将任意的音频文件翻译成英文。
Stringtranslation(StringfilePath,AudioResponseFormatEnumaudioResponseFormatEnum){...}1下面是一首许嵩的《想象之中》的翻译测试。
ChatGPTPropertiesproperties=ChatGPTProperties.builder().token("sk-xxx").proxyHost("127.0.0.1").proxyPort(7890).build();OpenAiProxyServiceopenAiProxyService=newOpenAiProxyService(properties);StringfilePath="src/main/resources/audio/想象之中-许嵩.mp3";System.out.println("语音文件翻译成英文后的json文本是:"+openAiProxyService.translation(filePath,AudioResponseFormatEnum.JSON));//Filefile=newFile("src/main/resources/audio/想象之中-许嵩.mp3");//System.out.println("语音文件翻译成英文后的json文本是:"+openAiProxyService.translation(file,AudioResponseFormatEnum.JSON));12345678910测试结果截取如下:
但是有时候需要在项目里自定义多个OpenAiProxyService实例,来装配不同的ChatGPTProperties信息(可以实例化多个Token(sk-xxxxxxxxxxx)使用)。
所以在1.1.6版本中新增了自定义OpenAiProxyService功能。在维持原有SpringBoot项目中全局的一个OpenAiUtils实例的基础上,现在可以自定义不同的OpenAiProxyService实例,并且实例之间的属性是完全隔离的。
下面是一个Demo用来展示使用方法。
这样直接使用new出来的openAiProxyService来调用方法,每个OpenAiProxyService都拥有自己的Token。
在一个SpringBoot项目中,就可以有多个Token,可以有更多的免费额度供使用了。
但是很多人没用国外的服务器,也没有自己的代理,在这里提供一种解决方法,通过使用这种方法,来实现无需国外服务器,也无需代理即可访问OpenAi服务。
在这里先简单介绍一下反向代理的概念。
反向代理是一种用于隐藏服务端真实地址的技术。比如说真实地址是B,但是我们不希望用户直连B,让B的地址暴露,我们就可以使用反向代理,让A的请求转发到B。
然后输入API-Key,点击开始部署(这里我测试只需要长度保持一致,后面几位的api-key可以随便填)。
先设置application.yml,只需要设置token和baseUrl即可。
启动SpringBoot项目。
使用Post工具调用下面的接口生成图片。
结果如下:
访问该链接,就可以得到一个生成的图片~
下面是使用Main方法进行调用的具体代码,只需要设置token和baseUrl即可。