浏览OpenAI的API接口文档,其实OpenAI的接入非常简单,只要有API_KEY就可以动手开发了(前提要会魔法)。他本身就是一个HTTP的POST和GET请求,以下我是从零开始接入接口的不同阶段和相关实现。
第一步,可能很多人都和我一样,关于HTTP的接口,首先使用的都是POSTMAN工具。
header这里,填写和修改这两个位置就可以了,Authorization就是你申请到的key,切记key前边必须要加Bearer 。
请求数据是json结构,按照官网的要求就可以,我只填写了我需要使用的。做完这一切很轻松就完成了接口调用并得到结果。
第二步,已经知道如何调用接口,那么剩下啦就是如何用delphi代码实现。我想很多人也一样首先想到的控件就是Idhttp。所以我也是先用这个控件实现。
//引用单元
uses System.NetEncoding;
//代码片段
procedure TForm1.Button2Click(Sender: TObject);
var
IdHTTP: TIdHTTP;
SSLIOHandler: TIdSSLIOHandlerSocketOpenSSL;
RequestBody, ResponseBody: TStringStream;
AccessToken: string;
begin
IdHTTP := TIdHTTP.Create(nil);
SSLIOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
RequestBody := TStringStream.Create
('{"model": "gpt-3.5-turbo","messages": [{"role": "user", "content": "重复一次这是个测试"}],"temperature": 0.8}',
TEncoding.UTF8);
ResponseBody := TStringStream.Create('', TEncoding.UTF8);
try
// 设置 SSLIOHandler
SSLIOHandler.SSLOptions.Method := sslvTLSv1_2;
SSLIOHandler.SSLOptions.Mode := sslmClient;
SSLIOHandler.SSLOptions.VerifyMode := [];
SSLIOHandler.SSLOptions.VerifyDepth := 0;
IdHTTP.IOHandler := SSLIOHandler;
// 设置 IdHTTP
IdHTTP.Request.Accept := 'application/json';
IdHTTP.Request.ContentType := 'application/json';
// IdHTTP.Request.BasicAuthentication := False;
IdHTTP.Request.CustomHeaders.Values['User-Agent'] :=
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36';
// 设置请求参数
IdHTTP.Request.CustomHeaders.Values['Authorization'] :=
'Bearer 这里填写你的API的KEY';
// 发送请求并获取响应
IdHTTP.Post('https://api.openai.com/v1/chat/completions', RequestBody,
ResponseBody);
// 解析响应数据
// Memo1.Text := ResponseBody.DataString;
// 处理身份验证令牌
// ...
finally
IdHTTP.Free;
SSLIOHandler.Free;
RequestBody.Free;
ResponseBody.Free;
end;
end;
这样就简单的实现了OpenAI的API接入,并可以与chatGpt沟通了。可是发现一个问题,这样实现不能像官网一样,逐字回复。只能等到全部回复完成,才会显示内容。那就让体验很不好。可是我试了很多次,可能是我对IDHTTP了解不够深入,我没有办法实现异步接收。这时候我想起了另一个控件,那就是NetHTTPClient。
第三步,那就用NetHTTPClient实现异步方式接收chatGpt的回复,实现与官网一致的逐字显示回复内容。
//首先实现发送数据
procedure TForm1.Button1Click(Sender: TObject);
var
RequestBody, ResponseBody: TStringStream;
begin
// "stream":true, JSON结构里添加这个参数,会让接口逐字推送
RequestBody := TStringStream.Create
('{"model": "gpt-3.5-turbo","messages": [{"role": "user", "content": "说出10个一千零一夜的故事"}],"stream":true,"temperature": 0.8}',
TEncoding.UTF8);
ResponseBody := TStringStream.Create('', TEncoding.UTF8);
//开启异步
HttpClient.Asynchronous := true;
HttpClient.Accept := 'application/json';
HttpClient.ContentType := 'application/json';
// IdHTTP.Request.BasicAuthentication := False;
HttpClient.CustomHeaders['User-Agent'] :=
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36';
// 设置请求参数
HttpClient.CustomHeaders['Authorization'] :='Bearer 这里填写你的API的KEY';
Memo1.Clear;
Memo1.Lines.Add(FormatDateTime('yyyy-mm-dd hh:nn:ss:zzz', now()) + '==>user');
Memo1.Lines.Add('说出10个一千零一夜的故事');
//这里有三个声明,放在单元的private里 rlength: integer;lstr: string;Response: IHTTPResponse;
rlength := 0;
lstr := '';
Response := HttpClient.Post('https://api.openai.com/v1/chat/completions',
RequestBody, ResponseBody);
end;
//这里实现异步接收数据,并把返回的数据显示在memo里。
//使用的NetHTTPClient的onReceiveData事件
procedure TForm1.HttpClientReceiveData(const Sender: TObject;
AContentLength, AReadCount: Int64; var AAbort: Boolean);
var
i: integer;
s, jstr, cstr: string;
jsonArr: TJSONArray;
begin
if Response.StatusCode = 200 then
begin
s := Response.ContentAsString(TEncoding.UTF8);
jstr := copy(s, rlength, length(s));
jstr := StringReplace(jstr, #$A#$A, '', [rfReplaceAll]);
if jstr.Trim <> '' then
begin
jsonArr := TJSONArray.Create;
try
//这个函数下边会写上代码,因返回不是标准JSON数据,前边带了data:,所以做字符串截取
getOpenAIdb(jstr, jsonArr);
for i := 0 to jsonArr.Count - 1 do
begin
// Memo1.Lines.Add();
lstr := lstr + jsonArr.Get(i).Value;
//getOpenAI_R这个函数下边会写上代码,获取消息的第一条,采用的也是字符串截取
cstr := getOpenAI_R(jsonArr.Get(i).Value).Trim;
if cstr <> '' then
begin
Memo1.Lines.Add(FormatDateTime('yyyy-mm-dd hh:nn:ss:zzz', now()) +
'==>' + cstr);
end;
//getOpenAI_C这个函数下边会写上代码,获取消息的内容,采用的也是字符串截取
cstr := getOpenAI_C(jsonArr.Get(i).Value).Trim;
if cstr <> '' then
begin
if cstr = '\n' then
Memo1.Text := Memo1.Text + #13#10
else
Memo1.Text := Memo1.Text + cstr;
end;
end;
finally
FreeAndNil(jsonArr);
end;
end;
rlength := length(s);
end;
end;
这里做了很对字符串的截取操作,原因就是返回的不是标准json结构,就算截取出json结构,想取到里面的内容也很深,相对很麻烦。但是考虑也简单的pos和copy来实现,发现一样麻烦,而且可能会取的不够准确。所以直接使用大招,就是问chatGpt有没有简单的办法。
说实话,让我自己想一辈子都想不到正则表达式库,满脑子都是不停循环的pos和copy。这样一个反馈,瞬间灵感爆发,如下是那三个截取字符串的代码
//首先引用正则库的单元uses System.RegularExpressions;
//这个大家自己去优化,这是截取上一个data:与下一个data:之间的json字符串部分
procedure TForm1.getOpenAIdb(instr: string; outarray: TJSONArray);
var
s: string;
RegEx: TRegEx;
Match: TMatch;
begin
RegEx := TRegEx.Create('data:\s*(.*?)(?=data:|$)');
Match := RegEx.Match(instr);
while Match.Success do
begin
s := Trim(Match.Groups[1].Value);
outarray.Add(s);
Match := Match.NextMatch;
end;
end;
//这是获取第一条回复数据,里面有role。
function TForm1.getOpenAI_R(instr: string): string;
var
RegEx: TRegEx;
Match: TMatch;
begin
Result := '';
RegEx := TRegEx.Create('"role"\s*:\s*"([^"]+)"');
Match := RegEx.Match(instr);
if Match.Success then
begin
Result := Match.Groups[1].Value;
end;
end;
//这里是获取content,文本内容
function TForm1.getOpenAI_C(instr: string): string;
var
RegEx: TRegEx;
Match: TMatch;
begin
// 查找 "content"
Result := '';
RegEx := TRegEx.Create('"content"\s*:\s*"([^"]+)"');
Match := RegEx.Match(instr);
if Match.Success then
begin
Result := Match.Groups[1].Value;
end;
end;
最后简单的接口实现就完成了,实现效果与官网的基本相同。