Accueil / Articles / Notes pratiques : Recette Spring AI : Filtrage des résultats RAG à l’aide de métadonnées

Notes pratiques : Recette Spring AI : Filtrage des résultats RAG à l’aide de métadonnées

Guide opérationnel des notes pratiques : Recette Spring AI – Filtrage des résultats RAG à l’aide de métadonnées : contrats, vérifications et emplacements de code prêts à l’emploi pour les équipes qui mettent en œuvre ce modèle.

2112 mots

Cette démarche reconstitue le parcours allant des matières premières à un système fonctionnel pour : Spring AI Recipe : Filtrage des résultats RAG avec des métadonnées. L’accent est mis sur des étapes opérationnelles, des vérifications explicites et du code que vous pouvez intégrer directement dans un dépôt sans devoir deviner l’intention derrière les actions. Pour l’étape d’aperçu, définissez les entrées, le responsable de l’étape et les critères d’achèvement avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans avoir à deviner l’état caché. Enregistrez les temps d’exécution ainsi que le coût en tokens ou en requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts permet d’éviter des factures inattendues lorsque le parcours passe de l’environnement de démonstration à des environnements partagés.

On each player's turn, they place a dog, food bowl, or toy into
one of the three play yards. The game ends when a player has
placed their last dog into a yard. Then each player who hasn't
played in that round gets to place one more item into a yard
until the last player so that all players have an equal number
of turns.
On their turn, a player starts by moving any flies on the board
to the same space as an adjacent frog (effectively the frog has
eaten that fly). Then the player can either place 1 fly adjacent
to a frog on the board as a distraction so that the frog won't
jump or they may place a frog of their player color onto any
empty space. If the frog is adjacent to another frog (even if it
is their own), the adjacent frog will jump two spaces in any
direction away from the frog that was just placed. The direction
of the jump is decided by the current player. If the frog jumps
off the board, then that frog is out of play.

The game ends when all players have placed all of their frogs
(not counting frogs that have jumped off the board). The player
with the most frogs remaining on the board wins.
When it is a player's turn, they will draw a card from the
ingredients deck and add it to their hand. Then they may play
any ingredient card from their hand to add that ingredient to
one of up to three tacos in progress. Or, if they have a crunch
card in their hand, then they may play it to remove one
ingredient from any one opponent's taco. The first player to
completely build 3 tacos wins.
@Configuration
public class RagIngestionConfig {

  private static final Logger logger =
      LoggerFactory.getLogger(RagIngestionConfig.class);

  @Value("${rag.documents}")
  Resource[] documentResources;

  @Bean
  @Order(-1)
  ApplicationRunner load(VectorStore vectorStore) {
    return args -> {
      for (Resource documentResource : documentResources) {
        var filename = documentResource.getFilename();
        logger.info("Loading document from {}.", filename);
        var reader = new TikaDocumentReader(documentResource);
        var splitter = TokenTextSplitter.builder().build();
        vectorStore.accept(
            splitter.apply(
                reader.get()));
      }
      logger.info("Document loading complete.");
    };
  }
}
How can I help?

> What can I do on my turn?

 - Draw a card from the ingredients deck and add it to your hand.
 - Then you may either:
    - Play any ingredient card from your hand to add that ingredient
      to one of up to three tacos in progress, or
    - Play a crunch card (if you have one) to remove one ingredient
      from any one opponent's taco.
 - On your turn you place a dog, a food bowl, or a toy into one of
   the three play yards.
 - The game ends when a player has placed their last dog; then players
   who haven't yet played in that round get one more placement so all
   players have equal turns.

Ajout de métadonnées aux documents

Lorsque vous travaillez sur l’ajout de métadonnées à l’étape en question, notez d’abord les exigences : entrées requises, signal de succès, et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les administrateurs peuvent auditer sans devoir lire l’ensemble du système. Mesurez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Un changement fréquent des prompts ne résout que rarement un système de récupération insuffisant.

@Bean
@Order(-1)
ApplicationRunner load(VectorStore vectorStore) {
  return args -> {
    for (Resource documentResource : documentResources) {
      var filename = documentResource.getFilename();

  logger.info("Loading document from {}.", filename);
      var reader = new TikaDocumentReader(documentResource);
      var splitter = TokenTextSplitter.builder().build();

      var titleTag =
          filename.substring(0, filename.lastIndexOf('.'));

      vectorStore.accept(
          splitter.apply(
              reader.get().stream()
                  .peek(document ->
                      document.getMetadata()
                          .put("title", titleTag))
                  .toList()));
    }
    logger.info("Document loading complete.");
  };
}
title = camp-bowwow

Déterminer quel jeu l’utilisateur demande

Lorsque vous travaillez sur l’étape de détermination du jeu à utiliser, notez d’abord le contrat : les entrées requises, le signal de succès et ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Documentez ensemble le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non traités font partie du produit, et non d’améliorations apportées ultérieurement. Mesurez le taux de rappel sur un ensemble fixe de questions avant d’ajuster les prompts. Un changement fréquent des prompts ne résout que rarement un système de récupération insuffisant.

@Service
public class TitleHelper {

  private final ChatClient chatClient;

  public TitleHelper(ChatModel chatModel) {
    this.chatClient = ChatClient.builder(chatModel).build();
  }

  public String determineGameTitle(String question) {
    var title = chatClient.prompt()
        .user(userSpec -> userSpec
            .text("""
                Your job is to try to determine the title of a game from
                the question asked.
                The game choices are:
                - camp-bowwow
                - frog-panic
                - taco-truck
                - unknown
                If the game's title isn't explicitly mentioned in the
                question, or you don't recognize the game's title, then
                say "unknown".
                The question is:
                {question}
                """)
            .param("question", question))
        .call()
        .content();
    return title.equals("unknown") ? null : title;
  }
}

