🚀 جرب Zilliz Cloud، الـ Milvus المدارة بالكامل، مجاناً — تجربة أداء أسرع بـ 10 أضعاف! جرب الآن>>

milvus-logo
LFAI
  • Home
  • Blog
  • البحث الدلالي مقابل البحث بالنص الكامل: أيهما أختار في ميلفوس 2.5؟

البحث الدلالي مقابل البحث بالنص الكامل: أيهما أختار في ميلفوس 2.5؟

  • Engineering
December 17, 2024
David Wang, Jiang Chen

شركة Milvus، وهي شركة رائدة في مجال قواعد البيانات المتجهة عالية الأداء، تخصصت منذ فترة طويلة في البحث الدلالي باستخدام التضمينات المتجهة من نماذج التعلم العميق. تعمل هذه التقنية على تشغيل تطبيقات الذكاء الاصطناعي مثل التوليد المعزز للاسترجاع (RAG) ومحركات البحث وأنظمة التوصية. ومع تزايد شعبية RAG وغيرها من تطبيقات البحث عن النصوص، أدرك المجتمع مزايا الجمع بين طرق مطابقة النصوص التقليدية مع البحث الدلالي، والمعروف باسم البحث الهجين. هذا النهج مفيد بشكل خاص في السيناريوهات التي تعتمد بشكل كبير على مطابقة الكلمات الرئيسية. لتلبية هذه الحاجة، يقدم الإصدار 2.5 من Milvus 2.5 وظيفة البحث في النص الكامل (FTS) ويدمجها مع البحث المتجه المتناثر وقدرات البحث الهجين المتوفرة بالفعل منذ الإصدار 2.4، مما يخلق تآزرًا قويًا.

البحث الهجين هو طريقة تجمع بين النتائج من مسارات بحث متعددة. يمكن للمستخدمين البحث في حقول بيانات مختلفة بطرق مختلفة، ثم دمج النتائج وترتيبها للحصول على نتيجة شاملة. في سيناريوهات RAG الشائعة اليوم، يجمع النهج الهجين النموذجي بين البحث الدلالي والبحث بالنص الكامل. وعلى وجه التحديد، يتضمن ذلك دمج النتائج من البحث الدلالي القائم على التضمين الكثيف والمطابقة المعجمية القائمة على BM25 باستخدام RRF (دمج الرتب المتبادلة) لتحسين ترتيب النتائج.

في هذه المقالة، سنقوم بتوضيح ذلك باستخدام مجموعة بيانات مقدمة من أنثروبيك، والتي تتكون من مقتطفات أكواد من تسعة مستودعات أكواد. يشبه هذا حالة استخدام شائعة لـ RAG: روبوت ترميز بمساعدة الذكاء الاصطناعي. نظرًا لأن بيانات الكود تحتوي على الكثير من التعريفات والكلمات المفتاحية وغيرها من المعلومات، يمكن أن يكون البحث المستند إلى النص فعالاً بشكل خاص في هذا السياق. وفي الوقت نفسه، يمكن لنماذج التضمين الكثيفة المدرّبة على مجموعات بيانات الكود الكبيرة أن تلتقط معلومات دلالية ذات مستوى أعلى. هدفنا هو مراقبة آثار الجمع بين هذين النهجين من خلال التجريب.

سنقوم بتحليل حالات محددة لتطوير فهم أوضح للبحث الهجين. كخط أساس، سنستخدم نموذجًا متقدمًا للتضمين الكثيف (voyage-2) تم تدريبه على حجم كبير من بيانات الأكواد. سنقوم بعد ذلك باختيار أمثلة يتفوق فيها البحث الهجين على كل من نتائج البحث الدلالي والبحث بالنص الكامل (أعلى 5) لتحليل الخصائص الكامنة وراء هذه الحالات.

الطريقةتمرير@5
بحث النص الكامل0.7318
البحث الدلالي0.8096
البحث الهجين0.8176
البحث الهجين (إضافة كلمة توقف)0.8418

