Csilk 0.2.1
A lightweight, high-performance C HTTP web framework
Loading...
Searching...
No Matches
ai.c File Reference

AI unified interface engine implementation. More...

#include "csilk/drivers/ai.h"
#include <ctype.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <uv.h>
#include "cJSON.h"
#include "csilk/csilk.h"
Include dependency graph for ai.c:

Data Structures

struct  csilk_ai_t
 Opaper AI engine handle wrapping a single driver backend. Each instance pairs a driver vtable with its private initialization state (API credentials, connection pool, etc.). Created via csilk_ai_new() and freed via csilk_ai_free(). Not thread-safe for concurrent use from multiple threads. More...
 
struct  async_chat_req_t
 Per-async-chat-request context passed between the work callback (on a thread-pool thread) and the after-work callback (on the main loop thread). Keeps the response on the heap so it survives across threads without data races on the caller's stack. More...
 
struct  async_emb_req_t
 Per-async-embedding-request context passed between the work callback (thread-pool thread) and after-work callback (main loop). More...
 

Macros

#define MAX_DRIVERS   8
 Maximum number of concurrently registered AI drivers.
 

Functions

static void ai_ensure_monitor_init (void)
 
static void _ai_broadcast (const char *event, const char *model, int status, int prompt_tokens, int completion_tokens, uint64_t duration_us, const char *error)
 
void csilk_ai_get_stats (csilk_ai_stats_t *stats)
 Get current AI engine statistics.
 
char * csilk_ai_stats_to_json (const csilk_ai_stats_t *stats)
 Convert AI statistics to a JSON string.
 
void csilk_ai_register_monitor (void *c)
 Register a WebSocket monitor for real-time AI events.
 
void csilk_ai_register_driver (const csilk_ai_driver_t *driver)
 Register an AI driver implementation in the global registry. Called during driver module initialization (e.g., csilk_ai_openai_init_driver()). Silently ignores registration if the registry is full.
 
static const csilk_ai_driver_tfind_driver (const char *name)
 Linear search of the global driver registry by name.
 
void csilk_ai_openai_init_driver (void)
 Register the OpenAI driver with the AI subsystem. Called during startup to make "openai" available to csilk_ai_new().
 
void csilk_ai_ollama_init_driver (void)
 Register the Ollama driver with the AI subsystem. Called during startup to make "ollama" available to csilk_ai_new().
 
csilk_ai_t * csilk_ai_new (const char *driver_name, const char *api_key, const char *base_url)
 Create a new AI engine instance bound to a specific driver backend.
 
int csilk_ai_chat (csilk_ai_t *ai, const csilk_ai_chat_request_t *req, csilk_ai_chat_response_t *res)
 Send a chat completion request with automatic retry on transient failures.
 
static void chat_work_cb (uv_work_t *req)
 libuv thread-pool work callback — runs csilk_ai_chat() off the main loop thread. The response is stored in the heap-allocated async context.
 
static void chat_after_work_cb (uv_work_t *req, int status)
 libuv after-work callback — delivers the result on the main loop thread via the user's callback, then frees the async context.
 
void csilk_ai_chat_async (csilk_ai_t *ai, const csilk_ai_chat_request_t *req, csilk_ai_chat_async_cb cb, void *user_data)
 Send a chat completion request asynchronously on the libuv thread pool.
 
int csilk_ai_embeddings (csilk_ai_t *ai, const char *model, const char **input, size_t count, csilk_ai_embeddings_response_t *res)
 Generate embeddings for a batch of input strings.
 
static void emb_work_cb (uv_work_t *req)
 libuv thread-pool work callback for async embeddings. Runs csilk_ai_embeddings() off the main loop thread, storing the result in the heap-allocated async context.
 
static void emb_after_work_cb (uv_work_t *req, int status)
 libuv after-work callback for async embeddings — delivers the result on the main loop thread via the user's callback, then frees the async context.
 
void csilk_ai_embeddings_async (csilk_ai_t *ai, const char *model, const char **input, size_t count, csilk_ai_embeddings_async_cb cb, void *user_data)
 Generate embeddings asynchronously on the libuv thread pool.
 
