This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git

commit 5ab9d43d2cbca928f72ed1d89ecec0ce04ba5090
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Wed Sep 23 08:36:34 2026 +0800

    branch-4.1: [Fix](be) Prevent BE crash on malformed adapter responses 
#68247 (#68333)
    
    Cherry-picked from #68247
    
    Co-authored-by: linrrarity <[email protected]>
---
 be/src/exprs/function/ai/ai_adapter.h | 102 ++++++++++++++++++++--------------
 be/test/ai/ai_adapter_test.cpp        |  56 +++++++++++++++++++
 be/test/ai/embed_test.cpp             |  96 ++++++++++++++++++++++++++++++++
 3 files changed, 213 insertions(+), 41 deletions(-)

diff --git a/be/src/exprs/function/ai/ai_adapter.h 
b/be/src/exprs/function/ai/ai_adapter.h
index bda8e6798b5..eff48ef83cd 100644
--- a/be/src/exprs/function/ai/ai_adapter.h
+++ b/be/src/exprs/function/ai/ai_adapter.h
@@ -20,7 +20,6 @@
 #include <gen_cpp/PaloInternalService_types.h>
 #include <rapidjson/rapidjson.h>
 
-#include <algorithm>
 #include <cctype>
 #include <memory>
 #include <string>
@@ -211,6 +210,27 @@ protected:
         return Status::OK();
     }
 
+    Status append_parsed_embedding_result(const rapidjson::Value& embedding,
+                                          std::vector<std::vector<float>>& 
results,
+                                          const std::string& response_body) 
const {
+        if (!embedding.IsArray()) {
+            return Status::InternalError("Invalid {} response format: {}", 
_config.provider_type,
+                                         response_body);
+        }
+
+        std::vector<float> parsed_embedding;
+        parsed_embedding.reserve(embedding.Size());
+        for (const auto& value : embedding.GetArray()) {
+            if (!value.IsNumber()) {
+                return Status::InternalError("Invalid {} response format: {}",
+                                             _config.provider_type, 
response_body);
+            }
+            parsed_embedding.emplace_back(value.GetFloat());
+        }
+        results.emplace_back(std::move(parsed_embedding));
+        return Status::OK();
+    }
+
     // return true if the model support dimension parameter
     virtual bool supports_dimension_param(const std::string& model_name) const 
{ return false; }
 
