Pascal (Lazarus/Delphi)
Pascal (Lazarus/Delphi)
Streaming AI with Manual AI Tool Function Calling
See more AI Examples
Demonstrates how to get AI responses in streaming mode, including manual tool function calls.Chilkat Pascal (Lazarus/Delphi) Downloads
program ChilkatDemo;
// Demonstrates using the Chilkat Pascal wrapper via the C bridge DLL.
// Builds as a console application under Lazarus (FPC) or Delphi.
{$IFDEF FPC}
{$MODE DELPHI}
{$ENDIF}
{$APPTYPE CONSOLE}
uses
{$IFDEF UNIX}
cthreads,
{$ENDIF}
SysUtils,
CkDllLoader,
Chilkat.StringBuilder,
Chilkat.Ai,
Chilkat.JsonObject;
// ---------------------------------------------------------------------------
procedure RunDemo;
var
success: Boolean;
jsonTools: TJsonObject;
toolIdx: Integer;
ai: TAi;
conversation_name: string;
sysMessage: string;
devMessage: string;
sbEventName: TStringBuilder;
sbDelta: TStringBuilder;
sbFullResponse: TStringBuilder;
maxWaitMs: Integer;
jsonFn: TJsonObject;
finished: Boolean;
numAsks: Integer;
madeFunctionCalls: Boolean;
streamingDone: Boolean;
result: Integer;
numFnCalls: Integer;
fn_idx: Integer;
sbFnName: TStringBuilder;
callId: string;
zodiac_sign: string;
applicationFnCallResult: string;
begin
success := False;
// Create the following JSON to define tool functions available for the AI to use.
// Note: You'll use the following JSON format regardless of the AI provider, whether
// it be ChatGPT, Gemini, Claude, Grok, etc. Chilkat automatically converts to the required
// format needed for a given AI provider.
// In this example, the application is providing a single function the AI may choose to call.
// {
// "tools": [
// {
// "name": "get_horoscope",
// "description": "Get today's horoscope for an astrological sign.",
// "parameters": {
// "properties": {
// "sign": {
// "type": "string",
// "description": "An astrological sign like Taurus or Aquarius"
// }
// }
// }
// }
// ]
// }
jsonTools := TJsonObject.Create;
toolIdx := 0;
jsonTools.I := toolIdx;
jsonTools.UpdateString('tools[i].name','get_horoscope');
jsonTools.UpdateString('tools[i].description','Get today''s horoscope for an astrological sign.');
jsonTools.UpdateString('tools[i].parameters.properties.sign.type','string');
jsonTools.UpdateString('tools[i].parameters.properties.sign.description','An astrological sign like Taurus or Aquarius');
// More tools can be added as desired..
jsonTools.EmitCompact := False;
WriteLn(jsonTools.Emit());
ai := TAi.Create;
// Register the tools that will be made available to the AI.
ai.RegisterManualTools(jsonTools);
// The provider can be "openai", "google", "claude", "grok", "mistral", "custom", etc.
ai.Provider := 'openai';
// Use your provider's API key.
ai.ApiKey := 'MY_API_KEY';
// Choose a model.
ai.Model := 'gpt-5-mini';
// Tool function calling must always occur within a conversation.
conversation_name := 'convo_astrology';
sysMessage := 'You are a helpful astrologer';
devMessage := 'Respond only with markdown.';
ai.NewConvo(conversation_name,sysMessage,devMessage);
// Provide inputs
ai.InputAddText('What is my horoscope? I am an Aquarius.');
// Get the response in streaming mode.
ai.Streaming := True;
// In streaming mode, if we receive an AI event that is a request for tool use,
// we'll need to make the call to the JavaScript and then continue with a followup Ask,
// until the final response is received.
sbEventName := TStringBuilder.Create;
sbDelta := TStringBuilder.Create;
sbFullResponse := TStringBuilder.Create;
// When PollAi returns with an event, it's highly unlikely the
// call to NextAiEvent does not immediately return. Setting a max
// timeout is just a precaution..
maxWaitMs := 5000;
jsonFn := TJsonObject.Create;
finished := False;
numAsks := 0;
// Set a max # of followup Asks to prevent any unexpected infinite looping.
while not finished and (numAsks < 10) do
begin
// Send the request to the AI model.
success := ai.Ask('text');
if (success = False) then
begin
WriteLn(ai.LastErrorText);
Exit;
end;
madeFunctionCalls := False;
streamingDone := False;
while not streamingDone do
begin
result := ai.PollAi(False);
if (result < 0) then
begin
WriteLn(ai.LastErrorText);
WriteLn('Failed.');
Exit;
end;
if (result > 0) then
begin
// We have an event..
success := ai.NextAiEvent(maxWaitMs,sbEventName,sbDelta);
if (success = False) then
begin
WriteLn(ai.LastErrorText);
Exit;
end;
// Is this an event where the AI is requesting a function call?
if (sbEventName.ContentsEqual('function_call',True)) then
begin
jsonFn.LoadSb(sbDelta);
// Note: Chilkat will convert responses from all AI providers to this format:
// {
// "function_call": [
// {
// "name": "get_horoscope",
// "call_id": "call_RYmeysYQFocFc7Z2ofkv61dW",
// "arguments": "{\"sign\":\"Aquarius\"}",
// "args": {
// "sign": "Aquarius"
// }
// }
// ]
// }
numFnCalls := jsonFn.SizeOfArray('function_call');
fn_idx := 0;
while (fn_idx < numFnCalls) do
begin
jsonFn.I := fn_idx;
sbFnName := TStringBuilder.Create;
jsonFn.StringOfSb('function_call[i].name',sbFnName);
callId := jsonFn.StringOf('function_call[i].call_id');
if (sbFnName.ContentsEqual('get_horoscope',True) = True) then
begin
// The get_horoscope function (as defined above) has one argument named "sign".
zodiac_sign := jsonFn.StringOf('function_call[i].args.sign');
WriteLn('zodiac_sign = ' + zodiac_sign);
// Insert application code here to call your app's get_horoscope function, passing the zodiac_sign to it..
// For this example, we'll pretend the app's get_horoscope function returned the following:
applicationFnCallResult := 'Aquarius: Next Tuesday you will befriend a baby otter.';
// Provide the tool call result as an input for the followup Ask.
ai.InputAddFnResult(callId,applicationFnCallResult);
madeFunctionCalls := True;
end;
// Your application would add code to check for and handle each possible function call.
fn_idx := fn_idx + 1;
end;
end
else
begin
if (not sbEventName.ContentsEqual('empty',True)) then
begin
sbFullResponse.AppendSb(sbDelta);
if (sbEventName.ContentsEqual('null_terminator',True)) then
begin
streamingDone := True;
end;
end;
end;
end
else
begin
// No event arrived, so wait a short time rather than spin in a loop..
ai.SleepMs(100);
end;
end;
if (not madeFunctionCalls) then
begin
finished := True;
end;
numAsks := numAsks + 1;
end;
WriteLn('Full Response:');
WriteLn(sbFullResponse.GetAsString());
jsonTools.Free;
ai.Free;
sbEventName.Free;
sbDelta.Free;
sbFullResponse.Free;
jsonFn.Free;
sbFnName.Free;
end;
// ---------------------------------------------------------------------------
begin
try
RunDemo;
except
on E: Exception do
WriteLn('Unhandled exception: ', E.ClassName, ': ', E.Message);
end;
WriteLn;
{$IFDEF MSWINDOWS}
WriteLn('Press Enter to exit...');
ReadLn;
{$ENDIF}
end.