void csilk_ai_free (csilk_ai_t *ai)
 Free an AI engine handle and its driver state. Calls the driver's free() callback first, then frees the handle.
 
void csilk_ai_chat_response_free (csilk_ai_chat_response_t *res)
 Free all dynamically allocated fields in a chat response. Frees content, tool calls (with their id/name/arguments), raw_response, and error_message. Does NOT free the struct itself.
 
void csilk_ai_embeddings_response_free (csilk_ai_embeddings_response_t *res)
 Free dynamically allocated fields in an embeddings response. Frees values array and error_message. Does NOT free the struct itself.
 
csilk_ai_context_tcsilk_ai_context_new (size_t max_history)
 Create a new conversation context with sliding-window history.
 
void csilk_ai_context_add (csilk_ai_context_t *ctx, const char *role, const char *content)
 Add a message to the conversation context with sliding-window eviction.
 
void csilk_ai_context_clear (csilk_ai_context_t *ctx)
 Clear all messages from the conversation context. Frees each message's role and content strings and resets the message count to zero. The internal array capacity is preserved to avoid repeated reallocation.
 
void csilk_ai_context_free (csilk_ai_context_t *ctx)
 Free a conversation context and all associated resources. Clears messages, frees the message array, and frees the context struct.
 

Variables

static atomic_uint_fast64_t ai_requests_total = 0
 
static atomic_uint_fast64_t ai_tokens_total = 0
 
static atomic_uint_fast64_t ai_prompt_tokens = 0
 
static atomic_uint_fast64_t ai_completion_tokens = 0
 
static atomic_uint_fast64_t ai_errors_total = 0
 
static atomic_uint_fast64_t ai_duration_us_total = 0
 
static csilk_ctx_t * g_ai_monitors [16]
 
static size_t g_ai_monitor_count = 0
 
static uv_mutex_t g_ai_monitor_mutex
 
static int g_ai_monitor_init = 0
 
static const csilk_ai_driver_tg_drivers [MAX_DRIVERS]
 Global registry of AI driver implementations. Populated once during the first csilk_ai_new() call via lazy init of the built-in drivers (OpenAI, Ollama). Not thread-safe for concurrent registration — all registration happens during the single-threaded startup phase.
 
static size_t g_driver_count = 0
 

Detailed Description

AI unified interface engine implementation.

Architecture: Facade pattern over pluggable AI driver backends (OpenAI, Ollama, etc.). The global driver registry is populated once at first use via lazy initialization. Each csilk_ai_t instance wraps a single driver with its private state.

The module provides both synchronous (blocking) and asynchronous (libuv thread-pool) variants for chat completions and embeddings. Chat requests include automatic retry with exponential backoff for transient failures (network errors, rate limits, server errors).

A context helper manages sliding-window conversation history for multi-turn interactions. Memory for async operations is heap-allocated and freed in the after-work callback on the main loop thread.


Data Structure Documentation

◆ csilk_ai_s

struct csilk_ai_s

Opaper AI engine handle wrapping a single driver backend. Each instance pairs a driver vtable with its private initialization state (API credentials, connection pool, etc.). Created via csilk_ai_new() and freed via csilk_ai_free(). Not thread-safe for concurrent use from multiple threads.

Opaque handle for an AI provider instance.

Collaboration diagram for csilk_ai_t:
Data Fields
const csilk_ai_driver_t * driver

Driver vtable (init, chat, embeddings, free).

void * driver_state

Driver-private state (API key, base URL, etc.).

◆ async_chat_req_t

struct async_chat_req_t

Per-async-chat-request context passed between the work callback (on a thread-pool thread) and the after-work callback (on the main loop thread). Keeps the response on the heap so it survives across threads without data races on the caller's stack.

Collaboration diagram for async_chat_req_t:
Data Fields
csilk_ai_t * ai

AI engine handle.

csilk_ai_chat_async_cb cb

Completion callback.

const csilk_ai_chat_request_t * req

