Delphi TNetHTTPClient 的分段读数据
书接上回,我自己电脑上的 AI 运行起来了。我想自己写程序去使用它提供的 API 而不是简单的用它的聊天网页。
向我自己电脑上的 AI 发送聊天问题,是 HTTP POST 命令,接收这个 POST 的返回字符串。 Ollama 发送回 HTTP POST 的聊天应答,是流式的,大概是一条一条地发送,每一条是一条 JSON; 如果使用 TIdHTTP 去执行 POST,因为 Indy 控件是阻塞模式,必须等到全部接收完毕,程序才能获得来自 Ollama 的聊天应答。 如果想要收到一条就程序马上处理一条,这里可以使用 TNetHTTPClient;
如何获得分段的数据
TNetHTTPClient.OnReceiveDataEx 这个事件方法,里面有当前分段数据的指针和长度。由此可以获得当前收到的这一条数据,而不必等到全部数据收完。
procedure TDmHttpClient.NetHTTPClient1ReceiveDataEx(const Sender: TObject;
AContentLength, AReadCount: Int64; AChunk: Pointer; AChunkLength: Cardinal;
var AAbort: Boolean);
var
B: TBytes;
i: Integer;
S: string;
begin
if not Assigned(AChunk) then Exit;
if AChunkLength = 0 then Exit;
SetLength(B, AChunkLength);
Move(AChunk^, B[0], AChunkLength);
//S := StringOf(B);
S := TEncoding.UTF8.GetString(B);
FResponseChunk := FResponseChunk + S;
//每一条结束,有没有结束符号比如回车符?
for i := 0 to Length(B) -1 do
begin
if ((Char(B[i]) = #13) or (Char(B[i]) = #10)) then
begin
//检测到有回车符号
if Char(B[i]) = #13 then
Self.Log('收到回车符号');
if Char(B[i]) = #10 then
Self.Log('收到换行符号');
//通过事件,把收到的一条 JSON 送去主界面
if Assigned(FOnChunk) then FOnChunk(Self);
FResponseChunk := '';
end;
end;
end;
发送聊天请求的代码:
procedure TDmHttpClient.TestAPIasync(const APrompt: string; var Response: string);
var
S: string;
URL: string;
Prompt: UTF8String;
AResponseContent, SrcContent: TStringStream;
begin
{
使用 NetHTTPClient 来处理 AI API 的返回。
}
URL := 'http://localhost:11434/api/generate';
S := '{"model": "gemma3:12b", "prompt": "Why is the sky blue?"}';
S := '{"model": "gemma3:12b", "prompt": "<#Prompt>"}';
S := S.Replace('<#Prompt>', APrompt);
Prompt := UTF8Encode(S);
SrcContent := TStringStream.Create(Prompt, TEncoding.UTF8);
AResponseContent := TStringStream.Create;
try
SrcContent.Position := 0;
NetHTTPClient1.Post(URL, SrcContent, AResponseContent);
Response := AResponseContent.DataString;
finally
SrcContent.Free;
AResponseContent.Free;
end;
end;
备注: 上述代码中的 Prompt 就是你要发送给 AI 的聊天内容;
上述代码,其实并不是真正的异步(线程里面的操作),而是在主线程里面的操作。因此,每一次事件触发,如果要更新 UI,需要加上 ProcessMessages
所以,在主界面里面,我发送一条聊天文字给 AI:
procedure TFmMain.Button4Click(Sender: TObject);
var
S: string;
begin
DmHttpClient.OnChunk := DoOnChunk;
DmHttpClient.TestAPIasync(Edit1.Text, S);
end;
在我的事件方法里面,也就是上面代码绑定的 DoOnChunk 方法里面:
procedure TFmMain.DoOnChunk(Sender: TObject);
var
S: string;
begin
Memo1.Lines.Add(DmHttpClient.ResponseChunk);
try
S := AnalizeOneJSON(DmHttpClient.ResponseChunk);
Self.FTempStr := Self.FTempStr + S;
Memo2.Lines.Text := Self.FTempStr;
except
end;
Application.ProcessMessages;
end;
上面是把收到的来自 AI 的 JSON 解析出具体内容,然后显示到 Memo2 里面。程序运行,确实能看到 AI 的回复在一个字一个字的往外蹦,和使用公网的 AI 服务比如 DeepSeek.com 的聊天页面类似。
解析 JSON 的代码这里就不贴了。解析方法很简单,因为它每一条数据的 JSON 格式是相同的,我针对这个 JSON 格式,构造一个 Delphi 的类,然后直接使用 TJson.JsonToObject 就能把 JSON 字符串转换为 Delphi 的一个对象,然后我直接读对象的属性就好了,代码非常简单。
本文的重点
本文的重点是,在 Delphi 程序里面,使用 HTTP 去访问一个 WEB 服务器,传统的 TIdHTTP 因为是阻塞式,就会直到通讯结束才返回,用户等待时间太长就不够好玩。
要想看到一个字一个字地往外蹦,就需要使用 TNetHTTPClient ,本文主要是讲如何使用 TNetHTTPClient 来获得通讯中的内容,而无需等到通讯结束。但这里它不是异步模式,而仅仅是通讯中的中断模式。中断体现在事件里面。
结论
网上很多开源的 AI Agent 比如 OpenClaw,功能很强大。其实我们完全可以用 Delphi 写代码来实现类似的功能。
3186

被折叠的 条评论
为什么被折叠?



