Commit 057c2d9

mo khan <mo@mokhan.ca>
2026-08-30 05:32:39
feat: gate GGUF/llama.cpp logging behind LOG_LEVEL
llama.cpp's native logger printed unconditional load-time chatter straight to stderr, bypassing Ruby entirely. Register a llama_log_set callback filtered by LOG_LEVEL (default warn, debug for full verbosity) and drive Elelem.logger's level from the same variable, so native and Ruby logging share one knob.
1 parent db4deb6
Changed files (2)
ext
elelem
lib
elelem
ext/elelem/llama/elelem_llama.cpp
@@ -8,6 +8,8 @@
 #include "chat.h"
 #include <nlohmann/json.hpp>
 #include <algorithm>
+#include <cstdlib>
+#include <cstring>
 #include <string>
 #include <vector>
 
@@ -26,10 +28,33 @@ struct el_handle {
 
 static bool g_backend = false;
 
+// llama.cpp/ggml log to stderr by default; gate that behind LOG_LEVEL so
+// GGUF model loads aren't noisy unless a caller opts in, matching the
+// verbosity Elelem.logger is configured with in lib/elelem.rb.
+static enum ggml_log_level el_log_threshold() {
+    const char *level = std::getenv("LOG_LEVEL");
+    if (!level) return GGML_LOG_LEVEL_WARN;
+    std::string v(level);
+    std::transform(v.begin(), v.end(), v.begin(), ::tolower);
+    if (v == "debug") return GGML_LOG_LEVEL_DEBUG;
+    if (v == "info") return GGML_LOG_LEVEL_INFO;
+    if (v == "error") return GGML_LOG_LEVEL_ERROR;
+    return GGML_LOG_LEVEL_WARN;
+}
+
+static void el_log_callback(enum ggml_log_level level, const char *text, void *user_data) {
+    if (level < el_log_threshold()) return;
+    fputs(text, stderr);
+}
+
 // temp <= 0 selects greedy/deterministic sampling; seed is the RNG seed for the
 // sampled path (both surfaced so callers -- notably the eval harness -- can pin them).
 void *el_open(const char *path, int n_gpu_layers, int n_ctx, int n_threads, float temp, int seed) {
-    if (!g_backend) { llama_backend_init(); g_backend = true; }
+    if (!g_backend) {
+        llama_log_set(el_log_callback, nullptr);
+        llama_backend_init();
+        g_backend = true;
+    }
 
     llama_model_params mp = llama_model_default_params();
     mp.n_gpu_layers = n_gpu_layers;
lib/elelem/llama/client.rb
@@ -38,6 +38,11 @@ module Elelem
         ptr = self.class.functions[:generate].call(@handle, JSON.generate(messages), JSON.generate(tools), @max_tokens)
         result = JSON.parse(Fiddle::Pointer.new(ptr).to_s)
 
+        Elelem.logger.debug("gguf: tool-call fallback used") if result["fallback"]
+        if result["tool_calls"].to_a.empty? && !tools.empty? && result["content"].to_s.include?("\"name\"")
+          Elelem.logger.debug("gguf: no tool calls parsed, tools offered")
+        end
+
         content = result["content"].to_s
         block&.call(type: "saying", text: content) unless content.empty?