Request parameters (caller-owned).

csilk_ai_chat_response_t res

Response buffer (filled by worker).

int status

Result code from csilk_ai_chat().

void * user_data

Opaque user context for callback.

◆ async_emb_req_t

struct async_emb_req_t

Per-async-embedding-request context passed between the work callback (thread-pool thread) and after-work callback (main loop).

Collaboration diagram for async_emb_req_t:
Data Fields
csilk_ai_t * ai
csilk_ai_embeddings_async_cb cb
size_t count
const char ** input
const char * model
csilk_ai_embeddings_response_t res
int status
void * user_data

Macro Definition Documentation

◆ MAX_DRIVERS

#define MAX_DRIVERS   8

Maximum number of concurrently registered AI drivers.

Function Documentation

◆ _ai_broadcast()

static void _ai_broadcast ( const char *  event,
const char *  model,
int  status,
int  prompt_tokens,
int  completion_tokens,
uint64_t  duration_us,
const char *  error 
)
static

◆ ai_ensure_monitor_init()

static void ai_ensure_monitor_init ( void  )
static

◆ chat_after_work_cb()

static void chat_after_work_cb ( uv_work_t *  req,
int  status 
)
static

libuv after-work callback — delivers the result on the main loop thread via the user's callback, then frees the async context.

◆ chat_work_cb()

static void chat_work_cb ( uv_work_t *  req)
static

libuv thread-pool work callback — runs csilk_ai_chat() off the main loop thread. The response is stored in the heap-allocated async context.

◆ csilk_ai_chat()

int csilk_ai_chat ( csilk_ai_t *  ai,
const csilk_ai_chat_request_t req,
csilk_ai_chat_response_t res 
)

Send a chat completion request with automatic retry on transient failures.

Perform a chat completion.

Algorithm:

  1. Zero out the response struct for each attempt.
  2. Call the driver's chat() implementation.
  3. On success (status == 0), return immediately.
  4. On failure, check the error message for known transient error patterns (CURL transport errors, HTTP 429 rate limit, 502/503 server errors).
  5. If retryable and attempts remain, sleep with exponential backoff (1s, 2s) and retry. Non-retryable errors break immediately.
Parameters
aiAI engine handle (must not be NULL).
reqChat request parameters (model, messages, temperature, tools).
res[out] Receives the chat response, including content, tool calls, and token usage. Zeroed on each retry attempt.
Returns
0 on success, -1 if all retries are exhausted or parameters invalid.
Note
Not thread-safe. The caller must serialize access to the same csilk_ai_t handle.

◆ csilk_ai_chat_async()

void csilk_ai_chat_async ( csilk_ai_t *  ai,
const csilk_ai_chat_request_t req,
csilk_ai_chat_async_cb  cb,
void *  user_data 
)

Send a chat completion request asynchronously on the libuv thread pool.

Perform an asynchronous chat completion.

Allocates a work request and an async context on the heap, queues the work via uv_queue_work(), and returns immediately. The callback fires on the main loop thread after the driver's chat() completes. The response is valid only during the callback invocation.

Ownership: The caller retains ownership of req. The response res is owned by the async context and is freed after the callback returns. If the caller needs the response beyond the callback, it must deep-copy it.

Parameters
aiAI engine handle.
reqChat request (must remain valid until the callback fires).
cbCompletion callback (required).
user_dataOpaque pointer passed through to the callback.
Note
Thread-safe to call from any thread (libuv queues the work). On allocation failure, this is a silent no-op.

◆ csilk_ai_chat_response_free()

void csilk_ai_chat_response_free ( csilk_ai_chat_response_t res)

Free all dynamically allocated fields in a chat response. Frees content, tool calls (with their id/name/arguments), raw_response, and error_message. Does NOT free the struct itself.

Free a chat response structure.

Parameters
resResponse struct to clean (may be NULL). Safe to call on a zero-initialized struct.

◆ csilk_ai_context_add()

void csilk_ai_context_add ( csilk_ai_context_t ctx,
const char *  role,
const char *  content 
)

