ChatOllama Notes | Implementación de RAG avanzado para la productividad y bases de datos de documentos basadas en Redis

 

ChatOllama Es un chatbot de código abierto basado en LLMs. Para obtener una descripción detallada de ChatOllama, haga clic en el siguiente enlace.

 

ChatOllama | Aplicación RAG local 100% basada en Ollama

 

ChatOllama El objetivo inicial era proporcionar a los usuarios una aplicación RAG nativa 100%.

 

A medida que ha ido creciendo, más y más usuarios se han acercado con valiosos requisitos. Ahora, `ChatOllama` soporta múltiples modelos de lenguaje incluyendo:

 

Con ChatOllama, los usuarios pueden:

  • Gestión de modelos Ollama (extracción/eliminación)
  • Gestionar la base de conocimientos (crear/borrar)
  • Diálogo libre con los LLM
  • Gestión de la base de conocimientos personales
  • Comunicación con los LLM a través de una base de conocimientos personal

 

En este artículo, voy a ver cómo conseguir RAG avanzado para producción. He aprendido las técnicas básicas y avanzadas de RAG, pero todavía hay muchas cosas de las que hay que ocuparse para llevar RAG a producción. Compartiré el trabajo que se ha hecho en ChatOllama y los preparativos que se han hecho para llevar RAG a producción.

 

ChatOllama 笔记 | 实现高级RAG的生产化和基于Redis的文档数据库

ChatOllama | RAG Corridas Construidas

 

En la plataforma ChatOllama, utilizamos la tecnologíaLangChain.jspara manipular el proceso de la RAG. En la base de conocimientos de ChatOllama, se utilizan recuperadores de documentos padres más allá de la RAG original. Vamos a profundizar en su arquitectura y detalles de implementación. Esperamos que le resulte esclarecedor.

 

 

Utilidad para recuperar documentos de los padres

 

Para que el recuperador de documentos matriz funcione en un producto real, tenemos que entender sus principios básicos y seleccionar los elementos de almacenamiento adecuados para la producción.

 

Principios básicos de los recuperadores de documentos para padres

A la hora de dividir documentos para su recuperación, a menudo surgen requisitos contradictorios:

  • Es posible que desee que los documentos sean lo más pequeños posible para que su contenido incrustado pueda representar su significado con la mayor precisión. Si el contenido es demasiado largo, la información incrustada puede perder su significado.
  • También es conveniente que los documentos sean lo suficientemente largos para garantizar que cada párrafo tenga un entorno de texto completo.

 

Desarrollado por LangChain.jsParentDocumentRetrieverEste equilibrio se consigue dividiendo el documento en trozos. Cada vez que recupera, extrae los trozos y luego busca el documento padre correspondiente a cada trozo, devolviendo una gama más amplia de documentos. En general, el documento padre está vinculado a los trozos por el identificador del documento. Más adelante explicaremos cómo funciona esto.

Tenga en cuenta que aquí elparent documentSe refiere a pequeños fragmentos de documentos fuente.

 

construya

Echemos un vistazo al diagrama general de la arquitectura del recuperador de documentos padre.

Cada trozo se procesará y transformará en datos vectoriales, que se almacenarán en la base de datos de vectores. El proceso de recuperación de estos trozos será como se hacía en la configuración original del GAR.

El documento principal (bloque-1, bloque-2, ...... bloque-m) se divide en partes más pequeñas. Cabe señalar que aquí se utilizan dos métodos diferentes de segmentación del texto: uno mayor para los documentos padre y otro menor para los bloques más pequeños. A cada documento padre se le asigna un ID de documento, y este ID se registra como metadatos en su chunk correspondiente. Así se garantiza que cada trozo pueda encontrar su documento principal correspondiente utilizando el ID del documento almacenado en los metadatos.

El proceso de recuperación de documentos padre no es el mismo. En lugar de una búsqueda por similitud, se proporciona un ID de documento para encontrar su correspondiente documento padre. En este caso, un sistema de almacenamiento clave-valor es suficiente para el trabajo.

 

ChatOllama 笔记 | 实现高级RAG的生产化和基于Redis的文档数据库

Recuperador de documentos para padres

 

Sección de almacenamiento

Hay dos tipos de datos que deben almacenarse:

  • Datos vectoriales a pequeña escala
  • Cadena de datos del documento padre que contiene su ID de documento

 

Están disponibles las principales bases de datos vectoriales. De `ChatOllama`, elegí Chroma.

Para los datos del documento principal, elegí Redis, el sistema de almacenamiento de valores clave más popular y altamente escalable disponible.

 

Lo que falta en LangChain.js

LangChain.js ofrece varias formas de integrar el almacenamiento de bytes y documentos:

 

Almacenamiento | 🦜️🔗 Langchain

El almacenamiento de datos como pares clave-valor es rápido y eficiente, y constituye una potente herramienta para las aplicaciones LLM. El repositorio base...

js.langchain.com

 

Soporte para `IORedis`:

 

IORedis | 🦜️🔗 Langchain

Este ejemplo muestra cómo utilizar la integración del repositorio base RedisByteStore para configurar el almacenamiento del historial de chat.

js.langchain.com

 

La parte que falta en RedisByteStore es la funcióncollectionMecanismos.

 

Al procesar documentos de diferentes bases de conocimiento, cada base de conocimiento se procesará en uncollectionLos fondos se organizan en forma de colecciones, y los documentos de una misma biblioteca se convierten en datos vectoriales y se almacenan en una base de datos vectorial como Chroma en uno de loscollectionEn la asamblea.

 