بالإضافة إلى تحليل الجودة على أساس كل حالة على حدة، قمنا بتوسيع نطاق تقييمنا من خلال حساب مقياس النجاح@5 عبر مجموعة البيانات بأكملها. يقيس هذا المقياس نسبة النتائج ذات الصلة الموجودة في أعلى 5 نتائج لكل استعلام. تُظهر النتائج التي توصلنا إليها أنه على الرغم من أن نماذج التضمين المتقدمة تُنشئ خط أساس قوي، فإن دمجها مع البحث في النص الكامل يؤدي إلى نتائج أفضل. من الممكن إجراء المزيد من التحسينات من خلال فحص نتائج BM25 وضبط المعلمات لسيناريوهات محددة، مما قد يؤدي إلى تحقيق مكاسب كبيرة في الأداء.

ندرس النتائج المحددة التي تم استرجاعها لثلاثة استعلامات بحث مختلفة، ونقارن بين البحث الدلالي والبحث بالنص الكامل والبحث الهجين. يمكنك أيضًا الاطلاع على الكود الكامل في هذا الريبو.

الاستعلام: كيف يتم إنشاء ملف السجل؟

يهدف هذا الاستعلام إلى الاستفسار عن إنشاء ملف السجل، ويجب أن تكون الإجابة الصحيحة هي مقتطف من شيفرة Rust التي تنشئ ملف السجل. في نتائج البحث الدلالي، رأينا في نتائج البحث الدلالي بعض الشيفرة التي تقدم ملف رأس السجل وشيفرة C++ للحصول على المسجل. لكن المفتاح هنا هو متغير "ملف السجل". في نتيجة البحث الهجين #hybrid 0، وجدنا هذه النتيجة ذات الصلة، والتي هي بطبيعة الحال من البحث بالنص الكامل لأن البحث الهجين يدمج نتائج البحث الدلالي والبحث بالنص الكامل.

بالإضافة إلى هذه النتيجة، يمكننا العثور على كود اختبار وهمي غير ذي صلة في #hybrid 2، خاصةً العبارة المتكررة "سلسلة طويلة لاختبار كيفية التعامل معها". وهذا يتطلب فهم المبادئ الكامنة وراء خوارزمية BM25 المستخدمة في البحث عن النص الكامل. يهدف البحث في النص الكامل إلى مطابقة الكلمات الأكثر ندرة (لأن الكلمات الشائعة تقلل من تميز النص وتعيق تمييز الكائنات). لنفترض أننا أجرينا تحليلًا إحصائيًا على مجموعة كبيرة من النصوص الطبيعية. في هذه الحالة، من السهل أن نستنتج أن كلمة "كيف" هي كلمة شائعة جدًا وتساهم بشكل ضئيل جدًا في درجة الملاءمة. ومع ذلك، في هذه الحالة، تتكون مجموعة البيانات في هذه الحالة من كود، ولا يوجد الكثير من التكرارات لكلمة "كيف" في الكود، مما يجعلها مصطلح بحث رئيسي في هذا السياق.

الحقيقة الأساسية: الإجابة الصحيحة هي كود Rust الذي ينشئ ملف سجل.

use {
    crate::args::LogArgs,
    anyhow::{anyhow, Result},
    simplelog::{Config, LevelFilter, WriteLogger},
    std::fs::File,
};

pub struct Logger;

impl Logger {
    pub fn init(args: &impl LogArgs) -> Result<()> {
        let filter: LevelFilter = args.log_level().into();
        if filter != LevelFilter::Off {
            let logfile = File::create(args.log_file())
                .map_err(|e| anyhow!("Failed to open log file: {e:}"))?;
            WriteLogger::init(filter, Config::default(), logfile)
                .map_err(|e| anyhow!("Failed to initalize logger: {e:}"))?;
        }
        Ok(())
    }
}

نتائج البحث الدلالي