Add a message to the conversation context with sliding-window eviction.

Add a message to the context.

Algorithm:

  1. If the internal message array is full, double its capacity.
  2. If max_history > 0 and count >= max_history, remove the oldest message (free its role and content strings, shift remaining messages left via memmove, decrement count).
  3. strdup the role and content, append to the message array.
Parameters
ctxConversation context.
roleMessage role (e.g., "user", "assistant", "system").
contentMessage content text.
Note
The role and content strings are deep-copied internally.

◆ csilk_ai_context_clear()

void csilk_ai_context_clear ( csilk_ai_context_t ctx)

Clear all messages from the conversation context. Frees each message's role and content strings and resets the message count to zero. The internal array capacity is preserved to avoid repeated reallocation.

Clear all messages from the context.

Parameters
ctxConversation context (may be NULL).

◆ csilk_ai_context_free()

void csilk_ai_context_free ( csilk_ai_context_t ctx)

Free a conversation context and all associated resources. Clears messages, frees the message array, and frees the context struct.

Free a conversation context.

Parameters
ctxContext to free (may be NULL).

◆ csilk_ai_context_new()

csilk_ai_context_t * csilk_ai_context_new ( size_t  max_history)

Create a new conversation context with sliding-window history.

Initialize a new conversation context.

Allocates a context struct that manages a rolling window of message history. When max_history messages are reached, the oldest message is evicted on each new add().

Parameters
max_historyMaximum number of messages to retain (0 = unlimited).
Returns
A new context handle, or NULL on allocation failure.
Note
The caller must free the handle with csilk_ai_context_free().

◆ csilk_ai_embeddings()

int csilk_ai_embeddings ( csilk_ai_t *  ai,
const char *  model,
const char **  input,
size_t  count,
csilk_ai_embeddings_response_t res 
)

Generate embeddings for a batch of input strings.

Generate embeddings for the given input strings.

Checks that the driver supports embeddings (optional operation), then delegates to the driver's embeddings() implementation.

Parameters
aiAI engine handle.
modelModel name (e.g., "text-embedding-3-small").
inputArray of input strings to embed.
countNumber of input strings.
res[out] Receives the embeddings values and error message.
Returns
0 on success, -1 if the driver lacks embeddings support or parameters are invalid.
Note
Not thread-safe on the same csilk_ai_t handle.

◆ csilk_ai_embeddings_async()

void csilk_ai_embeddings_async ( csilk_ai_t *  ai,
const char *  model,
const char **  input,
size_t  count,
csilk_ai_embeddings_async_cb  cb,
void *  user_data 
)

Generate embeddings asynchronously on the libuv thread pool.

Generate embeddings asynchronously.

Allocates a work request and async context on the heap, queues via uv_queue_work(), and returns immediately. The callback fires on the main loop thread after completion.

Parameters
aiAI engine handle.
modelModel name.
inputArray of input strings (must remain valid until callback).
countNumber of input strings.
cbCompletion callback (required).
user_dataOpaque pointer passed through to callback.
Note
The response is valid only during the callback invocation. Thread-safe to call from any thread.

◆ csilk_ai_embeddings_response_free()

void csilk_ai_embeddings_response_free ( csilk_ai_embeddings_response_t res)

Free dynamically allocated fields in an embeddings response. Frees values array and error_message. Does NOT free the struct itself.

Free an embeddings response structure.

Parameters
resResponse struct to clean (may be NULL).

◆ csilk_ai_free()

void csilk_ai_free ( csilk_ai_t *  ai)

Free an AI engine handle and its driver state. Calls the driver's free() callback first, then frees the handle.

Free an AI handle.

Parameters
aiThe handle to free (may be NULL).

◆ csilk_ai_get_stats()

void csilk_ai_get_stats ( csilk_ai_stats_t stats)

Get current AI engine statistics.

Parameters
stats[out] Pointer to stats struct to populate.

◆ csilk_ai_new()

csilk_ai_t * csilk_ai_new ( const char *  driver_name,
const char *  api_key,
const char *  base_url 
)

