Commit 56fd8a375 for llama.cpp

commit 56fd8a375f42dd89cfbbba744f097648214fdd61
Author: Roshan Kumar <52444089+roshankumarg529@users.noreply.github.com>
Date:   Sun Nov 15 18:23:05 2026 +0200

    server : ignore empty stop strings (#32093)

diff --git a/tests/test-server-component.cpp b/tests/test-server-component.cpp
index 3494cdf04..580203be8 100644
--- a/tests/test-server-component.cpp
+++ b/tests/test-server-component.cpp
@@ -718,6 +718,41 @@ static void test_dry_sequence_breakers(testing & t) {
     }
 }

+static void test_stop_strings(testing & t) {
+    common_params params_base;
+
+    const auto eval = [&](const json & data) {
+        return server_schema::eval_llama_cmpl_schema(nullptr, params_base, {}, data);
+    };
+
+    {
+        json data;
+        data["stop"] = "";
+        t.assert_true("empty string is ignored", eval(data).antiprompt.empty());
+    }
+
+    {
+        json data;
+        data["stop"] = json::array({""});
+        t.assert_true("empty string in array is ignored", eval(data).antiprompt.empty());
+    }
+
+    {
+        json data;
+        data["stop"] = json::array({"", "zzz"});
+        const std::vector<std::string> expected = {"zzz"};
+        t.assert_true("only non-empty strings are kept", eval(data).antiprompt == expected);
+    }
+
+    {
+        params_base.antiprompt = {"User:"};
+        json data;
+        data["stop"] = json::array({""});
+        const std::vector<std::string> expected = {"User:"};
+        t.assert_true("falls back to CLI defaults", eval(data).antiprompt == expected);
+    }
+}
+
 //
 // glob_match
 //
@@ -842,6 +877,7 @@ int main(int argc, char ** argv) {
     t.test("decision_depth",  test_decision_depth);
     t.test("glob_match",      test_glob_match);
     t.test("dry_sequence_breakers", test_dry_sequence_breakers);
+    t.test("stop_strings",    test_stop_strings);
     t.test("speech_convert",  test_speech_convert);
     t.test("pcm_f32_to_s16",  test_pcm_f32_to_s16);

diff --git a/tools/server/server-context.cpp b/tools/server/server-context.cpp
index 4015e23ea..7c2c35c35 100644
--- a/tools/server/server-context.cpp
+++ b/tools/server/server-context.cpp
@@ -692,6 +692,10 @@ struct server_slot {
         size_t stop_pos = std::string::npos;

         for (const std::string & word : task->params.antiprompt) {
+            if (word.empty()) {
+                continue;
+            }
+
             size_t pos;

             if (is_full_stop) {
diff --git a/tools/server/server-schema.cpp b/tools/server/server-schema.cpp
index 76d634d58..84e872f21 100644
--- a/tools/server/server-schema.cpp
+++ b/tools/server/server-schema.cpp
@@ -458,9 +458,9 @@ std::vector<std::unique_ptr<field>> make_llama_cmpl_schema(const common_params &
             const auto & stop = data.at("stop");
             if (stop.is_array()) {
                 for (const auto & word : stop) {
-                    if (!word.empty()) ctx.params.antiprompt.push_back(word);
+                    if (!word.empty() && word != "") ctx.params.antiprompt.push_back(word);
                 }
-            } else if (stop.is_string()) {
+            } else if (stop.is_string() && stop != "") {
                 ctx.params.antiprompt.push_back(stop.get<std::string>());
             }
             // fall back to CLI defaults if the request provided no effective stop strings
diff --git a/tools/server/tests/unit/test_completion.py b/tools/server/tests/unit/test_completion.py
index 0c5beb23e..618345585 100644
--- a/tools/server/tests/unit/test_completion.py
+++ b/tools/server/tests/unit/test_completion.py
@@ -118,6 +118,20 @@ def test_completion_connection_reuse():
         conn.close()


+def test_completion_empty_reverse_prompt():
+    global server
+    server.reverse_prompt = ""
+    server.start()
+    res = server.make_request("POST", "/completion", data={
+        "n_predict": 8,
+        "prompt": "I believe the meaning of life is",
+    })
+    assert res.status_code == 200
+    assert res.body["stop_type"] == "limit"
+    assert res.body["timings"]["predicted_n"] == 8
+    assert match_regex("(going|bed)+", res.body["content"])
+
+
 def test_completion_with_openai_library():
     global server
     server.start()
diff --git a/tools/server/tests/utils.py b/tools/server/tests/utils.py
index 56c88b354..f18b566aa 100644
--- a/tools/server/tests/utils.py
+++ b/tools/server/tests/utils.py
@@ -77,6 +77,7 @@ class ServerProcess:
     n_ga: int | None = None
     n_ga_w: int | None = None
     n_predict: int | None = None
+    reverse_prompt: str | None = None
     n_prompts: int | None = 0
     slot_save_path: str | None = None
     id_slot: int | None = None
@@ -248,6 +249,8 @@ class ServerProcess:
             server_args.extend(["-fa", self.fa])
         if self.n_predict:
             server_args.extend(["--n-predict", self.n_predict])
+        if self.reverse_prompt is not None:
+            server_args.extend(["--reverse-prompt", self.reverse_prompt])
         if self.slot_save_path:
             server_args.extend(["--slot-save-path", self.slot_save_path])
         if self.n_ga: