引言
网上有不少OpenAI的API资料,实测下来,可能是环境因素踩了不少坑,这里分享一下我实践成功的技术路线。出于篇幅考虑,本文不会对开发前的部分工作,例如openai账号注册,外网访问权限获取,java,python环境搭建等问题过多赘述
相关资源地址
名称 | 地址 | 作用 |
---|---|---|
ChatGPT访问地址 | https://chat.openai.com/chat | chatgpt官方访问网址 |
GPT_KEY | https://platform.openai.com/account/api-keys | 获取允许程序访问的key |
discord | https://dler.pro/auth/register?affid=142134 | 获取外网访问权限 |
高德开放平台 | https://console.amap.com/dev/key/app | 获取高德开发平台服务,此案例中是需要高德的天气预报服务 |
163邮箱服务 | https://mail.163.com | 此案例中,通过邮件通知用户天气信息 |
腾讯云函数服务 | https://console.cloud.tencent.com/scf/list?rid=15&ns=default | 在本案例中没用到这个服务,曾经尝试过使用此服务作为python中转服务,因为节点可以选在国外,天然支持外网访问 |
设计思路
java端技术点及代码示例
- 后台任务:目的是每天定时执行某段代码,此处采用的是公司产品自己封装的定时任务服务,用开源的同学可以自行去spring全家桶中找类似的框架
- HTTP请求:采用的是post请求,代码可复用提供的HttpConnection类
- 邮件发送:此处采用的是smtp协议,服务商为163,在使用163服务前需要去163邮箱官网开启smtp服务,并获取授权码。
定时任务及整体逻辑如下
package nc.plugin.cw_ext.reminder;import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;import nc.bs.logging.Logger;
import nc.bs.pub.pa.PreAlertObject;
import nc.bs.pub.taskcenter.BgWorkingContext;
import nc.bs.pub.taskcenter.IBackgroundWorkPlugin;
import nc.util.mrsy.CommonUtil;
import nc.util.mrsy.HttpConnection;
import nc.util.mrsy.UtilConstants.MaillSender;
import nc.vo.pub.BusinessException;
import nc.vo.pub.lang.UFDateTime;
import nccloud.message.util.MessageCenter;
import nccloud.message.vo.NCCMessage;
import nccloud.message.vo.NCCNoticeMessageVO;
import nccloud.util.cw_ext.JdbcUtil;
import nccloud.util.email.EmailUtil;/*** WeatherReminderTaskPlugin:天气预报邮件通知插件* @author CYQ* @date 2023年5月20日 下午5:05:14* @version 1.0.0
*/
public class WeatherReminderTaskPlugin implements IBackgroundWorkPlugin {private CommonUtil util = CommonUtil.getCommonUtil();@Overridepublic PreAlertObject executeTask(BgWorkingContext context) throws BusinessException {try {String msg = sendEmail();//通过系统内置能力,调用消息通知,请忽略此功能sendMsg("CYQ",msg);context.setLogStr("天气预报邮件通知任务调用完成!");return null;} catch (Exception e) {throw new BusinessException("发生了未定义异常,"+e.getMessage());}}/*** sendEmail: 发送邮件* @param data void TODO(参数说明)* 创 建 人 :CYQ* 创建时间:2023年5月20日-下午5:53:50* @throws Exception */private String sendEmail() throws Exception {Logger.error("begin...sendEmail");//获取明天天气String weather = getGDAPI();//通过gpt3.5获取信息String gptbody = getGPTbody(weather);//发送邮件EmailUtil.sendEmail(MaillSender.we, "明日天气预报,请查收~", gptbody);return "天气预报发送完毕";}/*** 获取明天的天气* @throws BusinessException*/private String getGDAPI() throws BusinessException {//url为高德天气预报服务的url,详情请参阅:https://console.amap.com/dev/key/appString url = util.getParameter("gd_url");String msg = HttpConnection.doGet(url, null, null);Logger.error("高德返回消息:"+msg);Map map = util.initMap(msg);JSONArray forecasts = (JSONArray)JSON.parse(util.initstr(map.get("forecasts")));Map info = util.initMap(forecasts.get(0));JSONArray casts = (JSONArray)JSON.parse(util.initstr(info.get("casts")));String tomorrow = util.initstr(casts.get(1));return tomorrow;}/*** 调用GPT3.5中转服务,获取报文* @param body* @return* @throws BusinessException */private String getGPTbody(String data) throws BusinessException {Logger.error("begin...getGPTbody");//请将XX,YY替换为身份和昵称String req = "请替我向我的XX,昵称YY,用中文讲述天气及注意事项,并问好。以下是明天的天气信息:"+data;JSONObject json = new JSONObject(); json.put("msg", req);//由于python服务和java服务部署在一台机器上,所以访问127即可String msg = HttpConnection.doPost("http://127.0.0.1:8001/openai_gpt3", json.toString(), null);StringBuffer sb = new StringBuffer(msg);sb.delete(sb.length()-1, sb.length());sb.delete(0, 1);//解码unicode字符String text = unicodeDecode(sb.toString());Logger.error("getGPTbody...msg="+text);return text;}/*** @param string* @return 转换之后的内容* @Title: unicodeDecode* @Description: unicode解码 将Unicode的编码转换为中文*/private String unicodeDecode(String string) {Pattern pattern = Pattern.compile("(\\\\u(\\p{XDigit}{4}))");Matcher matcher = pattern.matcher(string);char ch;while (matcher.find()) {ch = (char) Integer.parseInt(matcher.group(2), 16);string = string.replace(matcher.group(1), ch + "");}return string;}/*** 失败信息发送指定业务员* * @MethodName: sendMsg* memo by CYQ 2023年4月26日 理论代码没问题,前台不显示通知,怀疑是标准bug* @author CYQ* @date 2023年2月26日*/private void sendMsg(String msg_users, String msg) throws BusinessException {try {if (msg_users == null || msg_users.isEmpty()) {throw new BusinessException("未加载到有效的[msg_users]参数,请检查!");}NCCMessage message = new NCCMessage();NCCNoticeMessageVO msgvo = new NCCNoticeMessageVO();// 消息标题内容msgvo.setSubject(msg);msgvo.setSender("NC_USER0000000000000");// 可以一次群发,发送人String pk = JdbcUtil.queryColumn("sm_user", "cuserid", "user_code", msg_users);msgvo.setReceiver(pk);// 消息内容msgvo.setContent(msg);msgvo.setMsgsourcetype("reconcilemeg");msgvo.setSendtime(new UFDateTime());
// msgvo.setDetail(msg);msgvo.setContenttype("BIZ");// 内容格式msgvo.setMsgtype("nc");// 消息发送类型msgvo.setMsgsourcetype("notice");// 消息来源类型msgvo.setPriority(0);// 优先级msgvo.setSendtime(new UFDateTime());// 发送时间message.setMessage(msgvo);message.setMessageType("notice");// 消息类型——通知// 发送确认消息MessageCenter.sendMessage(new NCCMessage[] { message });} catch (Exception e) {throw new BusinessException("业务执行完毕,NC消息发送异常..." + e.getMessage());}}}
email工具类如下
package nccloud.util.email;
import java.util.Properties;import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;import nc.bs.logging.Logger;
import nc.util.mrsy.UtilConstants.MaillSender;
import nc.vo.pub.BusinessException;/*** 邮箱服务工具类*/
public class EmailUtil {public static void sendEmail(String to, String subject, String body) throws BusinessException {try {Logger.error("begin...sendEmail");// 设置邮件服务器属性Properties props = new Properties();props.put("mail.smtp.host", "smtp.163.com");props.put("mail.smtp.socketFactory.port", "465");props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");props.put("mail.smtp.auth", "true");props.put("mail.smtp.port", "465");// 创建邮件会话Session session = Session.getInstance(props,new javax.mail.Authenticator() {protected PasswordAuthentication getPasswordAuthentication() {return new PasswordAuthentication(MaillSender.me, MaillSender.mailkey);}});// 创建邮件消息Message message = new MimeMessage(session);message.setFrom(new InternetAddress(MaillSender.me));message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));message.setSubject(subject);message.setText(body);// 发送邮件Transport.send(message);} catch (Exception e) {// TODO: handle exceptionthrow new BusinessException("邮件发送失败,"+e.getMessage(),"-1001");}}
}
http工具类如下
package nc.util.mrsy;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;import nc.vo.pub.BusinessException;/*** HttpConnection:HTTP工具类* * @author CYQ* @date 2021年12月6日 下午5:45:31* @version 1.0.0*/
public class HttpConnection {/*** doPost: doPost方法* @param json* @param url* @return* @throws Exception String TODO(参数说明)* 创 建 人 :CYQ* 创建时间:2022年5月19日*/public static String doPost(String url, String json, Map<String,String> head) throws BusinessException {BufferedReader in = null;InputStream inputStream = null;OutputStream outputStream = null;HttpURLConnection httpURLConnection = null;try { if(url == null || url.isEmpty()) {throw new BusinessException("URL不能为空!");}URL adress=new URL(url);// 创建连接 测试httpURLConnection = (HttpURLConnection) adress.openConnection();// 设置请求的内容类型httpURLConnection.setRequestProperty("x-zop-ns", "budget");httpURLConnection.setRequestProperty("accept", "*/*");httpURLConnection.setRequestProperty("connection", "Keep-Alive");httpURLConnection.setRequestProperty("user-agent","Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");httpURLConnection.setRequestProperty("Content-Type","application/json");httpURLConnection.setRequestProperty("User-Agent","PostmanRuntime/7.32.2");httpURLConnection.setRequestProperty("Accept","*/*");httpURLConnection.setRequestProperty("Accept-Encoding","gzip, deflate, br");httpURLConnection.setRequestProperty("Connection","keep-alive");httpURLConnection.setConnectTimeout(30000000);httpURLConnection.setReadTimeout(30000000);//追加请求头if(head!=null && head.size()>0) {for(String key:head.keySet()) {httpURLConnection.setRequestProperty(key,head.get(key));}}// 设置发送数据httpURLConnection.setDoOutput(true);// 设置接受数据httpURLConnection.setDoInput(true);httpURLConnection.setUseCaches(false);// 发送数据,使用输出流outputStream = httpURLConnection.getOutputStream();// 发送的soap协议的数据String content = json.toString();// 发送数据outputStream.write(content.getBytes("UTF-8"));// 接收数据 inputStream = httpURLConnection.getInputStream();in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));StringBuffer buffer = new StringBuffer();String line = "";while ((line = in.readLine()) != null)buffer.append(line);String result = buffer.toString();outputStream.flush();outputStream.close();try {in.close();httpURLConnection.disconnect();} catch (IOException e) {e.printStackTrace();throw new Exception( e.getMessage()+e.getCause());}return result;} catch (Exception e) {e.printStackTrace();throw new BusinessException("doPost异常,"+e.getMessage()+e.getCause());}}}
python端技术点及代码示例
- python版本采用的是3.11
- 使用falcon发布http_post接口
- 调用openai依赖获取gpt服务
具体服务发布步骤如下:
- 请确保python服务及pip服务安装正确
- 执行以下指令安装falcon依赖
pip install falcon
- 执行以下指令安装openai依赖
pip install openai
- 编写openai调用工具,文件命名为【api.py】,具体代码如下
案例中使用的gpt-3.5-turbo模型是综合了使用体验和费用的综合选择,若想使用其他模型,可以修改模型编码
# -*- coding: utf-8 -*-import openaiopenai.api_key = "填入openai中的key"
model_engine = "gpt-3.5-turbo"def getgpt(reqmsg):# 发送API请求,获取响应response = openai.ChatCompletion.create(model=model_engine,messages=[{"role": "user", "content": reqmsg}])# 解析响应,获取回复output_text = response["choices"][0]["message"]["content"]return output_text
- 发布post接口,文件命名为【app.py】,具体代码如下
# -*- coding: utf-8 -*-import falcon
import json
import apiclass AppResource(object):# get请求def on_get(req, resp):msg = {"message": "Welcome to the Falcon"}resp.body = json.dumps(msg)# post请求def on_post(self, req, resp):try:result = req.mediareqmsg = result['msg']msg = api.getgpt(reqmsg)resp.body = json.dumps(msg)except Exception as e:print(str(e))resp.body = '调用发生异常'+str(e)except SyntaxError as e1:print(str(e1))resp.body = '调用发生异常'+str(e1)app = falcon.API()
app.add_route("/openai_gpt3", AppResource())if __name__ == "__main__":from wsgiref import simple_serverhttpd = simple_server.make_server("127.0.0.1", 8001, app)httpd.serve_forever()
- 在cmd中执行以下指令,启动python中转服务
python app.py
效果如图所示
效果展示
注:此技术路线仅为最终测试通过的路线,中间还验证过使用java,okhttp访问openai服务,使用腾讯云函数实现免费访问外网等技术路线,最终因种种原因未能验证通过。