Acesso no exterior: www.kdjingpai.com
Ctrl + D Marcar este site como favorito

ChatOllama Notes | Implementação do RAG avançado para produtividade e bancos de dados de documentos baseados em Redis

2024-04-02 2.6 K

 

ChatOllama Ele é um chatbot de código aberto baseado em LLMs. Para obter uma descrição detalhada do ChatOllama, clique no link abaixo.

 

ChatOllama | Aplicativo RAG local 100% baseado em Ollama

 

ChatOllama O objetivo inicial era fornecer aos usuários um aplicativo RAG nativo do 100%.

 

À medida que cresceu, mais e mais usuários apresentaram requisitos valiosos. Agora, o `ChatOllama` é compatível com vários modelos de idiomas, incluindo:

 

Com o ChatOllama, os usuários podem:

  • Gerenciar modelos Ollama (extrair/excluir)
  • Gerenciar a base de conhecimento (criar/excluir)
  • Diálogo livre com LLMs
  • Gerenciar a base de conhecimento pessoal
  • Comunicação com LLMs por meio de uma base de conhecimento pessoal

 

Neste artigo, verei como obter o RAG avançado para produção. Aprendi as técnicas básicas e avançadas do RAG, mas ainda há muitas coisas que precisam ser resolvidas para colocar o RAG em produção. Compartilharei o trabalho que foi feito no ChatOllama e os preparativos que foram feitos para colocar o RAG em produção.

 

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

ChatOllama | RAG Constructed Runs

 

Na plataforma ChatOllama, utilizamos oLangChain.jspara manipular o processo RAG. Na base de conhecimento do ChatOllama, são usados recuperadores de documentos principais além do RAG original. Vamos nos aprofundar em sua arquitetura e nos detalhes de implementação. Esperamos que você ache isso esclarecedor.

 

 

Utilitário de recuperação de documentos dos pais

 

Para que o recuperador de documentos originais funcione em um produto real, precisamos entender seus princípios fundamentais e selecionar os elementos de armazenamento corretos para fins de produção.

 

Princípios básicos dos recuperadores de documentos dos pais

Requisitos conflitantes são frequentemente encontrados ao dividir documentos para necessidades de recuperação:

  • Talvez você queira que os documentos sejam tão pequenos quanto possível para que o conteúdo incorporado possa representar o significado com mais precisão. Se o conteúdo for muito longo, as informações incorporadas poderão perder o significado.
  • Você também quer documentos que sejam longos o suficiente para garantir que cada parágrafo tenha um ambiente de texto completo.

 

Desenvolvido por LangChain.jsParentDocumentRetrieverEsse equilíbrio é obtido dividindo o documento em partes. A cada recuperação, ele extrai os blocos e, em seguida, procura o documento pai correspondente a cada bloco, retornando um intervalo maior de documentos. Em geral, o documento pai é vinculado aos blocos pelo ID do documento. Explicaremos mais sobre como isso funciona mais tarde.

Observe que aqui oparent documentRefere-se a pequenas partes de documentos de origem.

 

construir

Vamos dar uma olhada no diagrama geral da arquitetura do recuperador de documentos pai.

Cada bloco será processado e transformado em dados vetoriais, que serão armazenados no banco de dados de vetores. O processo de recuperação desses blocos será como foi feito na configuração original do RAG.

O documento principal (bloco-1, bloco-2, ...... bloco-m) é dividido em partes menores. É importante observar que dois métodos diferentes de segmentação de texto são usados aqui: um maior para documentos pai e um menor para blocos menores. Cada documento pai recebe um ID de documento, e esse ID é registrado como metadados em seu bloco correspondente. Isso garante que cada bloco possa encontrar seu documento pai correspondente usando o ID do documento armazenado nos metadados.

O processo de recuperação de documentos pai não é o mesmo. Em vez de uma pesquisa de similaridade, é fornecido um ID de documento para encontrar o documento pai correspondente. Nesse caso, um sistema de armazenamento de valor-chave é suficiente para o trabalho.

 

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

Recuperador de documentos dos pais

 

Seção de armazenamento

Há dois tipos de dados que precisam ser armazenados:

  • Dados vetoriais em pequena escala
  • Dados de cadeia de caracteres do documento pai contendo seu ID de documento

 

Todos os principais bancos de dados vetoriais estão disponíveis. Do `ChatOllama`, escolhi o Chroma.

