]> git.djapps.eu Git - pkg/ggml/sources/llama.cpp/commitdiff
server: return 400 instead of 500 on validation error with X-Conversation-Id (#25760)
authorPascal <redacted>
Tue, 21 Jul 2026 15:47:54 +0000 (17:47 +0200)
committerGitHub <redacted>
Tue, 21 Jul 2026 15:47:54 +0000 (17:47 +0200)
* server: return 400 instead of 500 on validation error with X-Conversation-Id

set_req() attaches the spipe as soon as the header is present, before the request
body is parsed. When params validation throws, set_next() never runs and next_orig
stays empty, so on_complete() called it and crashed with std::bad_function_call,
turning the prepared 400 JSON into a generic 500.

on_complete() now treats an empty next_orig as "streaming never started" and evicts
the session installed by set_req(), so a failed request leaves nothing behind for
discovery or replay. This also covers valid requests that carry the header but do
not stream, which previously left an empty finalized session in the map until the
GC TTL.

* ui: do not send the backend_sampling placeholder

On a fresh profile the syncable settings hold the empty string placeholder meaning
"let the server decide". Every neighbor field goes through the hasValue() guard
that filters it, except backend_sampling, which sent the placeholder verbatim and
made every default settings completion fail validation.

Guard the field with hasValue() like its neighbors. hasValue(false) is true, so an
explicit false still reaches the server and the intent of #18781 (send both true
and false) is preserved. Only the placeholder is filtered.

tools/server/server-stream.cpp
tools/ui/src/lib/stores/chat.svelte.ts

index 19db04988d817e3ef9549e4f9db1bfb129c2d571..f0a35b18e525404643355c93d3960f704a61f048 100644 (file)
@@ -632,6 +632,13 @@ void server_res_spipe::on_complete() {
     if (!spipe || next_finished) {
         return;
     }
+    // an empty next_orig means set_next() never ran: the request failed before streaming
+    // started, typically a params validation throw. evict the session installed by set_req()
+    // so the failed request leaves nothing behind for discovery or replay
+    if (!next_orig) {
+        g_stream_sessions.evict(server_stream_conv_id_from_headers(req->headers));
+        return;
+    }
     std::string chunk;
     while (!spipe->is_cancelled()) {
         chunk.clear();
index 658eb5978cfc5afa339613e8c4d8c0807aeaf24d..ab418ed9cdab1f4f09e40158fc634f1b54929b81 100644 (file)
@@ -2428,7 +2428,8 @@ class ChatStore {
 
                if (currentConfig.samplers) apiOptions.samplers = currentConfig.samplers;
 
-               apiOptions.backend_sampling = currentConfig.backend_sampling;
+               if (hasValue(currentConfig.backend_sampling))
+                       apiOptions.backend_sampling = currentConfig.backend_sampling;
 
                if (currentConfig.customJson) apiOptions.custom = currentConfig.customJson;