Sample code for 30+ languages & platforms
Objective-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 Objective-C Downloads

Objective-C
#import <CkoJsonObject.h>
#import <CkoAi.h>
#import <NSString.h>
#import <CkoStringBuilder.h>

BOOL success = NO;

// 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"
//           }
//         }
//       }
//     }
//   ]
// }

CkoJsonObject *jsonTools = [[CkoJsonObject alloc] init];

int toolIdx = 0;

jsonTools.I = [NSNumber numberWithInt: toolIdx];
[jsonTools UpdateString: @"tools[i].name" value: @"get_horoscope"];
[jsonTools UpdateString: @"tools[i].description" value: @"Get today's horoscope for an astrological sign."];
[jsonTools UpdateString: @"tools[i].parameters.properties.sign.type" value: @"string"];
[jsonTools UpdateString: @"tools[i].parameters.properties.sign.description" value: @"An astrological sign like Taurus or Aquarius"];
// More tools can be added as desired..

jsonTools.EmitCompact = NO;
NSLog(@"%@",[jsonTools Emit]);

CkoAi *ai = [[CkoAi alloc] init];

// 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.
NSString *conversation_name = @"convo_astrology";
NSString *sysMessage = @"You are a helpful astrologer";
NSString *devMessage = @"Respond only with markdown.";
[ai NewConvo: conversation_name sysMsg: sysMessage devMsg: devMessage];

// Provide inputs
[ai InputAddText: @"What is my horoscope? I am an Aquarius."];

// Get the response in streaming mode.
ai.Streaming = YES;

// 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.

CkoStringBuilder *sbEventName = [[CkoStringBuilder alloc] init];
CkoStringBuilder *sbDelta = [[CkoStringBuilder alloc] init];
CkoStringBuilder *sbFullResponse = [[CkoStringBuilder alloc] init];

// 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..
int maxWaitMs = 5000;

CkoJsonObject *jsonFn = [[CkoJsonObject alloc] init];

BOOL finished = NO;
int 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 = [ai Ask: @"text"];
    if (success == NO) {
        NSLog(@"%@",ai.LastErrorText);
        return;
    }

    BOOL madeFunctionCalls = NO;
    BOOL streamingDone = NO;

    while (!streamingDone) {
        int result = [[ai PollAi: NO] intValue];
        if (result < 0) {
            NSLog(@"%@",ai.LastErrorText);
            NSLog(@"%@",@"Failed.");
            return;
        }

        if (result > 0) {
            // We have an event..
            success = [ai NextAiEvent: [NSNumber numberWithInt: maxWaitMs] sbName: sbEventName sbDelta: sbDelta];
            if (success == NO) {
                NSLog(@"%@",ai.LastErrorText);
                return;
            }

            // Is this an event where the AI is requesting a function call?
            if ([sbEventName ContentsEqual: @"function_call" caseSensitive: YES]) {
                [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"
                //       }
                //     }
                //   ]
                // }

                int numFnCalls = [[jsonFn SizeOfArray: @"function_call"] intValue];
                int fn_idx = 0;
                while ((fn_idx < numFnCalls)) {
                    jsonFn.I = [NSNumber numberWithInt: fn_idx];

                    CkoStringBuilder *sbFnName = [[CkoStringBuilder alloc] init];
                    [jsonFn StringOfSb: @"function_call[i].name" sb: sbFnName];
                    NSString *callId = [jsonFn StringOf: @"function_call[i].call_id"];

                    if ([sbFnName ContentsEqual: @"get_horoscope" caseSensitive: YES] == YES) {

                        // The get_horoscope function (as defined above) has one argument named "sign".
                        NSString *zodiac_sign = [jsonFn StringOf: @"function_call[i].args.sign"];
                        NSLog(@"%@%@",@"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:
                        NSString *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 fnResult: applicationFnCallResult];

                        madeFunctionCalls = YES;
                    }

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

                    fn_idx = fn_idx + 1;
                }

            }
            else {
                if (![sbEventName ContentsEqual: @"empty" caseSensitive: YES]) {
                    [sbFullResponse AppendSb: sbDelta];

                    if ([sbEventName ContentsEqual: @"null_terminator" caseSensitive: YES]) {
                        streamingDone = YES;
                    }

                }

            }

        }
        else {
            // No event arrived, so wait a short time rather than spin in a loop..
            [ai SleepMs: [NSNumber numberWithInt: 100]];
        }

    }

    if (!madeFunctionCalls) {
        finished = YES;
    }

    numAsks = numAsks + 1;
}

NSLog(@"%@",@"Full Response:");
NSLog(@"%@",[sbFullResponse GetAsString]);