Sample code for 30+ languages & platforms
Xbase++ Requires Chilkat v11.4.0+

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

Xbase++
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()