@@ -408,14 +428,12 @@ public:
         const auto& data = doc["data"];
         results.reserve(data.Size());
         for (rapidjson::SizeType i = 0; i < data.Size(); i++) {
-            if (!data[i].HasMember("embedding") || 
!data[i]["embedding"].IsArray()) {
+            if (!data[i].IsObject() || !data[i].HasMember("embedding")) {
                 return Status::InternalError("Invalid {} response format: {}",
                                              _config.provider_type, 
response_body);
             }
-
-            std::transform(data[i]["embedding"].Begin(), 
data[i]["embedding"].End(),
-                           std::back_inserter(results.emplace_back()),
-                           [](const auto& val) { return val.GetFloat(); });
+            RETURN_IF_ERROR(
+                    append_parsed_embedding_result(data[i]["embedding"], 
results, response_body));
         }
 
         return Status::OK();
@@ -482,6 +500,14 @@ public:
             results.reserve(choices.Size());
 
             for (rapidjson::SizeType i = 0; i < choices.Size(); i++) {
+                if (!choices[i].IsObject()) {
+                    return Status::InternalError("Invalid {} response format: 
{}",
+                                                 _config.provider_type, 
response_body);
+                }
+                if (choices[i].HasMember("message") && 
!choices[i]["message"].IsObject()) {
+                    return Status::InternalError("Invalid {} response format: 
{}",
+                                                 _config.provider_type, 
response_body);
+                }
                 if (choices[i].HasMember("message") && 
choices[i]["message"].HasMember("content") &&
                     choices[i]["message"]["content"].IsString()) {
                     RETURN_IF_ERROR(append_parsed_text_result(
@@ -561,37 +587,31 @@ public:
         }
 
         // parse different response format
-        rapidjson::Value embedding;
         if (doc.HasMember("data") && doc["data"].IsArray()) {
             // "data":["object":"embedding", "embedding":[0.1, 0.2...], 
"index":0]
             const auto& data = doc["data"];
             results.reserve(data.Size());
             for (rapidjson::SizeType i = 0; i < data.Size(); i++) {
-                if (!data[i].HasMember("embedding") || 
!data[i]["embedding"].IsArray()) {
+                if (!data[i].IsObject() || !data[i].HasMember("embedding")) {
                     return Status::InternalError("Invalid {} response format",
                                                  _config.provider_type);
                 }
-
-                std::transform(data[i]["embedding"].Begin(), 
data[i]["embedding"].End(),
-                               std::back_inserter(results.emplace_back()),
-                               [](const auto& val) { return val.GetFloat(); });
+                
RETURN_IF_ERROR(append_parsed_embedding_result(data[i]["embedding"], results,
+                                                               response_body));
             }
         } else if (doc.HasMember("embeddings") && doc["embeddings"].IsArray()) 
{
             // "embeddings":[[0.1, 0.2, ...]]
-            results.reserve(1);
-            for (int i = 0; i < doc["embeddings"].Size(); i++) {
-                embedding = doc["embeddings"][i];
-                std::transform(embedding.Begin(), embedding.End(),
-                               std::back_inserter(results.emplace_back()),
-                               [](const auto& val) { return val.GetFloat(); });
+            const auto& embeddings = doc["embeddings"];
+            results.reserve(embeddings.Size());
+            for (rapidjson::SizeType i = 0; i < embeddings.Size(); i++) {
+                RETURN_IF_ERROR(
+                        append_parsed_embedding_result(embeddings[i], results, 
response_body));
             }
         } else if (doc.HasMember("embedding") && doc["embedding"].IsArray()) {
             // "embedding":[0.1, 0.2, ...]
             results.reserve(1);
-            embedding = doc["embedding"];
-            std::transform(embedding.Begin(), embedding.End(),
-                           std::back_inserter(results.emplace_back()),
-                           [](const auto& val) { return val.GetFloat(); });
+            RETURN_IF_ERROR(
+                    append_parsed_embedding_result(doc["embedding"], results, 
response_body));
         } else {
             return Status::InternalError("Invalid {} response format: {}", 
_config.provider_type,
                                          response_body);
@@ -946,7 +966,8 @@ public:
             results.reserve(choices.Size());
 
             for (rapidjson::SizeType i = 0; i < choices.Size(); i++) {
-                if (!choices[i].HasMember("message") ||
+                if (!choices[i].IsObject() || !choices[i].HasMember("message") 
||
+                    !choices[i]["message"].IsObject() ||
                     !choices[i]["message"].HasMember("content") ||
                     !choices[i]["message"]["content"].IsString()) {
                     return Status::InternalError("Invalid choice format in {} 
response: {}",
@@ -1130,14 +1151,12 @@ public:
             const auto& embeddings = doc["output"]["embeddings"];
             results.reserve(embeddings.Size());
             for (rapidjson::SizeType i = 0; i < embeddings.Size(); i++) {
-                if (!embeddings[i].HasMember("embedding") ||
-                    !embeddings[i]["embedding"].IsArray()) {
+                if (!embeddings[i].IsObject() || 
!embeddings[i].HasMember("embedding")) {
                     return Status::InternalError("Invalid {} response format: 
{}",
                                                  _config.provider_type, 
response_body);
                 }
-                std::transform(embeddings[i]["embedding"].Begin(), 
embeddings[i]["embedding"].End(),
-                               std::back_inserter(results.emplace_back()),
-                               [](const auto& val) { return val.GetFloat(); });
+                
RETURN_IF_ERROR(append_parsed_embedding_result(embeddings[i]["embedding"], 
results,
+                                                               response_body));
             }
             return Status::OK();
         }
@@ -1324,10 +1343,12 @@ public:
         results.reserve(candidates.Size());
 
         for (rapidjson::SizeType i = 0; i < candidates.Size(); i++) {
-            if (!candidates[i].HasMember("content") ||
+            if (!candidates[i].IsObject() || 
!candidates[i].HasMember("content") ||
+                !candidates[i]["content"].IsObject() ||
                 !candidates[i]["content"].HasMember("parts") ||
                 !candidates[i]["content"]["parts"].IsArray() ||
                 candidates[i]["content"]["parts"].Empty() ||
+                !candidates[i]["content"]["parts"][0].IsObject() ||
                 !candidates[i]["content"]["parts"][0].HasMember("text") ||
                 !candidates[i]["content"]["parts"][0]["text"].IsString()) {
                 return Status::InternalError("Invalid candidate format in {} 
response",
@@ -1499,13 +1520,12 @@ public:
             const auto& embeddings = doc["embeddings"];
             results.reserve(embeddings.Size());
             for (rapidjson::SizeType i = 0; i < embeddings.Size(); i++) {
-                if (!embeddings[i].HasMember("values") || 
!embeddings[i]["values"].IsArray()) {
+                if (!embeddings[i].IsObject() || 
!embeddings[i].HasMember("values")) {
                     return Status::InternalError("Invalid {} response format: 
{}",
                                                  _config.provider_type, 
response_body);
                 }
-                std::transform(embeddings[i]["values"].Begin(), 
embeddings[i]["values"].End(),
-                               std::back_inserter(results.emplace_back()),
-                               [](const auto& val) { return val.GetFloat(); });
+                
RETURN_IF_ERROR(append_parsed_embedding_result(embeddings[i]["values"], results,
+                                                               response_body));
             }
             return Status::OK();
         }
@@ -1520,13 +1540,12 @@ public:
           }
         }*/
         const auto& embedding = doc["embedding"];
-        if (!embedding.HasMember("values") || !embedding["values"].IsArray()) {
+        if (!embedding.HasMember("values")) {
             return Status::InternalError("Invalid {} response format: {}", 
_config.provider_type,
                                          response_body);
         }
-        std::transform(embedding["values"].Begin(), embedding["values"].End(),
-                       std::back_inserter(results.emplace_back()),
-                       [](const auto& val) { return val.GetFloat(); });
+        RETURN_IF_ERROR(
+                append_parsed_embedding_result(embedding["values"], results, 
response_body));
 
         return Status::OK();
     }
@@ -1626,6 +1645,10 @@ public:
 
         std::string result;
         for (rapidjson::SizeType i = 0; i < content.Size(); i++) {
+            if (!content[i].IsObject()) {
+                return Status::InternalError("Invalid {} response format: {}",
+                                             _config.provider_type, 
response_body);
+            }
             if (!content[i].HasMember("type") || 
!content[i]["type"].IsString() ||
                 !content[i].HasMember("text") || 
!content[i]["text"].IsString()) {
                 continue;
@@ -1697,10 +1720,7 @@ public:
         }
 
         results.reserve(1);
-        std::transform(doc["embedding"].Begin(), doc["embedding"].End(),
-                       std::back_inserter(results.emplace_back()),
-                       [](const auto& val) { return val.GetFloat(); });
-        return Status::OK();
+        return append_parsed_embedding_result(doc["embedding"], results, 
response_body);
     }
 
 private:
diff --git a/be/test/ai/ai_adapter_test.cpp b/be/test/ai/ai_adapter_test.cpp
index da40ef217dc..1eac053d205 100644
--- a/be/test/ai/ai_adapter_test.cpp
+++ b/be/test/ai/ai_adapter_test.cpp
@@ -863,6 +863,20 @@ TEST(AI_ADAPTER_TEST, parse_response_wrong_type) {
                 ::testing::HasSubstr("Unsupported response format from local 
AI."));
 }
 
+TEST(AI_ADAPTER_TEST, local_adapter_rejects_non_object_choice) {
+    LocalAdapter adapter;
+    std::vector<std::string> results;
+    Status st = adapter.parse_response(R"({"choices":[1]})", results);
+    ASSERT_FALSE(st.ok());
+}
+
+TEST(AI_ADAPTER_TEST, local_adapter_rejects_non_object_message) {
+    LocalAdapter adapter;
+    std::vector<std::string> results;
+    Status st = adapter.parse_response(R"({"choices":[{"message":1}]})", 
results);
+    ASSERT_FALSE(st.ok());
+}
+
 TEST(AI_ADAPTER_TEST, openai_adapter_parse_response_choice_format_error) {
     OpenAIAdapter adapter;
     // message field missing
@@ -880,6 +894,20 @@ TEST(AI_ADAPTER_TEST, 
openai_adapter_parse_response_choice_format_error) {
     EXPECT_THAT(st.to_string().c_str(), ::testing::HasSubstr("Invalid choice 
format in  response"));
 }
 
+TEST(AI_ADAPTER_TEST, openai_adapter_rejects_non_object_choice) {
+    OpenAIAdapter adapter;
+    std::vector<std::string> results;
+    Status st = adapter.parse_response(R"({"choices":[1]})", results);
+    ASSERT_FALSE(st.ok());
+}
+
+TEST(AI_ADAPTER_TEST, openai_adapter_rejects_non_object_message) {
+    OpenAIAdapter adapter;
+    std::vector<std::string> results;
+    Status st = adapter.parse_response(R"({"choices":[{"message":1}]})", 
results);
+    ASSERT_FALSE(st.ok());
+}
+
 TEST(AI_ADAPTER_TEST, openai_adapter_parse_response_parse_error) {
     OpenAIAdapter adapter;
     std::string resp = "not a json";
@@ -916,6 +944,27 @@ TEST(AI_ADAPTER_TEST, 
gemini_parse_response_missing_candidates) {
     EXPECT_THAT(st.to_string().c_str(), ::testing::HasSubstr("Invalid  
response format"));
 }
 
+TEST(AI_ADAPTER_TEST, gemini_adapter_rejects_non_object_candidate) {
+    GeminiAdapter adapter;
+    std::vector<std::string> results;
+    Status st = adapter.parse_response(R"({"candidates":[1]})", results);
+    ASSERT_FALSE(st.ok());
+}
+
+TEST(AI_ADAPTER_TEST, gemini_adapter_rejects_non_object_content) {
+    GeminiAdapter adapter;
+    std::vector<std::string> results;
+    Status st = adapter.parse_response(R"({"candidates":[{"content":1}]})", 
results);
+    ASSERT_FALSE(st.ok());
+}
+
+TEST(AI_ADAPTER_TEST, gemini_adapter_rejects_non_object_part) {
+    GeminiAdapter adapter;
+    std::vector<std::string> results;
+    Status st = 
adapter.parse_response(R"({"candidates":[{"content":{"parts":[1]}}]})", 
results);
+    ASSERT_FALSE(st.ok());
+}
+
 TEST(AI_ADAPTER_TEST, anthropic_adapter_parse_response_parse_error) {
     AnthropicAdapter adapter;
     std::string resp = "not a json";
@@ -934,6 +983,13 @@ TEST(AI_ADAPTER_TEST, 
anthropic_adapter_parse_response_content_not_array) {
     EXPECT_THAT(st.to_string().c_str(), ::testing::HasSubstr("Invalid  
response format"));
 }
 
+TEST(AI_ADAPTER_TEST, anthropic_adapter_rejects_non_object_content_item) {
+    AnthropicAdapter adapter;
+    std::vector<std::string> results;
+    Status st = adapter.parse_response(R"({"content":[1]})", results);
+    ASSERT_FALSE(st.ok());
+}
+
 TEST(AI_ADAPTER_TEST, voyage_adapter_chat_test) {
     VoyageAIAdapter adapter;
     TAIResource config;
diff --git a/be/test/ai/embed_test.cpp b/be/test/ai/embed_test.cpp
index c9bd32ed17c..3ed13921d19 100644
--- a/be/test/ai/embed_test.cpp
+++ b/be/test/ai/embed_test.cpp
@@ -951,6 +951,49 @@ TEST(EMBED_TEST, local_adapter_parse_embedding_response) {
     ASSERT_FLOAT_EQ(results[0][1], 0.7F);
 }
 
+TEST(EMBED_TEST, local_adapter_rejects_non_object_data_item) {
+    LocalAdapter adapter;
+    std::vector<std::vector<float>> results;
+    Status st = adapter.parse_embedding_response(R"({"data":[1]})", results);
+    ASSERT_FALSE(st.ok());
+}
+
+TEST(EMBED_TEST, local_adapter_rejects_non_numeric_data_embedding) {
+    LocalAdapter adapter;
+    std::vector<std::vector<float>> results;
+    Status st =
+            
adapter.parse_embedding_response(R"({"data":[{"embedding":[0.1,"bad"]}]})", 
results);
+    ASSERT_FALSE(st.ok());
+}
+
+TEST(EMBED_TEST, local_adapter_rejects_non_array_embeddings_item) {
+    LocalAdapter adapter;
+    std::vector<std::vector<float>> results;
+    Status st = 
adapter.parse_embedding_response(R"({"embeddings":[0.1,0.2]})", results);
+    ASSERT_FALSE(st.ok());
+}
+
+TEST(EMBED_TEST, local_adapter_rejects_non_numeric_embeddings_item) {
+    LocalAdapter adapter;
+    std::vector<std::vector<float>> results;
+    Status st = 
adapter.parse_embedding_response(R"({"embeddings":[[0.1,"bad"]]})", results);
+    ASSERT_FALSE(st.ok());
+}
+
+TEST(EMBED_TEST, local_adapter_rejects_non_numeric_embedding) {
+    LocalAdapter adapter;
+    std::vector<std::vector<float>> results;
+    Status st = 
adapter.parse_embedding_response(R"({"embedding":[0.1,"bad"]})", results);
+    ASSERT_FALSE(st.ok());
+}
+
+TEST(EMBED_TEST, mock_adapter_rejects_non_numeric_embedding) {
+    MockAdapter adapter;
+    std::vector<std::vector<float>> results;
+    Status st = 
adapter.parse_embedding_response(R"({"embedding":[0.1,"bad"]})", results);
+    ASSERT_FALSE(st.ok());
+}
+
 TEST(EMBED_TEST, openai_adapter_embedding_request) {
     OpenAIAdapter adapter;
     TAIResource config;
@@ -1117,6 +1160,21 @@ TEST(EMBED_TEST, qwen_embedding_request) {
     ASSERT_EQ(doc["dimension"].GetInt(), config.dimensions);
 }
 
+TEST(EMBED_TEST, qwen_adapter_rejects_non_object_embedding_item) {
+    QwenAdapter adapter;
+    std::vector<std::vector<float>> results;
+    Status st = 
adapter.parse_embedding_response(R"({"output":{"embeddings":[1]}})", results);
+    ASSERT_FALSE(st.ok());
+}
+
+TEST(EMBED_TEST, qwen_adapter_rejects_non_numeric_embedding) {
+    QwenAdapter adapter;
+    std::vector<std::vector<float>> results;
+    Status st = adapter.parse_embedding_response(
+            R"({"output":{"embeddings":[{"embedding":[0.1,"bad"]}]}})", 
results);
+    ASSERT_FALSE(st.ok());
+}
+
 TEST(EMBED_TEST, gemini_adapter_embedding_request) {
     GeminiAdapter adapter;
     TAIResource config;
@@ -1240,6 +1298,29 @@ TEST(EMBED_TEST, 
gemini_adapter_parse_embedding_response) {
     ASSERT_FLOAT_EQ(results[1][2], 2.3F);
 }
 
+TEST(EMBED_TEST, gemini_adapter_rejects_non_object_embedding_item) {
+    GeminiAdapter adapter;
+    std::vector<std::vector<float>> results;
+    Status st = adapter.parse_embedding_response(R"({"embeddings":[1]})", 
results);
+    ASSERT_FALSE(st.ok());
+}
+
+TEST(EMBED_TEST, gemini_adapter_rejects_non_numeric_batch_embedding) {
+    GeminiAdapter adapter;
+    std::vector<std::vector<float>> results;
+    Status st =
+            
adapter.parse_embedding_response(R"({"embeddings":[{"values":[0.1,"bad"]}]})", 
results);
+    ASSERT_FALSE(st.ok());
+}
+
+TEST(EMBED_TEST, gemini_adapter_rejects_non_numeric_single_embedding) {
+    GeminiAdapter adapter;
+    std::vector<std::vector<float>> results;
+    Status st =
+            
adapter.parse_embedding_response(R"({"embedding":{"values":[0.1,"bad"]}})", 
results);
+    ASSERT_FALSE(st.ok());
+}
+
 TEST(EMBED_TEST, voyageai_adapter_embedding_request) {
     VoyageAIAdapter adapter;
     TAIResource config;
@@ -1327,6 +1408,21 @@ TEST(EMBED_TEST, 
voyageai_adapter_parse_embedding_response) {
     ASSERT_FLOAT_EQ(results[1][1], 0.5F);
 }
 
+TEST(EMBED_TEST, voyageai_adapter_rejects_non_object_data_item) {
+    VoyageAIAdapter adapter;
+    std::vector<std::vector<float>> results;
+    Status st = adapter.parse_embedding_response(R"({"data":[1]})", results);
+    ASSERT_FALSE(st.ok());
+}
+
+TEST(EMBED_TEST, voyageai_adapter_rejects_non_numeric_embedding) {
+    VoyageAIAdapter adapter;
+    std::vector<std::vector<float>> results;
+    Status st =
+            
adapter.parse_embedding_response(R"({"data":[{"embedding":[0.1,"bad"]}]})", 
results);
+    ASSERT_FALSE(st.ok());
+}
+
 TEST(EMBED_TEST, voyageai_adapter_parse_error_test) {
     VoyageAIAdapter adapter;
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to