##dense 0 0.7745316028594971 
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "logunit.h"
#include <log4cxx/logger.h>
#include <log4cxx/simplelayout.h>
#include <log4cxx/fileappender.h>
#include <log4cxx/helpers/absolutetimedateformat.h>



 ##dense 1 0.769859254360199 
        void simple()
        {
                LayoutPtr layout = LayoutPtr(new SimpleLayout());
                AppenderPtr appender = FileAppenderPtr(new FileAppender(layout, LOG4CXX_STR("output/simple"), false));
                root->addAppender(appender);
                common();

                LOGUNIT_ASSERT(Compare::compare(LOG4CXX_FILE("output/simple"), LOG4CXX_FILE("witness/simple")));
        }

        std::string createMessage(int i, Pool & pool)
        {
                std::string msg("Message ");
                msg.append(pool.itoa(i));
                return msg;
        }

        void common()
        {
                int i = 0;

                // In the lines below, the logger names are chosen as an aid in
                // remembering their level values. In general, the logger names
                // have no bearing to level values.
                LoggerPtr ERRlogger = Logger::getLogger(LOG4CXX_TEST_STR("ERR"));
                ERRlogger->setLevel(Level::getError());



 ##dense 2 0.7591114044189453 
                log4cxx::spi::LoggingEventPtr logEvt = std::make_shared<log4cxx::spi::LoggingEvent>(LOG4CXX_STR("foo"),
                                                                                                                                                                                         Level::getInfo(),
                                                                                                                                                                                         LOG4CXX_STR("A Message"),
                                                                                                                                                                                         log4cxx::spi::LocationInfo::getLocationUnavailable());
                FMTLayout layout(LOG4CXX_STR("{d:%Y-%m-%d %H:%M:%S} {message}"));
                LogString output;
                log4cxx::helpers::Pool pool;
                layout.format( output, logEvt, pool);



 ##dense 3 0.7562235593795776 
#include "util/compare.h"
#include "util/transformer.h"
#include "util/absolutedateandtimefilter.h"
#include "util/iso8601filter.h"
#include "util/absolutetimefilter.h"
#include "util/relativetimefilter.h"
#include "util/controlfilter.h"
#include "util/threadfilter.h"
#include "util/linenumberfilter.h"
#include "util/filenamefilter.h"
#include "vectorappender.h"
#include <log4cxx/fmtlayout.h>
#include <log4cxx/propertyconfigurator.h>
#include <log4cxx/helpers/date.h>
#include <log4cxx/spi/loggingevent.h>
#include <iostream>
#include <iomanip>

#define REGEX_STR(x) x
#define PAT0 REGEX_STR("\\[[0-9A-FXx]*]\\ (DEBUG|INFO|WARN|ERROR|FATAL) .* - Message [0-9]\\{1,2\\}")
#define PAT1 ISO8601_PAT REGEX_STR(" ") PAT0
#define PAT2 ABSOLUTE_DATE_AND_TIME_PAT REGEX_STR(" ") PAT0
#define PAT3 ABSOLUTE_TIME_PAT REGEX_STR(" ") PAT0
#define PAT4 RELATIVE_TIME_PAT REGEX_STR(" ") PAT0
#define PAT5 REGEX_STR("\\[[0-9A-FXx]*]\\ (DEBUG|INFO|WARN|ERROR|FATAL) .* : Message [0-9]\\{1,2\\}")


 ##dense 4 0.7557586431503296 
                std::string msg("Message ");

                Pool pool;

                // These should all log.----------------------------
                LOG4CXX_FATAL(ERRlogger, createMessage(i, pool));
                i++; //0
                LOG4CXX_ERROR(ERRlogger, createMessage(i, pool));
                i++;

                LOG4CXX_FATAL(INF, createMessage(i, pool));
                i++; // 2
                LOG4CXX_ERROR(INF, createMessage(i, pool));
                i++;
                LOG4CXX_WARN(INF, createMessage(i, pool));
                i++;
                LOG4CXX_INFO(INF, createMessage(i, pool));
                i++;

                LOG4CXX_FATAL(INF_UNDEF, createMessage(i, pool));
                i++; //6
                LOG4CXX_ERROR(INF_UNDEF, createMessage(i, pool));
                i++;
                LOG4CXX_WARN(INF_UNDEF, createMessage(i, pool));
                i++;
                LOG4CXX_INFO(INF_UNDEF, createMessage(i, pool));
                i++;

                LOG4CXX_FATAL(INF_ERR, createMessage(i, pool));
                i++; // 10
                LOG4CXX_ERROR(INF_ERR, createMessage(i, pool));
                i++;

                LOG4CXX_FATAL(INF_ERR_UNDEF, createMessage(i, pool));
                i++;
                LOG4CXX_ERROR(INF_ERR_UNDEF, createMessage(i, pool));
                i++;


نتائج البحث الهجين

##hybrid 0 0.016393441706895828 
use {
    crate::args::LogArgs,
    anyhow::{anyhow, Result},
    simplelog::{Config, LevelFilter, WriteLogger},
    std::fs::File,
};

pub struct Logger;

impl Logger {
    pub fn init(args: &impl LogArgs) -> Result<()> {
        let filter: LevelFilter = args.log_level().into();
        if filter != LevelFilter::Off {
            let logfile = File::create(args.log_file())
                .map_err(|e| anyhow!("Failed to open log file: {e:}"))?;
            WriteLogger::init(filter, Config::default(), logfile)
                .map_err(|e| anyhow!("Failed to initalize logger: {e:}"))?;
        }
        Ok(())
    }
}

 
##hybrid 1 0.016393441706895828 
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "logunit.h"
#include <log4cxx/logger.h>
#include <log4cxx/simplelayout.h>
#include <log4cxx/fileappender.h>
#include <log4cxx/helpers/absolutetimedateformat.h>


 
##hybrid 2 0.016129031777381897 
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
    };
}


 
##hybrid 3 0.016129031777381897 
        void simple()
        {
                LayoutPtr layout = LayoutPtr(new SimpleLayout());
                AppenderPtr appender = FileAppenderPtr(new FileAppender(layout, LOG4CXX_STR("output/simple"), false));
                root->addAppender(appender);
                common();

                LOGUNIT_ASSERT(Compare::compare(LOG4CXX_FILE("output/simple"), LOG4CXX_FILE("witness/simple")));
        }

        std::string createMessage(int i, Pool & pool)
        {
                std::string msg("Message ");
                msg.append(pool.itoa(i));
                return msg;
        }

        void common()
        {
                int i = 0;

                // In the lines below, the logger names are chosen as an aid in
                // remembering their level values. In general, the logger names
                // have no bearing to level values.
                LoggerPtr ERRlogger = Logger::getLogger(LOG4CXX_TEST_STR("ERR"));
                ERRlogger->setLevel(Level::getError());


 
##hybrid 4 0.01587301678955555 
std::vector<std::string> MakeStrings() {
    return {
        "a", "ab", "abc", "abcd",
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "

الاستعلام: كيف تقوم بتهيئة المسجّل؟

هذا الاستعلام مشابه تمامًا للاستعلام السابق، والإجابة الصحيحة هي أيضًا نفس مقتطف الرمز، ولكن في هذه الحالة، وجد البحث الهجين الإجابة (عبر البحث الدلالي)، بينما لم يجدها البحث بالنص الكامل. يكمن السبب في هذا التناقض في الترجيح الإحصائي للكلمات في مجموعة الكلمات، والذي لا يتماشى مع فهمنا البديهي للسؤال. فشل النموذج في إدراك أن تطابق كلمة "كيف" لم يكن بنفس الأهمية هنا. ظهرت كلمة "logger" بشكل متكرر في الرمز أكثر من كلمة "كيف"، مما أدى إلى أن تصبح كلمة "كيف" أكثر أهمية في ترتيب البحث في النص الكامل.

GroundTruth

use {
    crate::args::LogArgs,
    anyhow::{anyhow, Result},
    simplelog::{Config, LevelFilter, WriteLogger},
    std::fs::File,
};

pub struct Logger;

impl Logger {
    pub fn init(args: &impl LogArgs) -> Result<()> {
        let filter: LevelFilter = args.log_level().into();
        if filter != LevelFilter::Off {
            let logfile = File::create(args.log_file())
                .map_err(|e| anyhow!("Failed to open log file: {e:}"))?;
            WriteLogger::init(filter, Config::default(), logfile)
                .map_err(|e| anyhow!("Failed to initalize logger: {e:}"))?;
        }
        Ok(())
    }
}

نتائج البحث بالنص الكامل

##sparse 0 10.17311954498291 
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
    };
}



 ##sparse 1 9.775702476501465 
std::vector<std::string> MakeStrings() {
    return {
        "a", "ab", "abc", "abcd",
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "


 ##sparse 2 7.638711452484131 
//   union ("x|y"), grouping ("(xy)"), brackets ("[xy]"), and
//   repetition count ("x{5,7}"), among others.
//
//   Below is the syntax that we do support.  We chose it to be a
//   subset of both PCRE and POSIX extended regex, so it's easy to
//   learn wherever you come from.  In the following: 'A' denotes a
//   literal character, period (.), or a single \\ escape sequence;
//   'x' and 'y' denote regular expressions; 'm' and 'n' are for


 ##sparse 3 7.1208391189575195 
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "logunit.h"
#include <log4cxx/logger.h>
#include <log4cxx/simplelayout.h>
#include <log4cxx/fileappender.h>
#include <log4cxx/helpers/absolutetimedateformat.h>



 ##sparse 4 7.066349029541016 
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <log4cxx/filter/denyallfilter.h>
#include <log4cxx/logger.h>
#include <log4cxx/spi/filter.h>
#include <log4cxx/spi/loggingevent.h>
#include "../logunit.h"

نتائج البحث الهجين


 ##hybrid 0 0.016393441706895828 
use {
    crate::args::LogArgs,
    anyhow::{anyhow, Result},
    simplelog::{Config, LevelFilter, WriteLogger},
    std::fs::File,
};

pub struct Logger;

impl Logger {
    pub fn init(args: &impl LogArgs) -> Result<()> {
        let filter: LevelFilter = args.log_level().into();
        if filter != LevelFilter::Off {
            let logfile = File::create(args.log_file())
                .map_err(|e| anyhow!("Failed to open log file: {e:}"))?;
            WriteLogger::init(filter, Config::default(), logfile)
                .map_err(|e| anyhow!("Failed to initalize logger: {e:}"))?;
        }
        Ok(())
    }
}

 
##hybrid 1 0.016393441706895828 
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
    };
}


 
##hybrid 2 0.016129031777381897 
std::vector<std::string> MakeStrings() {
    return {
        "a", "ab", "abc", "abcd",
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "
        "long string to test how those are handled. Here goes more text. "

 
##hybrid 3 0.016129031777381897 
                LoggerPtr INF = Logger::getLogger(LOG4CXX_TEST_STR("INF"));
                INF->setLevel(Level::getInfo());

                LoggerPtr INF_ERR = Logger::getLogger(LOG4CXX_TEST_STR("INF.ERR"));
                INF_ERR->setLevel(Level::getError());

                LoggerPtr DEB = Logger::getLogger(LOG4CXX_TEST_STR("DEB"));
                DEB->setLevel(Level::getDebug());

                // Note: categories with undefined level
                LoggerPtr INF_UNDEF = Logger::getLogger(LOG4CXX_TEST_STR("INF.UNDEF"));
                LoggerPtr INF_ERR_UNDEF = Logger::getLogger(LOG4CXX_TEST_STR("INF.ERR.UNDEF"));
                LoggerPtr UNDEF = Logger::getLogger(LOG4CXX_TEST_STR("UNDEF"));


 
##hybrid 4 0.01587301678955555 
//   union ("x|y"), grouping ("(xy)"), brackets ("[xy]"), and
//   repetition count ("x{5,7}"), among others.
//
//   Below is the syntax that we do support.  We chose it to be a
//   subset of both PCRE and POSIX extended regex, so it's easy to
//   learn wherever you come from.  In the following: 'A' denotes a
//   literal character, period (.), or a single \\ escape sequence;
//   'x' and 'y' denote regular expressions; 'm' and 'n' are for

من خلال ملاحظاتنا، وجدنا أنه في البحث المتجه المتناثر، كان سبب العديد من النتائج منخفضة الجودة هو مطابقة كلمات منخفضة المعلومات مثل "كيف" و "ماذا". من خلال فحص البيانات، أدركنا أن هذه الكلمات تسببت في حدوث تداخل في النتائج. تتمثل إحدى طرق التخفيف من هذه المشكلة في إضافة هذه الكلمات إلى قائمة كلمات التوقف وتجاهلها أثناء عملية المطابقة. سيساعد ذلك في التخلص من التأثير السلبي لهذه الكلمات الشائعة وتحسين جودة نتائج البحث.

بعد إضافة الكلمات الإيقاف لتصفية الكلمات ذات المعلومات المنخفضة مثل "كيف" و"ماذا"، قمنا بتحليل حالة كان أداء البحث الهجين المضبوط بدقة أفضل من البحث الدلالي. يرجع التحسن في هذه الحالة إلى مطابقة المصطلح "RegistryClient" في الاستعلام، مما سمح لنا بالعثور على نتائج لم يتم استدعاؤها بواسطة نموذج البحث الدلالي وحده.

علاوة على ذلك، لاحظنا أن البحث الهجين قلل من عدد المطابقات منخفضة الجودة في النتائج. في هذه الحالة، نجحت طريقة البحث الهجين في دمج البحث الدلالي مع البحث في النص الكامل، مما أدى إلى نتائج أكثر صلة مع تحسين الدقة.

الاستعلام: كيف يتم إنشاء مثيل RegistryClient في طرق الاختبار؟

استرجع البحث الهجين بفعالية الإجابة المتعلقة بإنشاء مثيل "RegistryClient"، والتي فشل البحث الدلالي وحده في العثور عليها. ساعدت إضافة كلمات الإيقاف في تجنب النتائج غير ذات الصلة من مصطلحات مثل "كيف"، مما أدى إلى نتائج مطابقة أفضل جودة ونتائج أقل جودة.

/** Integration tests for {@link BlobPuller}. */
public class BlobPullerIntegrationTest {

  private final FailoverHttpClient httpClient = new FailoverHttpClient(true, false, ignored -> {});

  @Test
  public void testPull() throws IOException, RegistryException {
    RegistryClient registryClient =
        RegistryClient.factory(EventHandlers.NONE, "gcr.io", "distroless/base", httpClient)
            .newRegistryClient();
    V22ManifestTemplate manifestTemplate =
        registryClient
            .pullManifest(
                ManifestPullerIntegrationTest.KNOWN_MANIFEST_V22_SHA, V22ManifestTemplate.class)
            .getManifest();

    DescriptorDigest realDigest = manifestTemplate.getLayers().get(0).getDigest();

نتائج البحث الدلالي


 

##dense 0 0.7411458492279053 
    Mockito.doThrow(mockRegistryUnauthorizedException)
        .when(mockJibContainerBuilder)
        .containerize(mockContainerizer);

    try {
      testJibBuildRunner.runBuild();
      Assert.fail();

    } catch (BuildStepsExecutionException ex) {
      Assert.assertEquals(
          TEST_HELPFUL_SUGGESTIONS.forHttpStatusCodeForbidden("someregistry/somerepository"),
          ex.getMessage());
    }
  }



 ##dense 1 0.7346029877662659 
    verify(mockCredentialRetrieverFactory).known(knownCredential, "credentialSource");
    verify(mockCredentialRetrieverFactory).known(inferredCredential, "inferredCredentialSource");
    verify(mockCredentialRetrieverFactory)
        .dockerCredentialHelper("docker-credential-credentialHelperSuffix");
  }



 ##dense 2 0.7285804748535156 
    when(mockCredentialRetrieverFactory.dockerCredentialHelper(anyString()))
        .thenReturn(mockDockerCredentialHelperCredentialRetriever);
    when(mockCredentialRetrieverFactory.known(knownCredential, "credentialSource"))
        .thenReturn(mockKnownCredentialRetriever);
    when(mockCredentialRetrieverFactory.known(inferredCredential, "inferredCredentialSource"))
        .thenReturn(mockInferredCredentialRetriever);
    when(mockCredentialRetrieverFactory.wellKnownCredentialHelpers())
        .thenReturn(mockWellKnownCredentialHelpersCredentialRetriever);



 ##dense 3 0.7279614210128784 
  @Test
  public void testBuildImage_insecureRegistryException()
      throws InterruptedException, IOException, CacheDirectoryCreationException, RegistryException,
          ExecutionException {
    InsecureRegistryException mockInsecureRegistryException =
        Mockito.mock(InsecureRegistryException.class);
    Mockito.doThrow(mockInsecureRegistryException)
        .when(mockJibContainerBuilder)
        .containerize(mockContainerizer);

    try {
      testJibBuildRunner.runBuild();
      Assert.fail();

    } catch (BuildStepsExecutionException ex) {
      Assert.assertEquals(TEST_HELPFUL_SUGGESTIONS.forInsecureRegistry(), ex.getMessage());
    }
  }



 ##dense 4 0.724872350692749 
  @Test
  public void testBuildImage_registryCredentialsNotSentException()
      throws InterruptedException, IOException, CacheDirectoryCreationException, RegistryException,
          ExecutionException {
    Mockito.doThrow(mockRegistryCredentialsNotSentException)
        .when(mockJibContainerBuilder)
        .containerize(mockContainerizer);

    try {
      testJibBuildRunner.runBuild();
      Assert.fail();

    } catch (BuildStepsExecutionException ex) {
      Assert.assertEquals(TEST_HELPFUL_SUGGESTIONS.forCredentialsNotSent(), ex.getMessage());
    }
  }

نتائج البحث الهجين


 ##hybrid 0 0.016393441706895828 
/** Integration tests for {@link BlobPuller}. */
public class BlobPullerIntegrationTest {

  private final FailoverHttpClient httpClient = new FailoverHttpClient(true, false, ignored -> {});

  @Test
  public void testPull() throws IOException, RegistryException {
    RegistryClient registryClient =
        RegistryClient.factory(EventHandlers.NONE, "gcr.io", "distroless/base", httpClient)
            .newRegistryClient();
    V22ManifestTemplate manifestTemplate =
        registryClient
            .pullManifest(
                ManifestPullerIntegrationTest.KNOWN_MANIFEST_V22_SHA, V22ManifestTemplate.class)
            .getManifest();

    DescriptorDigest realDigest = manifestTemplate.getLayers().get(0).getDigest();


 
##hybrid 1 0.016393441706895828 
    Mockito.doThrow(mockRegistryUnauthorizedException)
        .when(mockJibContainerBuilder)
        .containerize(mockContainerizer);

    try {
      testJibBuildRunner.runBuild();
      Assert.fail();

    } catch (BuildStepsExecutionException ex) {
      Assert.assertEquals(
          TEST_HELPFUL_SUGGESTIONS.forHttpStatusCodeForbidden("someregistry/somerepository"),
          ex.getMessage());
    }
  }


 
##hybrid 2 0.016129031777381897 
    verify(mockCredentialRetrieverFactory).known(knownCredential, "credentialSource");
    verify(mockCredentialRetrieverFactory).known(inferredCredential, "inferredCredentialSource");
    verify(mockCredentialRetrieverFactory)
        .dockerCredentialHelper("docker-credential-credentialHelperSuffix");
  }


 
##hybrid 3 0.016129031777381897 
  @Test
  public void testPull_unknownBlob() throws IOException, DigestException {
    DescriptorDigest nonexistentDigest =
        DescriptorDigest.fromHash(
            "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");

    RegistryClient registryClient =
        RegistryClient.factory(EventHandlers.NONE, "gcr.io", "distroless/base", httpClient)
            .newRegistryClient();

    try {
      registryClient
          .pullBlob(nonexistentDigest, ignored -> {}, ignored -> {})
          .writeTo(ByteStreams.nullOutputStream());
      Assert.fail("Trying to pull nonexistent blob should have errored");

    } catch (IOException ex) {
      if (!(ex.getCause() instanceof RegistryErrorException)) {
        throw ex;
      }
      MatcherAssert.assertThat(
          ex.getMessage(),
          CoreMatchers.containsString(
              "pull BLOB for gcr.io/distroless/base with digest " + nonexistentDigest));
    }
  }
}

 
##hybrid 4 0.01587301678955555 
    when(mockCredentialRetrieverFactory.dockerCredentialHelper(anyString()))
        .thenReturn(mockDockerCredentialHelperCredentialRetriever);
    when(mockCredentialRetrieverFactory.known(knownCredential, "credentialSource"))
        .thenReturn(mockKnownCredentialRetriever);
    when(mockCredentialRetrieverFactory.known(inferredCredential, "inferredCredentialSource"))
        .thenReturn(mockInferredCredentialRetriever);
    when(mockCredentialRetrieverFactory.wellKnownCredentialHelpers())
        .thenReturn(mockWellKnownCredentialHelpersCredentialRetriever);

الاستنتاجات

من تحليلنا، يمكننا استخلاص عدة استنتاجات حول أداء طرق الاسترجاع المختلفة. بالنسبة لمعظم الحالات، يساعدنا نموذج البحث الدلالي في الحصول على نتائج جيدة من خلال فهم القصد العام للاستعلام، لكنه يقصر عندما يحتوي الاستعلام على كلمات مفتاحية محددة نريد مطابقتها.

في هذه الحالات، لا يمثل نموذج التضمين هذا القصد بشكل صريح. من ناحية أخرى، يمكن أن يعالج البحث بالنص الكامل هذه المشكلة مباشرةً. ومع ذلك، فإنه يجلب أيضًا مشكلة النتائج غير ذات الصلة على الرغم من مطابقة الكلمات، مما قد يؤدي إلى تدهور جودة النتيجة الإجمالية. لذلك، من الضروري تحديد هذه الحالات السلبية والتعامل معها من خلال تحليل نتائج محددة وتطبيق استراتيجيات مستهدفة لتحسين جودة البحث. عادةً ما يكون البحث المختلط مع استراتيجيات الترتيب مثل RRF أو إعادة الترتيب الموزونة خيارًا أساسيًا جيدًا.

مع إصدار وظيفة البحث عن النص الكامل في الإصدار 2.5 من ميلفوس 2.5، نهدف إلى تزويد المجتمع بحلول مرنة ومتنوعة لاسترجاع المعلومات. سيسمح ذلك للمستخدمين باستكشاف مجموعات مختلفة من طرق البحث ومعالجة متطلبات البحث المعقدة والمتنوعة بشكل متزايد في عصر الذكاء الاصطناعي الجيني. اطلع على المثال البرمجي حول كيفية تنفيذ البحث في النص الكامل والبحث الهجين باستخدام Milvus 2.5.

Like the article? Spread the word

استمر في القراءة