Supongamos que queremos eliminar una base de conocimientos. Ciertamente podemos borrar una colección `collection` de la base de datos Chroma. Pero, ¿cómo limpiamos el almacén de documentos en función de las dimensiones de la base de conocimientos? Dado que componentes como RedisByteStore no soportan la funcionalidad `collection`, he tenido que implementarla yo mismo.

 

ChatOllama RedisDocStore

En Redis, no hay ninguna característica incorporada llamada `collection`. Los desarrolladores suelen implementar la funcionalidad `collection` mediante el uso de claves prefijadas. La siguiente figura muestra cómo los prefijos se pueden utilizar para identificar diferentes colecciones:

 

ChatOllama 笔记 | 实现高级RAG的生产化和基于Redis的文档数据库

Representaciones de claves Redis con prefijos

 

Veamos ahora cómo se aplicó paraRedisde la función de almacenamiento de documentos para el fondo.

 

Mensaje clave:

  • Cada `RedisDocstore` se inicializa con un parámetro `namespace`. (Es posible que la nomenclatura de los espacios de nombres y las colecciones no esté estandarizada por el momento).
  • Las claves se procesan primero `namespace` para las operaciones get y set.

 

import { Document } from "@langchain/core/documents" ;
import { BaseStoreInterface } from "@langchain/core/stores" ;
import { Redis } de "ioredis".
import { createRedisClient } from "@/servidor/almacén/redis" ;

export class RedisDocstore implements BaseStoreInterface
{
_namespace: cadena;
_client: Redis.

constructor(namespace: cadena) {
this._namespace = espacio de nombres;
this._client = createRedisClient();
}

serializarDocumento(doc: Documento): string {
return JSON.stringify(doc);
}

deserializarDocumento(jsonCadena: cadena): Documento {
const obj = JSON.parse(jsonString);
return nuevo Documento(obj);
}

getNamespacedKey(clave: cadena): cadena {
return `${this._namespace}:${key}`;
}

getKeys(): Promise {
return new Promise((resolver, rechazar) => {
const stream = this._client.scanStream({ match: this._namespace + '*' });

const keys: cadena[] = [];
stream.on('datos', (resultKeys) => {
keys.push(... .resultKeys);
});

stream.on('end', () => {
resolver(llaves).
});

stream.on('error', (err) => {
rechazar(err);
});
});
}

addText(clave: cadena, valor: cadena) {
this._client.set(this.getNamespacedKey(key), value);
}

async search(search: string): Promise {
const result = await this._client.get(this.getNamespacedKey(search));
if (!resultado) {
throw new Error(`ID ${search} not found.`);
} else {
const document = this.deserializeDocument(result);
devolver documento;
}
}

/**
* :: Añade nuevos documentos al almacén.
* @param texts Un objeto donde las claves son IDs de documentos y los valores son los propios documentos.
* @returns Void
*/
async add(textos: Registro): Promise {
for (const [clave, valor] of Object.entries(textos)) {
console.log(`Añadir ${clave} al almacén: ${this.serializeDocument(valor)}`);
}

const keys = [... .await this.getKeys()];
const solapamiento = Object.keys(textos).filter((x) => keys.includes(x));

si (solapamiento.longitud > 0) {
throw new Error(`Trató de agregar ids que ya existen: ${overlapping}`);
}

for (const [clave, valor] of Object.entries(textos)) {
this.addText(key, this.serialiseDocument(value)); this.
}
}

async mget(keys: string[]): Promise {
return Promise.all(keys.map((key) => {
const documento = this.buscar(clave);
devolver documento;
}));
}

async mset(paresClaveValor: [cadena, Documento][]): Promise {
await Promise.all(
keyValuePairs.map(([clave, valor]) => this.add({[clave]: valor }))
);
}

async mdelete(_claves: cadena[]): Promise {
throw new Error("No implementado.");
}

// eslint-disable-next-line require-yield
async *yieldKeys(_prefix?: string): AsyncGenerator {
throw new Error("No implementado");
}

async deleteAll(): Promise {
return new Promise((resolver, rechazar) => {
deje cursor = '0';

const scanCallback = (err, result) => {
if (err) {
rechazar(err);
volver;
}

const [siguienteCursor, claves] = resultado;

// Borrar las teclas que coincidan con el prefijo
keys.forEach((key) => {
this._client.del(key);
});

// Si el cursor es '0', hemos iterado por todas las teclas
if (nextCursor === '0') {
resolve().
} else {
// Continúe en exploración con el cursor siguiente
this._client.scan(nextCursor, 'MATCH', `${this._namespace}:*`, scanCallback);
}
};

// Iniciar la operación inicial de SCAN
this._client.scan(cursor, 'MATCH', `${this._namespace}:*`, scanCallback);
});
}
}

 

Puede utilizar este componente sin problemas con ParentDocumentRetriever:

retriever = new ParentDocumentRetriever({
vectorstore: chromaClient.
docstore: new RedisDocstore(collectionName),
parentSplitter: nuevo RecursiveCharacterTextSplitter({
chunkOverlap: 200,
chunkSize: 1000,
}),
childSplitter: new RecursiveCharacterTextSplitter({
chunkOverlap: 50,
chunkSize: 200,
}),
niñoK: 20.
parentK: 5.
});

Ahora disponemos de una solución de almacenamiento escalable para RAG avanzado, Parent Document Retriever, junto con `Chroma` y `Redis`.

© declaración de copyright

Artículos relacionados

Sin comentarios

Debe iniciar sesión para participar en los comentarios.
Acceder ahora
ninguno
Sin comentarios...