Para os dados do documento principal, escolhi o Redis, o sistema de armazenamento de valores-chave mais popular e altamente escalável disponível.

 

O que está faltando no LangChain.js

O LangChain.js oferece várias maneiras de integrar o armazenamento de bytes e documentos:

 

Armazenamento | 🦜️🔗 Langchain

O armazenamento de dados como pares de valores-chave é rápido e eficiente, além de ser uma ferramenta poderosa para aplicativos LLM. O repositório básico...

js.langchain.com

 

Suporte para `IORedis`:

 

IORedis | 🦜️🔗 Langchain

Este exemplo mostra como usar a integração do repositório básico RedisByteStore para configurar o armazenamento do histórico de bate-papo.

js.langchain.com

 

A parte que falta no RedisByteStore é ocollectionMecanismos.

 

Ao processar documentos de diferentes bases de conhecimento, cada base de conhecimento será processada em umcollectionAs coleções são organizadas na forma de coleções, e os documentos da mesma biblioteca são convertidos em dados vetoriais e armazenados em um banco de dados vetorial, como o Chroma, em um doscollectionNa assembleia.

 

Suponha que queiramos excluir uma base de conhecimento. Certamente podemos excluir uma coleção `collection` no banco de dados Chroma. Mas como limpar o armazenamento de documentos de acordo com as dimensões da base de conhecimento? Como componentes como o RedisByteStore não oferecem suporte à funcionalidade `collection`, tive que implementá-la eu mesmo.

 

ChatOllama RedisDocStore

No Redis, não existe um recurso embutido chamado `collection`. Os desenvolvedores geralmente implementam a funcionalidade `collection` usando chaves de prefixo. A figura a seguir mostra como os prefixos podem ser usados para identificar diferentes coleções:

 

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

Representações de chaves do Redis com prefixos

 

Agora vamos ver como isso foi implementado paraRedisda função de armazenamento de documentos para o plano de fundo.

 

Mensagem principal:

  • Cada `RedisDocstore` é inicializado com um parâmetro `namespace`. (A nomeação de namespaces e coleções pode não estar padronizada no momento).
  • As chaves são processadas primeiro no `namespace` para as operações get e 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<string, Document>
{
_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<string[]> {
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<Document> {
const result = await this._client.get(this.getNamespacedKey(search));
if (!result) {
throw new Error(`ID ${search} not found.`);
} else {
const document = this.deserializeDocument(result);
return document;
}
}

/**
* Adds new documents to the store.
* @param texts An object where the keys are document IDs and the values are the documents themselves.
* @returns Void
*/
async add(texts: Record<string, Document>): Promise<void> {
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.serializeDocument(value));
}
}

async mget(keys: string[]): Promise<Document[]> {
return Promise.all(keys.map((key) => {
const document = this.search(key);
return document;
}));
}

async mset(keyValuePairs: [string, Document][]): Promise<void> {
await Promise.all(
keyValuePairs.map(([key, value]) => this.add({ [key]: value }))
);
}

async mdelete(_keys: string[]): Promise<void> {
throw new Error(“Not implemented.”);
}

// eslint-disable-next-line require-yield
async *yieldKeys(_prefix?: string): AsyncGenerator<string> {
throw new Error(“Not implemented”);
}

async deleteAll(): Promise<void> {
return new Promise((resolve, reject) => {
let cursor = ‘0’;

const scanCallback = (err, result) => {
if (err) {
reject(err);
return;
}

const [nextCursor, keys] = result;

// Delete keys matching the prefix
keys.forEach((key) => {
this._client.del(key);
});

// If the cursor is ‘0’, we’ve iterated through all keys
if (nextCursor === ‘0’) {
resolve();
} else {
// Continue scanning with the next cursor
this._client.scan(nextCursor, ‘MATCH’, `${this._namespace}:*`, scanCallback);
}
};

// Start the initial SCAN operation
this._client.scan(cursor, ‘MATCH’, `${this._namespace}:*`, scanCallback);
});
}
}

 

Você pode usar esse componente sem problemas com o ParentDocumentRetriever:

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

Agora temos uma solução de armazenamento dimensionável para o RAG avançado, o Parent Document Retriever, juntamente com o `Chroma` e o `Redis`.

Recomendado

Não consegue encontrar ferramentas de IA? Tente aqui!

Basta digitar a palavra-chave Acessibilidade Bing SearchA seção Ferramentas de IA deste site é uma maneira rápida e fácil de encontrar todas as ferramentas de IA deste site.

voltar ao topo