DelphiOpenAI:让经典Delphi应用轻松集成现代AI能力
1. 项目概述DelphiOpenAI一个为Delphi开发者打造的AI桥梁如果你是一名Delphi开发者看着Python、JavaScript社区热火朝天地集成各种AI能力自己却只能望“码”兴叹感觉被时代抛在了后面那么今天这个项目可能就是为你准备的。DelphiOpenAI一个由社区驱动的、非官方的OpenAI API Delphi封装库它的出现就是为了解决这个痛点让经典的Delphi/VCL/FMX应用也能轻松拥抱现代AI无论是文本对话、代码生成、图像创作还是语音处理。这个库的核心价值在于它用纯Pascal代码为Delphi世界搭建了一座通往OpenAI及其兼容服务如Azure OpenAI、DeepSeek、通义千问等的桥梁。你不用再去纠结复杂的HTTP请求、JSON解析和API版本管理作者已经把这些脏活累活都封装好了。你只需要像调用一个普通的Delphi组件或接口一样传入你的API密钥和请求参数就能获得结构化的响应。这对于那些维护着庞大Delphi遗产代码库又急需为产品注入AI智能的团队来说无疑是一条高效的捷径。无论是想给桌面应用加一个智能助手还是为移动APP集成一个图片生成功能这个库都提供了清晰、类型安全的入口。2. 核心设计思路与架构解析2.1 为什么选择封装为组件和接口两种形式打开DelphiOpenAI的源码你会发现它提供了两种使用方式TOpenAIComponent组件和IOpenAI接口。这并非冗余而是针对不同开发场景和习惯的精心设计。组件形式 (TOpenAIComponent)主要服务于传统的、基于窗体的快速应用开发。你可以直接从IDE的工具面板拖拽一个TOpenAIComponent到你的窗体上在对象观察器里设置ApiKey属性然后双击事件写代码。这种方式对Delphi新手或开发小型工具特别友好它符合经典的RAD快速应用开发范式所有配置可视化集成度高。组件内部通常已经处理好了生命周期比如在窗体销毁时自动释放资源。接口形式 (IOpenAI)则更偏向于现代、松耦合的架构。通过TOpenAI.Create(API_TOKEN)工厂函数返回一个接口引用你获得的是一个纯粹的、不依赖VCL/FMX框架的服务对象。这种方式有三大优势第一便于单元测试你可以轻松地用Mock对象替换真实的AI服务进行测试第二适合在非UI层如数据模块、服务层中使用代码可移植性更强第三接口具有自动引用计数无需手动管理内存减少了内存泄漏的风险。对于构建大型、分层清晰的企业级应用接口模式是更佳选择。2.2 匿名函数配置灵活性与可读性的平衡库中几乎所有API调用如OpenAI.Chat.Create都采用了一个非常Delphi的风格使用匿名函数procedure(Params: TSomeParams)来配置参数。初看可能觉得有点绕不如直接传一个记录体直观。但深入想这正是设计上的巧妙之处。OpenAI的API参数众多且不同模型、不同调用场景下需要的参数子集差异很大。如果为每个可能的参数组合都设计一个重载的Create函数那将是一场灾难。采用匿名函数配置实际上是一种“流畅接口”或“建造者模式”的变体。它允许你在一个代码块内只设置你关心的参数其他参数保持默认值。代码写起来就像在说话“创建一个聊天请求参数是这样消息内容是用户输入最大令牌数是1024流式输出……” 这种内联配置的方式让代码的意图非常清晰参数设置紧挨着方法调用避免了在外部先构造一个庞大参数对象的繁琐。更重要的是它提供了编译时类型安全。Params对象会智能提示Code Insight所有可用的设置方法如.Model(),.MaxTokens(),.Temperature()你不可能拼错属性名或传入错误类型的值这比直接操作一个充满字符串键的TJSONObject要可靠得多。2.3 广泛的兼容性设计不止于OpenAI项目宣称兼容Azure OpenAI、DeepSeek、YandexGPT、通义千问、GigaChat等这并非简单的口号。其背后的设计关键在于对“OpenAI API兼容”这一标准的抽象。这些服务提供商为了降低开发者迁移成本纷纷提供了与OpenAI官方API在请求格式和响应格式上高度兼容的端点。DelphiOpenAI库的核心抽象层——TOpenAI类及其内部的TApiClient——并没有硬编码api.openai.com这个域名。相反它允许你在创建实例时传入一个基础URL。对于OpenAI就是https://api.openai.com/v1对于Azure OpenAI可能就是https://your-resource.openai.azure.com/openai/deployments/your-deployment对于其他服务则是它们提供的兼容端点。库中所有的请求构建和响应解析逻辑都基于OpenAI的公开文档。只要目标服务严格遵循这套格式那么更换一个URL和对应的API密钥或认证方式就能无缝切换。这种设计极大地扩展了库的实用性让你不被单一服务商绑定可以根据需求、成本、地域选择最合适的AI引擎。3. 核心功能模块深度解析与实操3.1 模型管理了解你的“引擎”在调用任何AI功能前了解可用模型是第一步。这好比开车前要知道自己用的是汽油车还是电动车。OpenAI.Model.List()方法返回一个TModels对象其中包含一个Data数组列出了你账户有权限访问的所有模型。var Models: TModels; Model: TModel; begin Models : OpenAI.Model.List(); try Memo1.Lines.Add(可用模型列表); for Model in Models.Data do begin Memo1.Lines.Add(Format(ID: %s, 所有者: %s, [Model.Id, Model.OwnedBy])); // 你还可以查看 Model.Created, Model.Permissions 等信息 end; finally Models.Free; // 注意使用接口模式时无需此步但组件模式或直接创建的对象需要 end; end;这里有一个关键细节返回的模型ID如gpt-3.5-turbo,text-davinci-003,dall-e-3直接决定了后续Chat、Completion、Image等终端的调用是否成功。例如你不能用gpt-3.5-turbo模型ID去调用图像生成接口。通常聊天用gpt-3.5-turbo或gpt-4补全旧版用text-davinci-003图像用dall-e-2或dall-e-3。注意模型列表的缓存。频繁调用Model.List()会产生不必要的API请求可能触及速率限制。在实际应用中建议在程序初始化时获取一次并缓存起来或者使用硬编码的已知模型ID列表。3.2 聊天补全与AI对话的核心这是目前最常用、功能最丰富的端点。OpenAI.Chat.Create是你的主要工具。基础对话var Chat: TChat; begin Chat : OpenAI.Chat.Create( procedure(Params: TChatParams) begin Params.Model(gpt-3.5-turbo); // 默认就是它但显式指定是好习惯 Params.Messages([TChatMessageBuild.User(你好请用Delphi写一个Hello World程序。)]); Params.MaxTokens(500); // 控制回复长度避免过长 Params.Temperature(0.7); // 控制创造性0.0最确定1.0最多样 end); try if Length(Chat.Choices) 0 then ShowMessage(Chat.Choices[0].Message.Content); finally Chat.Free; end; end;消息数组的构建是聊天的精髓。AI没有记忆每次请求的Messages数组就是全部的对话上下文。你需要把之前的历史对话也塞进去。库提供的TChatMessageBuild静态方法让构建角色消息变得简单TChatMessageBuild.User(‘用户内容’)用户消息。TChatMessageBuild.Assistant(‘助手回复’)AI之前的回复。TChatMessageBuild.System(‘系统指令’)用于设定AI的行为角色如“你是一个专业的Delphi代码助手”。一个典型的带上下文的对话请求应该是这样的Params.Messages([ TChatMessageBuild.System(你是一个乐于助人的编程助手。), TChatMessageBuild.User(如何声明一个整数变量), TChatMessageBuild.Assistant(在Delphi中你可以使用 var i: Integer; 来声明。), TChatMessageBuild.User(那字符串呢) // AI会基于前面所有消息来回答这个问题 ]);3.3 流式传输实现“打字机”效果如果你想要实现像ChatGPT网页版那样一个字一个字显示的效果就必须使用流式传输。这通过CreateStream方法实现。procedure TForm1.ButtonStreamChatClick(Sender: TObject); var Cancel: Boolean; begin Cancel : False; MemoAnswer.Clear; // 注意这是一个异步调用回调函数会在收到数据块时被触发 OpenAI.Chat.CreateStream( procedure(Params: TChatParams) begin Params.Messages([TChatMessageBuild.User(MemoQuestion.Text)]); Params.MaxTokens(1024); Params.Stream(True); // 关键开启流式 end, procedure(Chat: TChat; IsDone: Boolean; var Cancel: Boolean) begin // 每次收到一个数据块都会进入这个回调 if (not IsDone) and Assigned(Chat) and (Length(Chat.Choices) 0) then begin // 注意流式模式下Choice.Message.Content 是空的 // 增量内容在 Choice.Delta.Content 里 MemoAnswer.Text : MemoAnswer.Text Chat.Choices[0].Delta.Content; MemoAnswer.SelStart : Length(MemoAnswer.Text); // 滚动到末尾 Application.ProcessMessages; // 让UI更新 end else if IsDone then begin MemoAnswer.Lines.Add(#13#10--- 流式传输结束 ---); end; // 如果需要中途取消可以将 Cancel 参数设为 True end); end;流式传输的陷阱与技巧UI线程阻塞回调函数通常不在主UI线程执行。直接在上面更新VCL/FMX控件可能会引发线程安全问题。稳妥的做法是使用TThread.Synchronize或TThread.Queue将UI更新操作抛回主线程。上面的例子在简单场景下可能工作但复杂UI中需谨慎。DeltavsContent在流式响应中完整的消息内容Content是空的新增的内容在Delta属性里。这是与普通模式最大的不同。错误处理流式传输中的网络错误或API错误处理起来更复杂。你需要在回调中检查Chat对象的状态或者在外层用try..except包裹整个CreateStream调用。3.4 视觉理解让AI“看”图GPT-4 Vision模型可以让AI分析图像。DelphiOpenAI库通过TMessageContent类型来支持多模态输入。uses System.NetEncoding; // 用于Base64编码 function FileToBase64(const FileName: string): string; var Stream: TFileStream; Bytes: TBytes; begin Stream : TFileStream.Create(FileName, fmOpenRead); try SetLength(Bytes, Stream.Size); Stream.Read(Bytes[0], Stream.Size); Result : TNetEncoding.Base64.EncodeBytesToString(Bytes); finally Stream.Free; end; end; procedure TForm1.AnalyzeImage; var Chat: TChat; ImagePath: string; ContentArray: TArrayTMessageContent; begin ImagePath : C:\path\to\your\image.jpg; // 构建内容数组可以混合文本和图像 ContentArray : [ TMessageContent.CreateText(请描述这张图片里有什么。), TMessageContent.CreateImage(FileToBase64(ImagePath)) // TMessageContent.CreateImage(FileToBase64(ImagePath), high) // 可以指定细节程度low, high, auto ]; Chat : OpenAI.Chat.Create( procedure(Params: TChatParams) begin Params.Model(gpt-4-vision-preview); // 必须使用视觉模型 Params.Messages([TChatMessageBuild.User(ContentArray)]); Params.MaxTokens(300); end); try if Length(Chat.Choices) 0 then MemoResult.Lines.Text : Chat.Choices[0].Message.Content; finally Chat.Free; end; end;这里的关键是将图像文件转换为Base64字符串内联在请求中而不是上传文件。对于大图片注意控制尺寸和细节程度detail参数因为高细节度会消耗更多令牌增加成本和延迟。3.5 函数调用让AI驱动你的代码这是构建复杂AI代理Agent的基石。AI可以决定何时需要调用外部函数如查询数据库、获取天气、执行计算并返回结构化的参数供你执行。首先你需要定义函数列表TArrayIChatFunction来描述你的工具uses OpenAI.Chat.Functions; var MyFunctions: TArrayIChatFunction; begin // 定义一个获取天气的函数 MyFunctions : MyFunctions [ TChatFunction.Create(get_weather) .Description(获取指定城市的当前天气) .AddParam(location, string, 城市名例如北京) .AddParam(unit, string, 温度单位celsius 或 fahrenheit, False, [celsius, fahrenheit]) // 非必需有枚举值 ]; // 定义另一个函数... // MyFunctions : MyFunctions [TChatFunction.Create(...)...];然后在聊天请求中传入这个函数列表并设置FunctionCall策略var Chat: TChat; UserQuestion: string; begin UserQuestion : 北京今天的天气怎么样; Chat : OpenAI.Chat.Create( procedure(Params: TChatParams) begin Params.Model(gpt-3.5-turbo-0613); // 必须使用支持函数调用的模型 Params.Messages([TChatMessageBuild.User(UserQuestion)]); Params.Functions(MyFunctions); Params.FunctionCall(TFunctionCall.Auto); // 让AI决定是否调用函数 // Params.FunctionCall(get_weather); // 或者强制调用某个函数 end); try if (Length(Chat.Choices) 0) then begin var Choice : Chat.Choices[0]; if Choice.FinishReason TFinishReason.FunctionCall then begin // AI决定调用函数 var FuncCall : Choice.Message.FunctionCall; MemoLog.Lines.Add(Format(AI想调用函数%s 参数%s, [FuncCall.Name, FuncCall.Arguments])); // 在这里你根据 FuncCall.Name 和 FuncCall.Arguments (JSON字符串) // 去执行真正的业务逻辑如调用一个Web API var WeatherResult : ExecuteGetWeather(FuncCall.Arguments); // 将函数执行结果返回给AI让AI生成面向用户的回答 SendFunctionResultBackToAI(FuncCall.Name, WeatherResult, [TChatMessageBuild.User(UserQuestion)]); end else begin // AI直接给出了最终回答 MemoAnswer.Text : Choice.Message.Content; end; end; finally Chat.Free; end; end;SendFunctionResultBackToAI函数需要构造一个新的聊天请求并将整个对话历史包括最初的用户问题、AI的函数调用请求、以及你提供的函数执行结果一起发送回去procedure SendFunctionResultBackToAI(const FuncName, FuncResultJson: string; History: TArrayTChatMessage); var NextChat: TChat; begin NextChat : OpenAI.Chat.Create( procedure(Params: TChatParams) begin Params.Model(gpt-3.5-turbo-0613); Params.Functions(MyFunctions); // 同样的函数列表 Params.FunctionCall(TFunctionCall.Auto); // 关键构建完整的消息历史 Params.Messages(History [ // 之前的消息 TChatMessageBuild.NewAsistantFunc(FuncName, FuncResultJson), // AI之前请求调用的消息 TChatMessageBuild.Func(FuncResultJson, FuncName) // 你提供的函数执行结果 ]); end); try // 处理AI的最终回复... finally NextChat.Free; end; end;这个过程可以循环进行实现多轮的工具调用从而完成复杂的任务。4. 高级配置与生产环境实践4.1 错误处理与异常管理网络请求和远程API调用充满了不确定性健壮的错误处理至关重要。DelphiOpenAI定义了一系列精细化的异常类型让你能准确捕捉不同问题。uses OpenAI.Exceptions; procedure TForm1.MakeAICall; begin try var Chat : OpenAI.Chat.Create(...); try // 处理成功结果 finally Chat.Free; end; except on E: OpenAIExceptionRateLimitError do begin // 速率限制错误请求太快了 ShowMessage(Format(请求超速请稍后再试。详情%s, [E.Message])); // 可以在这里实现指数退避重试逻辑 end; on E: OpenAIExceptionAuthenticationError do begin // API密钥错误或无效 ShowMessage(API认证失败请检查密钥是否正确或是否已过期。); LogError(E.Message); // 记录到日志文件 end; on E: OpenAIExceptionInvalidRequestError do begin // 请求参数有误如模型不存在、参数格式错误 ShowMessage(Format(请求参数错误%s, [E.Message])); // 通常需要检查并修正调用代码 end; on E: OpenAIException do begin // 其他OpenAI API错误 ShowMessage(Format(OpenAI服务错误%s, [E.Message])); end; on E: Exception do begin // 网络错误、本地JSON解析错误等 ShowMessage(Format(发生未知错误%s, [E.Message])); end; end; end;生产环境建议实现重试机制对于OpenAIExceptionRateLimitError429错误和OpenAIExceptionTryAgain服务端过载应该实现带指数退避的重试逻辑。不要立即重试等待时间可以逐步增加如1秒、2秒、4秒…。集中式日志将所有异常信息包括错误类型、消息、请求参数、时间戳记录到文件或日志系统便于事后分析和监控。用户友好提示不要将原始的、技术性的异常消息直接抛给终端用户。将其转换为用户能理解的语言如“服务暂时繁忙请稍后重试”。4.2 代理与网络配置在企业内网或特定网络环境下直接访问外部API可能需要配置代理。库通过TProxySettings类提供了支持。// 在初始化OpenAI实例后配置 OpenAI.API.ProxySettings : TProxySettings.Create( proxy.mycompany.com, // 代理服务器地址 8080, // 端口 myusername, // 用户名如果需要认证 mypassword // 密码 ); // 或者如果需要更灵活的控制如根据系统设置动态获取代理 var ProxyInfo: TProxySettings; if GetSystemProxyForUrl(https://api.openai.com, ProxyHost, ProxyPort) then begin ProxyInfo : TProxySettings.Create(ProxyHost, ProxyPort); OpenAI.API.ProxySettings : ProxyInfo; end;注意事项代理设置是全局性的会影响该IOpenAI实例发出的所有请求。如果代理需要NTLM等复杂认证库内置的TProxySettings可能不够用。此时你可能需要更底层的网络库如Indy来配置或者考虑在系统层面设置全局代理。对于移动平台Android/iOS网络配置通常由操作系统管理在应用中设置代理可能无效或方式不同需要查阅对应平台的文档。4.3 自定义准备机制适配第三方兼容API对于像GigaChat这样需要额外认证步骤如先获取OAuth令牌的兼容API库提供了IAPIPrepare接口。你可以在请求发出前插入自定义的逻辑。type TMyApiPreparer class(TInterfacedObject, IAPIPrepare) private FAuthToken: string; FTokenUrl: string; FClientId: string; FClientSecret: string; FLastTokenTime: TDateTime; function GetFreshToken: string; public constructor Create(const AClientId, AClientSecret, ATokenUrl: string); procedure Prepare(var AHttpClient: THTTPClient; var AUrl: string; var AData: string); end; constructor TMyApiPreparer.Create(const AClientId, AClientSecret, ATokenUrl: string); begin inherited Create; FClientId : AClientId; FClientSecret : AClientSecret; FTokenUrl : ATokenUrl; end; function TMyApiPreparer.GetFreshToken: string; begin // 如果令牌过期例如超过1小时则重新获取 if (FAuthToken ) or (HoursBetween(Now, FLastTokenTime) 1) then begin // 这里实现向 FTokenUrl 发起请求用 FClientId 和 FClientSecret 换取 access_token // 假设我们从一个返回JSON {“access_token”: “xxx”, “expires_in”: 3600} 的端点获取 var LHttp : THTTPClient.Create; try var LResp : LHttp.Post(FTokenUrl, TStringStream.Create( Format(grant_typeclient_credentialsclient_id%sclient_secret%s, [FClientId, FClientSecret])), nil, TEncoding.UTF8); var LJson : TJSONObject.ParseJSONValue(LResp.ContentAsString) as TJSONObject; try FAuthToken : LJson.GetValue(access_token).Value; finally LJson.Free; end; finally LHttp.Free; end; FLastTokenTime : Now; end; Result : FAuthToken; end; procedure TMyApiPreparer.Prepare(var AHttpClient: THTTPClient; var AUrl: string; var AData: string); var LToken: string; begin LToken : GetFreshToken; // 将令牌添加到HTTP请求头中 AHttpClient.CustomHeaders[Authorization] : Bearer LToken; // 这里还可以修改AUrl或AData以适应特定API的细微差别 end; // 使用自定义准备器 var OpenAIForGiga: IOpenAI; begin OpenAIForGiga : TOpenAI.Create(https://gigachat.devices.sberbank.ru/api/v1, ); OpenAIForGiga.API.Prepare : TMyApiPreparer.Create(your_client_id, your_secret, https://.../token); // 现在所有通过 OpenAIForGiga 发起的请求都会自动带上有效的Bearer Token var Response : OpenAIForGiga.Chat.Create(...); end;这个机制非常强大它允许你无缝集成任何与OpenAI API格式兼容但认证方式不同的服务。5. 实战案例构建一个简易的Delphi AI代码助手让我们把上面的知识整合起来创建一个有实际用处的工具一个能回答Delphi编程问题、并能根据描述生成代码片段的桌面助手。5.1 项目搭建与界面设计新建一个VCL Forms Application。在窗体上放置以下组件TMemo(命名为MemoQuestion): 用于输入问题。TButton(命名为ButtonAsk): 发送问题。TMemo(命名为MemoAnswer): 显示AI回复。TCheckBox(命名为CheckBoxStream): 是否启用流式输出。TStatusBar(命名为StatusBar1): 显示状态。TOpenAIComponent(从组件面板拖拽命名为OpenAI): 在Object Inspector中设置其ApiKey属性或者留空在代码中设置。在uses子句中添加OpenAI, OpenAI.Chat, OpenAI.Exceptions, System.Threading。5.2 核心逻辑实现我们实现一个支持流式和非流式两种模式并带有基本错误处理的问答函数。procedure TForm1.AskQuestion(const Question: string; UseStream: Boolean); var ApiKey: string; begin ApiKey : EditApiKey.Text; // 假设有一个TEdit输入密钥 if ApiKey.IsEmpty then begin ShowMessage(请输入API Key); Exit; end; OpenAI.ApiKey : ApiKey; // 为组件设置密钥 MemoAnswer.Clear; StatusBar1.SimpleText : 思考中...; if UseStream then begin // 流式模式 - 异步 TTask.Run(procedure begin try OpenAI.Chat.CreateStream( procedure(Params: TChatParams) begin Params.Model(gpt-3.5-turbo); // 系统指令让AI扮演角色 Params.Messages([ TChatMessageBuild.System(你是一个资深的Delphi专家精通VCL、FMX、RTL以及现代Delphi版本10.4的所有特性。请用清晰、准确的中文回答涉及代码时请提供完整、可运行的示例。), TChatMessageBuild.User(Question) ]); Params.MaxTokens(1500); Params.Temperature(0.3); // 较低的温度让回答更专注准确 Params.Stream(True); end, procedure(Chat: TChat; IsDone: Boolean; var Cancel: Boolean) begin TThread.Queue(nil, procedure // 将UI更新抛回主线程 begin if Cancel then Exit; // 如果用户取消了 if (not IsDone) and Assigned(Chat) and (Length(Chat.Choices) 0) then begin MemoAnswer.Text : MemoAnswer.Text Chat.Choices[0].Delta.Content; MemoAnswer.SelStart : Length(MemoAnswer.Text); end else if IsDone then begin StatusBar1.SimpleText : 就绪; MemoAnswer.Lines.Add(#13#10#13#10--- 回答完毕 ---); end; end); end); except on E: Exception do TThread.Queue(nil, procedure begin HandleApiError(E); StatusBar1.SimpleText : 错误; end); end; end); end else begin // 非流式模式 - 同步在UI线程可能卡顿建议也用任务 TTask.Run(procedure begin var Chat: TChat : nil; try Chat : OpenAI.Chat.Create( procedure(Params: TChatParams) begin Params.Model(gpt-3.5-turbo); Params.Messages([ TChatMessageBuild.System(你是一个资深的Delphi专家...), TChatMessageBuild.User(Question) ]); Params.MaxTokens(1500); Params.Temperature(0.3); end); TThread.Synchronize(nil, procedure begin if Length(Chat.Choices) 0 then MemoAnswer.Text : Chat.Choices[0].Message.Content; StatusBar1.SimpleText : 就绪; end); except on E: Exception do TThread.Synchronize(nil, procedure begin HandleApiError(E); StatusBar1.SimpleText : 错误; end); end; if Assigned(Chat) then Chat.Free; end); end; end; procedure TForm1.HandleApiError(E: Exception); begin if E is OpenAIExceptionRateLimitError then ShowMessage(请求过于频繁请稍等一分钟再试。) else if E is OpenAIExceptionAuthenticationError then ShowMessage(API密钥无效请检查并重新输入。) else if E is ENetHTTPClientException then ShowMessage(网络连接失败请检查网络设置。) else ShowMessage(请求失败 E.Message); end; procedure TForm1.ButtonAskClick(Sender: TObject); begin if MemoQuestion.Text.Trim.IsEmpty then Exit; AskQuestion(MemoQuestion.Text.Trim, CheckBoxStream.Checked); end;5.3 功能扩展集成代码生成与解释我们可以增加一个按钮专门处理“生成代码”的请求使用更具体的系统指令。procedure TForm1.ButtonGenerateCodeClick(Sender: TObject); var CodeDesc: string; begin CodeDesc : InputBox(代码生成, 请描述你想要生成的Delphi代码功能, ); if CodeDesc.IsEmpty then Exit; AskQuestion( 请根据以下描述生成完整、可编译的Delphi代码。要求1. 代码需包含必要的uses子句。2. 如果是窗体程序请给出完整的单元代码。3. 添加关键注释。描述 CodeDesc, CheckBoxStream.Checked ); end;更进一步我们可以利用函数调用让AI不仅能生成代码还能在我们本地执行简单的语法检查通过调用一个本地的Pascal解析工具函数。6. 常见问题、性能调优与避坑指南6.1 令牌Token与成本控制这是使用任何OpenAI API最需要关注的一点。令牌是计费单位也是模型上下文长度的限制。什么是令牌对于英文大约1个令牌对应4个字符或0.75个单词。中文、日文等象形文字通常一个字符就是一个或多个令牌。如何估算你的请求消息Messages数组里所有内容和AI的回复都会消耗令牌。MaxTokens参数限制的是AI回复的最大令牌数不是总数。总数是输入输出。成本控制技巧精简系统提示系统消息也计入令牌。保持指令简洁精准。管理对话历史对于长对话不要无限制地发送全部历史。可以只保留最近N轮或者用AI总结之前的对话摘要再发送。设置合理的MaxTokens根据你期望的回答长度设置避免不必要的长回复。对于代码生成可以设大一点如2000对于简单问答500可能就够了。监控使用量定期查看OpenAI平台的使用仪表盘了解消耗模式和成本。6.2 超时与网络稳定性网络请求默认有超时限制。对于生成长文本或网络慢的情况可能需要调整。// 在初始化后可以设置底层的HTTP客户端的超时时间单位毫秒 OpenAI.API.HttpClient.ConnectionTimeout : 30000; // 连接超时 30秒 OpenAI.API.HttpClient.ResponseTimeout : 120000; // 响应超时 2分钟对于长生成任务生产环境建议对于关键业务实现请求队列和重试机制。将请求放入队列由后台线程处理失败后按策略重试。考虑使用Azure OpenAI等服务如果它们在你的区域有更好的网络延迟和稳定性。在UI上提供取消操作的按钮将取消标志传递给流式回调或终止异步任务。6.3 多线程与UI响应如前所述网络请求应该放在后台线程如TTask中执行避免阻塞主UI线程。但VCL/FMX的控件不是线程安全的。流式回调中的UI更新必须使用TThread.Synchronize或TThread.Queue。资源释放在后台线程中创建的对象如TChat如果要在主线程使用需要小心其生命周期。最好在后台线程解析完数据将需要的字符串结果传递回主线程显示。竞态条件如果用户快速连续点击发送按钮可能会触发多个并发请求。需要加锁或禁用按钮直到上一个请求完成。6.4 模型选择与版本管理OpenAI的模型在持续更新。gpt-3.5-turbo可能指向一个不断更新的默认版本。对于生产环境建议指定具体的模型版本号如gpt-3.5-turbo-0125以避免因默认版本升级导致的行为变化。定期查阅OpenAI的官方文档了解新模型如gpt-4-turbo的特性、价格和限制评估是否升级。6.5 安全性考量API密钥保护这是最高机密。绝对不要将密钥硬编码在客户端应用程序或提交到版本控制系统如Git。应该从配置文件、环境变量、加密存储或服务器端动态获取。用户输入净化虽然OpenAI模型有一定安全过滤但不要完全依赖它。对用户输入进行基本的检查和过滤防止提示注入攻击。输出内容审查对于将AI生成内容直接展示给其他用户的应用如社交平台必须建立自己的内容审核机制不能完全信任AI的输出。7. 进阶探索结合本地模型与混合架构DelphiOpenAI库的兼容性设计让你不仅可以连接云端AI还可以连接本地部署的、提供兼容API的模型服务比如Ollama。Ollama可以让你在本地机器上运行诸如Llama 2、CodeLlama、Mistral等开源大模型。它提供了一个与OpenAI API兼容的本地端点通常是http://localhost:11434/v1。// 连接到本地运行的Ollama服务 var LocalAI: IOpenAI; begin // Ollama通常不需要API密钥或者使用固定值 LocalAI : TOpenAI.Create(http://localhost:11434/v1, ollama); // 第二个参数在Ollama中有时可忽略或填任意值 // 查询Ollama中可用的模型 var Models : LocalAI.Model.List(); // 通常你会看到类似 llama2, codellama 这样的模型名 // 使用本地模型进行聊天 var Chat : LocalAI.Chat.Create( procedure(Params: TChatParams) begin Params.Model(llama2); // 使用Ollama中的模型名 Params.Messages([TChatMessageBuild.User(Hello)]); Params.MaxTokens(100); end); // ... 处理回复 end;这种混合架构非常有用开发与测试在没有网络或需要节省成本时使用本地模型进行功能开发和测试。数据隐私敏感数据无需离开本地环境。成本控制对于内部工具或低频应用本地模型可以避免云API调用费用。功能互补你可以让应用智能地选择路由简单任务用快速、免费的本地小模型复杂任务再调用强大的云端GPT-4。要实现智能路由你可以写一个简单的包装函数function AskAI(const Question: string; UseCloud: Boolean): string; var AI: IOpenAI; begin if UseCloud then AI : GetCloudOpenAIInstance // 你的云端实例 else AI : GetLocalOpenAIInstance; // 你的本地Ollama实例 var Chat : AI.Chat.Create(...); // ... 获取结果并返回 end;通过DelphiOpenAI这个统一的接口你可以用几乎相同的代码与云端和本地的AI服务对话这大大提升了开发的灵活性和应用的能力边界。