从 Delphi 调用 LLM
从 VCL、FMX 或控制台应用发送一段提示词并拿到回答,模型可以是 OpenAI 或 Anthropic Claude 这样的托管模型,也可以是通过 Ollama 跑在你自己机器上的模型。本页带你从一个空白窗体走到一次能跑通的调用,然后进入每个项目随后都会遇到的三件事:流式输出、工具调用,以及托管模型与本地模型之间的取舍。
从 VCL、FMX 或控制台应用发送一段提示词并拿到回答,模型可以是 OpenAI 或 Anthropic Claude 这样的托管模型,也可以是通过 Ollama 跑在你自己机器上的模型。本页带你从一个空白窗体走到一次能跑通的调用,然后进入每个项目随后都会遇到的三件事:流式输出、工具调用,以及托管模型与本地模型之间的取舍。
既有一个与厂商无关的聊天组件,也有针对每家厂商的专用 REST 客户端。两者都在同一个库里,因此你可以先用其中一个,需要时再切到另一个,不用更换工程。
设置 Provider、一个 API 密钥和一个模型,然后调用 Chat。组件会构造对应厂商的 JSON,维护会话历史,并返回助手的文本。从 OpenAI 切换到 Claude,或者切换到本地的 Ollama 模型,只是一次赋值。它由单元 sgcAI_Chat 中的 TsgcAI_Chat 实现,在组件面板上注册为 TsgcAIChat。
TsgcHTTP_API_OpenAI、TsgcHTTP_API_Anthropic 和 TsgcHTTP_API_Ollama 完整暴露各家厂商的 API,包括任何中立层都覆盖不了的部分:视觉输入、文档输入、扩展思考、批量任务、文件、向量嵌入、图像生成和语音转写。当你需要某个特定端点时就用它们。
AI 与 LLM 客户端属于 sgcWebSockets 的 Enterprise 功能,Standard 和 Professional 都不包含,你也可以单独购买 sgcAI 包。
平台在这里很关键。三个 REST 客户端可在 Windows、macOS、Linux、iOS 和 Android 上编译。TsgcAIChat 不行,它只为 Windows 编译,因此在 Linux 服务、macOS 构建或移动端目标上,你要直接调用 REST 客户端。本页的示例写法保证两条路线都能用。
放下组件,设置密钥和模型,发送提示词。选择你打算开始使用的厂商对应的标签页。最后一个标签页完全不需要 API 密钥,因为模型跑在你自己的机器上。
uses
Classes, SysUtils,
// sgc
sgcAI_Chat;
procedure TfrmMain.btnAskClick(Sender: TObject);
var
oChat: TsgcAI_Chat;
begin
oChat := TsgcAI_Chat.Create(nil);
try
oChat.Provider := aicpOpenAI;
oChat.ChatOptions.ApiKey := GetApiKey;
oChat.ChatOptions.Model := 'gpt-4o-mini';
oChat.ChatOptions.MaxTokens := 1024;
oChat.SystemMessage := 'You are a concise assistant inside a Delphi ERP.';
memoAnswer.Lines.Text := oChat.Chat(memoPrompt.Lines.Text);
finally
oChat.Free;
end;
end;
更换厂商只需要改一行。Provider 接受 aicpOpenAI、aicpAnthropic、aicpGemini、aicpDeepSeek、aicpOllama、aicpGrok 和 aicpMistral。代码里的其他部分都原封不动。
仅限 Windows。TsgcAI_Chat 及其面板组件 TsgcAIChat 只为 Windows 编译,包括 Win32 和 Win64。在 macOS、Linux、iOS 和 Android 上该单元根本不参与编译,因此如果你的目标是 Linux 服务或移动应用,请使用其他三个标签页中的厂商 REST 客户端,它们没有平台限制。
uses
Classes, SysUtils,
// sgc
sgcHTTP_API_OpenAI;
var
oOpenAI: TsgcHTTP_API_OpenAI;
begin
oOpenAI := TsgcHTTP_API_OpenAI.Create(nil);
try
oOpenAI.OpenAIOptions.ApiKey := GetApiKey;
// Shortcut: model plus one user message, raw JSON back
memoAnswer.Lines.Text := oOpenAI._CreateChatCompletion(
'gpt-4o', 'Say hello');
finally
oOpenAI.Free;
end;
end;
带下划线前缀的方法是返回原始响应体的字符串快捷方式。如果你想要解析好的对象,就构造一个 TsgcOpenAIClass_Request_ChatCompletion 并调用 CreateChatCompletion,本页后面有示例。
uses
Classes, SysUtils,
// sgc
sgcHTTP_API_Anthropic;
var
oAnthropic: TsgcHTTP_API_Anthropic;
begin
oAnthropic := TsgcHTTP_API_Anthropic.Create(nil);
try
oAnthropic.AnthropicOptions.ApiKey := GetApiKey;
oAnthropic.AnthropicOptions.AnthropicVersion := '2023-06-01';
// Model, prompt, max tokens
memoAnswer.Lines.Text := oAnthropic._CreateMessage(
'claude-sonnet-4-20250514',
'Summarise RFC 6455 in three bullet points.', 1024);
finally
oAnthropic.Free;
end;
end;
Claude 需要 API 版本请求头,所以要在设置密钥的同时设置 AnthropicVersion。_CreateMessageWithSystem 用于添加系统提示词,_CountTokens 则在发送之前先算出一段提示词的开销。
uses
Classes, SysUtils,
// sgc
sgcHTTP_API_Ollama;
var
oOllama: TsgcHTTP_API_Ollama;
begin
oOllama := TsgcHTTP_API_Ollama.Create(nil);
try
// Local server, no API key required
oOllama.OllamaOptions.Host := 'http://localhost:11434';
// Which models are pulled on this machine?
memoModels.Lines.Text := oOllama._GetTags;
memoAnswer.Lines.Text := oOllama._CreateMessage(
'llama3', 'Summarise this invoice in one line.');
finally
oOllama.Free;
end;
end;
Host 默认为 http://localhost:11434,所以在默认安装下你可以不管它。_PullModel 下载模型,_GetTags 列出磁盘上已有的模型,_ShowModel 读取模型详情。
一次性调用会一直阻塞到模型生成完毕,遇到长回答时会让人以为程序卡死了。流式输出分片返回回复,模型还在思考时文字就已经出现在备注框里。这一块是大多数人卡住的地方,所以这里把两个层面都讲清楚。
调用 ChatStream 而不是 Chat,并处理 OnChatStream。组件会向厂商请求流式响应,解析每一个 Server-Sent Event,然后把 aChunk 交给你,那就是新增的文本,别的什么都没有。你把它追加上去,整个实现就完成了。
每家厂商推送的 JSON 结构都不一样。OpenAI、DeepSeek、Ollama、Grok 和 Mistral 把文本放在 choices[0].delta.content,Claude 放在 delta.text,Gemini 则埋得更深,在 candidates 里面。TsgcAIChat 已经知道你选定的厂商对应哪一种,因此你的处理程序永远看不到 JSON。
在处理程序里把 Cancel 设为 True,就能中止一个失控的回答。请求会在那一刻被放弃,不再有新的分片到达。流结束时,拼接好的文本会加入历史并由 ChatStream 返回,同时 OnChatMessage 会带着完整回答触发一次。
procedure TfrmMain.btnStreamClick(Sender: TObject);
begin
FChat.Provider := aicpAnthropic;
FChat.ChatOptions.ApiKey := GetApiKey;
FChat.ChatOptions.Model := 'claude-sonnet-4-20250514';
FChat.OnChatStream := OnChatStream;
FChat.OnChatError := OnChatError;
memoAnswer.Lines.Clear;
FChat.ChatStream(memoPrompt.Lines.Text);
end;
procedure TfrmMain.OnChatStream(Sender: TObject;
const aChunk: string; var Cancel: Boolean);
begin
memoAnswer.Text := memoAnswer.Text + aChunk;
Cancel := FUserPressedStop;
end;
procedure TfrmMain.OnChatError(Sender: TObject;
const aError: string);
begin
memoAnswer.Lines.Add('ERROR: ' + aError);
end;
REST 客户端同样支持流式。给 OnHTTPAPISSE 赋值并调用流式快捷方法:Claude 和 Ollama 上是 _CreateMessageStream,OpenAI 上则是把 TsgcOpenAIClass_Request_ChatCompletion 的 Stream 设为 True。事件会给你 aEvent,也就是 Server-Sent Event 的名称,以及 aData,也就是该事件的有效载荷,与厂商发出的内容完全一致。
在这个层面上,JSON 由你自己解析,而这正是重点:你能看到工具调用增量、停止原因、用量记录,以及厂商放到线路上的任何其他内容。网络的部分读取已经替你重新拼装好,因此一个被拆到两次 TCP 读取里的事件仍然会完整到达,而 Ollama 的换行分隔 JSON 也通过同一个事件送达。
终止标记因厂商而异。OpenAI 以字面量 [DONE] 结束,Claude 则以名为 message_stop 的事件结束。如果你的代码同时面向两家,就把两种都处理掉。
procedure TfrmMain.FormCreate(Sender: TObject);
begin
FAnthropic := TsgcHTTP_API_Anthropic.Create(nil);
FAnthropic.AnthropicOptions.ApiKey := GetApiKey;
FAnthropic.AnthropicOptions.AnthropicVersion := '2023-06-01';
FAnthropic.OnHTTPAPISSE := HandleSSE;
FAnthropic.OnHTTPAPIException := HandleException;
end;
procedure TfrmMain.btnStreamClick(Sender: TObject);
begin
memoRaw.Lines.Clear;
FAnthropic._CreateMessageStream(
'claude-sonnet-4-20250514',
'Write a haiku about Object Pascal.', 1024);
end;
procedure TfrmMain.HandleSSE(Sender: TObject;
const aEvent, aData: string; var Cancel: Boolean);
begin
if (aEvent = 'message_stop') or (aData = '[DONE]') then
Exit;
memoRaw.Lines.Add(aEvent + ': ' + aData);
end;
procedure TfrmMain.HandleException(Sender: TObject;
E: Exception);
begin
memoRaw.Lines.Add('ERROR: ' + E.Message);
end;
线程。Chat 和 ChatStream 是同步的,所以直接在按钮点击里调用它们,会让窗体在整个请求期间失去响应。在 Delphi 2010 及以后的版本上,ChatAsync 会把调用放到工作线程上执行,并返回一个 IsgcFuture<string>。用 ThenProc 链接结果,用 OnError 链接失败,调用 Cancel 可以放弃一个进行中的请求。ThenProc 回调在主线程上派发,因此你可以直接在里面操作界面。
工具调用,也叫函数调用,是模型请求你的应用去查询某些内容或执行某个动作的方式。你用 JSON Schema 描述这个函数,模型回复它想要的参数,你执行 Pascal 代码并把结果送回去。业务线应用里每一个真正好用的助手,背后都是这套机制。
构造一个 TsgcAnthropicClass_Request_Messages,挂上一个或多个 TsgcAnthropicClass_Request_Tool 条目,然后调用 CreateMessage。每个工具带有 Name、Description 和 InputSchema,后者就是它参数的 JSON Schema。
回复是一个 TsgcAnthropicClass_Response_Messages,它的 Content 是一个块数组。ContentType 等于 'tool_use' 的块携带工具的 Name、位于 Input 中的参数,以及一个 Id。执行你的函数,然后发送一条后续消息,其中包含一个 TsgcAnthropicClass_Request_Content_Block,把 ContentType 设为 'tool_result',带上同一个 ToolUseId,并把你的答案放在 Content 里。调用失败时请设置 IsError,好让模型能够纠正而不是靠猜。
注意所有权:Anthropic 请求并不拥有你挂上去的消息和工具,所以要像示例里那样自己释放它们。
var
oRequest: TsgcAnthropicClass_Request_Messages;
oMessage: TsgcAnthropicClass_Request_Message;
oTool: TsgcAnthropicClass_Request_Tool;
oMessages: TsgcAnthropicArray_Request_Messages;
oTools: TsgcAnthropicArray_Request_Tools;
oResponse: TsgcAnthropicClass_Response_Messages;
i: Integer;
begin
oRequest := TsgcAnthropicClass_Request_Messages.Create;
try
oRequest.Model := 'claude-sonnet-4-20250514';
oRequest.MaxTokens := 4096;
oMessage := TsgcAnthropicClass_Request_Message.Create;
oMessage.Role := 'user';
oMessage.Content := 'What is the stock of SKU 8841?';
SetLength(oMessages, 1);
oMessages[0] := oMessage;
oRequest.Messages := oMessages;
oTool := TsgcAnthropicClass_Request_Tool.Create;
oTool.Name := 'get_stock';
oTool.Description := 'Read the on-hand stock for a SKU';
oTool.InputSchema :=
'{"type":"object","properties":{"sku":{"type":"string",' +
'"description":"The product code"}},"required":["sku"]}';
SetLength(oTools, 1);
oTools[0] := oTool;
oRequest.Tools := oTools;
oResponse := FAnthropic.CreateMessage(oRequest);
try
for i := 0 to Length(oResponse.Content) - 1 do
if oResponse.Content[i].ContentType = 'tool_use' then
// .Name is the tool, .Input the JSON arguments,
// .Id the value to echo back as ToolUseId
RunTool(oResponse.Content[i].Name,
oResponse.Content[i].Input, oResponse.Content[i].Id)
else if oResponse.Content[i].ContentType = 'text' then
memoAnswer.Lines.Add(oResponse.Content[i].Text);
finally
oResponse.Free;
end;
finally
sgcFree(oMessage);
sgcFree(oTool);
sgcFree(oRequest);
end;
end;
思路相同,形态不同。填好一个 TsgcOpenAIClass_Request_ChatCompletion,给 Messages 数组赋值,把工具定义以 JSON 数组的形式放进 Tools,必要时再用 ToolChoice 引导模型。ParallelToolCalls 控制模型是否可以一次请求多个工具,而当你需要解析回答时,ResponseFormat 可以把回答固定为 JSON。
CreateChatCompletion 返回一个已解析的 TsgcOpenAIClass_Response_ChatCompletion。从 Choices[0]._Message.Content 读取回答,从 Choices[0]._Message.ToolCalls 读取它请求的调用,从 Choices[0].FinishReason 读取模型停止的原因,从 Usage.PromptTokens、Usage.CompletionTokens 和 Usage.TotalTokens 读取开销。与 Anthropic 请求不同,这个请求拥有你挂上去的消息对象,并会随自身一起释放它们。
较早的 Functions 和 FunctionCall 属性依然保留,供按照 OpenAI 最初的函数调用形态编写的代码使用。
var
oRequest: TsgcOpenAIClass_Request_ChatCompletion;
oResponse: TsgcOpenAIClass_Response_ChatCompletion;
oSystem, oUser: TsgcOpenAIClass_Request_Completion_Message;
oMessages: TsgcOpenAIArray_Request_Completion_Messages;
begin
oRequest := TsgcOpenAIClass_Request_ChatCompletion.Create;
try
oRequest.Model := 'gpt-4o';
oRequest.MaxTokens := 1024;
oRequest.Temperature := 0.2;
oSystem := TsgcOpenAIClass_Request_Completion_Message.Create;
oSystem.Role := 'system';
oSystem.Content := 'You are a warehouse assistant.';
oUser := TsgcOpenAIClass_Request_Completion_Message.Create;
oUser.Role := 'user';
oUser.Content := 'What is the stock of SKU 8841?';
SetLength(oMessages, 2);
oMessages[0] := oSystem;
oMessages[1] := oUser;
oRequest.Messages := oMessages;
// Tool definitions as a JSON array
oRequest.Tools :=
'[{"type":"function","function":{"name":"get_stock",' +
'"description":"Read the on-hand stock for a SKU",' +
'"parameters":{"type":"object","properties":' +
'{"sku":{"type":"string"}},"required":["sku"]}}}]';
oRequest.ToolChoice := 'auto';
oResponse := FOpenAI.CreateChatCompletion(oRequest);
try
if Length(oResponse.Choices) > 0 then
begin
memoAnswer.Lines.Text := oResponse.Choices[0]._Message.Content;
memoTools.Lines.Text := oResponse.Choices[0]._Message.ToolCalls;
lblStop.Caption := oResponse.Choices[0].FinishReason;
end;
lblTokens.Caption := IntToStr(oResponse.Usage.TotalTokens);
finally
oResponse.Free;
end;
finally
// frees the attached Messages too
oRequest.Free;
end;
end;
如果你的提示词里包含客户记录、医疗数据、合同,或者任何受数据处理协议约束的内容,那这就不是一个性能问题,而是一个合规问题。下面是对比。
| 托管,OpenAI 或 Claude | 本地,Ollama | |
|---|---|---|
| 提示词去了哪里 | 发往厂商,走 HTTPS,受其条款约束 | 哪里都不去。请求发往 http://localhost:11434 |
| 凭据 | 一个 API 密钥,必须避免进入源码管理和二进制文件 | 默认不需要。OllamaOptions.ApiKey 供代理或远程实例使用 |
| 回答质量 | 当前可用的最强模型 | 不错且在进步,在高难度推理上明显落后于前沿水平 |
| 成本 | 按 token 计费,长期如此。请留意 Usage.TotalTokens |
一次性的硬件投入。一个好用的模型需要大量内存或一块 GPU |
| 延迟 | 一次网络往返,繁忙时段还要加上厂商侧的排队 | 不走网络。速度就是你机器的速度 |
| 离线与物理隔离环境 | 否 | 是 |
| 速率限制与服务中断 | 由厂商侧决定。使用 RetryOptions 并遵守 Retry-After |
只受你自己的容量限制 |
| 组件 | TsgcHTTP_API_OpenAI, TsgcHTTP_API_Anthropic |
TsgcHTTP_API_Ollama |
常见的答案是两者都要。由于 TsgcAIChat 把所有厂商放在同一套 API 之后,你可以在运行时按数据分级来路由:凡是涉及客户记录的走本地,其余的走托管。切换开关就是 Provider,当厂商是 Ollama 时再加上 ChatOptions.BaseUrl。
if aContainsPersonalData then
begin
FChat.Provider := aicpOllama;
FChat.ChatOptions.BaseUrl := 'http://localhost:11434';
FChat.ChatOptions.Model := 'llama3';
end
else
begin
FChat.Provider := aicpOpenAI;
FChat.ChatOptions.ApiKey := GetApiKey;
FChat.ChatOptions.Model := 'gpt-4o-mini';
end;
memoAnswer.Lines.Text := FChat.Chat(memoPrompt.Lines.Text);
在你桌上跑得通的演示,和一个能扛住速率限制、慢速模型以及一句“它突然就不动了”的支持工单的客户端,不是一回事。
每个客户端都带有一个 RetryOptions 配置块:Enabled、Retries、Wait、Multiplier、MaxInterval、Jitter 和 HonorRetryAfter。打开它之后,瞬时故障会以指数退避方式重试,而不是直接抛出异常。HonorRetryAfter 让客户端遵守厂商的 Retry-After 响应头,而不是靠猜。
需要调大的是 HttpOptions.ReadTimeout。一次长时间生成,尤其是在本地模型上,可能超过默认的 HTTP 读取超时,让一个本来进行得很顺利的回答中途失败。
LogOptions.Enabled 加上 LogOptions.FileName 会把流量写入文件。当一段提示词在生产环境中表现不同时,这是查看你的代码究竟发出了什么 JSON 的最快方式。
TsgcAIChat 会保留对话内容,并在下一次调用时重放,这正是追问能生效的原因。用 MaxHistoryMessages 给它设上限,避免长会话让提示词,进而让账单无限增长。ClearHistory 用于重新开始,GetHistory 则公开 Count 和逐条消息。
把 OpenAIOptions.Provider 设为 oapvAzure,并填写 AzureOptions.ResourceName、AzureOptions.DeploymentId 和 AzureOptions.APIVersion。代码的其余部分不变,当采购部门坚持流量必须留在你自己的 Azure 租户内时,这一点很重要。
OnHTTPAPIException 暴露 REST 客户端的失败,OnChatError 对 TsgcAIChat 起同样的作用。HTTP 错误以 EsgcHTTPAPIProtocolException 的形式到达,它依然派生自 EIdHTTPProtocolException,并额外携带响应头。客户端上还提供 CircuitBreaker 和 RateLimit,供你自己发起的调用使用。
一个聊天框只是开始。下面是这项工作通常会走的四个方向,而每一个方向库里都已经有了。
用 TsgcAIOpenAIEmbeddings 把你的文档转成向量,存进 TsgcAIDatabaseVectorFile 或 TsgcAIDatabaseVectorPinecone,再检索出最相近的段落放进提示词。这就是检索增强生成,也是你阻止模型凭空编造与你业务有关的答案的方法。
TsgcAIOpenAIChatBot 把录音、语音转写、聊天调用和语音合成串在一个组件里,让用户可以和你的应用进行语音对话。TsgcAIOpenAITranslator 则以同样的方式实现实时翻译。
Model Context Protocol 是助手发现并调用工具的方式。TsgcWSServer_API_MCP 把你的 Delphi 应用变成一个 MCP 服务器,供 Claude 和其他客户端驱动,而 TsgcWSAPI_Client_MCP 则让你的代码去消费其他服务器。它们在组件面板上显示为 TsgcWSAPIServer_MCP 和 TsgcWSAPIClient_MCP。
值得一提:与聊天组件不同,MCP 单元并不限于 Windows,因此用 Delphi 写的 MCP 服务器可以跑在 Linux 上。
组件参考文档记录了每一个属性和事件。可直接运行的示例工程随库一起发布,位于 Demos\AI 目录下。
参考文档,OpenAI 客户端
TsgcHTTP_API_OpenAI 上的每一个方法、选项和事件。
|
打开 | |
参考文档,Anthropic 客户端
TsgcHTTP_API_Anthropic 上的消息、工具、视觉、批量任务和 token 计数。
|
打开 | |
| 教程,从 Delphi 使用 Claude Anthropic 客户端的长篇完整讲解,从头到尾。 | 打开 | |
| 教程,用 Ollama 运行本地模型 拉取一个模型,把客户端指向 localhost,然后离线运行。 | 打开 | |
| 教程,函数调用 一步步把模型接到你自己的 Pascal 函数上。 | 打开 | |
| 用户手册(PDF) 覆盖库中每一个组件的完整手册。 | 打开 |
延伸阅读:从 Delphi 使用 OpenAI 客户端、构建 AI 聊天机器人、构建 AI 智能体,以及 MCP 客户端。相关组件的产品页是 Delphi OpenAI 客户端和 Anthropic API。
本页属于 Delphi 使用场景之一,每一篇都把一个任务从头做到尾。目前还有 用 OAuth2 和 PKCE 完成用户登录,以及 用 WebRTC 让两个应用点对点连接。
sgcAI_Chat 创建一个 TsgcAI_Chat,把 Provider 设为你想要的厂商,设置 ChatOptions.ApiKey 和 ChatOptions.Model,然后调用 Chat('your prompt'),它会以字符串形式返回答案。如果你更想直接对接某一家厂商的 API,就使用 TsgcHTTP_API_OpenAI、TsgcHTTP_API_Anthropic 或 TsgcHTTP_API_Ollama,并调用 _CreateChatCompletion 或 _CreateMessage。两种做法都随 sgcWebSockets 和独立的 sgcAI 包一起发布,并且从 Delphi 7 到 RAD Studio 13 都可用。
ChatStream 而不是 Chat,并处理 OnChatStream,它会带着 aChunk 触发,也就是每一个增量解码后的文本,以及一个 Cancel 标志,你可以设置它来提前停止。在 REST 客户端层面,给 OnHTTPAPISSE 赋值,并在 Claude 和 Ollama 客户端上调用 _CreateMessageStream,或者把 TsgcOpenAIClass_Request_ChatCompletion 的 Stream 设为 True。该事件会给你原始的 Server-Sent Event 名称和数据,并且网络的部分读取已经重新拼装好。
TsgcHTTP_API_Ollama 指向它。OllamaOptions.Host 已经默认为 http://localhost:11434,所以在默认安装下你只需要选一个模型并调用 _CreateMessage。没有任何数据离开这台机器,不需要 API 密钥,应用也能离线工作。通过 TsgcAIChat 访问同一个服务器,只要把 Provider 设为 aicpOllama,如果服务器不在 localhost 上,再设置 ChatOptions.BaseUrl。
Name、Description 和 InputSchema 的 TsgcAnthropicClass_Request_Tool 加入 TsgcAnthropicClass_Request_Messages 的 Tools 数组。随后回复中会包含一个 ContentType 为 tool_use 的内容块,携带 Name、Input 和 Id,你则用一个引用相同 ToolUseId 的 tool_result 块作答。在 OpenAI 上,把定义放进 TsgcOpenAIClass_Request_ChatCompletion 的 Tools 属性,并从 Choices[0]._Message.ToolCalls 读取它请求的调用。
TsgcHTTP_API_OpenAI、TsgcHTTP_API_Anthropic 和 TsgcHTTP_API_Ollama 可为 Windows、macOS、Linux、iOS 和 Android 编译,而 TsgcAIChat 只为 Windows 编译,包括 Win32 和 Win64。MCP 客户端和服务器同样不限于 Windows。
ChatOptions.ApiKey,或者赋给诸如 OpenAIOptions.ApiKey 这样的厂商选项。用量由该厂商按你的密钥计费,你可以通过 Usage.PromptTokens、Usage.CompletionTokens 和 Usage.TotalTokens 逐次响应地追踪。Ollama 不需要密钥,因为模型跑在本地。
Chat 和 ChatStream 是同步的。在 Delphi 2010 及以后的版本上,改为调用 ChatAsync:它会把请求放到工作线程上执行,并返回一个字符串的 IsgcFuture,于是你可以链接 ThenProc 来接收答案,链接 OnError 来接收异常,调用 Cancel 来放弃进行中的请求。ThenProc 回调在主线程上派发,因此从中更新界面是安全的。另外请调大 HttpOptions.ReadTimeout,因为一次长时间生成可能超过默认的读取超时。
OpenAIOptions.Provider 设为 oapvAzure,并填写 AzureOptions.ResourceName、AzureOptions.DeploymentId 和 AzureOptions.APIVersion。客户端随后会指向你的 Azure 部署,而你已经写好的调用完全不用改。