Aprendizagem pessoal com IA
e orientação prática

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

 

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 Notes | Implementação da produtividade avançada do RAG e do banco de dados de documentos baseado em 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 odocumento paiRefere-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 pai (block-1, block-2, ...... block-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 o 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 Notes | Implementação da produtividade avançada do RAG e do banco de dados de documentos baseado em 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 é ocoleçãoMecanismos.

 

Ao processar documentos de diferentes bases de conhecimento, cada base de conhecimento será processada em umcoleçãoAs 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 doscoleçãoNa 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 Notes | Implementação da produtividade avançada do RAG e do banco de dados de documentos baseado em 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.

 

importar { Document } de "@langchain/core/documents".
importar { BaseStoreInterface } de "@langchain/core/stores" ;
importar { Redis } de "ioredis".
importar { createRedisClient } de "@/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));
Se (!result) {
lançar novo Error(`ID ${search} não encontrado.`);
} else {
const document = this.deserializeDocument(result);
devolver documento;
}
}

/**
* :: Adiciona novos documentos ao repositório.
* @param texts Um objeto em que as chaves são IDs de documentos e os valores são os próprios documentos.
* @retorna Void
*/
async add(texts: Record): Promise {
for (const [key, value] of Object.entries(texts)) {
console.log(`Adicionando ${key} ao armazenamento: ${this.serializeDocument(value)}`);
}

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

Se (overlapping.length > 0) {
lançar novo Error(`Tentou adicionar ids que já existem: ${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);
devolver documento;
}));
}

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

async mdelete(_keys: string[]): Promise {
lançar novo erro ("Não implementado.");
}

// eslint-disable-next-line require-yield
async *yieldKeys(_prefix?: string): AsyncGenerator {
lançar um novo erro ("Não implementado");
}

async deleteAll(): Promise {
return new Promise((resolve, reject) => {
deixar cursor = '0';

const scanCallback = (err, result) => {
se (err) {
reject(err);
retorno;
}

const [nextCursor, keys] = result;

// Excluir chaves que correspondam ao prefixo
keys.forEach((key) => {
this._client.del(key);
});

// Se o cursor for '0', teremos iterado por todas as teclas
Se (nextCursor === '0') {
resolve().
} else {
// Continuar a varredura com o próximo cursor
this._client.scan(nextCursor, 'MATCH', `${this._namespace}:*`, scanCallback);
}
};

// Iniciar a operação SCAN inicial
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,
}),
criançaK: 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`.

Aprendizagem fácil com IA

O guia do leigo para começar a usar a IA

Ajuda você a aprender a utilizar as ferramentas de IA com baixo custo e a partir de uma base zero.A IA, assim como o software de escritório, é uma habilidade essencial para todos. Dominar a IA lhe dará uma vantagem em sua busca de emprego e metade do esforço em seu trabalho e estudos futuros.

Ver detalhes>
Não pode ser reproduzido sem permissão:Chefe do Círculo de Compartilhamento de IA " ChatOllama Notes | Implementação do RAG avançado para produtividade e bancos de dados de documentos baseados em Redis

Chefe do Círculo de Compartilhamento de IA

O Chief AI Sharing Circle se concentra no aprendizado de IA, fornecendo conteúdo abrangente de aprendizado de IA, ferramentas de IA e orientação prática. Nosso objetivo é ajudar os usuários a dominar a tecnologia de IA e explorar juntos o potencial ilimitado da IA por meio de conteúdo de alta qualidade e compartilhamento de experiências práticas. Seja você um iniciante em IA ou um especialista sênior, este é o lugar ideal para adquirir conhecimento, aprimorar suas habilidades e realizar inovações.

Entre em contato conosco
pt_BRPortuguês do Brasil