Create a new AI engine instance bound to a specific driver backend.

Create a new AI instance with a specific driver.

On the very first call, lazy-initializes the built-in driver registry (OpenAI and Ollama). Subsequent calls reuse the already-registered drivers. If the named driver is not found or its init() fails, returns NULL.

Parameters
driver_nameBackend name (e.g., "openai", "ollama").
api_keyAPI key for authentication (e.g., OpenAI API key).
base_urlOptional custom base URL (NULL for driver default).
Returns
Newly allocated csilk_ai_t, or NULL on failure.
Note
The caller owns the returned handle and must free with csilk_ai_free(). On allocation failure, the driver state is freed internally to avoid leaks.

◆ csilk_ai_ollama_init_driver()

void csilk_ai_ollama_init_driver ( void  )
extern

Register the Ollama driver with the AI subsystem. Called during startup to make "ollama" available to csilk_ai_new().

◆ csilk_ai_openai_init_driver()

void csilk_ai_openai_init_driver ( void  )
extern

Register the OpenAI driver with the AI subsystem. Called during startup to make "openai" available to csilk_ai_new().

◆ csilk_ai_register_driver()

void csilk_ai_register_driver ( const csilk_ai_driver_t driver)

Register an AI driver implementation in the global registry. Called during driver module initialization (e.g., csilk_ai_openai_init_driver()). Silently ignores registration if the registry is full.

Register a new AI driver.

Parameters
driverDriver vtable with name, init, chat, embeddings, free.

◆ csilk_ai_register_monitor()

void csilk_ai_register_monitor ( void *  c)

Register a WebSocket monitor for real-time AI events.

Parameters
cFramework context (WebSocket connection).

◆ csilk_ai_stats_to_json()

char * csilk_ai_stats_to_json ( const csilk_ai_stats_t stats)

Convert AI statistics to a JSON string.

Parameters
statsPointer to stats struct.
Returns
Heap-allocated JSON string (must be freed).

◆ emb_after_work_cb()

static void emb_after_work_cb ( uv_work_t *  req,
int  status 
)
static

libuv after-work callback for async embeddings — delivers the result on the main loop thread via the user's callback, then frees the async context.

◆ emb_work_cb()

static void emb_work_cb ( uv_work_t *  req)
static

libuv thread-pool work callback for async embeddings. Runs csilk_ai_embeddings() off the main loop thread, storing the result in the heap-allocated async context.

◆ find_driver()

static const csilk_ai_driver_t * find_driver ( const char *  name)
static

Linear search of the global driver registry by name.

Parameters
nameDriver name (e.g., "openai", "ollama").
Returns
Pointer to the driver vtable, or NULL if not found.

Variable Documentation

◆ ai_completion_tokens

atomic_uint_fast64_t ai_completion_tokens = 0
static

◆ ai_duration_us_total

atomic_uint_fast64_t ai_duration_us_total = 0
static

◆ ai_errors_total

atomic_uint_fast64_t ai_errors_total = 0
static

◆ ai_prompt_tokens

atomic_uint_fast64_t ai_prompt_tokens = 0
static

◆ ai_requests_total

atomic_uint_fast64_t ai_requests_total = 0
static

◆ ai_tokens_total

atomic_uint_fast64_t ai_tokens_total = 0
static

◆ g_ai_monitor_count

size_t g_ai_monitor_count = 0
static

◆ g_ai_monitor_init

int g_ai_monitor_init = 0
static

◆ g_ai_monitor_mutex

uv_mutex_t g_ai_monitor_mutex
static

◆ g_ai_monitors

csilk_ctx_t* g_ai_monitors[16]
static

◆ g_driver_count

size_t g_driver_count = 0
static

◆ g_drivers

const csilk_ai_driver_t* g_drivers[MAX_DRIVERS]
static

Global registry of AI driver implementations. Populated once during the first csilk_ai_new() call via lazy init of the built-in drivers (OpenAI, Ollama). Not thread-safe for concurrent registration — all registration happens during the single-threaded startup phase.