Filtrage de la recherche vectorielle

Lors du traitement de l’étape de filtrage de la recherche vectorielle, notez d’abord les spécifications : entrées requises, signal de succès et conséquences en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité et non un processus embrouillé. Mesurez le taux de rappel sur un ensemble de questions fixe avant d’ajuster les prompts. Changer fréquemment les prompts ne résout généralement pas un système de récupération insuffisant. Lors du traitement de l’étape de filtrage de la recherche vectorielle, notez d’abord les spécifications : entrées requises, signal de succès et conséquences en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Enregistrez les temps d’exécution ainsi que le coût en tokens ou requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les surprises financières lorsque le système passe d’un environnement de démonstration à des environnements partagés.

@Bean
ApplicationRunner go(
    ChatClient chatClient,
    TitleHelper titleHelper) {

  return args -> {
    System.out.println("How can I help?\n");

    try (Scanner scanner = new Scanner(System.in)) {
      while (true) {
        System.out.print("> ");
        if (!scanner.hasNextLine()) break;
        var input = scanner.nextLine();
        if (input.isBlank()) continue;

        var requestSpec = chatClient.prompt(input);

        var gameTitle =
            titleHelper.determineGameTitle(input);

        var answer = requestSpec
            .advisors(spec -> {
              spec.param(
                  ChatMemory.CONVERSATION_ID,
                  "DEMO");
              if (gameTitle != null) {
                spec.param(
                    QuestionAnswerAdvisor.FILTER_EXPRESSION,
                    String.format(
                        "title == '%s'",
                        gameTitle));
              }
            })
            .call()
            .content();

        System.out.println("\n - " + answer);
      }
    }
  };
}
title == 'camp-bowwow'

Essai en pratique

L’étape d’essai en pratique fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un exemple réussi, un cas d’échec ainsi que la note de réversion avant d’élargir le périmètre. Gardez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système. Séparez la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent.

How can I help?

> I'm playing Camp Bowwow. What do I do when it's my turn?

 - On your turn you place one item - either a dog, a food bowl,
   or a toy - into one of the three play yards.

(For reference: the game ends when a player has placed their last
dog; then any players who haven't yet played in that round each get
one more item turn in order until the last player, so all players
have equal turns.)
> I'm playing Frog Panic. What do I do when it's my turn?

- On your turn:
1. First, move any flies on the board onto the same space as an
   adjacent frog (the frog eats that fly).
2. Then choose one action:
   - Place 1 fly adjacent to a frog on the board as a distraction
     (so that frog won't jump), or
   - Place a frog of your color onto any empty space.
If the frog you place is adjacent to another frog (even your own),
that adjacent frog jumps two spaces away in any direction you choose.
If a frog jumps off the board it is out of play.

(For reference: the game ends when all players have placed all their
frogs; the player with the most frogs remaining on the board wins.)

Mieux que la simple similarité

La phase « More Than Similarity » fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre.

Liste de contrôle opérationnelle

La phase de la liste de contrôle opérationnelle fonctionne le mieux lorsqu’elle est considérée comme une surface mesurable. Capturez un transcript idéal, un cas d’échec et la note de réversion avant d’élargir le périmètre.

Considérez cette phase comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définez des vérifications de succès et refusez les terminations partielles silencieuses.

Séparer la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent.

Ajouter un test de base qui exerce le chemin critique dans l’environnement d’intégration continue en utilisant des fixtures, et non des API payantes en ligne, chaque fois que le budget le permet.

Enregistrer les temps d’exécution ainsi que le coût des tokens ou des requêtes à côté des résultats fonctionnels. Une visibilité précoce des coûts évite les factures inattendues lorsque le processus passe d’un environnement de démonstration à des environnements partagés.

Séparer la politique de segmentation de la politique de récupération. Modifier l’une ne doit pas obliger à réécrire l’autre lorsque les métriques de qualité évoluent.

Au préalable de promouvoir l’ensemble technologique, figer les versions, capturer une transcription exemplaire pour le chemin critique et confirmer les étapes de réversion. Les environnements partagés nécessitent des limites de débit, des vérifications d’attribution et un responsable clair pour la rotation des secrets. Préférer une fiabilité solide à des démonstrations temporaires ingénieuses.

Note de lot pour bef2f8a7cb72 : éviter d’inclure les clés du fournisseur dans le répertoire, fixer une limite pour les tokens par session, et stocker les transcriptions à côté des fichiers de test d’évaluation afin que les remplacements ultérieurs de modèles restent comparables.

Pour la note de renforcement au stade 0, définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Documenter conjointement le parcours normal et le parcours de récupération. Les tentatives répétées, les contrôles humains et la gestion des messages non traités font partie du produit, et non d’améliorations ultérieures.

Détail de renforcement 0/963 : mesurer le temps d’exécution, la catégorie de l’erreur et la consommation de tokens pour cette note, puis décider de conserver ou non le changement en se basant sur un ensemble de questions prédéfini plutôt que sur des observations subjectives.

Lors de la réalisation de la première étape des notes de renforcement de sécurité, notez d’abord les conditions contractuelles : les entrées requises, le signal de succès, ainsi que ce qui se passe en cas d’échec partiel. Cette liste de contrôle permet de rester honnête lors des modifications ultérieures du code. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux éléments concernés, définez des vérifications de succès, et refusez les terminaisons partielles silencieuses.

Détail de renforcement 1/963 : mesurez le temps d’exécution, la catégorie de l’erreur et la consommation de tokens pour cette note, puis décidez si vous souhaitez conserver la modification en vous basant sur un ensemble de questions prédéfini plutôt que sur des observations subjectives.