summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2026-02-13 18:07:45 +0100
committerMitja Felicijan <mitja.felicijan@gmail.com>2026-02-13 18:07:45 +0100
commit2a6fd554998c64733e2d97aecf653f6e48e0f8b4 (patch)
treef53c2f0b4dc42825f426ada0ebb591dbb80c89e9
parenta1a595a3305727d30e16e856f4faf95980643e1c (diff)
downloadllmnpc-2a6fd554998c64733e2d97aecf653f6e48e0f8b4.tar.gz
Store context documents to Vector Database
-rw-r--r--.gitignore4
-rw-r--r--Makefile13
-rw-r--r--context.c174
-rw-r--r--minunit.h391
-rw-r--r--models.h110
-rw-r--r--nonstd.h835
-rw-r--r--prompt.c11
-rw-r--r--vectordb.c118
-rw-r--r--vectordb.h3
9 files changed, 1581 insertions, 78 deletions
diff --git a/.gitignore b/.gitignore
index b24bdcc..e84887f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,9 @@
# Build artefacts
-prompt
models/
+prompt
+context
# Other files
.DS_Store
*.log
+*.vdb
diff --git a/Makefile b/Makefile
index 94f009d..537ea3b 100644
--- a/Makefile
+++ b/Makefile
@@ -12,24 +12,27 @@ LDFLAGS = -L$(LLAMA_DIR)/build/src -L$(LLAMA_DIR)/build/ggml/src \
help: .help
-prompt: prompt.c vectordb.c models.h # Build prompt binary for testing
+build/prompt: prompt.c vectordb.c models.h # Build prompt binary for testing
$(CC) $(CFLAGS) prompt.c vectordb.c -o prompt $(LDFLAGS)
-llamacpp: .assure # Build llama.cpp libraries
+build/context: context.c vectordb.c models.h # Build context binary for testing
+ $(CC) $(CFLAGS) context.c vectordb.c -o context $(LDFLAGS)
+
+build/llama.cpp: .assure # Build llama.cpp libraries
mkdir $(LLAMA_DIR)/build && \
cd $(LLAMA_DIR)/build && \
cmake ../ -DBUILD_SHARED_LIBS=OFF && \
make -j8
-fetchmodels: .assure # Fetch GGUF models
+run/fetch-models: .assure # Fetch GGUF models
-mkdir -p models
cd models && wget -nc -i ../models.txt
-docker: .assure # Runs prompt in Docker container
+run/docker: .assure # Runs prompt in Docker container
docker build -t promptd .
docker run -it promptd
-clean: # Cleans up all the build artefacts
+run/clean: # Cleans up all the build artefacts
-rm -f prompt
cd $(LLAMA_DIR)/build && make clean
-rm -Rf $(LLAMA_DIR)/build
diff --git a/context.c b/context.c
new file mode 100644
index 0000000..9b9770c
--- /dev/null
+++ b/context.c
@@ -0,0 +1,174 @@
+#include "llama.h"
+#include "vectordb.h"
+#include "models.h"
+
+#define NONSTD_IMPLEMENTATION
+#include "nonstd.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+
+#define MAX_TOKENS 512
+#define MAX_TOKEN_LEN 32
+
+typedef struct {
+
+} Engine;
+
+static void llama_log_callback(enum ggml_log_level level, const char *text, void *user_data) {
+ (void)level;
+ (void)user_data;
+ (void)text;
+}
+
+void list_available_models() {
+ printf("Model list:\n");
+ ModelConfig model;
+ static_foreach(ModelConfig, model, models) {
+ printf(" - %s [ctx: %d, temp: %f]\n", model.name, model.n_ctx, model.temperature);
+ }
+}
+
+static void show_help(const char *prog) {
+ printf("Usage: %s [OPTIONS]\n", prog);
+ printf("Options:\n");
+ printf(" -m, --model <name> Specify model to use (default: first model)\n");
+ printf(" -i, --in <file> Specify input context file\n");
+ printf(" -o, --out <file> Specify output vector database file\n");
+ printf(" -l, --list Lists all available models\n");
+ printf(" -v, --verbose Enable verbose logging\n");
+ printf(" -h, --help Show this help message\n");
+}
+
+int main(int argc, char **argv) {
+ set_log_level(LOG_DEBUG);
+
+ const char *model_name = NULL;
+ const char *in_file = NULL;
+ const char *out_file = NULL;
+ int list_models = 0;
+ int verbose = 0;
+
+ static struct option long_options[] = {
+ {"model", required_argument, 0, 'm'},
+ {"in", required_argument, 0, 'i'},
+ {"out", required_argument, 0, 'o'},
+ {"list", no_argument, 0, 'l'},
+ {"verbose", no_argument, 0, 'v'},
+ {"help", no_argument, 0, 'h'},
+ {0, 0, 0, 0}
+ };
+
+ int opt;
+ int option_index = 0;
+ while ((opt = getopt_long(argc, argv, "m:i:o:lvh", long_options, &option_index)) != -1) {
+ switch (opt) {
+ case 'm':
+ model_name = optarg;
+ break;
+ case 'i':
+ in_file = optarg;
+ break;
+ case 'o':
+ out_file = optarg;
+ break;
+ case 'l':
+ list_models = 1;
+ break;
+ case 'v':
+ verbose = 1;
+ break;
+ case 'h':
+ show_help(argv[0]);
+ return 0;
+ default:
+ fprintf(stderr, "Usage: %s [-m model] [-i file] [-o file] [-lvh]\n", argv[0]);
+ return 1;
+ }
+ }
+
+ if (verbose == 0) {
+ llama_log_set(llama_log_callback, NULL);
+ }
+
+ if (list_models == 1) {
+ list_available_models();
+ return 0;
+ }
+
+ if (in_file == NULL) {
+ log_message(stderr, LOG_ERROR, "Input context file must be provided. Exiting...");
+ return 1;
+ }
+
+ if (out_file == NULL) {
+ log_message(stderr, LOG_ERROR, "Output vector context file must be provided. Exiting...");
+ return 1;
+ }
+
+ llama_backend_init();
+
+ const ModelConfig *cfg = NULL;
+ if (model_name != NULL) {
+ cfg = get_model_by_name(model_name);
+ if (cfg == NULL) {
+ log_message(stderr, LOG_ERROR, "Unknown model '%s'", model_name);
+ llama_backend_free();
+ return 1;
+ }
+ } else {
+ cfg = &models[0];
+ }
+
+ struct llama_model *model = llama_model_load_from_file(cfg->filepath, llama_model_default_params());
+ if (model == NULL) {
+ log_message(stderr, LOG_ERROR, "Unable to load embedding model");
+ llama_backend_free();
+ return 1;
+ }
+
+ struct llama_context_params cparams = llama_context_default_params();
+ cparams.embeddings = true;
+
+ struct llama_context *embed_ctx = llama_init_from_model(model, cparams);
+ if (embed_ctx == NULL) {
+ log_message(stderr, LOG_ERROR, "Failed to create embedding context");
+ llama_model_free(model);
+ llama_backend_free();
+ return 1;
+ }
+
+ FILE *context_fp = fopen(in_file, "r");
+ if (context_fp == NULL) {
+ log_message(stderr, LOG_ERROR, "Unable to open context file %s", in_file);
+ return 1;
+ }
+
+ VectorDB db;
+ vdb_init(&db, embed_ctx);
+
+ char line[1024];
+ while (fgets(line, sizeof(line), context_fp) != NULL) {
+ size_t len = strlen(line);
+ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) {
+ line[len - 1] = '\0';
+ len--;
+ }
+ if (len == 0) {
+ continue;
+ }
+ vdb_add_document(&db, line);
+ }
+
+ if (vdb_save(&db, out_file) > 0) {
+ log_message(stderr, LOG_ERROR, "Something went wrong saving file %s", out_file);
+ fclose(context_fp);
+ return 1;
+ }
+
+ log_message(stdout, LOG_INFO, "Context vector database file %s successfully written", out_file);
+ fclose(context_fp);
+ return 0;
+}
diff --git a/minunit.h b/minunit.h
new file mode 100644
index 0000000..5e89245
--- /dev/null
+++ b/minunit.h
@@ -0,0 +1,391 @@
+/*
+ * Copyright (c) 2012 David SiƱuela Pastor, siu.4coders@gmail.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef MINUNIT_MINUNIT_H
+#define MINUNIT_MINUNIT_H
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#if defined(_WIN32)
+#include <Windows.h>
+#if defined(_MSC_VER) && _MSC_VER < 1900
+ #define snprintf _snprintf
+ #define __func__ __FUNCTION__
+#endif
+
+#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
+
+/* Change POSIX C SOURCE version for pure c99 compilers */
+#if !defined(_POSIX_C_SOURCE) || _POSIX_C_SOURCE < 200112L
+#undef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200112L
+#endif
+
+#include <unistd.h> /* POSIX flags */
+#include <time.h> /* clock_gettime(), time() */
+#include <sys/time.h> /* gethrtime(), gettimeofday() */
+#include <sys/resource.h>
+#include <sys/times.h>
+#include <string.h>
+
+#if defined(__MACH__) && defined(__APPLE__)
+#include <mach/mach.h>
+#include <mach/mach_time.h>
+#endif
+
+#if __GNUC__ >= 5 && !defined(__STDC_VERSION__)
+#define __func__ __extension__ __FUNCTION__
+#endif
+
+#else
+#error "Unable to define timers for an unknown OS."
+#endif
+
+#include <stdio.h>
+#include <math.h>
+
+/* Maximum length of last message */
+#define MINUNIT_MESSAGE_LEN 1024
+/* Accuracy with which floats are compared */
+#define MINUNIT_EPSILON 1E-12
+
+/* Misc. counters */
+static int minunit_run = 0;
+static int minunit_assert = 0;
+static int minunit_fail = 0;
+static int minunit_status = 0;
+
+/* Timers */
+static double minunit_real_timer = 0;
+static double minunit_proc_timer = 0;
+
+/* Last message */
+static char minunit_last_message[MINUNIT_MESSAGE_LEN];
+
+/* Test setup and teardown function pointers */
+static void (*minunit_setup)(void) = NULL;
+static void (*minunit_teardown)(void) = NULL;
+
+/* Definitions */
+#define MU_TEST(method_name) static void method_name(void)
+#define MU_TEST_SUITE(suite_name) static void suite_name(void)
+
+#define MU__SAFE_BLOCK(block) do {\
+ block\
+} while(0)
+
+/* Run test suite and unset setup and teardown functions */
+#define MU_RUN_SUITE(suite_name) MU__SAFE_BLOCK(\
+ suite_name();\
+ minunit_setup = NULL;\
+ minunit_teardown = NULL;\
+)
+
+/* Configure setup and teardown functions */
+#define MU_SUITE_CONFIGURE(setup_fun, teardown_fun) MU__SAFE_BLOCK(\
+ minunit_setup = setup_fun;\
+ minunit_teardown = teardown_fun;\
+)
+
+/* Test runner */
+#define MU_RUN_TEST(test) MU__SAFE_BLOCK(\
+ if (minunit_real_timer==0 && minunit_proc_timer==0) {\
+ minunit_real_timer = mu_timer_real();\
+ minunit_proc_timer = mu_timer_cpu();\
+ }\
+ if (minunit_setup) (*minunit_setup)();\
+ minunit_status = 0;\
+ test();\
+ minunit_run++;\
+ if (minunit_status) {\
+ minunit_fail++;\
+ printf("F");\
+ printf("\n%s\n", minunit_last_message);\
+ }\
+ (void)fflush(stdout);\
+ if (minunit_teardown) (*minunit_teardown)();\
+)
+
+/* Report */
+#define MU_REPORT() MU__SAFE_BLOCK(\
+ double minunit_end_real_timer;\
+ double minunit_end_proc_timer;\
+ printf("\n\n%d tests, %d assertions, %d failures\n", minunit_run, minunit_assert, minunit_fail);\
+ minunit_end_real_timer = mu_timer_real();\
+ minunit_end_proc_timer = mu_timer_cpu();\
+ printf("\nFinished in %.8f seconds (real) %.8f seconds (proc)\n\n",\
+ minunit_end_real_timer - minunit_real_timer,\
+ minunit_end_proc_timer - minunit_proc_timer);\
+)
+#define MU_EXIT_CODE minunit_fail
+
+/* Assertions */
+#define mu_check(test) MU__SAFE_BLOCK(\
+ minunit_assert++;\
+ if (!(test)) {\
+ (void)snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, #test);\
+ minunit_status = 1;\
+ return;\
+ } else {\
+ printf(".");\
+ }\
+)
+
+#define mu_fail(message) MU__SAFE_BLOCK(\
+ minunit_assert++;\
+ (void)snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, message);\
+ minunit_status = 1;\
+ return;\
+)
+
+#define mu_assert(test, message) MU__SAFE_BLOCK(\
+ minunit_assert++;\
+ if (!(test)) {\
+ (void)snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, message);\
+ minunit_status = 1;\
+ return;\
+ } else {\
+ printf(".");\
+ }\
+)
+
+#define mu_assert_int_eq(expected, result) MU__SAFE_BLOCK(\
+ int minunit_tmp_e;\
+ int minunit_tmp_r;\
+ minunit_assert++;\
+ minunit_tmp_e = (expected);\
+ minunit_tmp_r = (result);\
+ if (minunit_tmp_e != minunit_tmp_r) {\
+ (void)snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %d expected but was %d", __func__, __FILE__, __LINE__, minunit_tmp_e, minunit_tmp_r);\
+ minunit_status = 1;\
+ return;\
+ } else {\
+ printf(".");\
+ }\
+)
+
+#define mu_assert_double_eq(expected, result) MU__SAFE_BLOCK(\
+ double minunit_tmp_e;\
+ double minunit_tmp_r;\
+ minunit_assert++;\
+ minunit_tmp_e = (expected);\
+ minunit_tmp_r = (result);\
+ if (fabs(minunit_tmp_e-minunit_tmp_r) > MINUNIT_EPSILON) {\
+ int minunit_significant_figures = 1 - log10(MINUNIT_EPSILON);\
+ (void)snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %.*g expected but was %.*g", __func__, __FILE__, __LINE__, minunit_significant_figures, minunit_tmp_e, minunit_significant_figures, minunit_tmp_r);\
+ minunit_status = 1;\
+ return;\
+ } else {\
+ printf(".");\
+ }\
+)
+
+#define mu_assert_string_eq(expected, result) MU__SAFE_BLOCK(\
+ const char* minunit_tmp_e = expected;\
+ const char* minunit_tmp_r = result;\
+ minunit_assert++;\
+ if (!minunit_tmp_e) {\
+ minunit_tmp_e = "<null pointer>";\
+ }\
+ if (!minunit_tmp_r) {\
+ minunit_tmp_r = "<null pointer>";\
+ }\
+ if(strcmp(minunit_tmp_e, minunit_tmp_r) != 0) {\
+ (void)snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: '%s' expected but was '%s'", __func__, __FILE__, __LINE__, minunit_tmp_e, minunit_tmp_r);\
+ minunit_status = 1;\
+ return;\
+ } else {\
+ printf(".");\
+ }\
+)
+
+/*
+ * The following two functions were written by David Robert Nadeau
+ * from http://NadeauSoftware.com/ and distributed under the
+ * Creative Commons Attribution 3.0 Unported License
+ */
+
+/**
+ * Returns the real time, in seconds, or -1.0 if an error occurred.
+ *
+ * Time is measured since an arbitrary and OS-dependent start time.
+ * The returned real time is only useful for computing an elapsed time
+ * between two calls to this function.
+ */
+static double mu_timer_real(void)
+{
+#if defined(_WIN32)
+ /* Windows 2000 and later. ---------------------------------- */
+ LARGE_INTEGER Time;
+ LARGE_INTEGER Frequency;
+
+ QueryPerformanceFrequency(&Frequency);
+ QueryPerformanceCounter(&Time);
+
+ Time.QuadPart *= 1000000;
+ Time.QuadPart /= Frequency.QuadPart;
+
+ return (double)Time.QuadPart / 1000000.0;
+
+#elif (defined(__hpux) || defined(hpux)) || ((defined(__sun__) || defined(__sun) || defined(sun)) && (defined(__SVR4) || defined(__svr4__)))
+ /* HP-UX, Solaris. ------------------------------------------ */
+ return (double)gethrtime( ) / 1000000000.0;
+
+#elif defined(__MACH__) && defined(__APPLE__)
+ /* OSX. ----------------------------------------------------- */
+ static double timeConvert = 0.0;
+ if ( timeConvert == 0.0 )
+ {
+ mach_timebase_info_data_t timeBase;
+ (void)mach_timebase_info( &timeBase );
+ timeConvert = (double)timeBase.numer /
+ (double)timeBase.denom /
+ 1000000000.0;
+ }
+ return (double)mach_absolute_time( ) * timeConvert;
+
+#elif defined(_POSIX_VERSION)
+ /* POSIX. --------------------------------------------------- */
+ struct timeval tm;
+#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)
+ {
+ struct timespec ts;
+#if defined(CLOCK_MONOTONIC_PRECISE)
+ /* BSD. --------------------------------------------- */
+ const clockid_t id = CLOCK_MONOTONIC_PRECISE;
+#elif defined(CLOCK_MONOTONIC_RAW)
+ /* Linux. ------------------------------------------- */
+ const clockid_t id = CLOCK_MONOTONIC_RAW;
+#elif defined(CLOCK_HIGHRES)
+ /* Solaris. ----------------------------------------- */
+ const clockid_t id = CLOCK_HIGHRES;
+#elif defined(CLOCK_MONOTONIC)
+ /* AIX, BSD, Linux, POSIX, Solaris. ----------------- */
+ const clockid_t id = CLOCK_MONOTONIC;
+#elif defined(CLOCK_REALTIME)
+ /* AIX, BSD, HP-UX, Linux, POSIX. ------------------- */
+ const clockid_t id = CLOCK_REALTIME;
+#else
+ const clockid_t id = (clockid_t)-1; /* Unknown. */
+#endif /* CLOCK_* */
+ if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 )
+ return (double)ts.tv_sec +
+ (double)ts.tv_nsec / 1000000000.0;
+ /* Fall thru. */
+ }
+#endif /* _POSIX_TIMERS */
+
+ /* AIX, BSD, Cygwin, HP-UX, Linux, OSX, POSIX, Solaris. ----- */
+ gettimeofday( &tm, NULL );
+ return (double)tm.tv_sec + (double)tm.tv_usec / 1000000.0;
+#else
+ return -1.0; /* Failed. */
+#endif
+}
+
+/**
+ * Returns the amount of CPU time used by the current process,
+ * in seconds, or -1.0 if an error occurred.
+ */
+static double mu_timer_cpu(void)
+{
+#if defined(_WIN32)
+ /* Windows -------------------------------------------------- */
+ FILETIME createTime;
+ FILETIME exitTime;
+ FILETIME kernelTime;
+ FILETIME userTime;
+
+ /* This approach has a resolution of 1/64 second. Unfortunately, Windows' API does not offer better */
+ if ( GetProcessTimes( GetCurrentProcess( ),
+ &createTime, &exitTime, &kernelTime, &userTime ) != 0 )
+ {
+ ULARGE_INTEGER userSystemTime;
+ memcpy(&userSystemTime, &userTime, sizeof(ULARGE_INTEGER));
+ return (double)userSystemTime.QuadPart / 10000000.0;
+ }
+
+#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
+ /* AIX, BSD, Cygwin, HP-UX, Linux, OSX, and Solaris --------- */
+
+#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)
+ /* Prefer high-res POSIX timers, when available. */
+ {
+ clockid_t id;
+ struct timespec ts;
+#if _POSIX_CPUTIME > 0
+ /* Clock ids vary by OS. Query the id, if possible. */
+ if ( clock_getcpuclockid( 0, &id ) == -1 )
+#endif
+#if defined(CLOCK_PROCESS_CPUTIME_ID)
+ /* Use known clock id for AIX, Linux, or Solaris. */
+ id = CLOCK_PROCESS_CPUTIME_ID;
+#elif defined(CLOCK_VIRTUAL)
+ /* Use known clock id for BSD or HP-UX. */
+ id = CLOCK_VIRTUAL;
+#else
+ id = (clockid_t)-1;
+#endif
+ if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 )
+ return (double)ts.tv_sec +
+ (double)ts.tv_nsec / 1000000000.0;
+ }
+#endif
+
+#if defined(RUSAGE_SELF)
+ {
+ struct rusage rusage;
+ if ( getrusage( RUSAGE_SELF, &rusage ) != -1 )
+ return (double)rusage.ru_utime.tv_sec +
+ (double)rusage.ru_utime.tv_usec / 1000000.0;
+ }
+#endif
+
+#if defined(_SC_CLK_TCK)
+ {
+ const double ticks = (double)sysconf( _SC_CLK_TCK );
+ struct tms tms;
+ if ( times( &tms ) != (clock_t)-1 )
+ return (double)tms.tms_utime / ticks;
+ }
+#endif
+
+#if defined(CLOCKS_PER_SEC)
+ {
+ clock_t cl = clock( );
+ if ( cl != (clock_t)-1 )
+ return (double)cl / (double)CLOCKS_PER_SEC;
+ }
+#endif
+
+#endif
+
+ return -1; /* Failed. */
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MINUNIT_MINUNIT_H */
diff --git a/models.h b/models.h
index ac242fc..e296971 100644
--- a/models.h
+++ b/models.h
@@ -6,64 +6,64 @@
#include <string.h>
typedef struct {
- const char *name;
- const char *filepath;
- int n_gpu_layers;
- bool use_mmap;
- int n_ctx;
- int n_batch;
- bool embeddings;
- float temperature;
- float min_p;
- uint32_t seed;
-} model_config;
+ const char *name;
+ const char *filepath;
+ int n_gpu_layers;
+ bool use_mmap;
+ int n_ctx;
+ int n_batch;
+ bool embeddings;
+ float temperature;
+ float min_p;
+ uint32_t seed;
+} ModelConfig;
-model_config models[] = {
- {
- .name = "flan-t5-small",
- .filepath = "models/flan-t5-small.F16.gguf",
- .n_gpu_layers = 0,
- .use_mmap = false,
- .n_ctx = 512,
- .n_batch = 512,
- .embeddings = false,
- .temperature = 0.8f,
- .min_p = 0.05f,
- .seed = LLAMA_DEFAULT_SEED,
- },
- {
- .name = "phi-4-mini-instruct",
- .filepath = "models/Phi-4-mini-instruct.Q2_K.gguf",
- .n_gpu_layers = 0,
- .use_mmap = false,
- .n_ctx = 131072,
- .n_batch = 4096,
- .embeddings = false,
- .temperature = 0.8f,
- .min_p = 0.05f,
- .seed = LLAMA_DEFAULT_SEED,
- },
- {
- .name = "tinyllama-1",
- .filepath = "models/TinyLlama-1.1B-intermediate-step-1431k-3T-Q2_K.gguf",
- .n_gpu_layers = 0,
- .use_mmap = false,
- .n_ctx = 2048,
- .n_batch = 4096,
- .embeddings = false,
- .temperature = 0.8f,
- .min_p = 0.05f,
- .seed = LLAMA_DEFAULT_SEED,
- },
+ModelConfig models[] = {
+ {
+ .name = "tinyllama-1",
+ .filepath = "models/TinyLlama-1.1B-intermediate-step-1431k-3T-Q2_K.gguf",
+ .n_gpu_layers = 0,
+ .use_mmap = false,
+ .n_ctx = 2048,
+ .n_batch = 4096,
+ .embeddings = false,
+ .temperature = 0.8f,
+ .min_p = 0.05f,
+ .seed = LLAMA_DEFAULT_SEED,
+ },
+ {
+ .name = "flan-t5-small",
+ .filepath = "models/flan-t5-small.F16.gguf",
+ .n_gpu_layers = 0,
+ .use_mmap = false,
+ .n_ctx = 512,
+ .n_batch = 512,
+ .embeddings = false,
+ .temperature = 0.8f,
+ .min_p = 0.05f,
+ .seed = LLAMA_DEFAULT_SEED,
+ },
+ {
+ .name = "phi-4-mini-instruct",
+ .filepath = "models/Phi-4-mini-instruct.Q2_K.gguf",
+ .n_gpu_layers = 0,
+ .use_mmap = false,
+ .n_ctx = 131072,
+ .n_batch = 4096,
+ .embeddings = false,
+ .temperature = 0.8f,
+ .min_p = 0.05f,
+ .seed = LLAMA_DEFAULT_SEED,
+ },
};
-const model_config *get_model_by_name(const char *name) {
- for (size_t i = 0; i < sizeof(models) / sizeof(models[0]); i++) {
- if (models[i].name != NULL && strcmp(models[i].name, name) == 0) {
- return &models[i];
- }
- }
- return NULL;
+const ModelConfig *get_model_by_name(const char *name) {
+ for (size_t i = 0; i < sizeof(models) / sizeof(models[0]); i++) {
+ if (models[i].name != NULL && strcmp(models[i].name, name) == 0) {
+ return &models[i];
+ }
+ }
+ return NULL;
}
#endif
diff --git a/nonstd.h b/nonstd.h
new file mode 100644
index 0000000..146b12d
--- /dev/null
+++ b/nonstd.h
@@ -0,0 +1,835 @@
+// nonstd.h
+// A collection of useful functions and macros.
+// This library is licensed under the BSD 2-Clause License.
+//
+// This file provides both the interface and the implementation.
+// To instantiate the implementation,
+// #define NONSTD_IMPLEMENTATION
+// before including this file.
+
+#ifdef NONSTD_IMPLEMENTATION
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+#endif
+
+#ifndef NONSTD_H
+#define NONSTD_H
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifndef NONSTD_DEF
+#ifdef NONSTD_STATIC
+#define NONSTD_DEF static
+#else
+#define NONSTD_DEF extern
+#endif
+#endif
+
+typedef int8_t i8;
+typedef uint8_t u8;
+typedef int16_t i16;
+typedef uint16_t u16;
+typedef int32_t i32;
+typedef uint32_t u32;
+typedef int64_t i64;
+typedef uint64_t u64;
+typedef unsigned int uint;
+typedef unsigned long ulong;
+typedef intptr_t isize;
+typedef uintptr_t usize;
+typedef char c8;
+
+#define countof(a) (sizeof(a) / sizeof((a)[0]))
+
+#define static_foreach(type, var, array) \
+ for (size_t _i_##var = 0, _n_##var = countof(array); \
+ _i_##var < _n_##var && ((var) = (array)[_i_##var], 1); ++_i_##var)
+
+#define ALLOC(type, n) ((type *)safe_malloc(sizeof(type), (n)))
+#define REALLOC(ptr, type, n) ((type *)safe_realloc((ptr), sizeof(type), (n)))
+#define FREE(ptr) \
+ do { \
+ free(ptr); \
+ ptr = NULL; \
+ } while (0)
+
+NONSTD_DEF void *safe_malloc(size_t item_size, size_t count);
+NONSTD_DEF void *safe_realloc(void *ptr, size_t item_size, size_t count);
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define CLAMP(x, lo, hi) (MIN((hi), MAX((lo), (x))))
+
+// From https://github.com/tsoding/nob.h/blob/e2c9a46f01d052ab740140e74453665dc3334832/nob.h#L205-L206.
+#define UNUSED(value) (void)(value)
+#define TODO(message) \
+ do { \
+ fprintf(stderr, "%s:%d: TODO: %s\n", __FILE__, __LINE__, message); \
+ abort(); \
+ } while (0)
+#define UNREACHABLE(message) \
+ do { \
+ fprintf(stderr, "%s:%d: UNREACHABLE: %s\n", __FILE__, __LINE__, message); \
+ abort(); \
+ } while (0)
+
+#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
+#define STATIC_ASSERT(expr, msg) _Static_assert((expr), msg)
+#else
+#define STATIC_ASSERT(expr, msg) \
+ typedef char static_assertion_##msg[(expr) ? 1 : -1]
+#endif
+
+// String view - read-only, non-owning reference to a string
+typedef struct {
+ const char *data;
+ size_t length;
+} stringv;
+
+NONSTD_DEF stringv sv_from_cstr(const char *s);
+NONSTD_DEF stringv sv_from_parts(const char *data, size_t length);
+NONSTD_DEF stringv sv_slice(stringv sv, size_t start, size_t end);
+NONSTD_DEF int sv_equals(stringv a, stringv b);
+NONSTD_DEF int sv_starts_with(stringv sv, stringv prefix);
+NONSTD_DEF int sv_ends_with(stringv sv, stringv suffix);
+
+// String builder - owning, mutable, dynamically growing string buffer
+typedef struct {
+ char *data;
+ size_t length;
+ size_t capacity;
+} stringb;
+
+NONSTD_DEF void sb_init(stringb *sb, size_t initial_cap);
+NONSTD_DEF void sb_free(stringb *sb);
+NONSTD_DEF void sb_ensure(stringb *sb, size_t additional);
+NONSTD_DEF void sb_append_cstr(stringb *sb, const char *s);
+NONSTD_DEF void sb_append_sv(stringb *sb, stringv sv);
+NONSTD_DEF void sb_append_char(stringb *sb, char c);
+NONSTD_DEF stringv sb_as_sv(const stringb *sb);
+
+// Slice - generic non-owning view into an array
+// Usage: SLICE_DEF(int); slice(int) view = ...;
+#define SLICE_DEF(T) \
+ typedef struct { \
+ T *data; \
+ size_t length; \
+ } slice_##T
+
+#define slice(T) slice_##T
+
+#define make_slice(T, ptr, len) ((slice(T)){.data = (ptr), .length = (len)})
+
+#define array_as_slice(T, arr) \
+ ((slice(T)){.data = (arr).data, .length = (arr).length})
+
+// Dynamic array - generic type-safe growable array using macros
+// Usage: array(int) numbers; array_init(numbers);
+#define array(T) \
+ struct { \
+ T *data; \
+ size_t length; \
+ size_t capacity; \
+ }
+
+#define array_init(arr) \
+ do { \
+ (arr).capacity = 0; \
+ (arr).data = NULL; \
+ (arr).length = 0; \
+ } while (0)
+
+#define array_init_cap(arr, initial_cap) \
+ do { \
+ (arr).capacity = (initial_cap) ? (initial_cap) : 16; \
+ (arr).data = ALLOC(__typeof__(*(arr).data), (arr).capacity); \
+ (arr).length = 0; \
+ } while (0)
+
+#define array_free(arr) \
+ do { \
+ FREE((arr).data); \
+ (arr).length = 0; \
+ (arr).capacity = 0; \
+ } while (0)
+
+#define array_ensure(arr, additional) \
+ do { \
+ size_t _needed = (arr).length + (additional); \
+ if (_needed > (arr).capacity) { \
+ size_t _new_cap = (arr).capacity ? (arr).capacity : 16; \
+ while (_new_cap < _needed) { \
+ if (_new_cap > SIZE_MAX / 2) { \
+ _new_cap = SIZE_MAX; \
+ break; \
+ } \
+ _new_cap *= 2; \
+ } \
+ if (_new_cap < _needed) { /* Overflow or OOM */ \
+ break; \
+ } \
+ void *_new_data = \
+ safe_realloc((arr).data, sizeof(*(arr).data), _new_cap); \
+ if (_new_data) { \
+ (arr).data = _new_data; \
+ (arr).capacity = _new_cap; \
+ } \
+ } \
+ } while (0)
+
+#define array_push(arr, value) \
+ do { \
+ array_ensure((arr), 1); \
+ if ((arr).length < (arr).capacity) { \
+ (arr).data[(arr).length++] = (value); \
+ } \
+ } while (0)
+
+#define array_pop(arr) ((arr).length > 0 ? (arr).data[--(arr).length] : 0)
+
+#define array_get(arr, index) ((arr).data[index])
+
+#define array_set(arr, index, value) \
+ do { \
+ if ((index) < (arr).length) { \
+ (arr).data[index] = (value); \
+ } \
+ } while (0)
+
+#define array_insert(arr, index, value) \
+ do { \
+ if ((index) <= (arr).length) { \
+ array_ensure((arr), 1); \
+ if ((arr).length < (arr).capacity) { \
+ for (size_t _i = (arr).length; _i > (index); --_i) { \
+ (arr).data[_i] = (arr).data[_i - 1]; \
+ } \
+ (arr).data[index] = (value); \
+ (arr).length++; \
+ } \
+ } \
+ } while (0)
+
+#define array_remove(arr, index) \
+ do { \
+ if ((index) < (arr).length) { \
+ for (size_t _i = (index); _i < (arr).length - 1; ++_i) { \
+ (arr).data[_i] = (arr).data[_i + 1]; \
+ } \
+ (arr).length--; \
+ } \
+ } while (0)
+
+#define array_clear(arr) \
+ do { \
+ (arr).length = 0; \
+ } while (0)
+
+#define array_reserve(arr, new_capacity) \
+ do { \
+ if ((new_capacity) > (arr).capacity) { \
+ void *_new_data = \
+ safe_realloc((arr).data, sizeof(*(arr).data), (new_capacity)); \
+ if (_new_data) { \
+ (arr).data = _new_data; \
+ (arr).capacity = (new_capacity); \
+ } \
+ } \
+ } while (0)
+
+#define array_foreach(arr, var) \
+ for (size_t _i_##var = 0; \
+ _i_##var < (arr).length && ((var) = (arr).data[_i_##var], 1); \
+ ++_i_##var)
+
+#define array_foreach_idx(arr, var, index) \
+ for (size_t index = 0; \
+ index < (arr).length && ((var) = (arr).data[index], 1); ++index)
+
+// Arena - block-based memory allocator
+typedef struct {
+ char *ptr;
+ char *end;
+ array(char *) blocks;
+} Arena;
+
+#define ARENA_DEFAULT_BLOCK_SIZE (4096)
+
+NONSTD_DEF Arena arena_make(void);
+NONSTD_DEF void arena_grow(Arena *a, size_t min_size);
+NONSTD_DEF void *arena_alloc(Arena *a, size_t size);
+NONSTD_DEF void arena_free(Arena *a);
+
+// Image - simple RGB image structure
+typedef struct {
+ u8 r, g, b;
+} Color;
+
+typedef struct {
+ u32 width;
+ u32 height;
+ Color *pixels;
+} Canvas;
+
+#define COLOR_RGB(r, g, b) ((Color){(u8)(r), (u8)(g), (u8)(b)})
+#define COLOR_HEX(hex) ((Color){(u8)(((hex) >> 16) & 0xFF), (u8)(((hex) >> 8) & 0xFF), (u8)((hex) & 0xFF)})
+
+#define COLOR_BLACK COLOR_RGB(0, 0, 0)
+#define COLOR_WHITE COLOR_RGB(255, 255, 255)
+#define COLOR_RED COLOR_RGB(255, 0, 0)
+#define COLOR_GREEN COLOR_RGB(0, 255, 0)
+#define COLOR_BLUE COLOR_RGB(0, 0, 255)
+#define COLOR_YELLOW COLOR_RGB(255, 255, 0)
+#define COLOR_MAGENTA COLOR_RGB(255, 0, 255)
+#define COLOR_CYAN COLOR_RGB(0, 255, 255)
+
+NONSTD_DEF Canvas ppm_init(u32 width, u32 height);
+NONSTD_DEF void ppm_free(Canvas *img);
+NONSTD_DEF void ppm_set_pixel(Canvas *img, u32 x, u32 y, Color color);
+NONSTD_DEF Color ppm_get_pixel(const Canvas *img, u32 x, u32 y);
+NONSTD_DEF int ppm_save(const Canvas *img, const char *filename);
+NONSTD_DEF Canvas ppm_read(const char *filename);
+NONSTD_DEF void ppm_fill(Canvas *canvas, Color color);
+NONSTD_DEF void ppm_draw_rect(Canvas *canvas, u32 x, u32 y, u32 w, u32 h, Color color);
+NONSTD_DEF void ppm_draw_line(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, Color color);
+NONSTD_DEF void ppm_draw_circle(Canvas *canvas, i32 x, i32 y, i32 r, Color color);
+NONSTD_DEF void ppm_draw_triangle(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, i32 x2, i32 y2, Color color);
+
+// File I/O helpers
+NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size);
+NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size);
+NONSTD_DEF stringb read_entire_file_sb(const char *filepath);
+NONSTD_DEF int write_file_sv(const char *filepath, stringv sv);
+NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb);
+
+// Logging
+typedef enum {
+ LOG_ERROR,
+ LOG_WARN,
+ LOG_INFO,
+ LOG_DEBUG,
+} LogLevel;
+
+NONSTD_DEF void set_log_level(LogLevel level);
+NONSTD_DEF LogLevel get_log_level_from_env(void);
+NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ...);
+
+#define LOG_INFO_MSG(...) log_message(stdout, LOG_INFO, __VA_ARGS__)
+#define LOG_DEBUG_MSG(...) log_message(stdout, LOG_DEBUG, __VA_ARGS__)
+#define LOG_WARN_MSG(...) log_message(stderr, LOG_WARN, __VA_ARGS__)
+#define LOG_ERROR_MSG(...) log_message(stderr, LOG_ERROR, __VA_ARGS__)
+
+#define COLOR_RESET "\033[0m"
+#define COLOR_INFO "\033[32m"
+#define COLOR_DEBUG "\033[36m"
+#define COLOR_WARNING "\033[33m"
+#define COLOR_ERROR "\033[31m"
+
+#endif // NONSTD_H
+
+#ifdef NONSTD_IMPLEMENTATION
+
+NONSTD_DEF void *safe_malloc(size_t item_size, size_t count) {
+ if (count != 0 && item_size > SIZE_MAX / count) {
+ return NULL;
+ }
+ return malloc(item_size * count);
+}
+
+NONSTD_DEF void *safe_realloc(void *ptr, size_t item_size, size_t count) {
+ if (count != 0 && item_size > SIZE_MAX / count) {
+ return NULL;
+ }
+ return realloc(ptr, item_size * count);
+}
+
+// String View Implementation
+
+NONSTD_DEF stringv sv_from_cstr(const char *s) {
+ return (stringv){.data = s, .length = s ? strlen(s) : 0};
+}
+
+NONSTD_DEF stringv sv_from_parts(const char *data, size_t length) {
+ return (stringv){.data = data, .length = length};
+}
+
+NONSTD_DEF stringv sv_slice(stringv sv, size_t start, size_t end) {
+ if (start > sv.length) {
+ start = sv.length;
+ }
+ if (end > sv.length) {
+ end = sv.length;
+ }
+ if (start > end) {
+ start = end;
+ }
+ return (stringv){.data = sv.data + start, .length = end - start};
+}
+
+NONSTD_DEF int sv_equals(stringv a, stringv b) {
+ return a.length == b.length && (a.length == 0 || memcmp(a.data, b.data, a.length) == 0);
+}
+
+NONSTD_DEF int sv_starts_with(stringv sv, stringv prefix) {
+ return sv.length >= prefix.length && memcmp(sv.data, prefix.data, prefix.length) == 0;
+}
+
+NONSTD_DEF int sv_ends_with(stringv sv, stringv suffix) {
+ return sv.length >= suffix.length && memcmp(sv.data + sv.length - suffix.length, suffix.data, suffix.length) == 0;
+}
+
+// String Builder Implementation
+
+NONSTD_DEF void sb_init(stringb *sb, size_t initial_cap) {
+ sb->capacity = initial_cap ? initial_cap : 16;
+ sb->data = ALLOC(char, sb->capacity);
+ sb->length = 0;
+ if (sb->data) {
+ sb->data[0] = '\0';
+ }
+}
+
+NONSTD_DEF void sb_free(stringb *sb) {
+ FREE(sb->data);
+ sb->length = 0;
+ sb->capacity = 0;
+}
+
+NONSTD_DEF void sb_ensure(stringb *sb, size_t additional) {
+ size_t needed = sb->length + additional + 1;
+ size_t new_cap = sb->capacity;
+
+ if (needed > new_cap) {
+ while (new_cap < needed) {
+ if (new_cap > SIZE_MAX / 2) {
+ new_cap = SIZE_MAX;
+ break;
+ }
+ new_cap *= 2;
+ }
+ if (new_cap < needed)
+ return; // Overflow
+
+ char *new_data = safe_realloc(sb->data, sizeof(char), new_cap);
+ if (new_data) {
+ sb->data = new_data;
+ sb->capacity = new_cap;
+ }
+ }
+}
+
+NONSTD_DEF void sb_append_cstr(stringb *sb, const char *s) {
+ if (!s) {
+ return;
+ }
+ size_t slength = strlen(s);
+ sb_ensure(sb, slength);
+ if (sb->length + slength + 1 <= sb->capacity) {
+ memcpy(sb->data + sb->length, s, slength);
+ sb->length += slength;
+ sb->data[sb->length] = '\0';
+ }
+}
+
+NONSTD_DEF void sb_append_sv(stringb *sb, stringv sv) {
+ if (!sv.data || sv.length == 0) {
+ return;
+ }
+ sb_ensure(sb, sv.length);
+ if (sb->length + sv.length + 1 <= sb->capacity) {
+ memcpy(sb->data + sb->length, sv.data, sv.length);
+ sb->length += sv.length;
+ sb->data[sb->length] = '\0';
+ }
+}
+
+NONSTD_DEF void sb_append_char(stringb *sb, char c) {
+ sb_ensure(sb, 1);
+ if (sb->length + 2 <= sb->capacity) {
+ sb->data[sb->length++] = c;
+ sb->data[sb->length] = '\0';
+ }
+}
+
+NONSTD_DEF stringv sb_as_sv(const stringb *sb) {
+ return (stringv){.data = sb->data, .length = sb->length};
+}
+
+NONSTD_DEF Arena arena_make(void) {
+ Arena a = {0};
+ array_init(a.blocks);
+ return a;
+}
+
+// Arena Implementation
+
+NONSTD_DEF void arena_grow(Arena *a, size_t min_size) {
+ size_t size = MAX(ARENA_DEFAULT_BLOCK_SIZE, min_size);
+ char *block = ALLOC(char, size);
+ a->ptr = block;
+ a->end = block + size;
+ array_push(a->blocks, block);
+}
+
+NONSTD_DEF void *arena_alloc(Arena *a, size_t size) {
+ // Align to 8 bytes basically
+ size_t align = sizeof(void *);
+ uintptr_t current = (uintptr_t)a->ptr;
+ uintptr_t aligned = (current + align - 1) & ~(align - 1);
+ uintptr_t end = (uintptr_t)a->end;
+
+ // Check for overflow (aligned wrapped around) or out of bounds (aligned >= end)
+ // or not enough space ((end - aligned) < size)
+ if (aligned < current || aligned >= end || (end - aligned) < size) {
+ arena_grow(a, size);
+ current = (uintptr_t)a->ptr;
+ aligned = (current + align - 1) & ~(align - 1);
+ end = (uintptr_t)a->end;
+ }
+
+ // Double check after grow (in case grow failed or size is just too huge)
+ if (aligned < current || aligned >= end || (end - aligned) < size) {
+ return NULL;
+ }
+
+ a->ptr = (char *)(aligned + size);
+ return (void *)aligned;
+}
+
+NONSTD_DEF void arena_free(Arena *a) {
+ char *block;
+ array_foreach(a->blocks, block) { FREE(block); }
+ array_free(a->blocks);
+ a->ptr = NULL;
+ a->end = NULL;
+}
+
+// File I/O Implementation
+
+NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size) {
+ FILE *f = fopen(filepath, "rb");
+ if (!f) {
+ return NULL;
+ }
+
+ fseek(f, 0, SEEK_END);
+ long length = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ if (length < 0) {
+ fclose(f);
+ return NULL;
+ }
+
+ size_t size = (size_t)length;
+ char *buffer = ALLOC(char, size + 1);
+ if (!buffer) {
+ fclose(f);
+ return NULL;
+ }
+
+ size_t read = fread(buffer, 1, size, f);
+ fclose(f);
+
+ if (read != size) {
+ FREE(buffer);
+ return NULL;
+ }
+
+ buffer[size] = '\0';
+ if (out_size) {
+ *out_size = size;
+ }
+
+ return buffer;
+}
+
+NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size) {
+ FILE *f = fopen(filepath, "wb");
+ if (!f) {
+ return 0;
+ }
+
+ size_t written = fwrite(data, 1, size, f);
+ fclose(f);
+
+ return written == size;
+}
+
+NONSTD_DEF stringb read_entire_file_sb(const char *filepath) {
+ size_t size = 0;
+ char *data = read_entire_file(filepath, &size);
+ stringb sb = {0};
+ if (data) {
+ sb.data = data;
+ sb.length = size;
+ sb.capacity = size + 1;
+ }
+ return sb;
+}
+
+NONSTD_DEF int write_file_sv(const char *filepath, stringv sv) {
+ return write_entire_file(filepath, sv.data, sv.length);
+}
+
+NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb) {
+ return write_entire_file(filepath, sb->data, sb->length);
+}
+
+// Logging Implementation
+
+static LogLevel max_level = LOG_INFO;
+
+static const char *level_strings[] = {
+ "ERROR",
+ "WARN",
+ "INFO",
+ "DEBUG",
+};
+
+static const char *level_colors[] = {
+ COLOR_ERROR,
+ COLOR_WARNING,
+ COLOR_INFO,
+ COLOR_DEBUG,
+};
+
+NONSTD_DEF void set_log_level(LogLevel level) {
+ max_level = level;
+}
+
+NONSTD_DEF LogLevel get_log_level_from_env(void) {
+ const char *env = getenv("LOG_LEVEL");
+ if (env) {
+ int level = atoi(env);
+ if (level >= 0 && level <= 3) {
+ return (LogLevel)level;
+ }
+ }
+
+ return max_level;
+}
+
+NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ...) {
+ if (max_level < level)
+ return;
+
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ struct tm *tm_info = localtime(&tv.tv_sec);
+
+ char time_str[24];
+ strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
+
+ const char *color = isatty(fileno(stream)) ? level_colors[level] : "";
+ const char *reset = isatty(fileno(stream)) ? COLOR_RESET : "";
+
+ const char *log_format = "%s[%s.%03d] [%-5s] ";
+ fprintf(stream, log_format, color, time_str, (int)(tv.tv_usec / 1000), level_strings[level]);
+
+ va_list args;
+ va_start(args, format);
+ vfprintf(stream, format, args);
+ va_end(args);
+
+ fprintf(stream, "%s\n", reset);
+ fflush(stream);
+}
+
+// PPM Image Implementation
+
+NONSTD_DEF Canvas ppm_init(u32 width, u32 height) {
+ Canvas img = {0};
+ img.width = width;
+ img.height = height;
+ img.pixels = ALLOC(Color, width * height);
+ if (img.pixels) {
+ memset(img.pixels, 0, sizeof(Color) * width * height);
+ }
+ return img;
+}
+
+NONSTD_DEF void ppm_free(Canvas *img) {
+ if (img->pixels) {
+ FREE(img->pixels);
+ }
+ img->width = 0;
+ img->height = 0;
+}
+
+NONSTD_DEF void ppm_set_pixel(Canvas *img, u32 x, u32 y, Color color) {
+ if (x < img->width && y < img->height) {
+ img->pixels[y * img->width + x] = color;
+ }
+}
+
+NONSTD_DEF Color ppm_get_pixel(const Canvas *img, u32 x, u32 y) {
+ if (x < img->width && y < img->height) {
+ return img->pixels[y * img->width + x];
+ }
+ return (Color){0, 0, 0};
+}
+
+NONSTD_DEF int ppm_save(const Canvas *img, const char *filename) {
+ FILE *f = fopen(filename, "w");
+ if (!f) {
+ return 0;
+ }
+
+ fprintf(f, "P3\n%u %u\n255\n", img->width, img->height);
+ for (u32 y = 0; y < img->height; ++y) {
+ for (u32 x = 0; x < img->width; ++x) {
+ Color c = ppm_get_pixel(img, x, y);
+ fprintf(f, "%d %d %d ", c.r, c.g, c.b);
+ }
+ fprintf(f, "\n");
+ }
+
+ fclose(f);
+ return 1;
+}
+
+NONSTD_DEF Canvas ppm_read(const char *filename) {
+ Canvas img = {0};
+ FILE *f = fopen(filename, "r");
+ if (!f) {
+ return img;
+ }
+
+ char magic[3];
+ if (fscanf(f, "%2s", magic) != 1 || strcmp(magic, "P3") != 0) {
+ fclose(f);
+ return img;
+ }
+
+ u32 w, h, max_val;
+ if (fscanf(f, "%u %u %u", &w, &h, &max_val) != 3) {
+ fclose(f);
+ return img;
+ }
+
+ img = ppm_init(w, h);
+ if (!img.pixels) {
+ fclose(f);
+ return img;
+ }
+
+ for (u32 i = 0; i < w * h; ++i) {
+ int r, g, b;
+ if (fscanf(f, "%d %d %d", &r, &g, &b) != 3) {
+ ppm_free(&img);
+ fclose(f);
+ return (Canvas){0};
+ }
+ img.pixels[i] = (Color){(u8)r, (u8)g, (u8)b};
+ }
+
+ fclose(f);
+ return img;
+}
+
+NONSTD_DEF void ppm_fill(Canvas *canvas, Color color) {
+ for (u32 i = 0; i < canvas->width * canvas->height; ++i) {
+ canvas->pixels[i] = color;
+ }
+}
+
+NONSTD_DEF void ppm_draw_rect(Canvas *canvas, u32 x, u32 y, u32 w, u32 h, Color color) {
+ if (w == 0 || h == 0) {
+ return;
+ }
+ for (u32 i = x; i < x + w; ++i) {
+ ppm_set_pixel(canvas, i, y, color);
+ ppm_set_pixel(canvas, i, y + h - 1, color);
+ }
+ for (u32 j = y; j < y + h; ++j) {
+ ppm_set_pixel(canvas, x, j, color);
+ ppm_set_pixel(canvas, x + w - 1, j, color);
+ }
+}
+
+NONSTD_DEF void ppm_draw_line(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, Color color) {
+ i32 dx = abs(x1 - x0);
+ i32 dy = -abs(y1 - y0);
+ i32 sx = x0 < x1 ? 1 : -1;
+ i32 sy = y0 < y1 ? 1 : -1;
+ i32 err = dx + dy;
+
+ while (1) {
+ ppm_set_pixel(canvas, (u32)x0, (u32)y0, color);
+ if (x0 == x1 && y0 == y1) {
+ break;
+ }
+
+ i32 e2 = 2 * err;
+ if (e2 >= dy) {
+ err += dy;
+ x0 += sx;
+ }
+ if (e2 <= dx) {
+ err += dx;
+ y0 += sy;
+ }
+ }
+}
+
+NONSTD_DEF void ppm_draw_circle(Canvas *canvas, i32 xm, i32 ym, i32 r, Color color) {
+ i32 x = -r, y = 0, err = 2 - 2 * r;
+ do {
+ ppm_set_pixel(canvas, (u32)(xm - x), (u32)(ym + y), color);
+ ppm_set_pixel(canvas, (u32)(xm - y), (u32)(ym - x), color);
+ ppm_set_pixel(canvas, (u32)(xm + x), (u32)(ym - y), color);
+ ppm_set_pixel(canvas, (u32)(xm + y), (u32)(ym + x), color);
+ r = err;
+ if (r <= y) {
+ err += ++y * 2 + 1;
+ }
+ if (r > x || err > y) {
+ err += ++x * 2 + 1;
+ }
+ } while (x < 0);
+}
+
+NONSTD_DEF void ppm_draw_triangle(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, i32 x2, i32 y2, Color color) {
+ ppm_draw_line(canvas, x0, y0, x1, y1, color);
+ ppm_draw_line(canvas, x1, y1, x2, y2, color);
+ ppm_draw_line(canvas, x2, y2, x0, y0, color);
+}
+
+#endif // NONSTD_IMPLEMENTATION
+
+/*
+BSD 2-Clause License
+
+Copyright (c) 2026, Mitja Felicijan <mitja.felicijan@gmail.com>
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/ \ No newline at end of file
diff --git a/prompt.c b/prompt.c
index be6f85f..c5bd4cb 100644
--- a/prompt.c
+++ b/prompt.c
@@ -11,6 +11,10 @@
#define MAX_TOKENS 512
#define MAX_TOKEN_LEN 32
+typedef struct {
+
+} Engine;
+
static const char *refusal_text = "I don't have that information.";
static void llama_log_callback(enum ggml_log_level level, const char *text, void *user_data) {
@@ -58,7 +62,7 @@ static int collect_tokens(const char *text, char tokens[MAX_TOKENS][MAX_TOKEN_LE
buf[len] = '\0';
if (len >= 4 && !is_stopword(buf, (size_t)len)) {
if (!token_exists(tokens, count, buf) && count < MAX_TOKENS) {
- strncpy(tokens[count], buf, MAX_TOKEN_LEN - 1);
+ memcpy(tokens[count], buf, (size_t)len + 1);
tokens[count][MAX_TOKEN_LEN - 1] = '\0';
count++;
}
@@ -412,12 +416,16 @@ static void show_help(const char *prog) {
printf("Options:\n");
printf(" -m, --model <name> Specify model to use (default: first model)\n");
printf(" -p, --prompt <text> Specify prompt text (default: \"What is 2+2?\")\n");
+ printf(" -b, --build <file> Specify context file\n");
printf(" -c, --context <text> Specify context file\n");
printf(" -v, --verbose Enable verbose logging\n");
printf(" -h, --help Show this help message\n");
}
int main(int argc, char **argv) {
+ /* Engine engine = {}; */
+
+
const char *model_name = NULL;
const char *prompt = NULL;
const char *context_file = NULL;
@@ -429,6 +437,7 @@ int main(int argc, char **argv) {
{"model", required_argument, 0, 'm'},
{"prompt", required_argument, 0, 'p'},
{"context", required_argument, 0, 'c'},
+ {"build", required_argument, 0, 'b'},
{"verbose", no_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
diff --git a/vectordb.c b/vectordb.c
index 5e45cc4..b6fae64 100644
--- a/vectordb.c
+++ b/vectordb.c
@@ -1,33 +1,38 @@
#include <stdio.h>
#include <string.h>
#include <math.h>
+#include <stdint.h>
#include "llama.h"
#include "vectordb.h"
+#include "nonstd.h"
+
+#define VDB_MAGIC 0x31424456u /* "VDB1" */
+#define VDB_VERSION 1u
+
+typedef struct {
+ uint32_t magic;
+ uint32_t version;
+ uint32_t embed_size;
+ uint32_t max_text;
+ uint32_t count;
+} VdbFileHeader;
static float cosine_similarity(float *a, float *b, int n) {
- float dot = 0, normA = 0, normB = 0;
+ float dot = 0, norm_a = 0, norm_b = 0;
for (int i = 0; i < n; i++) {
dot += a[i] * b[i];
- normA += a[i] * a[i];
- normB += b[i] * b[i];
+ norm_a += a[i] * a[i];
+ norm_b += b[i] * b[i];
}
- return dot / (sqrtf(normA) * sqrtf(normB) + 1e-8f);
+ return dot / (sqrtf(norm_a) * sqrtf(norm_b) + 1e-8f);
}
static void embed_text(struct llama_context *ctx, const char *text, float *out) {
llama_token tokens[512];
const struct llama_model *model = llama_get_model(ctx);
const struct llama_vocab *vocab = llama_model_get_vocab(model);
- int n_tokens = llama_tokenize(
- vocab,
- text,
- strlen(text),
- tokens,
- 512,
- true,
- true
- );
+ int n_tokens = llama_tokenize(vocab, text, strlen(text), tokens, 512, true, true);
if (n_tokens < 0) {
return;
}
@@ -46,12 +51,12 @@ void vdb_init(VectorDB *db, struct llama_context *embed_ctx) {
}
void vdb_free(VectorDB *db) {
- (void)db; // nothing yet (future persistence etc.)
+ (void)db;
}
void vdb_add_document(VectorDB *db, const char *text) {
if (db->count >= VDB_MAX_DOCS) {
- printf("VectorDB full!\n");
+ log_message(stdout, LOG_INFO, "Vector database full");
return;
}
@@ -59,7 +64,7 @@ void vdb_add_document(VectorDB *db, const char *text) {
strncpy(doc->text, text, VDB_MAX_TEXT - 1);
doc->text[VDB_MAX_TEXT - 1] = 0;
- printf("Embedding doc %d...\n", db->count);
+ log_message(stdout, LOG_INFO, "Embedding doc %d...", db->count);
embed_text(db->embed_ctx, text, doc->embedding);
}
@@ -90,3 +95,84 @@ void vdb_search(VectorDB *db, float *query, int top_k, int *results) {
}
}
}
+
+int vdb_save(const VectorDB *db, const char *path) {
+ FILE *fp = fopen(path, "wb");
+ if (!fp) {
+ return 1;
+ }
+
+ VdbFileHeader header = {
+ .magic = VDB_MAGIC,
+ .version = VDB_VERSION,
+ .embed_size = VDB_EMBED_SIZE,
+ .max_text = VDB_MAX_TEXT,
+ .count = (uint32_t)db->count,
+ };
+
+ if (fwrite(&header, sizeof(header), 1, fp) != 1) {
+ fclose(fp);
+ return 2;
+ }
+
+ if (db->count > 0) {
+ size_t wrote = fwrite(db->docs, sizeof(VectorDoc), (size_t)db->count, fp);
+ if (wrote != (size_t)db->count) {
+ fclose(fp);
+ return 3;
+ }
+ }
+
+ if (fclose(fp) != 0) {
+ return 4;
+ }
+
+ return 0;
+}
+
+int vdb_load(VectorDB *db, const char *path) {
+ struct llama_context *ctx = db->embed_ctx;
+ FILE *fp = fopen(path, "rb");
+ if (!fp) {
+ return -1;
+ }
+
+ VdbFileHeader header = {0};
+ if (fread(&header, sizeof(header), 1, fp) != 1) {
+ fclose(fp);
+ return -2;
+ }
+
+ if (header.magic != VDB_MAGIC || header.version != VDB_VERSION) {
+ fclose(fp);
+ return -3;
+ }
+
+ if (header.embed_size != VDB_EMBED_SIZE || header.max_text != VDB_MAX_TEXT) {
+ fclose(fp);
+ return -4;
+ }
+
+ if (header.count > VDB_MAX_DOCS) {
+ fclose(fp);
+ return -5;
+ }
+
+ memset(db, 0, sizeof(VectorDB));
+ db->embed_ctx = ctx;
+ db->count = (int)header.count;
+
+ if (db->count > 0) {
+ size_t read = fread(db->docs, sizeof(VectorDoc), (size_t)db->count, fp);
+ if (read != (size_t)db->count) {
+ fclose(fp);
+ return -6;
+ }
+ }
+
+ if (fclose(fp) != 0) {
+ return -7;
+ }
+
+ return 0;
+}
diff --git a/vectordb.h b/vectordb.h
index 3b375bb..db1d33d 100644
--- a/vectordb.h
+++ b/vectordb.h
@@ -26,4 +26,7 @@ void vdb_add_document(VectorDB *db, const char *text);
void vdb_embed_query(VectorDB *db, const char *text, float *out_embedding);
void vdb_search(VectorDB *db, float *query_embedding, int top_k, int *results);
+int vdb_save(const VectorDB *db, const char *path);
+int vdb_load(VectorDB *db, const char *path);
+
#endif