ChatOllama Notes | Mise en œuvre de RAG avancé pour la productivité et les bases de données de documents basées sur Redis
ChatOllama Il s'agit d'un chatbot open source basé sur les LLM. Pour une description détaillée de ChatOllama, cliquez sur le lien ci-dessous.
ChatOllama | Application locale RAG 100% basée sur Ollama
ChatOllama L'objectif initial était de fournir aux utilisateurs une application RAG native 100%.
Au fur et à mesure de son développement, de plus en plus d'utilisateurs ont fait part de leurs besoins. Aujourd'hui, `ChatOllama` prend en charge plusieurs modèles linguistiques, notamment :
- Ollama Modèles pris en charge
- OpenAI
- Azure OpenAI
- Anthropique
Avec ChatOllama, les utilisateurs peuvent :
- Gérer les modèles Ollama (retirer/supprimer)
- Gestion de la base de connaissances (création/suppression)
- Dialogue libre avec les LLM
- Gestion de la base de connaissances personnelle
- Communiquer avec les LLM par le biais d'une base de connaissances personnelle
Dans cet article, je vais voir comment réaliser un RAG avancé pour la production. J'ai appris les techniques de base et avancées de la RAG, mais il reste encore beaucoup de choses à faire pour mettre la RAG en production. Je partagerai le travail qui a été fait dans ChatOllama et les préparations qui ont été faites pour amener RAG en production.

ChatOllama | RAG Constructed Runs
Dans la plateforme ChatOllama, nous utilisons la technologieLangChain.jspour manipuler le processus RAG. Dans la base de connaissances de ChatOllama, des extracteurs de documents parents sont utilisés en plus du RAG original. Nous allons nous plonger dans son architecture et dans les détails de sa mise en œuvre. Nous espérons que vous trouverez cela instructif.
Utilitaire de recherche de documents pour les parents
Pour que le récupérateur de documents parentaux fonctionne dans un produit réel, nous devons comprendre ses principes fondamentaux et sélectionner les éléments de stockage appropriés à des fins de production.
Principes fondamentaux de la recherche de documents par les parents
Des exigences contradictoires sont souvent rencontrées lorsqu'il s'agit de diviser des documents à des fins de recherche :
- Vous pouvez souhaiter que les documents soient aussi petits que possible afin que leur contenu intégré puisse représenter le plus fidèlement possible leur signification. Si le contenu est trop long, les informations intégrées risquent de perdre leur signification.
- Les documents doivent également être suffisamment longs pour que chaque paragraphe dispose d'un environnement complet en texte intégral.
Propulsé par LangChain.jsParentDocumentRetriever
Cet équilibre est obtenu en divisant le document en morceaux. Chaque fois qu'il effectue une recherche, il extrait les morceaux et recherche ensuite le document parent correspondant à chaque morceau, ce qui lui permet d'obtenir un plus grand nombre de documents. En général, le document parent est lié aux morceaux par l'identifiant du document. Nous expliquerons plus tard comment cela fonctionne.
Il convient de noter qu'ici, leparent document
Se réfère à de petits morceaux de documents sources.
construire
Jetons un coup d'œil au diagramme d'architecture global du récupérateur de documents parentaux.
Chaque morceau sera traité et transformé en données vectorielles, qui seront ensuite stockées dans la base de données vectorielle. Le processus de récupération de ces morceaux sera le même que dans la configuration originale de RAG.
Le document parent (bloc-1, bloc-2, ...... bloc-m) est divisé en plusieurs parties. Il convient de noter que deux méthodes différentes de segmentation du texte sont utilisées ici : une plus grande pour les documents parents et une plus petite pour les blocs plus petits. Chaque document parent se voit attribuer un identifiant de document, qui est ensuite enregistré comme métadonnée dans le bloc correspondant. Cela garantit que chaque bloc peut trouver son document parent correspondant à l'aide de l'ID du document stocké dans les métadonnées.
Le processus de récupération des documents parents n'est pas le même. Au lieu d'une recherche de similarité, un identifiant de document est fourni pour trouver le document parent correspondant. Dans ce cas, un système de stockage clé-valeur suffit.

Récupérateur de documents pour les parents
Espace de stockage
Deux types de données doivent être stockés :
- Données vectorielles à petite échelle
- Données sous forme de chaîne du document parent contenant l'ID du document
Toutes les principales bases de données vectorielles sont disponibles. Sur `ChatOllama`, j'ai choisi Chroma.
Pour les données des documents parents, j'ai choisi Redis, le système de stockage clé-valeur le plus populaire et le plus évolutif qui soit.
Ce qui manque à LangChain.js
LangChain.js propose plusieurs façons d'intégrer le stockage d'octets et de documents :
Support pour `IORedis` :
La partie manquante du RedisByteStore est la fonctioncollection
Mécanismes.
Lors du traitement de documents provenant de différentes bases de connaissances, chaque base de connaissances sera traitée dans unecollection
Les collections sont organisées sous forme de collections, et les documents d'une même bibliothèque sont convertis en données vectorielles et stockés dans une base de données vectorielles telle que Chroma dans l'une des bibliothèques de l'Union européenne.collection
Dans l'assemblée.
Supposons que nous voulions supprimer une base de connaissances. Nous pouvons certainement supprimer une collection `collection` dans la base de données Chroma. Mais comment nettoyer le magasin de documents en fonction des dimensions de la base de connaissances ? Comme des composants tels que RedisByteStore ne supportent pas la fonctionnalité `collection`, j'ai dû l'implémenter moi-même.
ChatOllama RedisDocStore
Dans Redis, il n'y a pas de fonctionnalité intégrée appelée `collection`. Les développeurs implémentent généralement la fonctionnalité `collection` en utilisant des clés préfixes. La figure suivante montre comment les préfixes peuvent être utilisés pour identifier différentes collections :

