Pencarian Semantik vs Pencarian Teks Lengkap: Mana yang Harus Saya Pilih di Milvus 2.5?
Milvus, database vektor berkinerja tinggi yang terkemuka, telah lama mengkhususkan diri dalam pencarian semantik menggunakan penyematan vektor dari model pembelajaran mendalam. Teknologi ini mendukung aplikasi AI seperti Retrieval-Augmented Generation (RAG), mesin pencari, dan sistem rekomendasi. Dengan meningkatnya popularitas RAG dan aplikasi pencarian teks lainnya, komunitas telah mengakui keuntungan dari menggabungkan metode pencocokan teks tradisional dengan pencarian semantik, yang dikenal sebagai pencarian hibrida. Pendekatan ini sangat bermanfaat dalam skenario yang sangat bergantung pada pencocokan kata kunci. Untuk memenuhi kebutuhan ini, Milvus 2.5 memperkenalkan fungsionalitas pencarian teks lengkap (FTS) dan mengintegrasikannya dengan kemampuan pencarian vektor dan pencarian hibrida yang telah tersedia sejak versi 2.4, menciptakan sinergi yang kuat.
Pencarian hibrida adalah metode yang menggabungkan hasil dari beberapa jalur pencarian. Pengguna dapat mencari bidang data yang berbeda dengan berbagai cara, kemudian menggabungkan dan mengurutkan hasilnya untuk mendapatkan hasil yang komprehensif. Dalam skenario RAG yang populer saat ini, pendekatan hibrida yang umum digunakan menggabungkan pencarian semantik dengan pencarian teks lengkap. Secara khusus, hal ini melibatkan penggabungan hasil dari pencarian semantik berbasis sematan padat dan pencocokan leksikal berbasis BM25 menggunakan RRF (Reciprocal Rank Fusion) untuk meningkatkan pemeringkatan hasil.
Dalam artikel ini, kami akan mendemonstrasikan hal ini dengan menggunakan dataset yang disediakan oleh Anthropic, yang terdiri dari potongan kode dari sembilan repositori kode. Ini menyerupai kasus penggunaan RAG yang populer: bot pengkodean dengan bantuan AI. Karena data kode mengandung banyak definisi, kata kunci, dan informasi lainnya, pencarian berbasis teks bisa sangat efektif dalam konteks ini. Sementara itu, model embedding padat yang dilatih pada kumpulan data kode yang besar dapat menangkap informasi semantik tingkat tinggi. Tujuan kami adalah untuk mengamati efek dari penggabungan kedua pendekatan ini melalui eksperimen.
Kami akan menganalisis kasus-kasus tertentu untuk mengembangkan pemahaman yang lebih jelas tentang pencarian hybrid. Sebagai dasar, kami akan menggunakan model embedding padat tingkat lanjut (voyage-2) yang dilatih pada sejumlah besar data kode. Kami kemudian akan memilih contoh-contoh di mana pencarian hibrida mengungguli hasil pencarian semantik dan teks lengkap (5 besar) untuk menganalisis karakteristik di balik kasus-kasus ini.
Metode | Lulus @ 5 |
---|---|
Pencarian Teks Lengkap | 0.7318 |
Pencarian Semantik | 0.8096 |
Pencarian Hibrida | 0.8176 |
Penelusuran Hibrida (tambahkan kata henti) | 0.8418 |
Selain menganalisis kualitas berdasarkan kasus per kasus, kami memperluas evaluasi kami dengan menghitung metrik Pass@5 di seluruh dataset. Metrik ini mengukur proporsi hasil relevan yang ditemukan dalam 5 hasil teratas dari setiap kueri. Temuan kami menunjukkan bahwa meskipun model penyematan canggih membentuk dasar yang kuat, mengintegrasikannya dengan penelusuran teks lengkap memberikan hasil yang lebih baik. Peningkatan lebih lanjut dapat dilakukan dengan memeriksa hasil BM25 dan menyempurnakan parameter untuk skenario tertentu, yang dapat menghasilkan peningkatan kinerja yang signifikan.
Kami memeriksa hasil spesifik yang diambil untuk tiga kueri penelusuran yang berbeda, membandingkan penelusuran semantik dan teks lengkap dengan penelusuran hibrida. Anda juga dapat melihat kode lengkapnya di repositori ini.
Kasus 1: Pencarian Hibrida Mengungguli Pencarian Semantik
Kueri: Bagaimana file log dibuat?
Kueri ini bertujuan untuk menanyakan tentang pembuatan berkas log, dan jawaban yang benar adalah potongan kode Rust yang membuat berkas log. Dalam hasil pencarian semantik, kami melihat beberapa kode yang memperkenalkan file header log dan kode C++ untuk mendapatkan pencatat. Namun, kuncinya di sini adalah variabel "logfile". Pada hasil pencarian hibrida #hybrid 0, kami menemukan hasil yang relevan ini, yang secara alami berasal dari pencarian teks lengkap karena pencarian hibrida menggabungkan hasil pencarian semantik dan teks lengkap.
Selain hasil ini, kita dapat menemukan kode uji coba yang tidak terkait di #hybrid 2, terutama frasa yang diulang-ulang, "string panjang untuk menguji bagaimana hal itu ditangani." Hal ini membutuhkan pemahaman prinsip-prinsip di balik algoritme BM25 yang digunakan dalam pencarian teks lengkap. Pencarian teks lengkap bertujuan untuk mencocokkan kata-kata yang lebih jarang digunakan (karena kata-kata yang umum mengurangi keunikan teks dan menghambat diskriminasi objek). Misalkan kita melakukan analisis statistik pada sebuah korpus teks alami yang besar. Dalam hal ini, mudah untuk menyimpulkan bahwa "bagaimana" adalah kata yang sangat umum dan hanya berkontribusi sedikit terhadap skor relevansi. Namun, dalam kasus ini, kumpulan data terdiri dari kode, dan tidak banyak kemunculan kata "bagaimana" dalam kode tersebut, sehingga menjadikannya istilah pencarian utama dalam konteks ini.
Kebenaran Dasar: Jawaban yang benar adalah kode Rust yang membuat file log.
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(())
}
}
Hasil Pencarian Semantik
##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++;
Hasil Pencarian Hibrida
##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. "
Kasus 2: Pencarian Hibrida Mengungguli Pencarian Teks Lengkap
Pertanyaan: Bagaimana Anda menginisialisasi pencatat?
Kueri ini sangat mirip dengan kueri sebelumnya, dan jawaban yang benar juga merupakan cuplikan kode yang sama, tetapi dalam kasus ini, pencarian hibrida menemukan jawabannya (melalui pencarian semantik), sedangkan pencarian teks lengkap tidak. Alasan dari perbedaan ini terletak pada pembobotan statistik dari kata-kata di dalam korpus, yang tidak sesuai dengan pemahaman intuitif kita terhadap pertanyaan tersebut. Model gagal mengenali bahwa kecocokan untuk kata "bagaimana" tidak begitu penting di sini. Kata "logger" muncul lebih sering dalam kode daripada "how", yang menyebabkan kata "how" menjadi lebih penting dalam peringkat pencarian teks lengkap.
Kebenaran Dasar
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(())
}
}
Hasil Pencarian Teks Lengkap
##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"
Hasil Pencarian Hibrida
##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
Dalam pengamatan kami, kami menemukan bahwa dalam pencarian vektor jarang, banyak hasil berkualitas rendah disebabkan oleh pencocokan kata-kata yang informasinya rendah seperti "Bagaimana" dan "Apa". Dengan memeriksa data, kami menyadari bahwa kata-kata ini menyebabkan gangguan pada hasil pencarian. Salah satu pendekatan untuk mengatasi masalah ini adalah dengan menambahkan kata-kata ini ke daftar stopword dan mengabaikannya selama proses pencocokan. Hal ini akan membantu menghilangkan dampak negatif dari kata-kata umum ini dan meningkatkan kualitas hasil pencarian.
Kasus 3: Pencarian Hibrida (dengan Penambahan Stopword) Mengungguli Pencarian Semantik
Setelah menambahkan stopword untuk menyaring kata-kata dengan informasi rendah seperti "Bagaimana" dan "Apa", kami menganalisis kasus di mana pencarian hybrid yang disesuaikan dengan baik berkinerja lebih baik daripada pencarian semantik. Peningkatan dalam kasus ini disebabkan oleh pencocokan istilah "RegistryClient" dalam kueri, yang memungkinkan kami menemukan hasil yang tidak dapat ditemukan oleh model pencarian semantik saja.
Selain itu, kami melihat bahwa pencarian hybrid mengurangi jumlah kecocokan berkualitas rendah dalam hasil pencarian. Dalam kasus ini, metode pencarian hybrid berhasil mengintegrasikan pencarian semantik dengan pencarian teks lengkap, sehingga menghasilkan hasil yang lebih relevan dengan akurasi yang lebih baik.
Pertanyaan: Bagaimana instance RegistryClient dibuat dalam metode pengujian?
Pencarian hibrida secara efektif mendapatkan jawaban yang terkait dengan pembuatan instance "RegistryClient", yang tidak dapat ditemukan oleh pencarian semantik saja. Menambahkan stopword membantu menghindari hasil yang tidak relevan dari istilah seperti "How", sehingga menghasilkan kecocokan yang berkualitas lebih baik dan lebih sedikit hasil berkualitas rendah.
/** 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();
Hasil Pencarian Semantik
##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());
}
}
Hasil Pencarian Hibrida
##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);
Kesimpulan
Dari analisis kami, kami dapat menarik beberapa kesimpulan tentang kinerja metode pencarian yang berbeda. Untuk sebagian besar kasus, model pencarian semantik membantu kami mendapatkan hasil yang baik dengan memahami maksud keseluruhan dari kueri, tetapi model ini gagal ketika kueri berisi kata kunci spesifik yang ingin kami cocokkan.
Dalam kasus ini, model penyematan tidak secara eksplisit mewakili maksud ini. Di sisi lain, pencarian teks lengkap dapat mengatasi masalah ini secara langsung. Namun, hal ini juga membawa masalah hasil yang tidak relevan meskipun ada kata-kata yang cocok, yang dapat menurunkan kualitas hasil secara keseluruhan. Oleh karena itu, sangat penting untuk mengidentifikasi dan menangani kasus-kasus negatif ini dengan menganalisis hasil spesifik dan menerapkan strategi yang ditargetkan untuk meningkatkan kualitas pencarian. Pencarian hibrida dengan strategi pemeringkatan seperti RRF atau perangking berbobot biasanya merupakan opsi dasar yang baik.
Dengan dirilisnya fungsionalitas pencarian teks lengkap di Milvus 2.5, kami bertujuan untuk memberikan solusi pencarian informasi yang fleksibel dan beragam kepada komunitas. Hal ini akan memungkinkan pengguna untuk mengeksplorasi berbagai kombinasi metode pencarian dan memenuhi permintaan pencarian yang semakin kompleks dan beragam di era GenAI. Lihat contoh kode tentang bagaimana mengimplementasikan pencarian teks lengkap dan pencarian hybrid dengan Milvus 2.5.
- Kasus 1: Pencarian Hibrida Mengungguli Pencarian Semantik
- Kasus 2: Pencarian Hibrida Mengungguli Pencarian Teks Lengkap
- Kasus 3: Pencarian Hibrida (dengan Penambahan Stopword) Mengungguli Pencarian Semantik
- Kesimpulan
On This Page
Try Managed Milvus for Free
Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.
Get StartedLike the article? Spread the word