Xbase++ Requires Chilkat v11.4.0+
Xbase++
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 Xbase++ Downloads
LOCAL nSuccess
LOCAL oJsonTools
LOCAL nToolIdx
LOCAL oAi
LOCAL cConversation_name
LOCAL cSysMessage
LOCAL cDevMessage
LOCAL oSbEventName
LOCAL oSbDelta
LOCAL oSbFullResponse
LOCAL nMaxWaitMs
LOCAL oJsonFn
LOCAL nFinished
LOCAL nNumAsks
LOCAL nMadeFunctionCalls
LOCAL nStreamingDone
LOCAL nResult
LOCAL nNumFnCalls
LOCAL nFn_idx
LOCAL oSbFnName
LOCAL cCallId
LOCAL cZodiac_sign
LOCAL cApplicationFnCallResult
nSuccess := 0
// 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"
// }
// }
// }
// }
// ]
// }
oJsonTools := CreateObject("Chilkat.JsonObject")
nToolIdx := 0
oJsonTools:I := nToolIdx
oJsonTools:UpdateString("tools[i].name", "get_horoscope")
oJsonTools:UpdateString("tools[i].description", "Get today's horoscope for an astrological sign.")
oJsonTools:UpdateString("tools[i].parameters.properties.sign.type", "string")
oJsonTools:UpdateString("tools[i].parameters.properties.sign.description", "An astrological sign like Taurus or Aquarius")
// More tools can be added as desired..
oJsonTools:EmitCompact := 0
? oJsonTools:Emit()
oAi := CreateObject("Chilkat.Ai")
// Register the tools that will be made available to the AI.
oAi:RegisterManualTools(oJsonTools)
// The provider can be "openai", "google", "claude", "grok", "mistral", "custom", etc.
oAi:Provider := "openai"
// Use your provider's API key.
oAi:ApiKey := "MY_API_KEY"
// Choose a model.
oAi:Model := "gpt-5.6-luna"
// Tool function calling must always occur within a conversation.
cConversation_name := "convo_astrology"
cSysMessage := "You are a helpful astrologer"
cDevMessage := "Respond only with markdown."
oAi:NewConvo(cConversation_name, cSysMessage, cDevMessage)
// Provide inputs
oAi:InputAddText("What is my horoscope? I am an Aquarius.")
// Get the response in streaming mode.
oAi:Streaming := 1
// 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.
oSbEventName := CreateObject("Chilkat.StringBuilder")
oSbDelta := CreateObject("Chilkat.StringBuilder")
oSbFullResponse := CreateObject("Chilkat.StringBuilder")
// 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..
nMaxWaitMs := 5000
oJsonFn := CreateObject("Chilkat.JsonObject")
nFinished := 0
nNumAsks := 0
// Set a max # of followup Asks to prevent any unexpected infinite looping.
DO WHILE .NOT. nFinished .AND. (nNumAsks < 10)
// Send the request to the AI model.
nSuccess := oAi:Ask("text")
IF (nSuccess == 0)
? oAi:LastErrorText
oJsonTools:destroy()
oAi:destroy()
oSbEventName:destroy()
oSbDelta:destroy()
oSbFullResponse:destroy()
oJsonFn:destroy()
RETURN
ENDIF
nMadeFunctionCalls := 0
nStreamingDone := 0
DO WHILE .NOT. nStreamingDone
nResult := oAi:PollAi(0)
IF (nResult < 0)
? oAi:LastErrorText
? "Failed."
oJsonTools:destroy()
oAi:destroy()
oSbEventName:destroy()
oSbDelta:destroy()
oSbFullResponse:destroy()
oJsonFn:destroy()
RETURN
ENDIF
IF (nResult > 0)
// We have an event..
nSuccess := oAi:NextAiEvent(nMaxWaitMs, oSbEventName, oSbDelta)
IF (nSuccess == 0)
? oAi:LastErrorText
oJsonTools:destroy()
oAi:destroy()
oSbEventName:destroy()
oSbDelta:destroy()
oSbFullResponse:destroy()
oJsonFn:destroy()
RETURN
ENDIF
// Is this an event where the AI is requesting a function call?
IF (oSbEventName:ContentsEqual("function_call", 1))
oJsonFn:LoadSb(oSbDelta)
// 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"
// }
// }
// ]
// }
nNumFnCalls := oJsonFn:SizeOfArray("function_call")
nFn_idx := 0
DO WHILE (nFn_idx < nNumFnCalls)
oJsonFn:I := nFn_idx
oSbFnName := CreateObject("Chilkat.StringBuilder")
oJsonFn:StringOfSb("function_call[i].name", oSbFnName)
cCallId := oJsonFn:StringOf("function_call[i].call_id")
IF (oSbFnName:ContentsEqual("get_horoscope", 1) == 1)
// The get_horoscope function (as defined above) has one argument named "sign".
cZodiac_sign := oJsonFn:StringOf("function_call[i].args.sign")
? "zodiac_sign = " + cZodiac_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:
cApplicationFnCallResult := "Aquarius: Next Tuesday you will befriend a baby otter."
// Provide the tool call result as an input for the followup Ask.
oAi:InputAddFnResult(cCallId, cApplicationFnCallResult)
nMadeFunctionCalls := 1
ENDIF
// Your application would add code to check for and handle each possible function call.
nFn_idx := nFn_idx + 1
ENDDO
ELSE
IF (.NOT. oSbEventName:ContentsEqual("empty", 1))
oSbFullResponse:AppendSb(oSbDelta)
IF (oSbEventName:ContentsEqual("null_terminator", 1))
nStreamingDone := 1
ENDIF
ENDIF
ENDIF
ELSE
// No event arrived, so wait a short time rather than spin in a loop..
oAi:SleepMs(100)
ENDIF
ENDDO
IF (.NOT. nMadeFunctionCalls)
nFinished := 1
ENDIF
nNumAsks := nNumAsks + 1
ENDDO
? "Full Response:"
? oSbFullResponse:GetAsString()
oJsonTools:destroy()
oAi:destroy()
oSbEventName:destroy()
oSbDelta:destroy()
oSbFullResponse:destroy()
oJsonFn:destroy()
oSbFnName:destroy()