diff --git a/common/chat.cpp b/common/chat.cpp
index 7f6809a4ed..34e6152164 100644
--- a/common/chat.cpp
+++ b/common/chat.cpp
@@ -1312,6 +1312,12 @@ static common_chat_params common_chat_params_init_deepseek_r1(const common_chat_
     }
     return data;
 }
+
+static common_chat_params common_chat_params_init_deepseek_v3_1(const common_chat_template & tmpl, const struct templates_params & inputs) {
+    // For now, use the same implementation as R1
+    return common_chat_params_init_deepseek_r1(tmpl, inputs);
+}
+
 static void common_chat_parse_deepseek_r1(common_chat_msg_parser & builder) {
     builder.try_parse_reasoning("", "");
     if (!builder.syntax().parse_tool_calls) {
@@ -1333,6 +1339,32 @@ static void common_chat_parse_deepseek_r1(common_chat_msg_parser & builder) {
         tool_calls_end);
 }
 
+static void common_chat_parse_deepseek_v3_1(common_chat_msg_parser & builder) {
+    // DeepSeek V3.1 outputs reasoning content followed by "" and then regular content
+    // There's no opening "" tag, so we need to handle this differently
+    
+    // First, try to find the "" tag that separates thinking from regular content
+    static const common_regex thinking_end_regex("");
+    if (auto res = builder.try_find_regex(thinking_end_regex)) {
+        // Extract everything before "" as reasoning content
+        auto reasoning_content = builder.str(common_string_range{0, res->groups[0].begin});
+        auto stripped_reasoning = string_strip(reasoning_content);
+        
+        if (!stripped_reasoning.empty()) {
+            builder.add_reasoning_content(stripped_reasoning);
+        }
+        
+        // Move past the "" tag
+        builder.move_to(res->groups[0].end);
+        
+        // The rest is regular content
+        builder.add_content(builder.consume_rest());
+    } else {
+        // If no "" tag found, treat everything as regular content
+        builder.add_content(builder.consume_rest());
+    }
+}
+
 static common_chat_params common_chat_params_init_gpt_oss(const common_chat_template & tmpl, const struct templates_params & inputs) {
     common_chat_params data;
     auto prompt = apply(tmpl, inputs);
@@ -2100,6 +2132,12 @@ static common_chat_params common_chat_templates_apply_jinja(
         }
     }
 
+    // DeepSeek V3.1: detect based on specific patterns in the template
+    if (src.find("message['prefix'] is defined and message['prefix'] and thinking") != std::string::npos && 
+        params.json_schema.is_null()) {
+        return common_chat_params_init_deepseek_v3_1(tmpl, params);
+    }
+
     // DeepSeek R1: use handler in all cases except json schema (thinking / tools).
     if (src.find("<|tool▁calls▁begin|>") != std::string::npos && params.json_schema.is_null()) {
         return common_chat_params_init_deepseek_r1(tmpl, params);
@@ -2262,6 +2300,9 @@ static void common_chat_parse(common_chat_msg_parser & builder) {
         case COMMON_CHAT_FORMAT_DEEPSEEK_R1:
             common_chat_parse_deepseek_r1(builder);
             break;
+        case COMMON_CHAT_FORMAT_DEEPSEEK_V3_1:
+            common_chat_parse_deepseek_v3_1(builder);
+            break;
         case COMMON_CHAT_FORMAT_FUNCTIONARY_V3_2:
             common_chat_parse_functionary_v3_2(builder);
             break;
diff --git a/common/chat.h b/common/chat.h
index d1e480c918..f7c31221cd 100644
--- a/common/chat.h
+++ b/common/chat.h
@@ -107,6 +107,7 @@ enum common_chat_format {
     COMMON_CHAT_FORMAT_FIREFUNCTION_V2,
     COMMON_CHAT_FORMAT_FUNCTIONARY_V3_2,
     COMMON_CHAT_FORMAT_FUNCTIONARY_V3_1_LLAMA_3_1,
+    COMMON_CHAT_FORMAT_DEEPSEEK_V3_1,
     COMMON_CHAT_FORMAT_HERMES_2_PRO,
     COMMON_CHAT_FORMAT_COMMAND_R7B,
     COMMON_CHAT_FORMAT_GRANITE,