Repository: openvinotoolkit/openvino
Pull request #29982: [C API] Implement `ov_tensor_create_from_string_array` c-api
Author: login: rahulchaphalkar
Labels: name: bug
description: Something isn't working
name: category: C API
description: OpenVINO C API bindings
name: category: docs
description: OpenVINO documentation
name: ExternalIntelPR
description: External contributor from Intel
Created: 2025-04-07T17:20:20Z | Merged: 2025-04-11T16:24:13Z

### Details:
Closes https://github.com/openvinotoolkit/openvino/issues/26906#issuecomment-2399900237

- Implement `ov_tensor_create_from_string_array` c-api
- Adds test



Diff:
diff --git a/src/bindings/c/docs/api_overview.md b/src/bindings/c/docs/api_overview.md
index a19786b284d05b..168b16bae599ee 100644
--- a/src/bindings/c/docs/api_overview.md
+++ b/src/bindings/c/docs/api_overview.md
@@ -1080,6 +1080,14 @@ This struct provides an interface to infer requests of `ov_compiled_model_t` and
     - `tensor` - A point to ov_tensor_t
   -  Return value: Status code of the operation: OK(0) for success.
 
+- `ov_status_e ov_tensor_create_from_string_array(const char** string_array, const size_t array_size, const ov_shape_t shape, ov_tensor_t** tensor)`
+  - Description: Constructs Tensor using string array and size.
+    - `string_array` - String array
+    - `array_size` - Number of elements in string array
+    - `shape` - Tensor shape
+    - `tensor` - A point to ov_tensor_t
+  -  Return value: Status code of the operation: OK(0) for success.
+
 - `ov_status_e ov_tensor_create(const ov_element_type_e type, const ov_shape_t shape, ov_tensor_t** tensor)`
   - Description: Constructs Tensor using element type and shape. Allocate internal host storage using default allocator.
   - Parameters:
diff --git a/src/bindings/c/include/openvino/c/ov_tensor.h b/src/bindings/c/include/openvino/c/ov_tensor.h
index 4bd0b657ca0096..8efb149813cb22 100644
--- a/src/bindings/c/include/openvino/c/ov_tensor.h
+++ b/src/bindings/c/include/openvino/c/ov_tensor.h
@@ -57,6 +57,21 @@ ov_tensor_create(const ov_element_type_e type, const ov_shape_t shape, ov_tensor
 OPENVINO_C_API(ov_status_e)
 ov_tensor_set_shape(ov_tensor_t* tensor, const ov_shape_t shape);
 
+/**
+ * @brief Constructs a new tensor using a string array.
+ * @ingroup ov_tensor_c_api
+ * @param string_array An array of strings
+ * @param array_size The size of the string array
+ * @param shape Tensor shape
+ * @param tensor A point to ov_tensor_t
+ * @return Status code of the operation: OK(0) for success.
+ */
+OPENVINO_C_API(ov_status_e)
+ov_tensor_create_from_string_array(const char** string_array,
+                                   const size_t array_size,
+                                   const ov_shape_t shape,
+                                   ov_tensor_t** tensor);
+
 /**
  * @brief Get shape for tensor.
  * @ingroup ov_tensor_c_api
diff --git a/src/bindings/c/src/ov_tensor.cpp b/src/bindings/c/src/ov_tensor.cpp
index bcd52734c86d16..1d5d7f8274ccae 100644
--- a/src/bindings/c/src/ov_tensor.cpp
+++ b/src/bindings/c/src/ov_tensor.cpp
@@ -83,6 +83,28 @@ ov_status_e ov_tensor_create_from_host_ptr(const ov_element_type_e type,
     return ov_status_e::OK;
 }
 
+ov_status_e ov_tensor_create_from_string_array(const char** string_array,
+                                               const size_t array_size,
+                                               const ov_shape_t shape,
+                                               ov_tensor_t** tensor) {
+    if (!tensor || !string_array) {
+        return ov_status_e::INVALID_C_PARAM;
+    }
+    try {
+        auto _tensor = std::make_unique<ov_tensor_t>();
+        ov::Shape tmp_shape;
+        std::copy_n(shape.dims, shape.rank, std::back_inserter(tmp_shape));
+        if (shape_size(tmp_shape) < array_size) {
+            return ov_status_e::INVALID_C_PARAM;
+        }
+        _tensor->object = std::make_shared<ov::Tensor>(ov::element::string, tmp_shape);
+        std::copy_n(string_array, array_size, _tensor->object->data<std::string>());
+        *tensor = _tensor.release();
+    }
+    CATCH_OV_EXCEPTIONS
+    return ov_status_e::OK;
+}
+
 ov_status_e ov_tensor_set_shape(ov_tensor_t* tensor, const ov_shape_t shape) {
     if (!tensor) {
         return ov_status_e::INVALID_C_PARAM;
diff --git a/src/bindings/c/tests/ov_tensor_test.cpp b/src/bindings/c/tests/ov_tensor_test.cpp
index 163c4f4079491b..00e874992c97c9 100644
--- a/src/bindings/c/tests/ov_tensor_test.cpp
+++ b/src/bindings/c/tests/ov_tensor_test.cpp
@@ -31,6 +31,17 @@ TEST(ov_tensor, ov_tensor_create_from_host_ptr) {
     ov_shape_free(&shape);
 }
 
+TEST(ov_tensor, ov_tensor_create_from_string_array) {
+    const char* string_array[4] = {"test", "me", "hi", "there"};
+    ov_tensor_t* tensor = nullptr;
+    ov_shape_t shape;
+    const int64_t dims[2] = {2, 2};
+    ov_shape_create(2, dims, &shape);
+    OV_EXPECT_OK(ov_tensor_create_from_string_array(string_array, 4, shape, &tensor));
+    EXPECT_NE(nullptr, tensor);
+    ov_tensor_free(tensor);
+}
+
 static size_t product(const std::vector<size_t>& dims) {
     if (dims.empty())
         return 0;


Reviews:
user: login: praasz
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T05:48:14Z
user: login: rkazants
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-08T07:33:40Z
user: login: rkazants
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-08T07:50:09Z
user: login: praasz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T07:59:32Z
user: login: t-jankowski
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T08:54:48Z
user: login: t-jankowski
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T09:00:33Z
user: login: rahulchaphalkar
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T18:03:18Z
user: login: rahulchaphalkar
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T18:03:29Z
user: login: rahulchaphalkar
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T18:04:12Z
user: login: rahulchaphalkar
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T18:10:14Z
user: login: rkazants
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-09T05:32:44Z
user: login: rkazants
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-09T05:34:51Z
user: login: rkazants
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-09T05:47:57Z
user: login: rahulchaphalkar
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T20:42:28Z
user: login: rahulchaphalkar
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T20:42:34Z
user: login: rkazants
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-11T06:09:48Z

Review comments:
diff_hunk: @@ -83,6 +83,30 @@ ov_status_e ov_tensor_create_from_host_ptr(const ov_element_type_e type,
     return ov_status_e::OK;
 }
 
+ov_status_e ov_tensor_create_from_string_array(const char** string_array, size_t array_size, ov_tensor_t** tensor) {
+    if (!tensor || !string_array || array_size == 0) {
+        return ov_status_e::INVALID_C_PARAM;
+    }
+    try {
+        for (size_t i = 0; i < array_size; ++i) {
+            if (!string_array[i]) {
+                return ov_status_e::INVALID_C_PARAM;
+            }
+        }
+        ov::Shape shape{array_size};
+        std::unique_ptr<ov_tensor_t> _tensor(new ov_tensor_t);
path: src/bindings/c/src/ov_tensor.cpp
user: login: praasz
body: ```suggestion
        auto _tensor = std::make_unique<ov_tensor_t>()
```
created_at: 2025-04-08T05:33:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -83,6 +83,30 @@ ov_status_e ov_tensor_create_from_host_ptr(const ov_element_type_e type,
     return ov_status_e::OK;
 }
 
+ov_status_e ov_tensor_create_from_string_array(const char** string_array, size_t array_size, ov_tensor_t** tensor) {
+    if (!tensor || !string_array || array_size == 0) {
+        return ov_status_e::INVALID_C_PARAM;
+    }
+    try {
+        for (size_t i = 0; i < array_size; ++i) {
+            if (!string_array[i]) {
+                return ov_status_e::INVALID_C_PARAM;
+            }
+        }
+        ov::Shape shape{array_size};
+        std::unique_ptr<ov_tensor_t> _tensor(new ov_tensor_t);
+        _tensor->object = std::make_shared<ov::Tensor>(ov::element::string, shape);
+        std::string* tensor_data = _tensor->object->data<std::string>();
+        for (size_t i = 0; i < array_size; ++i) {
+            tensor_data[i] = std::string(string_array[i]);
+        }
path: src/bindings/c/src/ov_tensor.cpp
user: login: praasz
body: ```suggestion
        _tensor->object = std::make_shared<ov::Tensor>(ov::element::string, shape);
        std::copy_n(string_array, array_size, _tenosr->object->data<std::string>());
```
?
created_at: 2025-04-08T05:45:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -57,6 +57,17 @@ ov_tensor_create(const ov_element_type_e type, const ov_shape_t shape, ov_tensor
 OPENVINO_C_API(ov_status_e)
 ov_tensor_set_shape(ov_tensor_t* tensor, const ov_shape_t shape);
 
+/**
+ * @brief Constructs a new tensor using a string array.
+ * @ingroup ov_tensor_c_api
+ * @param string_array An array of strings
+ * @param array_size The size of the string array
+ * @param tensor A point to ov_tensor_t
+ * @return Status code of the operation: OK(0) for success.
+ */
+OPENVINO_C_API(ov_status_e)
+ov_tensor_create_from_string_array(const char** string_array, size_t array_size, ov_tensor_t** tensor);
path: src/bindings/c/include/openvino/c/ov_tensor.h
user: login: rkazants
body: I think we also need to have ` const ov_shape_t shape` aurgument. String tensor can be of any shape as numeric one.
created_at: 2025-04-08T07:33:40Z
author_association: COLLABORATOR
diff_hunk: @@ -57,6 +57,17 @@ ov_tensor_create(const ov_element_type_e type, const ov_shape_t shape, ov_tensor
 OPENVINO_C_API(ov_status_e)
 ov_tensor_set_shape(ov_tensor_t* tensor, const ov_shape_t shape);
 
+/**
+ * @brief Constructs a new tensor using a string array.
+ * @ingroup ov_tensor_c_api
+ * @param string_array An array of strings
+ * @param array_size The size of the string array
+ * @param tensor A point to ov_tensor_t
+ * @return Status code of the operation: OK(0) for success.
+ */
+OPENVINO_C_API(ov_status_e)
+ov_tensor_create_from_string_array(const char** string_array, size_t array_size, ov_tensor_t** tensor);
path: src/bindings/c/include/openvino/c/ov_tensor.h
user: login: rkazants
body: ```suggestion
ov_tensor_create_from_string_array(const char** string_array, const size_t array_size, ov_tensor_t** tensor);
```

created_at: 2025-04-08T07:50:08Z
author_association: COLLABORATOR
diff_hunk: @@ -57,6 +57,17 @@ ov_tensor_create(const ov_element_type_e type, const ov_shape_t shape, ov_tensor
 OPENVINO_C_API(ov_status_e)
 ov_tensor_set_shape(ov_tensor_t* tensor, const ov_shape_t shape);
 
+/**
+ * @brief Constructs a new tensor using a string array.
+ * @ingroup ov_tensor_c_api
+ * @param string_array An array of strings
+ * @param array_size The size of the string array
+ * @param tensor A point to ov_tensor_t
+ * @return Status code of the operation: OK(0) for success.
+ */
+OPENVINO_C_API(ov_status_e)
+ov_tensor_create_from_string_array(const char** string_array, size_t array_size, ov_tensor_t** tensor);
path: src/bindings/c/include/openvino/c/ov_tensor.h
user: login: praasz
body: Maybe change `array_size` to `shape` where shape size is number of strings in `string_array`
created_at: 2025-04-08T07:59:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -57,6 +57,17 @@ ov_tensor_create(const ov_element_type_e type, const ov_shape_t shape, ov_tensor
 OPENVINO_C_API(ov_status_e)
 ov_tensor_set_shape(ov_tensor_t* tensor, const ov_shape_t shape);
 
+/**
+ * @brief Constructs a new tensor using a string array.
+ * @ingroup ov_tensor_c_api
+ * @param string_array An array of strings
+ * @param array_size The size of the string array
+ * @param tensor A point to ov_tensor_t
+ * @return Status code of the operation: OK(0) for success.
+ */
+OPENVINO_C_API(ov_status_e)
+ov_tensor_create_from_string_array(const char** string_array, size_t array_size, ov_tensor_t** tensor);
path: src/bindings/c/include/openvino/c/ov_tensor.h
user: login: t-jankowski
body: It's implementation detail not affecting function's signature - w/o `const` is fine enough.
created_at: 2025-04-08T08:54:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -57,6 +57,17 @@ ov_tensor_create(const ov_element_type_e type, const ov_shape_t shape, ov_tensor
 OPENVINO_C_API(ov_status_e)
 ov_tensor_set_shape(ov_tensor_t* tensor, const ov_shape_t shape);
 
+/**
+ * @brief Constructs a new tensor using a string array.
+ * @ingroup ov_tensor_c_api
+ * @param string_array An array of strings
+ * @param array_size The size of the string array
+ * @param tensor A point to ov_tensor_t
+ * @return Status code of the operation: OK(0) for success.
+ */
+OPENVINO_C_API(ov_status_e)
+ov_tensor_create_from_string_array(const char** string_array, size_t array_size, ov_tensor_t** tensor);
path: src/bindings/c/include/openvino/c/ov_tensor.h
user: login: rahulchaphalkar
body: Done
created_at: 2025-04-08T18:03:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -83,6 +83,30 @@ ov_status_e ov_tensor_create_from_host_ptr(const ov_element_type_e type,
     return ov_status_e::OK;
 }
 
+ov_status_e ov_tensor_create_from_string_array(const char** string_array, size_t array_size, ov_tensor_t** tensor) {
+    if (!tensor || !string_array || array_size == 0) {
+        return ov_status_e::INVALID_C_PARAM;
+    }
+    try {
+        for (size_t i = 0; i < array_size; ++i) {
+            if (!string_array[i]) {
+                return ov_status_e::INVALID_C_PARAM;
+            }
+        }
+        ov::Shape shape{array_size};
+        std::unique_ptr<ov_tensor_t> _tensor(new ov_tensor_t);
+        _tensor->object = std::make_shared<ov::Tensor>(ov::element::string, shape);
+        std::string* tensor_data = _tensor->object->data<std::string>();
+        for (size_t i = 0; i < array_size; ++i) {
+            tensor_data[i] = std::string(string_array[i]);
+        }
path: src/bindings/c/src/ov_tensor.cpp
user: login: rahulchaphalkar
body: Done
created_at: 2025-04-08T18:03:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -83,6 +83,30 @@ ov_status_e ov_tensor_create_from_host_ptr(const ov_element_type_e type,
     return ov_status_e::OK;
 }
 
+ov_status_e ov_tensor_create_from_string_array(const char** string_array, size_t array_size, ov_tensor_t** tensor) {
+    if (!tensor || !string_array || array_size == 0) {
+        return ov_status_e::INVALID_C_PARAM;
+    }
+    try {
+        for (size_t i = 0; i < array_size; ++i) {
+            if (!string_array[i]) {
+                return ov_status_e::INVALID_C_PARAM;
+            }
+        }
+        ov::Shape shape{array_size};
+        std::unique_ptr<ov_tensor_t> _tensor(new ov_tensor_t);
path: src/bindings/c/src/ov_tensor.cpp
user: login: rahulchaphalkar
body: Done
created_at: 2025-04-08T18:04:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -57,6 +57,17 @@ ov_tensor_create(const ov_element_type_e type, const ov_shape_t shape, ov_tensor
 OPENVINO_C_API(ov_status_e)
 ov_tensor_set_shape(ov_tensor_t* tensor, const ov_shape_t shape);
 
+/**
+ * @brief Constructs a new tensor using a string array.
+ * @ingroup ov_tensor_c_api
+ * @param string_array An array of strings
+ * @param array_size The size of the string array
+ * @param tensor A point to ov_tensor_t
+ * @return Status code of the operation: OK(0) for success.
+ */
+OPENVINO_C_API(ov_status_e)
+ov_tensor_create_from_string_array(const char** string_array, size_t array_size, ov_tensor_t** tensor);
path: src/bindings/c/include/openvino/c/ov_tensor.h
user: login: rahulchaphalkar
body: Good points about incorporating `shape` here. I've done this, but have also kept `array_size`. If `array_size` is removed, we have following options which I didn't think were better -

- Find size from `string_array`. This requires `string_array` to be null terminated, so user will have to explicitly add a nullptr, e.g. `const char* string_array[4] = {"test", "me", "hi", nullptr};`. Probably not what we want.
- Find size from given `shape`. This can be done by `std::accumulate` of all dims with a `std::multiplies` operation. We assume that `shape` size is equal to `string_array` size.
created_at: 2025-04-08T18:10:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -83,6 +83,25 @@ ov_status_e ov_tensor_create_from_host_ptr(const ov_element_type_e type,
     return ov_status_e::OK;
 }
 
+ov_status_e ov_tensor_create_from_string_array(const char** string_array,
+                                               const size_t array_size,
+                                               const ov_shape_t shape,
+                                               ov_tensor_t** tensor) {
+    if (!tensor || !string_array || array_size < 1) {
+        return ov_status_e::INVALID_C_PARAM;
+    }
+    try {
+        auto _tensor = std::make_unique<ov_tensor_t>();
+        ov::Shape tmp_shape;
+        std::copy_n(shape.dims, shape.rank, std::back_inserter(tmp_shape));
+        _tensor->object = std::make_shared<ov::Tensor>(ov::element::string, tmp_shape);
+        std::copy_n(string_array, array_size, _tensor->object->data<std::string>());
path: src/bindings/c/src/ov_tensor.cpp
user: login: rkazants
body: probably, need to check that `shape_size(tmp_shape) <= array_size`.
created_at: 2025-04-09T05:34:51Z
author_association: COLLABORATOR
diff_hunk: @@ -83,6 +83,25 @@ ov_status_e ov_tensor_create_from_host_ptr(const ov_element_type_e type,
     return ov_status_e::OK;
 }
 
+ov_status_e ov_tensor_create_from_string_array(const char** string_array,
+                                               const size_t array_size,
+                                               const ov_shape_t shape,
+                                               ov_tensor_t** tensor) {
+    if (!tensor || !string_array || array_size < 1) {
path: src/bindings/c/src/ov_tensor.cpp
user: login: rkazants
body: I think `array_size < 1` is redundant because string tensor can be empty
created_at: 2025-04-09T05:47:57Z
author_association: COLLABORATOR
diff_hunk: @@ -83,6 +83,25 @@ ov_status_e ov_tensor_create_from_host_ptr(const ov_element_type_e type,
     return ov_status_e::OK;
 }
 
+ov_status_e ov_tensor_create_from_string_array(const char** string_array,
+                                               const size_t array_size,
+                                               const ov_shape_t shape,
+                                               ov_tensor_t** tensor) {
+    if (!tensor || !string_array || array_size < 1) {
+        return ov_status_e::INVALID_C_PARAM;
+    }
+    try {
+        auto _tensor = std::make_unique<ov_tensor_t>();
+        ov::Shape tmp_shape;
+        std::copy_n(shape.dims, shape.rank, std::back_inserter(tmp_shape));
+        _tensor->object = std::make_shared<ov::Tensor>(ov::element::string, tmp_shape);
+        std::copy_n(string_array, array_size, _tensor->object->data<std::string>());
path: src/bindings/c/src/ov_tensor.cpp
user: login: rahulchaphalkar
body: Done
created_at: 2025-04-10T20:42:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -83,6 +83,25 @@ ov_status_e ov_tensor_create_from_host_ptr(const ov_element_type_e type,
     return ov_status_e::OK;
 }
 
+ov_status_e ov_tensor_create_from_string_array(const char** string_array,
+                                               const size_t array_size,
+                                               const ov_shape_t shape,
+                                               ov_tensor_t** tensor) {
+    if (!tensor || !string_array || array_size < 1) {
path: src/bindings/c/src/ov_tensor.cpp
user: login: rahulchaphalkar
body: Done
created_at: 2025-04-10T20:42:33Z
author_association: CONTRIBUTOR

PR comments:
user: login: praasz
created_at: 2025-04-08T05:48:29Z
body: build_jenkins
author_association: CONTRIBUTOR
user: login: mlukasze
created_at: 2025-04-09T05:11:24Z
body: build_jenkins
author_association: CONTRIBUTOR
user: login: mlukasze
created_at: 2025-04-10T12:48:48Z
body: build_jenkins
author_association: CONTRIBUTOR
user: login: rkazants
created_at: 2025-04-11T06:07:35Z
body: build_jenkins
author_association: COLLABORATOR