Sample code for 30+ languages & platforms
Unicode C

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 Unicode C Downloads

Unicode C
#include <C_CkJsonObjectW.h>
#include <C_CkAiW.h>
#include <C_CkStringBuilderW.h>

void ChilkatSample(void)
    {
    BOOL success;
    HCkJsonObjectW jsonTools;
    int toolIdx;
    HCkAiW ai;
    const wchar_t *conversation_name;
    const wchar_t *sysMessage;
    const wchar_t *devMessage;
    HCkStringBuilderW sbEventName;
    HCkStringBuilderW sbDelta;
    HCkStringBuilderW sbFullResponse;
    int maxWaitMs;
    HCkJsonObjectW jsonFn;
    BOOL finished;
    int numAsks;
    BOOL madeFunctionCalls;
    BOOL streamingDone;
    int result;
    int numFnCalls;
    int fn_idx;
    HCkStringBuilderW sbFnName;
    const wchar_t *callId;
    const wchar_t *zodiac_sign;
    const wchar_t *applicationFnCallResult;

    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 = CkJsonObjectW_Create();

    toolIdx = 0;

    CkJsonObjectW_putI(jsonTools,toolIdx);
    CkJsonObjectW_UpdateString(jsonTools,L"tools[i].name",L"get_horoscope");
    CkJsonObjectW_UpdateString(jsonTools,L"tools[i].description",L"Get today's horoscope for an astrological sign.");
    CkJsonObjectW_UpdateString(jsonTools,L"tools[i].parameters.properties.sign.type",L"string");
    CkJsonObjectW_UpdateString(jsonTools,L"tools[i].parameters.properties.sign.description",L"An astrological sign like Taurus or Aquarius");
    // More tools can be added as desired..

    CkJsonObjectW_putEmitCompact(jsonTools,FALSE);
    wprintf(L"%s\n",CkJsonObjectW_emit(jsonTools));

    ai = CkAiW_Create();

    // Register the tools that will be made available to the AI.
    CkAiW_RegisterManualTools(ai,jsonTools);

    // The provider can be "openai", "google", "claude", "grok", "mistral", "custom", etc.
    CkAiW_putProvider(ai,L"openai");
    // Use your provider's API key.
    CkAiW_putApiKey(ai,L"MY_API_KEY");
    // Choose a model.
    CkAiW_putModel(ai,L"gpt-5-mini");

    // Tool function calling must always occur within a conversation.
    conversation_name = L"convo_astrology";
    sysMessage = L"You are a helpful astrologer";
    devMessage = L"Respond only with markdown.";
    CkAiW_NewConvo(ai,conversation_name,sysMessage,devMessage);

    // Provide inputs
    CkAiW_InputAddText(ai,L"What is my horoscope? I am an Aquarius.");

    // Get the response in streaming mode.
    CkAiW_putStreaming(ai,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 = CkStringBuilderW_Create();
    sbDelta = CkStringBuilderW_Create();
    sbFullResponse = CkStringBuilderW_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 = CkJsonObjectW_Create();

    finished = FALSE;
    numAsks = 0;
    // Set a max # of followup Asks to prevent any unexpected infinite looping.
    while (!finished && (numAsks < 10)) {

        // Send the request to the AI model.
        success = CkAiW_Ask(ai,L"text");
        if (success == FALSE) {
            wprintf(L"%s\n",CkAiW_lastErrorText(ai));
            CkJsonObjectW_Dispose(jsonTools);
            CkAiW_Dispose(ai);
            CkStringBuilderW_Dispose(sbEventName);
            CkStringBuilderW_Dispose(sbDelta);
            CkStringBuilderW_Dispose(sbFullResponse);
            CkJsonObjectW_Dispose(jsonFn);
            return;
        }

        madeFunctionCalls = FALSE;
        streamingDone = FALSE;

        while (!streamingDone) {
            result = CkAiW_PollAi(ai,FALSE);
            if (result < 0) {
                wprintf(L"%s\n",CkAiW_lastErrorText(ai));
                wprintf(L"Failed.\n");
                CkJsonObjectW_Dispose(jsonTools);
                CkAiW_Dispose(ai);
                CkStringBuilderW_Dispose(sbEventName);
                CkStringBuilderW_Dispose(sbDelta);
                CkStringBuilderW_Dispose(sbFullResponse);
                CkJsonObjectW_Dispose(jsonFn);
                return;
            }

            if (result > 0) {
                // We have an event..
                success = CkAiW_NextAiEvent(ai,maxWaitMs,sbEventName,sbDelta);
                if (success == FALSE) {
                    wprintf(L"%s\n",CkAiW_lastErrorText(ai));
                    CkJsonObjectW_Dispose(jsonTools);
                    CkAiW_Dispose(ai);
                    CkStringBuilderW_Dispose(sbEventName);
                    CkStringBuilderW_Dispose(sbDelta);
                    CkStringBuilderW_Dispose(sbFullResponse);
                    CkJsonObjectW_Dispose(jsonFn);
                    return;
                }

                // Is this an event where the AI is requesting a function call?
                if (CkStringBuilderW_ContentsEqual(sbEventName,L"function_call",TRUE)) {
                    CkJsonObjectW_LoadSb(jsonFn,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 = CkJsonObjectW_SizeOfArray(jsonFn,L"function_call");
                    fn_idx = 0;
                    while ((fn_idx < numFnCalls)) {
                        CkJsonObjectW_putI(jsonFn,fn_idx);

                        sbFnName = CkStringBuilderW_Create();
                        CkJsonObjectW_StringOfSb(jsonFn,L"function_call[i].name",sbFnName);
                        callId = CkJsonObjectW_stringOf(jsonFn,L"function_call[i].call_id");

                        if (CkStringBuilderW_ContentsEqual(sbFnName,L"get_horoscope",TRUE) == TRUE) {

                            // The get_horoscope function (as defined above) has one argument named "sign".
                            zodiac_sign = CkJsonObjectW_stringOf(jsonFn,L"function_call[i].args.sign");
                            wprintf(L"zodiac_sign = %s\n",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 = L"Aquarius: Next Tuesday you will befriend a baby otter.";

                            // Provide the tool call result as an input for the followup Ask.
                            CkAiW_InputAddFnResult(ai,callId,applicationFnCallResult);

                            madeFunctionCalls = TRUE;
                        }

                        // Your application would add code to check for and handle each possible function call.

                        fn_idx = fn_idx + 1;
                    }

                }
                else {
                    if (!CkStringBuilderW_ContentsEqual(sbEventName,L"empty",TRUE)) {
                        CkStringBuilderW_AppendSb(sbFullResponse,sbDelta);

                        if (CkStringBuilderW_ContentsEqual(sbEventName,L"null_terminator",TRUE)) {
                            streamingDone = TRUE;
                        }

                    }

                }

            }
            else {
                // No event arrived, so wait a short time rather than spin in a loop..
                CkAiW_SleepMs(ai,100);
            }

        }

        if (!madeFunctionCalls) {
            finished = TRUE;
        }

        numAsks = numAsks + 1;
    }

    wprintf(L"Full Response:\n");
    wprintf(L"%s\n",CkStringBuilderW_getAsString(sbFullResponse));


    CkJsonObjectW_Dispose(jsonTools);
    CkAiW_Dispose(ai);
    CkStringBuilderW_Dispose(sbEventName);
    CkStringBuilderW_Dispose(sbDelta);
    CkStringBuilderW_Dispose(sbFullResponse);
    CkJsonObjectW_Dispose(jsonFn);
    CkStringBuilderW_Dispose(sbFnName);

    }