Unicode C
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
#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);
}