Représentations de clés Redis avec préfixes
Voyons maintenant comment il a été mis en œuvre pourRedisde la fonction de stockage des documents pour l'arrière-plan.
Message clé :
- Chaque `RedisDocstore` est initialisé avec un paramètre `namespace`. (Le nommage des espaces de noms et des collections peut ne pas être standardisé pour le moment).
- Les clés sont d'abord traitées dans l'espace de noms pour les opérations get et set.
import { Document } from "@langchain/core/documents".
import { BaseStoreInterface } from "@langchain/core/stores" ;
import { Redis } from "ioredis".
import { createRedisClient } from "@/server/store/redis" ;export class RedisDocstore implements BaseStoreInterface
{
_namespace : string ;
Client : Redis.constructor(namespace : string) {
this._namespace = namespace ;
this._client = createRedisClient() ;
}serializeDocument(doc : Document) : string {
return JSON.stringify(doc) ;
}deserializeDocument(jsonString : string) : Document {
const obj = JSON.parse(jsonString) ;
return new Document(obj) ;
}getNamespacedKey(key : string) : string {
return `${this._namespace}:${key}` ;
}getKeys() : Promise {
return new Promise((resolve, reject) => {
const stream = this._client.scanStream({ match : this._namespace + '*' }) ;const keys : string[] = [] ;
stream.on('data', (resultKeys) => {
keys.push(... .resultKeys) ;
});stream.on('end', () => {
resolve(keys).
});stream.on('error', (err) => {
reject(err) ;
});
});
}addText(key : string, value : string) {
this._client.set(this.getNamespacedKey(key), value) ;
}async search(search : string) : Promise {
const result = await this._client.get(this.getNamespacedKey(search)) ;
if (!result) {
lancer une nouvelle erreur (`ID ${recherche} introuvable.`) ;
} else {
const document = this.deserializeDocument(result) ;
retourner le document ;
}
}/**
* : : Ajoute de nouveaux documents au magasin.
* @param texts Un objet dont les clés sont les ID des documents et les valeurs les documents eux-mêmes.
* @returns Void
*/
async add(texts : Record) : Promise {
for (const [key, value] of Object.entries(texts)) {
console.log(`Adding ${key} to the store : ${this.serializeDocument(value)}`) ;
}const keys = [... .await this.getKeys()] ;
const overlapping = Object.keys(texts).filter((x) => keys.includes(x)) ;if (overlapping.length > 0) {
throw new Error(`Tried to add ids that already exist : ${overlapping}`) ;
}for (const [key, value] of Object.entries(texts)) {
this.addText(key, this.serialiseDocument(value)) ; this.
}
}async mget(keys : string[]) : Promise {
return Promise.all(keys.map((key) => {
const document = this.search(key) ;
retourner le document ;
}));
}async mset(keyValuePairs : [string, Document][]) : Promise {
attend Promise.all(
keyValuePairs.map(([key, value]) => this.add({ [key] : value }))
);
}async mdelete(_keys : string[]) : Promise {
lancer une nouvelle erreur ("Non implémenté.") ;
}// eslint-disable-next-line require-yield
async *yieldKeys(_prefix? : string) : AsyncGenerator {
lancer une nouvelle erreur ("Non implémenté") ;
}async deleteAll() : Promise {
return new Promise((resolve, reject) => {
laisser curseur = '0';const scanCallback = (err, result) => {
if (err) {
reject(err) ;
retour ;
}const [nextCursor, keys] = result ;
// Supprimer les touches correspondant au préfixe
keys.forEach((key) => {
this._client.del(key) ;
});// Si le curseur est à '0', nous avons parcouru toutes les touches.
if (nextCursor === '0') {
resolve().
} else {
// Continuer balayage avec le curseur suivant
this._client.scan(nextCursor, 'MATCH', `${this._namespace}:*`, scanCallback) ;
}
};// Lancer l'opération initiale de balayage (SCAN)
this._client.scan(cursor, 'MATCH', `${this._namespace}:*`, scanCallback) ;
});
}
}
Vous pouvez utiliser ce composant en toute transparence avec ParentDocumentRetriever :
retriever = new ParentDocumentRetriever({
vectorstore : chromaClient.
docstore : new RedisDocstore(collectionName),
parentSplitter : new RecursiveCharacterTextSplitter({
chunkOverlap : 200,
chunkSize : 1000,
}),
childSplitter : new RecursiveCharacterTextSplitter({
chunkOverlap : 50,
chunkSize : 200,
}),
enfantK : 20.
parentK : 5.
});
Nous disposons désormais d'une solution de stockage évolutive pour le RAG avancé, Parent Document Retriever, ainsi que pour `Chroma` et `Redis`.
© déclaration de droits d'auteur
Article copyright Cercle de partage de l'IA Tous, prière de ne pas reproduire sans autorisation.
Articles connexes
Pas de commentaires...