Overview
RCLI provides callback functions for real-time event handling:
- Transcript: Real-time STT updates (streaming partial and final results)
- State: Pipeline state transitions (idle → listening → processing → speaking)
- Action: Action execution results (success/failure)
- Tool Trace: LLM tool call observability (detected → result)
- Event: Generic events (file processing, benchmarks, timings)
Callbacks fire on worker threads. If you need to update UI, marshal the callback to your main/UI thread.
Callback Types
RCLITranscriptCallback
Real-time transcript updates from streaming STT.
typedef void (*RCLITranscriptCallback)(
const char* text,
int is_final,
void* user_data
);
Transcript text. Partial results update continuously; final results are stable.
1: Final transcript (speech endpoint detected)
0: Partial transcript (still speaking)
User data pointer passed to rcli_set_transcript_callback()
Example
void on_transcript(const char* text, int is_final, void* user_data) {
if (is_final) {
printf("\nFinal: %s\n", text);
} else {
printf("\rPartial: %-50s", text); // Live updates
fflush(stdout);
}
}
rcli_set_transcript_callback(handle, on_transcript, NULL);
rcli_start_listening(handle);
RCLIStateCallback
Pipeline state transitions.
typedef void (*RCLIStateCallback)(
int old_state,
int new_state,
void* user_data
);
Previous state (0=IDLE, 1=LISTENING, 2=PROCESSING, 3=SPEAKING, 4=INTERRUPTED)
Example
void on_state(int old_state, int new_state, void* user_data) {
const char* names[] = {"IDLE", "LISTENING", "PROCESSING", "SPEAKING", "INTERRUPTED"};
printf("State: %s -> %s\n", names[old_state], names[new_state]);
// Update UI indicator
if (new_state == 1) {
show_listening_indicator();
} else if (new_state == 3) {
show_speaking_indicator();
}
}
rcli_set_state_callback(handle, on_state, NULL);
RCLIActionCallback
Action execution results (macOS actions like opening apps, creating reminders).
typedef void (*RCLIActionCallback)(
const char* action_name,
const char* result_json,
int success,
void* user_data
);
Name of the action that was executed (e.g., "open_app", "create_reminder")
JSON result from the action:{"success": true, "output": "Opened Safari", "error": ""}
1: Action succeeded
0: Action failed
Example
void on_action(const char* name, const char* result_json, int success, void* user_data) {
if (success) {
printf("[Action] %s completed successfully\n", name);
} else {
printf("[Action] %s failed: %s\n", name, result_json);
}
}
rcli_set_action_callback(handle, on_action, NULL);
// Trigger an action
rcli_process_command(handle, "open Safari");
// Callback fires: on_action("open_app", "{\"success\":true,...}", 1, NULL)
Tool call observability - tracks both detection and execution.
typedef void (*RCLIToolTraceCallback)(
const char* event,
const char* tool_name,
const char* data,
int success,
void* user_data
);
"detected": LLM emitted a tool call
"result": Tool execution completed
Name of the tool (e.g., "open_app", "get_current_time", "calculate")
- For
"detected": JSON arguments
- For
"result": JSON result
- For
"detected": Always 0
- For
"result": 1 if successful, 0 if failed
This callback covers all tools: both built-in tools (get_current_time, calculate) and actions. RCLIActionCallback only fires for actions.
void on_tool_trace(const char* event, const char* tool_name,
const char* data, int success, void* user_data) {
if (strcmp(event, "detected") == 0) {
printf("[LLM] Calling tool: %s(%s)\n", tool_name, data);
} else if (strcmp(event, "result") == 0) {
if (success) {
printf("[Tool] %s -> %s\n", tool_name, data);
} else {
printf("[Tool] %s FAILED: %s\n", tool_name, data);
}
}
}
rcli_set_tool_trace_callback(handle, on_tool_trace, NULL);
rcli_process_command(handle, "What time is it?");
// Output:
// [LLM] Calling tool: get_current_time({})
// [Tool] get_current_time -> {"time": "2:30 PM"}
RCLIEventCallback
Generic event callback for file processing, benchmarks, and timings.
typedef void (*RCLIEventCallback)(
const char* event,
const char* data,
void* user_data
);
Event type:
"state_change": Pipeline state transition
"timings": Performance timings (JSON)
"benchmark_progress": Benchmark iteration progress
"benchmark_run": Single benchmark run result
"benchmark_result": Aggregate benchmark results
Event data (format depends on event type)
Example: File Processing with Timings
void on_event(const char* event, const char* data, void* user_data) {
if (strcmp(event, "state_change") == 0) {
printf("State: %s\n", data);
} else if (strcmp(event, "timings") == 0) {
printf("Timings: %s\n", data);
}
}
rcli_process_wav(handle, "input.wav", "output.wav", on_event, NULL);
// Output:
// State: listening
// State: processing
// State: speaking
// Timings: {"stt_ms":234,"llm_ttft_ms":89,...}
Callback Registration
rcli_set_transcript_callback
void rcli_set_transcript_callback(
RCLIHandle handle,
RCLITranscriptCallback cb,
void* user_data
);
rcli_set_state_callback
void rcli_set_state_callback(
RCLIHandle handle,
RCLIStateCallback cb,
void* user_data
);
rcli_set_action_callback
void rcli_set_action_callback(
RCLIHandle handle,
RCLIActionCallback cb,
void* user_data
);
void rcli_set_tool_trace_callback(
RCLIHandle handle,
RCLIToolTraceCallback cb,
void* user_data
);
Thread Safety
Callbacks fire on worker threads:
RCLITranscriptCallback: STT thread
RCLIStateCallback: State transition thread (varies)
RCLIActionCallback: LLM thread
RCLIToolTraceCallback: LLM thread (synchronous with rcli_process_command())
RCLIEventCallback: Worker thread (file processing, benchmarks)
Swift Example: Dispatch to Main Thread
func onTranscript(text: UnsafePointer<CChar>?, isFinal: Int32, userData: UnsafeMutableRawPointer?) {
guard let text = text else { return }
let transcript = String(cString: text)
DispatchQueue.main.async {
// Update UI on main thread
self.transcriptLabel.text = transcript
}
}
rcli_set_transcript_callback(handle, onTranscript, nil)
C Example: User Data
struct AppState {
int message_count;
FILE* log_file;
};
void on_transcript(const char* text, int is_final, void* user_data) {
struct AppState* state = (struct AppState*)user_data;
if (is_final) {
state->message_count++;
fprintf(state->log_file, "[%d] %s\n", state->message_count, text);
}
}
struct AppState state = { .message_count = 0, .log_file = fopen("log.txt", "w") };
rcli_set_transcript_callback(handle, on_transcript, &state);
Complete Example: Event Monitor
#include "api/rcli_api.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
void on_transcript(const char* text, int is_final, void* user_data) {
printf("[STT] %s %s\n", is_final ? "FINAL:" : "partial:", text);
}
void on_state(int old_state, int new_state, void* user_data) {
const char* names[] = {"IDLE", "LISTENING", "PROCESSING", "SPEAKING", "INTERRUPTED"};
printf("[State] %s -> %s\n", names[old_state], names[new_state]);
}
void on_action(const char* name, const char* result, int success, void* user_data) {
printf("[Action] %s: %s\n", name, success ? "OK" : "FAILED");
}
void on_tool_trace(const char* event, const char* tool, const char* data,
int success, void* user_data) {
if (strcmp(event, "detected") == 0) {
printf("[LLM] -> %s(%s)\n", tool, data);
} else {
printf("[Tool] %s <- %s\n", tool, success ? "success" : "error");
}
}
int main() {
RCLIHandle handle = rcli_create(NULL);
rcli_init(handle, "/path/to/models", 99);
// Register all callbacks
rcli_set_transcript_callback(handle, on_transcript, NULL);
rcli_set_state_callback(handle, on_state, NULL);
rcli_set_action_callback(handle, on_action, NULL);
rcli_set_tool_trace_callback(handle, on_tool_trace, NULL);
// Start listening
rcli_start_listening(handle);
sleep(30);
rcli_stop_listening(handle);
rcli_destroy(handle);
return 0;
}
See Also