Recherche sémantique vs. recherche en texte intégral : Que dois-je choisir dans Milvus 2.5 ?
Milvus, une base de données vectorielles haute performance de premier plan, s'est depuis longtemps spécialisée dans la recherche sémantique à l'aide d'enchâssements vectoriels issus de modèles d'apprentissage profond. Cette technologie alimente des applications d'IA telles que la génération améliorée de recherche (RAG), les moteurs de recherche et les systèmes de recommandation. Avec la popularité croissante de la RAG et d'autres applications de recherche de texte, la communauté a reconnu les avantages de la combinaison des méthodes traditionnelles de mise en correspondance de texte avec la recherche sémantique, connue sous le nom de recherche hybride. Cette approche est particulièrement bénéfique dans les scénarios qui s'appuient fortement sur la correspondance des mots-clés. Pour répondre à ce besoin, Milvus 2.5 introduit la fonctionnalité de recherche en texte intégral (FTS) et l'intègre aux capacités de recherche vectorielle éparse et de recherche hybride déjà disponibles depuis la version 2.4, créant ainsi une puissante synergie.
La recherche hybride est une méthode qui combine les résultats de plusieurs chemins de recherche. Les utilisateurs peuvent rechercher différents champs de données de diverses manières, puis fusionner et classer les résultats pour obtenir un résultat complet. Dans les scénarios RAG les plus courants aujourd'hui, une approche hybride typique combine la recherche sémantique et la recherche en texte intégral. Plus précisément, il s'agit de fusionner les résultats de la recherche sémantique basée sur l'intégration dense et de l'appariement lexical basé sur le BM25 en utilisant le RRF (Reciprocal Rank Fusion) pour améliorer le classement des résultats.
Dans cet article, nous démontrons cela en utilisant un ensemble de données fourni par Anthropic, qui consiste en des extraits de code provenant de neuf dépôts de code. Cela ressemble à un cas d'utilisation populaire de RAG : un robot de codage assisté par l'IA. Comme les données de code contiennent beaucoup de définitions, de mots-clés et d'autres informations, la recherche textuelle peut être particulièrement efficace dans ce contexte. Parallèlement, les modèles d'intégration dense formés sur de grands ensembles de données de code peuvent capturer des informations sémantiques de plus haut niveau. Notre objectif est d'observer les effets de la combinaison de ces deux approches par le biais de l'expérimentation.
Nous analyserons des cas spécifiques afin de mieux comprendre la recherche hybride. Comme base de référence, nous utiliserons un modèle avancé d'intégration dense (voyage-2) entraîné sur un grand volume de données de code. Nous sélectionnerons ensuite des exemples où la recherche hybride surpasse les résultats de la recherche sémantique et de la recherche en texte intégral (top 5) afin d'analyser les caractéristiques de ces cas.
Méthode | Pass@5 |
---|---|
Recherche en texte intégral | 0.7318 |
Recherche sémantique | 0.8096 |
Recherche hybride | 0.8176 |
Recherche hybride (ajout d'un mot-clé) | 0.8418 |
Outre l'analyse de la qualité au cas par cas, nous avons élargi notre évaluation en calculant la métrique Pass@5 pour l'ensemble de la base de données. Cette métrique mesure la proportion de résultats pertinents trouvés dans les 5 premiers résultats de chaque requête. Nos résultats montrent que si les modèles d'intégration avancés constituent une base solide, leur intégration avec la recherche en texte intégral permet d'obtenir des résultats encore meilleurs. D'autres améliorations sont possibles en examinant les résultats de BM25 et en affinant les paramètres pour des scénarios spécifiques, ce qui peut conduire à des gains de performance significatifs.
Nous examinons les résultats spécifiques obtenus pour trois requêtes de recherche différentes, en comparant la recherche sémantique et la recherche en texte intégral à la recherche hybride. Vous pouvez également consulter le code complet dans ce repo.
Cas 1 : La recherche hybride surpasse la recherche sémantique
Requête : Comment le fichier journal est-il créé ?
Cette requête vise à obtenir des informations sur la création d'un fichier journal, et la réponse correcte devrait être un extrait de code Rust qui crée un fichier journal. Dans les résultats de la recherche sémantique, nous avons vu un peu de code introduisant le fichier d'en-tête du journal et le code C++ pour obtenir le logger. Cependant, la clé ici est la variable "logfile". Dans le résultat de la recherche hybride #hybrid 0, nous avons trouvé ce résultat pertinent, qui provient naturellement de la recherche en texte intégral puisque la recherche hybride fusionne les résultats de la recherche sémantique et de la recherche en texte intégral.
En plus de ce résultat, nous pouvons trouver un code de test fictif non lié dans #hybrid 2, en particulier la phrase répétée, "long string to test how those are handled" (longue chaîne de caractères pour tester la façon dont ils sont traités). Il faut pour cela comprendre les principes qui sous-tendent l'algorithme BM25 utilisé dans la recherche en texte intégral. La recherche en texte intégral vise à faire correspondre les mots les moins fréquents (puisque les mots courants réduisent le caractère distinctif du texte et entravent la discrimination des objets). Supposons que nous effectuions une analyse statistique sur un grand corpus de textes naturels. Dans ce cas, il est facile de conclure que "comment" est un mot très courant et qu'il contribue très peu au score de pertinence. Toutefois, dans le cas présent, l'ensemble de données consiste en du code et il n'y a pas beaucoup d'occurrences du mot "comment" dans le code, ce qui en fait un terme de recherche clé dans ce contexte.
Vérité de terrain : la bonne réponse est le code Rust qui crée un fichier journal.
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(())
}
}
Résultats de la recherche sémantique
##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++;
Résultats de la recherche hybride
##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. "
Cas 2 : La recherche hybride est plus performante que la recherche en texte intégral
Requête : Comment initialiser le logger ?
Cette question est assez similaire à la précédente, et la réponse correcte est également le même extrait de code, mais dans ce cas, la recherche hybride a trouvé la réponse (via la recherche sémantique), alors que la recherche en texte intégral ne l'a pas trouvée. La raison de cette divergence réside dans la pondération statistique des mots dans le corpus, qui ne correspond pas à notre compréhension intuitive de la question. Le modèle n'a pas reconnu que la correspondance avec le mot "comment" n'était pas aussi importante ici. Le mot "logger" apparaissait plus fréquemment dans le code que le mot "how", ce qui a permis à ce dernier de prendre plus d'importance dans le classement de la recherche en texte intégral.
Vérité sur le terrain
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(())
}
}
Résultats de la recherche en texte intégral
##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"
Résultats de la recherche hybride
##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
Nos observations nous ont permis de constater que, dans la recherche vectorielle éparse, de nombreux résultats de faible qualité étaient dus à la correspondance avec des mots peu informatifs tels que "Comment" et "Quoi". En examinant les données, nous avons réalisé que ces mots provoquaient des interférences dans les résultats. Une approche permettant d'atténuer ce problème consiste à ajouter ces mots à une liste de mots vides et à les ignorer au cours du processus de mise en correspondance. Cela permettrait d'éliminer l'impact négatif de ces mots courants et d'améliorer la qualité des résultats de la recherche.
Cas 3 : La recherche hybride (avec ajout de mots vides) est plus performante que la recherche sémantique
Après avoir ajouté des mots vides pour filtrer les mots à faible valeur informative tels que "Comment" et "Quoi", nous avons analysé un cas où une recherche hybride finement réglée a donné de meilleurs résultats qu'une recherche sémantique. L'amélioration dans ce cas est due à la correspondance du terme "RegistryClient" dans la requête, ce qui nous a permis de trouver des résultats non rappelés par le modèle de recherche sémantique seul.
En outre, nous avons remarqué que la recherche hybride réduisait le nombre de correspondances de faible qualité dans les résultats. Dans ce cas, la méthode de recherche hybride a réussi à intégrer la recherche sémantique à la recherche en texte intégral, ce qui a permis d'obtenir des résultats plus pertinents et plus précis.
Question : Comment l'instance RegistryClient est-elle créée dans les méthodes de test ?
La recherche hybride a permis de retrouver la réponse relative à la création de l'instance "RegistryClient", que la recherche sémantique seule ne parvenait pas à trouver. L'ajout de mots d'arrêt a permis d'éviter les résultats non pertinents provenant de termes tels que "Comment", ce qui a conduit à des correspondances de meilleure qualité et à moins de résultats de qualité médiocre.
/** 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();
Résultats de la recherche sémantique
##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());
}
}
Résultats de la recherche hybride
##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);
Conclusions
Notre analyse nous permet de tirer plusieurs conclusions sur les performances des différentes méthodes de recherche. Dans la plupart des cas, le modèle de recherche sémantique nous aide à obtenir de bons résultats en saisissant l'intention générale de la requête, mais il ne suffit pas lorsque la requête contient des mots-clés spécifiques que nous voulons faire correspondre.
Dans ce cas, le modèle d'intégration ne représente pas explicitement cette intention. D'autre part, la recherche en texte intégral peut résoudre ce problème directement. Cependant, elle pose également le problème des résultats non pertinents malgré la correspondance des mots, ce qui peut dégrader la qualité globale des résultats. Il est donc essentiel d'identifier et de traiter ces cas négatifs en analysant des résultats spécifiques et en appliquant des stratégies ciblées pour améliorer la qualité de la recherche. Une recherche hybride avec des stratégies de classement telles que RRF ou reranker pondéré est généralement une bonne option de base.
Avec la sortie de la fonctionnalité de recherche en texte intégral dans Milvus 2.5, nous visons à fournir à la communauté des solutions de recherche d'informations flexibles et diversifiées. Cela permettra aux utilisateurs d'explorer diverses combinaisons de méthodes de recherche et de répondre aux demandes de recherche de plus en plus complexes et variées à l'ère de la GenAI. Consultez l'exemple de code sur la mise en œuvre de la recherche en texte intégral et de la recherche hybride avec Milvus 2.5.
- Cas 1 : La recherche hybride surpasse la recherche sémantique
- Cas 2 : La recherche hybride est plus performante que la recherche en texte intégral
- Cas 3 : La recherche hybride (avec ajout de mots vides) est plus performante que la recherche sémantique
- Conclusions
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