用Delphi写和本机大模型聊天的流式HTTP程序

SmallThinker-3B-Preview

SmallThinker-3B-Preview

文本生成
Ollama
Qwen

SmallThinker-3B-preview,这是一个从 Qwen2.5-3b-Instruct 模型微调而来的新模型。

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 写代码来实现类似的功能。

您可能感兴趣的与本文相关的镜像

SmallThinker-3B-Preview

SmallThinker-3B-Preview

文本生成
Ollama
Qwen

SmallThinker-3B-preview,这是一个从 Qwen2.5-3b-Instruct 模型微调而来的新模型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值