- 这两天我在自己的网站中集成了openai API,引入chatgpt对话机器人,中途遇到了很多坑,记录一下。
- 文章中会涉及一些付费工具,如果你有类似功能的工具,完全可以使用自己的。(主要是我想澄清一下,我不是广告)。
- 如果当前什么都没有的小伙伴,在我的这篇教程里可能需要支出最少两三百块,我得提前说好,避免某些不愿意在这些事情上花钱的小伙伴浪费时间。
- 有问题可以私信我,看到的话,能帮忙我会尽量帮忙的。
目录
一、关于如何访问到Openai的接口(已经有api key的小伙伴可以跳过这一章)
二、在linux上部署Clash(不部署在Linux服务器上的小伙伴可以跳过这一章)
三、工程代码
1.配置文件增添配置项
2.根据openai文档中提到的参数,创建发送请求和接收回复需要的四个类(其中有一个是内部类,哈哈哈)
1)消息类
2)请求类
3)回应类(其中有内部类Choice)
3.RestTemplat设置代理以及添加api key
4.chatgpt对话工具类
5.service服务类
接口
实现类
6.Controller控制器类
7.页面
四、运行代码可能出现的一些问题
1.出现SSL问题,远程终端停止握手
2.启用了代理,也有ssl证书了,发送请求总是回复429 too many requests
关于充值的问题
五、看效果
一、关于如何访问到Openai的接口(已经有api key的小伙伴可以跳过这一章)
能看到这篇文章的各位,应该都是有自己的科学工具的,没有的话可以用我现在正在使用的科学工具。
注册一个openai账号,我是去年注册的账号,当时海外手机号是用的sms-activate,不知道现在行不行了。
然后跟着下面的步骤走
二、在linux上部署Clash(不部署在Linux服务器上的小伙伴可以跳过这一章)
如果觉得我写的太乱了不好看,可以看这篇文章
- 访问Clash资源站,在内核板块找一个合适的。
- 下载Country.mmdb文件,连接中下载过来的把名字前缀去掉就行了。
- configure.yaml文件,可以在1中提到的Clash资源站中下载windows版本的,根据科学工具网站中的教程导入到windows版中,再拷贝出来(主要是我没有找到直接导出的方式)。
- 仪表盘在github中下载。
- 把上面下载的五个文件传入到/root/.config/clash文件夹下。
- 解压clash压缩包文件并改名为clash(不改也可以,就是后面打这么多字比较麻烦),解压仪表盘并改名为dashboard。
- 修改configure.ymal文件。加入
external-controller: '0.0.0.0:9090' external-ui: /root/.config/clash/dashboard
放在顶格整齐的位置就行,我放在dns的上面了
- 使用命令启动clash
/root/.config/clash/clash -d /root/.config/clash/ &
能够访问ip:9090/ui就是成功了。
- 最后我发现如果单纯使用命令来启动clash,如果我退出shell终端clash就会关闭,如果你也有这样的问题,可以把clash配置为系统服务。创建文件/etc/systemd/system/clash.service,写入
[Unit] Description=Clash daemon, A rule-based proxy in Go. [Service] Type=simple User=root ExecStart=/root/.config/clash/clash -d /root/.config/clash/ Restart=on-failure [Install] WantedBy=multi-user.target
- 执行命令
后就可以使用systemctl start clash.service命令来启动clash,使用systemctl status clash查看clash状态。systemctl daemon-reload
三、工程代码
配置完服务器环境就是编码部分了。
1.配置文件增添配置项
在application.properties中添加
openai.key=你的api key
openai.chatgtp.model=gpt-3.5-turbo
openai.chatgtp.api.url=https://api.openai.com/v1/chat/completions
2.根据openai文档中提到的参数,创建发送请求和接收回复需要的四个类(其中有一个是内部类,哈哈哈)
下文中的类都没有写构造器和set、get方法,为了缩减篇幅。在toString()方法中调用jackson是为了在RestTemplate中直接发送。
1)消息类
用于保存消息和发出消息的角色
public class ChatMessage {
// 角色
private String role;
// 消息内容
private String content;
@Override
public String toString() {
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
}
2)请求类
用于指定模型以及发送历史对话记录
public class ChatRequest {
// 使用的模型
private String model;
// 历史对话记录
private List<ChatMessage> messages;
@Override
public String toString() {
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
}
3)回应类(其中有内部类Choice)
public class ChatResponse {
// GPT返回的对话列表
private List<Choice> choices;
public static class Choice {
private int index;
private ChatMessage message;
}
}
3.RestTemplat设置代理以及添加api key
这里使用了springboot提供的钩子
@Component
public class ApiCodeLoadAware implements EnvironmentAware, ApplicationContextAware {
Environment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// chatgpt、gpt4
RestTemplate restTemplate = new RestTemplate();
// 设置代理
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 7890));
simpleClientHttpRequestFactory.setProxy(proxy);
restTemplate.setRequestFactory(simpleClientHttpRequestFactory);
// 设置key
restTemplate.getInterceptors().add(((request, body, execution) -> {
request.getHeaders().add("Authorization", "Bearer " + environment.getProperty("openai.key"));
return execution.execute(request, body);
}));
ChatGptModelService chatGptModelService = applicationContext.getBean(ChatGptModelService.class);
chatGptModelService.setRestTemplate(restTemplate);
}
}
4.chatgpt对话工具类
实际对接了多个模型,所以使用工具类实现模型的对话功能,再由service调用各个工具类。
这里直接返回的context是markdown格式的,有需要的小伙伴可以自行查找markdown转html的工具,java和js都有大佬写好的现成的。
@Component
public class ChatGptModelService implements AiModelService{
private static final Logger logger = LoggerFactory.getLogger(ChatGptModelService.class);
@Value("${openai.chatgtp.api.url}")
private String endpoint;
@Value(("${openai.chatgtp.model}"))
private String model;
@Override
public String answer(String prompt, HttpServletRequest request) {
// 使用session保存历史对话记录
HttpSession session = request.getSession();
// 获取历史对话列表,chatMessages实现连续对话、chatDialogues便于页面显示
List<ChatMessage> chatMessages = (List<ChatMessage>) session.getAttribute(ConstValuePool.CHAT_MESSAGE_DIALOGUES);
List<String> chatDialogues = (List<String>) session.getAttribute(ConstValuePool.CHAT_DIALOGUES);
if (chatMessages == null) {
chatMessages = new ArrayList<>();
chatDialogues = new ArrayList<>();
session.setAttribute(ConstValuePool.CHAT_DIALOGUES, chatDialogues);
session.setAttribute(ConstValuePool.CHAT_MESSAGE_DIALOGUES, chatMessages);
}
chatMessages.add(new ChatMessage("user", prompt));
chatDialogues.add(prompt);
ChatRequest chatRequest = new ChatRequest(this.model, chatMessages);
ChatResponse response = ConstValuePool.PROXY_OPENAI_REST_TEMPLATE
.postForObject(
this.endpoint,
chatRequest,
ChatResponse.class
);
logger.debug(response.toString());
List<ChatResponse.Choice> choices = response.getChoices();
ChatMessage message = null;
if (CollectionUtil.isEmpty(choices)) {
message = new ChatMessage(null, "error:发生了错误");
}else {
message = choices.get(choices.size() - 1).getMessage();
chatMessages.add(message);
}
chatDialogues.add(message.getContent());
return message.getContent();
}
}
5.service服务类
接口
public interface AiService {
String getAnswer(String prompt, HttpServletRequest request);
}
实现类
@Service
public class AiServiceImpl implements AiService {
@Resource
private ChatGptModelService chatGptModelService;
@Override
public String getAnswer(String prompt, HttpServletRequest request) {
return chatGptModelService.answer(prompt, request);
}
}
6.Controller控制器类
@Controller
public class AiController {
@Resource
private AiService aiService;
@PostMapping("/tools/ai/submitQuestion")
@ResponseBody
public Result submitQuestion(String prompt, HttpServletRequest request) {
return Result.success(aiService.getAnswer(prompt, request));
}
@PostMapping("/tools/ai/historyDialogus")
@ResponseBody
public Result historyDialogus(HttpSession session) {
List<String> res = (List<String>)session.getAttribute(ConstValuePool.CHAT_DIALOGUES);
if (res == null) {
res = ConstValuePool.EMPTY_STRING_LIST;
}
return Result.success(res);
}
}
7.页面
我的前端能力很差,就不发我的前端页面了,相信大家来看这篇文章,或多或少都有自己编码和上网查询资料的能力了。
四、运行代码可能出现的一些问题
写这篇文章就是为了总结这些问题,我搞了三天都在搞这些问题。
1.出现SSL问题,远程终端停止握手
启动clash调用代码以后可能会出现报错ssl peer shut down incorrectly,这是由于openai的api处于安全性考虑要求调用接口的网站也必须使用https协议。
但是他们好像只有是https协议就行,我用的不安全的证书在本地做测试也能成功调用。
我自己使用的服务器是阿里云的,我本来想在阿里云申请一个ssl证书。但是阿里云的免费证书有效期只有三个月,而且我在昨天晚上六点发起的申请,到现在还没见到证书的影子。
真的不是黑阿里,这次效率确实不高,我还特地回去查了一下记录
腾讯云的免费证书有效期有一年,而且处理速度蛮快,昨天五分钟就给我搞好了,可以试试看腾讯云。
2.启用了代理,也有ssl证书了,发送请求总是回复429 too many requests
这个点太离谱了,我看文档里免费用户对gpt-3.5-turbo模型有3RPM(3个请求每分钟),4000TPM(4000个tokens每分钟,没记错的话应该是4000)的配额,而且也有五刀的免费额度的。
我一开始以为是使用代理的原因会发送不止三个请求导致的,所以我打算氪点金,成为一阶用户。于是我就找了一个虚拟卡运营商,充了10刀
充完了发现五刀的免费额度没了,只有10刀,这里我测试用了一点所以只有9.94刀了。
关于充值的问题
网上有些不建议用虚拟卡的教程,但是得用到苹果机,555,我是低人一等的卑微安卓机用户。
一开始我是看到了WildCard的,结果到现在(2024/02/19 22:46)为止,它还是无法注册。
后来看到depay以及被禁用了。。。
还有OneKey不让中国地区做身份验证。。。
后来我在犄角旮旯里找到了一个还能用的FOMEPay,开卡我花了246.81,真的很心痛。
但是氪完以后,429的问题就没有了。
五、看效果
欸~嘶~为什么第一次回复是英文的