Ó théama an iris go cleachtas tionscadail
Leathanaigh seirbhíse agus teicniúla oiriúnacha don alt
Cén fáth nach bhfuil „ChatGPT API le Delphi FMX/VCL“ sa chleachtas ach POST amháin
Má tá tú ag iarraidh an ChatGPT API le Delphi FMX/VCL a nascadh, téann tú go tapa chuig HTTP-POST simplí. I n-imshaoil réadúil bogearraí gnó, áfach, tagann teipeanna i bhfeidhm trí thrí threo: (1) caithfidh Timeouts agus Retries a bheith deterministiciúil, toisc go mbíonn úsáideoirí ag teacht trasna ar chomhéadan úsáide a chuidíonn „greamaithe“, (2) is minic a bhíonn Streaming (Server-Sent Events, ar a dtugtar go gairid SSE) oiriúnach don UX, ach tá sé tapa le bheith leochaileach i threading Delphi, agus (3) níl JSON ach „réad amháin“: caithfear earráidí, fadhbanna cainníochta, réimsí folmha nó foirmeacha freagra beagáinín éagsúil a láimhseáil go láidir.
Scaipeann an slisín foinse thíos cur chuige a oibríonn go comhionann i FMX agus VCL: cliant neamhspleách agus inúsáidte le haghaidh tástála a oibríonn go roghnach i mód nicht-streaming nó streaming, a láimhseálann nuashonruithe UI go soiléir trí shioncrónú leis an Main-Thread agus a dhéanann logáil léiritheach ar earráidí. Ina theannta sin tá sé tógtha ionas gur féidir é a ionchuimsiú i struchtúir sraithe atá fásaithe (m.sh. „API-Client“ sa sraith ionchuimsithe, fanann an UI tanaí).
Sceitse ailtireachta: UI a dhícheangal, cliant a choinneáil tástáilte
I dtionscadail Delphi le stair fhada is minic a aimsítear „HTTP im ButtonClick“. Oibríonn sin go dtí an chéad eachtra. Moltar cliant beag le:
- Cumraíocht: Base-URL, API-Key, Modell, Timeouts.
- Sraith iompair: HTTP-Request/Response, Retry, Timeout, Proxy/SSL-Optionen (je nach Betrieb).
- Parsálaí: JSON-Decoding, Fehlerobjekte, Ergebnisextraktion.
- Hookanna UI: Callback für Token-/Text-Streaming, aber ohne harte Abhängigkeit auf VCL/FMX Controls.
Ar an gcaoi sin is féidir an comhtháthú i mbogearraí corparáideacha shonraithe a reáchtáil go glan: is féidir an cliant a athúsáid i Services, Desktop-Clients, uirlisí riarthóra nó i test-harnesses.
Source-Schnipsel: Delphi-Client mit SSE-Streaming, Timeout/Retry und robustem JSON
Úsáideann an cód THTTPClient (System.Net.HttpClient) agus parsaítear go cúramach, ach go híostach, le System.JSON. Maidir le SSE léitear líne ar líne agus freagraítear ar „data: …“. Ní „WebSocket“ é seo, ach sruth freagra HTTP a sholáthraíonn línte téacs leanúnacha. Tábhachtach: léimid sa Worker-Thread agus marshallaimid nuashonruithe UI chuig an Main Thread.
unit Net-Base.OpenAI.ChatClient;
interface
uses
System.SysUtils, System.Classes, System.Net.URLClient, System.Net.HttpClient,
System.Net.HttpClientComponent, System.JSON, System.Threading,
System.SyncObjs;
type
EChatApiError = class(Exception)
private
FHttpStatus: Integer;
FResponseText: string;
public
constructor Create(const Msg: string; AHttpStatus: Integer; const AResponseText: string);
property HttpStatus: Integer read FHttpStatus;
property ResponseText: string read FResponseText;
end;
TChatStreamEvent = reference to procedure(const AChunkText: string; AIsFinal: Boolean);
TChatCompletionOptions = record
Model: string;
Temperature: Double;
MaxTokens: Integer;
constructor Create(const AModel: string; ATemperature: Double = 0.2; AMaxTokens: Integer = 512);
end;
TOpenAIChatClient = class
private
FBaseUrl: string;
FApiKey: string;
FConnectTimeoutMs: Integer;
FResponseTimeoutMs: Integer;
function BuildChatRequestBody(const AUserPrompt: string; const AOptions: TChatCompletionOptions;
AStream: Boolean): TJSONObject;
function ExtractTextFromNonStreamingResponse(const AJsonText: string): string;
function TryExtractErrorMessage(const AJsonText: string; out AMessage: string): Boolean;
procedure ApplyAuthHeaders(ARequest: IHTTPRequest);
function ExecuteWithRetry(const ADoRequest: TFunc): IHTTPResponse;
public
constructor Create(const ABaseUrl, AApiKey: string);
property ConnectTimeoutMs: Integer read FConnectTimeoutMs write FConnectTimeoutMs;
property ResponseTimeoutMs: Integer read FResponseTimeoutMs write FResponseTimeoutMs;
function ChatOnce(const AUserPrompt: string; const AOptions: TChatCompletionOptions): string;
procedure ChatStream(const AUserPrompt: string; const AOptions: TChatCompletionOptions;
const AOnEvent: TChatStreamEvent);
end;
implementation
{ EChatApiError }
constructor EChatApiError.Create(const Msg: string; AHttpStatus: Integer; const AResponseText: string);
begin
inherited Create(Msg);
FHttpStatus := AHttpStatus;
FResponseText := AResponseText;
end;
{ TChatCompletionOptions }
constructor TChatCompletionOptions.Create(const AModel: string; ATemperature: Double; AMaxTokens: Integer);
begin
Model := AModel;
Temperature := ATemperature;
MaxTokens := AMaxTokens;
end;
{ TOpenAIChatClient }
constructor TOpenAIChatClient.Create(const ABaseUrl, AApiKey: string);
begin
inherited Create;
FBaseUrl := ABaseUrl.TrimRight([‚/‘]);
FApiKey := AApiKey;
FConnectTimeoutMs := 8000;
FResponseTimeoutMs := 60000;
end;
procedure TOpenAIChatClient.ApplyAuthHeaders(ARequest: IHTTPRequest);
begin
// Bearer Token: anseo an API-Key mar ‚Authorization: Bearer …‘.
// I dtimpeallachtaí corparáideacha, cinntigh nach gcuirtear eochracha isteach sna loganna.
ARequest.AddHeader(‚Authorization‘, ‚Bearer ‚ + FApiKey);
ARequest.AddHeader(‚Content-Type‘, ‚application/json‘);
ARequest.AddHeader(‚Accept‘, ‚application/json‘);
end;
function TOpenAIChatClient.BuildChatRequestBody(const AUserPrompt: string;
const AOptions: TChatCompletionOptions; AStream: Boolean): TJSONObject;
var
Msgs: TJSONArray;
Msg: TJSONObject;
begin
Result := TJSONObject.Create;
Result.AddPair(‚model‘, AOptions.Model);
Result.AddPair(‚temperature‘, TJSONNumber.Create(AOptions.Temperature));
Result.AddPair(‚max_tokens‘, TJSONNumber.Create(AOptions.MaxTokens));
Result.AddPair(’stream‘, TJSONBool.Create(AStream));
// Struchtúr íosta Messages (Chat Completions): role/content.
Msgs := TJSONArray.Create;
Msg := TJSONObject.Create;
Msg.AddPair(‚role‘, ‚user‘);
Msg.AddPair(‚content‘, AUserPrompt);
Msgs.AddElement(Msg);
Result.AddPair(‚messages‘, Msgs);
end;
function TOpenAIChatClient.TryExtractErrorMessage(const AJsonText: string; out AMessage: string): Boolean;
var
J: TJSONValue;
EObj: TJSONObject;
begin
Result := False;
AMessage := “;
J := TJSONObject.ParseJSONValue(AJsonText);
try
if (J is TJSONObject) then
begin
// Foirm choitianta: { „error“: { „message“: „…“, „type“: „…“ } }
EObj := (J as TJSONObject).GetValue<TJSONObject>(‚error‘);
if Assigned(EObj) then
begin
AMessage := EObj.GetValue<string>(‚message‘, “);
Result := AMessage <> “;
end;
end;
finally
J.Free;
end;
end;
function TOpenAIChatClient.ExtractTextFromNonStreamingResponse(const AJsonText: string): string;
var
J: TJSONValue;
Root: TJSONObject;
Choices: TJSONArray;
Choice0: TJSONObject;
Msg: TJSONObject;
begin
Result := “;
J := TJSONObject.ParseJSONValue(AJsonText);
try
if not (J is TJSONObject) then
raise EChatApiError.Create(‚Freagra JSON gan choinne (ní réad).‘, 0, AJsonText);
Root := J as TJSONObject;
Choices := Root.GetValue<TJSONArray>(‚choices‘);
if (Choices = nil) or (Choices.Count = 0) then
raise EChatApiError.Create(‚Freagra JSON gan choinne: tá „choices“ ar iarraidh nó folamh.‘, 0, AJsonText);
Choice0 := Choices.Items[0] as TJSONObject;
// Chat Completions: choices[0].message.content
Msg := Choice0.GetValue<TJSONObject>(‚message‘);
if Msg = nil then
raise EChatApiError.Create(‚Freagra JSON gan choinne: tá „message“ ar iarraidh.‘, 0, AJsonText);
Result := Msg.GetValue<string>(‚content‘, “);
finally
J.Free;
end;
end;
function TOpenAIChatClient.ExecuteWithRetry(const ADoRequest: TFunc<IHTTPResponse>): IHTTPResponse;
const
MaxAttempts = 3;
var
Attempt: Integer;
DelayMs: Integer;
begin
DelayMs := 350;
for Attempt := 1 to MaxAttempts do
begin
try
Exit(ADoRequest());
except
on E: ENetHTTPClientException do
begin
// Earráidí líonra/TLS/timeout: athghairm shimplí le backoff.
if Attempt = MaxAttempts then
raise;
Sleep(DelayMs);
DelayMs := DelayMs * 2;
end;
end;
end;
Result := nil;
end;
function TOpenAIChatClient.ChatOnce(const AUserPrompt: string; const AOptions: TChatCompletionOptions): string;
var
Http: THTTPClient;
Req: IHTTPRequest;
Resp: IHTTPResponse;
Body: TJSONObject;
Payload: TStringStream;
RespText: string;
ErrMsg: string;
begin
Http := THTTPClient.Create;
try
Http.ConnectionTimeout := FConnectTimeoutMs;
Http.ResponseTimeout := FResponseTimeoutMs;
Body := BuildChatRequestBody(AUserPrompt, AOptions, False);
try
Payload := TStringStream.Create(Body.ToJSON, TEncoding.UTF8);
try
Req := Http.GetRequest(‚POST‘, FBaseUrl + ‚/v1/chat/completions‘);
ApplyAuthHeaders(Req);
Resp := ExecuteWithRetry(
function: IHTTPResponse
begin
Result := Http.Execute(Req, Payload);
end
);
RespText := Resp.ContentAsString(TEncoding.UTF8);
if (Resp.StatusCode < 200) or (Resp.StatusCode >= 300) then
begin
if TryExtractErrorMessage(RespText, ErrMsg) then
raise EChatApiError.Create(ErrMsg, Resp.StatusCode, RespText)
else
raise EChatApiError.Create(‚Earráid HTTP ‚ + Resp.StatusCode.ToString, Resp.StatusCode, RespText);
end;
Result := ExtractTextFromNonStreamingResponse(RespText);
finally
Payload.Free;
end;
finally
Body.Free;
end;
finally
Http.Free;
end;
end;
procedure TOpenAIChatClient.ChatStream(const AUserPrompt: string; const AOptions: TChatCompletionOptions;
const AOnEvent: TChatStreamEvent);
var
Task: ITask;
begin
// Tosaigh an sruthú go deonach asíncrónach ionas nach mbeidh FMX/VCL faoi bhac.
Task := TTask.Run(
procedure
var
Http: THTTPClient;
Req: IHTTPRequest;
Resp: IHTTPResponse;
Body: TJSONObject;
Payload: TStringStream;
Stream: TStream;
Reader: TStreamReader;
Line, Data: string;
J: TJSONValue;
Delta, Choice0, Choices: TJSONValue;
ContentChunk: string;
begin
Http := THTTPClient.Create;
try
Http.ConnectionTimeout := FConnectTimeoutMs;
Http.ResponseTimeout := FResponseTimeoutMs;
Body := BuildChatRequestBody(AUserPrompt, AOptions, True);
try
Payload := TStringStream.Create(Body.ToJSON, TEncoding.UTF8);
try
Req := Http.GetRequest(‚POST‘, FBaseUrl + ‚/v1/chat/completions‘);
ApplyAuthHeaders(Req);
Req.AddHeader(‚Accept‘, ‚text/event-stream‘);
Resp := ExecuteWithRetry(
function: IHTTPResponse
begin
Result := Http.Execute(Req, Payload);
end
);
if (Resp.StatusCode < 200) or (Resp.StatusCode >= 300) then
begin
// Bei Streaming-Fehlern ist Content oft trotzdem JSON.
TThread.Queue(nil,
procedure
begin
AOnEvent(‚Níor éirigh le tosú an sruthú (HTTP ‚ + Resp.StatusCode.ToString + ‚).‘, True);
end);
Exit;
end;
Stream := Resp.ContentStream;
Reader := TStreamReader.Create(Stream, TEncoding.UTF8, True, 4096, False);
try
while not Reader.EndOfStream do
begin
Line := Reader.ReadLine;
if Line = “ then
Continue;
// Formáid SSE: línte cosúil le „data: {…}“ nó „data: [DONE]“
if Line.StartsWith(‚data:‘) then
begin
Data := Line.Substring(5).Trim;
if SameText(Data, ‚[DONE]‘) then
begin
TThread.Queue(nil,
procedure
begin
AOnEvent(“, True);
end);
Break;
end;
// Anailís ar iota JSON: choices[0].delta.content
J := TJSONObject.ParseJSONValue(Data);
try
ContentChunk := “;
if J <> nil then
begin
Choices := (J as TJSONObject).GetValue(‚choices‘);
if (Choices is TJSONArray) and (TJSONArray(Choices).Count > 0) then
begin
Choice0 := TJSONArray(Choices).Items[0];
Delta := (Choice0 as TJSONObject).GetValue(‚delta‘);
if (Delta is TJSONObject) then
ContentChunk := TJSONObject(Delta).GetValue<string>(‚content‘, “);
end;
end;
finally
J.Free;
end;
if ContentChunk <> “ then
TThread.Queue(nil,
procedure
begin
AOnEvent(ContentChunk, False);
end);
end;
end;
finally
Reader.Free;
end;
finally
Payload.Free;
end;
finally
Body.Free;
end;
finally
Http.Free;
end;
end);
end;
end.
Cén fáth atá an cur chuige seo oiriúnach
Réitíonn an cód trí chineál fhadhbanna tipiciúla a éiríonn costasach go tapa i VCL/FMX:
- Sruthú gan reothadh an UI: Léitear an sruth HTTP sa chúlra; rithann nuashonruithe an UI trí
TThread.Queue(asynchron i bPríomh-Thread). Is é seo an modh caighdeánach láidir i FMX agus VCL. - Ath-iarracht do earráidí líonra: I gcás
ENetHTTPClientExceptiondéantar iarracht arís ag úsáid backoff. Tá sé seo i gceist simplí agus is féidir é a leathnú níos déanaí le haghaidh cóid stádais (429/5xx). - Parsláil JSON chobhsaí: In ionad réimsí a chlaochlú go dall, seiceálfar iad céim ar chéim. Laghdaíonn sé seo earráidí „Invalid type cast“ i gcás freagraí neamhrialta.
Coinníollacha teorannacha, trapláin agus éagsúlachtaí
- SSE is ní „JSON gnáth“: I sruthú tagann sraith imeachtaí, ní freagra JSON aonair. Dá bhrí sin tá an léamh líne le líne agus an aithint ar
[DONE]lárnach. - THTTPClient agus Proxies/SSL: I líonraí riarthóra tá iniúchadh TLS agus éilimh proxy fíor. Smaoinigh ar
THTTPClient.ProxySettingsagus, más gá, ar cheisteanna teastais. Debugging: déan logáil i gcónaí ar statuscode/headers (gan API-Key). - Straitéis Timeout:
ResponseTimeouttá sé íogair do sruthú: má roghnaíonn tú luach ró-ghiorra gearraíonn an cliant freagraí fada. I uirlisí UI bíonn timeout níos faide agus cnaipe „Cealaigh“ go minic níos oiriúnaí ná cur chuige „gearr agus dian“. - Stopáil snáithe: Níl Cancel-Token le feiceáil sa shnipsil. Do uirlisí táirgeachta is fiú córas cealaithe a chur i bhfeidhm (m.sh. flag +
Http.CancelAlli leaganacha níos nua Delphi nó trí stopadh rialaithe ar an sruth). - Forbairt múnla agus API: D’fhéadfadh struchtúr na bhfreagraí athrú. Coinnigh an parser go cosanta agus lárnach, ní scaipthe i gcód fhorim.
Debugging i gcliaint fhorbartha Delphi: Cad ba chóir a logáil i ndáiríre
I dtionscadail chomhtháthaithe teipeann an chéad rith is minice ní de bharr JSON ach de bharr sonraí comhshaoil. I log theicniúil (comhad, Eventlog, logger lárnach) ba chóir sa chleachtas a bheith ann:
- Request-ID (arna shannadh leat féin), stampa ama, URL chinn (gan Secret-Querystrings).
- HTTP-Statuscode, Content-Type, fad an fhreagra, am rith.
- Comhlacht freagra gearrtha i gcás earráidí (m.sh. uasmhéid 4–8 KB), chun earráidí Quota/Policy a aithint.
- Sainaithint shoiléir ar iarrachtaí Retry: iarracht, moill, aicme eisceacht.
Níor chóir an API-Key a ghiaráil riamh sa log. Má logálann tú comhlacht an iarratais, déan é ach i tógálacha diagnóis agus le mascaíocht, toisc gur féidir le prompts ábhar pearsanta nó tráchtála a áireamh.
Cur i gcomhthéacs do chásanna legacy: VCL, FMX agus Layer-3 ailtireacht
Ritheann go leor aipeanna Delphi i loighic traidisiúnta trí-sraithe („Layer-3 Architektur“: UI, loighic ghnó, sonraí/ionchur). Tá sé seo úsáideach don nascadh le ChatGPT: ba chóir don chliant a thaispeántar a bheith sa sraith chomhtháthaithe; cinneann an loighic ghnó cad atá le iarraidh; taispeánann an UI ach an stair agus an stádas. Sa tslí seo seachnófar go ndéanfaidh athrú ina dhiaidh sin (soláthraí eile, On-Prem-Proxy, pointí deiridh nua) damáiste do na foirmeacha.
Maidir le nuachóiriú Delphi is pointe iontrála maith é seo: ar dtús cliant seasmhach, ansin feabhsúcháin UI (Streaming, Cealaigh, Stair), agus ansin gnéithe níos „intleachtúla” cosúil le freagraí struchtúrtha nó glaonna uirlisí.
Conclúid: Bunús daingean, ach ní gá do gach feidhmchlár sruthú
Tá sé go háirithe tairbheach an ChatGPT API a chomhtháthú go glan le Delphi FMX/VCL i gcásanna ina bhfuil freagarthacht UI, iontaofacht oibríochta agus inrochtana do dhífhabhtaithe tábhachtach: uirlisí riaracháin, cliaint deisce gar don phróiseas nó uirlisí tacaíochta i réitigh fhiontraíochta dhigiteacha. Tá an snipéad taispeántais go cúramach pragmatach: sruthú SSE gan leabharlanna speisialta, iarracht arís ach amháin do earráidí líonra fíor, JSON á pharsáil go cúramach.
Teorainneacha úsáide: Má tá riachtanais chasta comhlíonta, rialachas lárnach ar prompts, tacaíocht d’ili-chustaiméirí nó slite iniúchta mionsonraithe uait, is minic nach leor „cliant ar an deisce“. Sa chás sin de ghnáth bíonn sé níos oiriúnaí an nasc a chur i bhfreastalaí rialaithe (m.sh. seirbhís REST féin), a chuireann polasaithe, logáil agus rialú rochtana i bhfeidhm go lárnach. Do go leor suiteálacha Delphi is pointe tosaigh iontaofa é an cliant taispeána seo, ar féidir é a thiontú de réir a chéile i dtreo ailtireacht iomlán níos glaine.
Sa chomhthéacs teicniúil, tá ról tábhachtach freisin ag Openai API In Delphi agus ag Delphi Http Client Timeout Retry nuair is gá go n-oibreodh comhtháthuithe, sreafaí sonraí agus forbairt leanúnach go comhsheasmhach le chéile.
Nächster Schritt
Wenn aus dem Thema ein reales Projekt wird, sollten Architektur, Bestand und Betrieb früh zusammen betrachtet werden.
Ní hamháin go dtacaímid le ceisteanna aonair, ach freisin nuair is gá ó shlisíní cód foinse, ó ábhair legacy nó ó smaointe portail tionscadal corparáideach iontaofa a fhorbairt.
- Measúnítear an staid reatha, an stát sprioc agus na rioscaí teicniúla le chéile.
- REST, Datenzugriff, Portale und Rollout werden nicht als Spätfolgen verschoben.
- Sie sehen früh, welcher Weg wirtschaftlich und betrieblich tragfähig ist.