Delphi对接ChatGPT实战指南
在这个AI快速发展的时代,如何让我们熟悉的Delphi也能搭上AI的快车?本文将详细介绍如何在Delphi中集成ChatGPT API,打造属于自己的AI助手。
前言
作为一名"老炮"程序员,我深知Delphi在开发效率和程序性能上的优势。现在,让我们给它注入AI的能力,将传统应用提升到一个新的水平。
技术准备
在开始之前,我们需要准备以下资源:
-
Delphi 10.4或更高版本:本文示例基于Delphi 11,但10.4及以上版本都应该可以正常工作。
-
OpenAI API密钥:访问OpenAI官网注册并获取API密钥。
-
REST客户端组件:可以使用Delphi自带的REST组件,也可以选择第三方组件如Indy或REST Debugger。
实现步骤
1. 环境搭建
首先,创建一个新的Delphi VCL或FMX应用程序,并添加必要的组件:
// 在窗体上添加以下组件
// TRESTClient, TRESTRequest, TRESTResponse, TRESTResponseDataSetAdapter
// TMemo (用于输入), TMemo (用于显示结果), TButton (发送请求)
设置REST组件的基本属性:
procedure TForm1.FormCreate(Sender: TObject);
begin
RESTClient1.BaseURL := 'https://api.openai.com/v1';
RESTRequest1.Resource := 'chat/completions';
RESTRequest1.Method := TRESTRequestMethod.rmPOST;
// 添加API密钥到请求头
RESTRequest1.Params.AddHeader('Authorization', 'Bearer YOUR_API_KEY_HERE');
RESTRequest1.Params.AddHeader('Content-Type', 'application/json');
// 设置响应格式
RESTRequest1.Response := RESTResponse1;
end;
2. API封装
创建一个专门的单元来封装ChatGPT API调用:
unit ChatGPTAPI;
interface
uses
System.SysUtils, System.Classes, System.JSON, REST.Client, REST.Types;
type
TChatGPTAPI = class
private
FRESTClient: TRESTClient;
FRESTRequest: TRESTRequest;
FRESTResponse: TRESTResponse;
FAPIKey: string;
FModel: string;
FMaxTokens: Integer;
FTemperature: Double;
public
constructor Create(const APIKey: string);
destructor Destroy; override;
function SendPrompt(const Prompt: string): string;
function SendChatMessage(const Messages: TJSONArray): string;
property Model: string read FModel write FModel;
property MaxTokens: Integer read FMaxTokens write FMaxTokens;
property Temperature: Double read FTemperature write FTemperature;
end;
implementation
constructor TChatGPTAPI.Create(const APIKey: string);
begin
inherited Create;
FAPIKey := APIKey;
FModel := 'gpt-3.5-turbo';
FMaxTokens := 1000;
FTemperature := 0.7;
FRESTClient := TRESTClient.Create(nil);
FRESTRequest := TRESTRequest.Create(nil);
FRESTResponse := TRESTResponse.Create(nil);
FRESTClient.BaseURL := 'https://api.openai.com/v1';
FRESTRequest.Client := FRESTClient;
FRESTRequest.Response := FRESTResponse;
FRESTRequest.Method := TRESTRequestMethod.rmPOST;
FRESTRequest.Resource := 'chat/completions';
FRESTRequest.Params.AddHeader('Authorization', 'Bearer ' + FAPIKey);
FRESTRequest.Params.AddHeader('Content-Type', 'application/json');
end;
destructor TChatGPTAPI.Destroy;
begin
FRESTResponse.Free;
FRESTRequest.Free;
FRESTClient.Free;
inherited;
end;
function TChatGPTAPI.SendPrompt(const Prompt: string): string;
var
Messages: TJSONArray;
MessageObj: TJSONObject;
begin
Messages := TJSONArray.Create;
try
MessageObj := TJSONObject.Create;
MessageObj.AddPair('role', 'user');
MessageObj.AddPair('content', Prompt);
Messages.AddElement(MessageObj);
Result := SendChatMessage(Messages);
finally
Messages.Free;
end;
end;
function TChatGPTAPI.SendChatMessage(const Messages: TJSONArray): string;
var
RequestBody: TJSONObject;
ResponseObj: TJSONObject;
Choices: TJSONArray;
Choice: TJSONObject;
Message: TJSONObject;
begin
Result := '';
RequestBody := TJSONObject.Create;
try
RequestBody.AddPair('model', FModel);
RequestBody.AddPair('messages', Messages.Clone as TJSONArray);
RequestBody.AddPair('max_tokens', TJSONNumber.Create(FMaxTokens));
RequestBody.AddPair('temperature', TJSONNumber.Create(FTemperature));
FRESTRequest.Body.JSONObject := RequestBody;
try
FRESTRequest.Execute;
if FRESTResponse.StatusCode = 200 then
begin
ResponseObj := FRESTResponse.JSONObject;
Choices := ResponseObj.GetValue('choices') as TJSONArray;
if (Choices <> nil) and (Choices.Count > 0) then
begin
Choice := Choices.Items[0] as TJSONObject;
Message := Choice.GetValue('message') as TJSONObject;
Result := Message.GetValue('content').Value;
end;
end
else
begin
Result := 'Error: ' + FRESTResponse.StatusText;
end;
except
on E: Exception do
Result := 'Exception: ' + E.Message;
end;
finally
// 不要释放RequestBody,因为它已经被赋值给FRESTRequest.Body.JSONObject
end;
end;
end.
3. 界面设计
设计一个简单的聊天界面,包括: - 输入框(用户输入问题) - 发送按钮 - 聊天历史记录显示区域
// 在窗体上添加以下组件
// TMemo (InputMemo) - 用于用户输入
// TMemo (ChatHistoryMemo) - 用于显示聊天历史
// TButton (SendButton) - 发送按钮
实现发送按钮的点击事件:
procedure TForm1.SendButtonClick(Sender: TObject);
var
UserMessage: string;
AIResponse: string;
begin
UserMessage := InputMemo.Text;
if Trim(UserMessage) = '' then Exit;
// 显示用户消息
ChatHistoryMemo.Lines.Add('You: ' + UserMessage);
InputMemo.Clear;
// 禁用发送按钮,显示正在处理
SendButton.Enabled := False;
ChatHistoryMemo.Lines.Add('AI: Thinking...');
Application.ProcessMessages;
try
// 调用API获取回复
AIResponse := ChatGPTAPI.SendPrompt(UserMessage);
// 更新聊天历史
ChatHistoryMemo.Lines.Delete(ChatHistoryMemo.Lines.Count - 1); // 删除"Thinking..."
ChatHistoryMemo.Lines.Add('AI: ' + AIResponse);
finally
SendButton.Enabled := True;
end;
end;
4. 实现对话历史记录
为了实现连续对话,我们需要保存对话历史:
unit ChatHistory;
interface
uses
System.SysUtils, System.Classes, System.JSON;
type
TChatMessage = record
Role: string;
Content: string;
end;
TChatHistory = class
private
FMessages: TJSONArray;
public
constructor Create;
destructor Destroy; override;
procedure AddMessage(const Role, Content: string);
function GetMessagesAsJSON: TJSONArray;
procedure Clear;
end;
implementation
constructor TChatHistory.Create;
begin
inherited;
FMessages := TJSONArray.Create;
end;
destructor TChatHistory.Destroy;
begin
FMessages.Free;
inherited;
end;
procedure TChatHistory.AddMessage(const Role, Content: string);
var
MessageObj: TJSONObject;
begin
MessageObj := TJSONObject.Create;
MessageObj.AddPair('role', Role);
MessageObj.AddPair('content', Content);
FMessages.AddElement(MessageObj);
end;
function TChatHistory.GetMessagesAsJSON: TJSONArray;
begin
Result := FMessages.Clone as TJSONArray;
end;
procedure TChatHistory.Clear;
begin
FMessages.Free;
FMessages := TJSONArray.Create;
end;
end.
然后在主窗体中使用这个类:
unit MainForm;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
REST.Client, REST.Types, ChatGPTAPI, ChatHistory;
type
TForm1 = class(TForm)
InputMemo: TMemo;
ChatHistoryMemo: TMemo;
SendButton: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure SendButtonClick(Sender: TObject);
private
FChatGPTAPI: TChatGPTAPI;
FChatHistory: TChatHistory;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
FChatGPTAPI := TChatGPTAPI.Create('YOUR_API_KEY_HERE');
FChatHistory := TChatHistory.Create;
// 添加系统消息,设置AI助手的角色
FChatHistory.AddMessage('system', '你是一个有用的助手,用简洁明了的中文回答问题。');
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FChatHistory.Free;
FChatGPTAPI.Free;
end;
procedure TForm1.SendButtonClick(Sender: TObject);
var
UserMessage: string;
AIResponse: string;
begin
UserMessage := InputMemo.Text;
if Trim(UserMessage) = '' then Exit;
// 显示用户消息
ChatHistoryMemo.Lines.Add('You: ' + UserMessage);
InputMemo.Clear;
// 添加到历史记录
FChatHistory.AddMessage('user', UserMessage);
// 禁用发送按钮,显示正在处理
SendButton.Enabled := False;
ChatHistoryMemo.Lines.Add('AI: Thinking...');
Application.ProcessMessages;
try
// 调用API获取回复
AIResponse := FChatGPTAPI.SendChatMessage(FChatHistory.GetMessagesAsJSON);
// 添加AI回复到历史记录
FChatHistory.AddMessage('assistant', AIResponse);
// 更新聊天历史
ChatHistoryMemo.Lines.Delete(ChatHistoryMemo.Lines.Count - 1); // 删除"Thinking..."
ChatHistoryMemo.Lines.Add('AI: ' + AIResponse);
finally
SendButton.Enabled := True;
end;
end;
end.
高级功能实现
1. 流式响应
OpenAI API支持流式响应,可以实现打字机效果:
function TChatGPTAPI.SendStreamingPrompt(const Messages: TJSONArray;
OnChunkReceived: TProc<string>): string;
var
RequestBody: TJSONObject;
HttpClient: THTTPClient;
Response: IHTTPResponse;
Stream: TStringStream;
Line: string;
JSONObj: TJSONObject;
Choices: TJSONArray;
Choice: TJSONObject;
Delta: TJSONObject;
Content: string;
FullResponse: string;
begin
Result := '';
FullResponse := '';
RequestBody := TJSONObject.Create;
try
RequestBody.AddPair('model', FModel);
RequestBody.AddPair('messages', Messages.Clone as TJSONArray);
RequestBody.AddPair('max_tokens', TJSONNumber.Create(FMaxTokens));
RequestBody.AddPair('temperature', TJSONNumber.Create(FTemperature));
RequestBody.AddPair('stream', TJSONBool.Create(True));
HttpClient := THTTPClient.Create;
try
HttpClient.CustomHeaders['Authorization'] := 'Bearer ' + FAPIKey;
HttpClient.CustomHeaders['Content-Type'] := 'application/json';
Stream := TStringStream.Create('');
try
Response := HttpClient.Post('https://api.openai.com/v1/chat/completions',
TStringStream.Create(RequestBody.ToJSON), Stream);
if Response.StatusCode = 200 then
begin
Stream.Position := 0;
while not Stream.EOS do
begin
Line := Stream.ReadLine;
if Line.StartsWith('data: ') and (Line <> 'data: [DONE]') then
begin
Line := Line.Substring(6);
try
JSONObj := TJSONObject.ParseJSONValue(Line) as TJSONObject;
try
Choices := JSONObj.GetValue('choices') as TJSONArray;
if (Choices <> nil) and (Choices.Count > 0) then
begin
Choice := Choices.Items[0] as TJSONObject;
Delta := Choice.GetValue('delta') as TJSONObject;
if (Delta <> nil) and Delta.TryGetValue('content', Content) then
begin
FullResponse := FullResponse + Content;
if Assigned(OnChunkReceived) then
OnChunkReceived(Content);
end;
end;
finally
JSONObj.Free;
end;
except
// 忽略解析错误
end;
end;
end;
end
else
begin
Result := 'Error: ' + Response.StatusText;
end;
finally
Stream.Free;
end;
finally
HttpClient.Free;
end;
finally
RequestBody.Free;
end;
Result := FullResponse;
end;
在窗体中使用流式响应:
procedure TForm1.SendButtonClick(Sender: TObject);
var
UserMessage: string;
CurrentLine: Integer;
begin
UserMessage := InputMemo.Text;
if Trim(UserMessage) = '' then Exit;
// 显示用户消息
ChatHistoryMemo.Lines.Add('You: ' + UserMessage);
InputMemo.Clear;
// 添加到历史记录
FChatHistory.AddMessage('user', UserMessage);
// 禁用发送按钮,显示正在处理
SendButton.Enabled := False;
ChatHistoryMemo.Lines.Add('AI: ');
CurrentLine := ChatHistoryMemo.Lines.Count - 1;
Application.ProcessMessages;
try
// 调用流式API
FChatGPTAPI.SendStreamingPrompt(FChatHistory.GetMessagesAsJSON,
procedure(const Chunk: string)
begin
// 更新UI(需要同步到主线程)
TThread.Synchronize(nil,
procedure
begin
ChatHistoryMemo.Lines[CurrentLine] := 'AI: ' +
ChatHistoryMemo.Lines[CurrentLine].Substring(4) + Chunk;
// 滚动到底部
SendMessage(ChatHistoryMemo.Handle, EM_SCROLLCARET, 0, 0);
end);
end);
// 获取完整响应并添加到历史
FChatHistory.AddMessage('assistant', ChatHistoryMemo.Lines[CurrentLine].Substring(4));
finally
SendButton.Enabled := True;
end;
end;
2. 函数调用
OpenAI API支持函数调用,可以让AI调用我们定义的函数:
type
TFunctionDefinition = record
Name: string;
Description: string;
Parameters: TJSONObject;
end;
TFunctionCall = record
Name: string;
Arguments: string;
end;
TFunctionCallHandler = reference to function(const FunctionName, Arguments: string): string;
// 在TChatGPTAPI类中添加
procedure TChatGPTAPI.AddFunction(const FunctionDef: TFunctionDefinition);
var
FunctionObj: TJSONObject;
begin
if FFunctions = nil then
FFunctions := TJSONArray.Create;
FunctionObj := TJSONObject.Create;
FunctionObj.AddPair('name', FunctionDef.Name);
FunctionObj.AddPair('description', FunctionDef.Description);
FunctionObj.AddPair('parameters', FunctionDef.Parameters.Clone as TJSONObject);
FFunctions.AddElement(FunctionObj);
end;
function TChatGPTAPI.SendChatMessageWithFunctions(const Messages: TJSONArray;
FunctionCallHandler: TFunctionCallHandler): string;
var
RequestBody, ResponseObj, FunctionCallObj: TJSONObject;
Choices: TJSONArray;
Choice, Message: TJSONObject;
FunctionCall: TFunctionCall;
FunctionResponse: string;
NewMessages: TJSONArray;
begin
Result := '';
RequestBody := TJSONObject.Create;
try
RequestBody.AddPair('model', FModel);
RequestBody.AddPair('messages', Messages.Clone as TJSONArray);
RequestBody.AddPair('max_tokens', TJSONNumber.Create(FMaxTokens));
RequestBody.AddPair('temperature', TJSONNumber.Create(FTemperature));
if FFunctions <> nil then
RequestBody.AddPair('functions', FFunctions.Clone as TJSONArray);
FRESTRequest.Body.JSONObject := RequestBody;
try
FRESTRequest.Execute;
if FRESTResponse.StatusCode = 200 then
begin
ResponseObj := FRESTResponse.JSONObject;
Choices := ResponseObj.GetValue('choices') as TJSONArray;
if (Choices <> nil) and (Choices.Count > 0) then
begin
Choice := Choices.Items[0] as TJSONObject;
Message := Choice.GetValue('message') as TJSONObject;
// 检查是否有函数调用
if Message.TryGetValue('function_call', FunctionCallObj) then
begin
// 解析函数调用
FunctionCall.Name := FunctionCallObj.GetValue('name').Value;
FunctionCall.Arguments := FunctionCallObj.GetValue('arguments').Value;
// 调用处理函数
if Assigned(FunctionCallHandler) then
FunctionResponse := FunctionCallHandler(FunctionCall.Name, FunctionCall.Arguments);
// 创建新的消息数组,包含函数调用结果
NewMessages := Messages.Clone as TJSONArray;
// 添加AI的函数调用消息
var AIMessage := TJSONObject.Create;
AIMessage.AddPair('role', 'assistant');
AIMessage.AddPair('content', TJSONNull.Create);
AIMessage.AddPair('function_call', FunctionCallObj.Clone as TJSONObject);
NewMessages.AddElement(AIMessage);
// 添加函数响应消息
var FunctionMessage := TJSONObject.Create;
FunctionMessage.AddPair('role', 'function');
FunctionMessage.AddPair('name', FunctionCall.Name);
FunctionMessage.AddPair('content', FunctionResponse);
NewMessages.AddElement(FunctionMessage);
// 递归调用,但不再传递函数处理器,避免无限循环
Result := SendChatMessage(NewMessages);
NewMessages.Free;
end
else
begin
// 普通消息响应
Result := Message.GetValue('content').Value;
end;
end;
end
else
begin
Result := 'Error: ' + FRESTResponse.StatusText;
end;
except
on E: Exception do
Result := 'Exception: ' + E.Message;
end;
finally
// 不要释放RequestBody
end;
end;
使用函数调用功能:
procedure TForm1.FormCreate(Sender: TObject);
var
WeatherFunctionDef: TFunctionDefinition;
ParametersObj: TJSONObject;
PropertiesObj: TJSONObject;
LocationObj, UnitObj: TJSONObject;
begin
FChatGPTAPI := TChatGPTAPI.Create('YOUR_API_KEY_HERE');
FChatHistory := TChatHistory.Create;
// 定义天气查询函数
WeatherFunctionDef.Name := 'get_current_weather';
WeatherFunctionDef.Description := '获取指定位置的当前天气';
ParametersObj := TJSONObject.Create;
ParametersObj.AddPair('type', 'object');
PropertiesObj := TJSONObject.Create;
LocationObj := TJSONObject.Create;
LocationObj.AddPair('type', 'string');
LocationObj.AddPair('description', '城市名称,如北京、上海');
UnitObj := TJSONObject.Create;
UnitObj.AddPair('type', 'string');
UnitObj.AddPair('enum', TJSONArray.Create(['celsius', 'fahrenheit']));
UnitObj.AddPair('description', '温度单位');
PropertiesObj.AddPair('location', LocationObj);
PropertiesObj.AddPair('unit', UnitObj);
ParametersObj.AddPair('properties', PropertiesObj);
ParametersObj.AddPair('required', TJSONArray.Create(['location']));
WeatherFunctionDef.Parameters := ParametersObj;
// 添加函数定义
FChatGPTAPI.AddFunction(WeatherFunctionDef);
// 添加系统消息
FChatHistory.AddMessage('system', '你是一个有用的助手,可以回答问题并查询天气信息。');
end;
procedure TForm1.SendButtonClick(Sender: TObject);
var
UserMessage: string;
AIResponse: string;
begin
UserMessage := InputMemo.Text;
if Trim(UserMessage) = '' then Exit;
// 显示用户消息
ChatHistoryMemo.Lines.Add('You: ' + UserMessage);
InputMemo.Clear;
// 添加到历史记录
FChatHistory.AddMessage('user', UserMessage);
// 禁用发送按钮,显示正在处理
SendButton.Enabled := False;
ChatHistoryMemo.Lines.Add('AI: Thinking...');
Application.ProcessMessages;
try
// 调用API获取回复,包含函数处理
AIResponse := FChatGPTAPI.SendChatMessageWithFunctions(
FChatHistory.GetMessagesAsJSON,
function(const FunctionName, Arguments: string): string
var
ArgsObj: TJSONObject;
Location, Unit_: string;
Temperature: Integer;
begin
if FunctionName = 'get_current_weather' then
begin
ArgsObj := TJSONObject.ParseJSONValue(Arguments) as TJSONObject;
try
Location := ArgsObj.GetValue('location').Value;
if ArgsObj.TryGetValue('unit', Unit_) then
Unit_ := 'celsius';
// 这里应该是实际的天气API调用,这里简化处理
Randomize;
Temperature := Random(30);
Result := Format('{"location": "%s", "temperature": %d, "unit": "%s", "condition": "晴朗"}',
[Location, Temperature, Unit_]);
finally
ArgsObj.Free;
end;
end
else
begin
Result := '{"error": "Unknown function"}';
end;
end);
// 添加AI回复到历史记录
FChatHistory.AddMessage('assistant', AIResponse);
// 更新聊天历史
ChatHistoryMemo.Lines.Delete(ChatHistoryMemo.Lines.Count - 1); // 删除"Thinking..."
ChatHistoryMemo.Lines.Add('AI: ' + AIResponse);
finally
SendButton.Enabled := True;
end;
end;
实战案例
智能客服系统
结合以上技术,我们可以创建一个智能客服系统:
- 添加知识库功能,让AI能够回答关于产品的问题
- 集成函数调用,允许AI查询订单状态、产品库存等
- 实现对话历史保存和加载,方便用户查看历史对话
- 添加用户反馈机制,不断优化AI回答质量
代码助手
为开发者创建一个Delphi代码助手:
- 让AI理解Delphi语法和常用组件
- 实现代码生成、代码解释和代码优化功能
- 添加代码片段库,方便快速插入常用代码
- 集成到IDE中,提供实时编码建议
注意事项
- API密钥安全:不要在代码中硬编码API密钥,应该使用配置文件或环境变量
- 错误处理:添加完善的错误处理机制,处理网络问题和API限制
- 用户体验:使用流式响应提高用户体验,避免长时间等待
- 成本控制:监控API使用情况,避免意外的高额费用
- 内容过滤:根据需要添加内容过滤,避免不适当的内容
总结
通过本文的实践,我们成功地将现代AI能力注入传统Delphi应用,打造了一个功能强大的ChatGPT客户端。这不仅提升了应用的智能化水平,还为用户提供了更加便捷的AI交互体验。
随着AI技术的不断发展,Delphi应用与AI的结合将会有更多可能性。希望本文能够帮助更多Delphi开发者踏上AI集成之旅,创造出更加智能、更有价值的应用程序。
如果你有任何问题或建议,欢迎在评论区留言交流!