Construa um sistema RAG multimodal impulsionado por IA com Docling e Granite

Autores

BJ Hargrave

Open Source Developer, STSM

Erika Russi

Data Scientist

IBM

Neste tutorial, você usará o Docling da IBM e o IBM Granite Vision de código aberto, embeddings baseadas em texto e modelos de IA para criar um sistema RAG. Esses modelos estão disponíveis via vários frameworks de código aberto. Neste tutorial, usaremos Replicate para conectar aos modelos de visão do IBM Granite e IA generativa e HuggingFace para conectar ao embedding.

Geração aumentada de recuperação multimodal

A geração aumentada de recuperação (RAG) é uma técnica usada com grandes modelos de linguagem (LLMs) para conectar o modelo a uma base de conhecimento de informações fora dos dados nos quais o LLM foi treinado sem precisar realizar ajuste fino. A RAG tradicional é limitada a casos de uso baseados em texto, como resumo de texto e chatbot.

A RAG multimodal pode usar LLMs multimodais (MLLM) para processar informações de vários tipos de dados a serem incluídos como parte da base de conhecimento externa usada na RAG. Dados multimodais podem incluir texto, imagens, áudio, vídeo ou outras formas. Os LLMs multimodais populares incluem o Gemini do Google, o Llama 3.2 da Meta e o GPT-4 e GPT-4o da OpenAI.

Para esta receita, você utilizará um modelo do IBM Granite capaz de processar diversas modalidades. Você criará um sistema de IA para responder a consultas de usuários em tempo real a partir de dados não estruturados em um PDF.

Visão geral do tutorial

Boas-vindas a este tutorial do Granite. Neste tutorial, você aprenderá a aproveitar o poder de ferramentas avançadas para criar um pipeline RAG multimodal impulsionado por IA. Este tutorial guiará você pelos seguintes processos:

  • Pré-processamento de documentos: aprenda a lidar com documentos de várias fontes, analisá-los e transformá-los em formatos utilizáveis e armazená-los em bancos de dados de vetores usando Docling. Você usará um MLLM Granite para gerar descrições de imagens nos documentos.
  • RAG: entenda como conectar LLMs como o Granite com bases de conhecimento externas para melhorar as respostas às consultas e gerar insights valiosos.
  • LangChain para integração de fluxos de trabalho: descubra como usar o LangChain para otimizar e orquestrar fluxos de trabalho de processamento e recuperação de documentos, permitindo interação perfeita entre diferentes componentes do sistema.

Este tutorial usa três tecnologias de ponta:

  1. Docling:  um toolkit de código aberto usado para analisar e converter documentos.
  2. Granite:  um LLM de última geração que oferece recursos robustos de linguagem natural e um modelo de linguagem de visão que reproduz a geração de texto para imagem.
  3. LangChain: um framework poderoso usado para criar aplicações baseadas em modelos de linguagem, projetadas para simplificar fluxos de trabalho complexos e integrar ferramentas externas sem dificuldades.

Até o final deste tutorial, você realizará o seguinte:

  • Ganhe proficiência em pré-processamento de documentos, fragmentação e compreensão de imagens.
  • Integre bancos de dados de vetores para aprimorar os recursos.
  • Use a RAG para realizar recuperação de dados eficiente e precisa para aplicações do mundo real.

Este tutorial foi criado para desenvolvedores, pesquisadores e entusiastas da IA que buscam aprimorar seus conhecimentos sobre gerenciamento de documentos e técnicas avançadas de processamento de linguagem natural (NLP). O tutorial também pode ser encontrado no Granite Snack Cookbook GitHub da IBM Granite Community na forma de um Jupyter Notebook.

Pré-requisitos

  • Familiaridade com programação Python.
  • Conhecimento básico de LLMs, conceitos de NLP e computer vision.

Etapas

Etapa 1: Instale dependências

! echo "::group::Install Dependencies"
%pip install uv
! uv pip install git+https://github.com/ibm-granite-community/utils.git \
    transformers \
    pillow \
    langchain_classic \
    langchain_core \
    langchain_huggingface sentence_transformers \
    langchain_milvus 'pymilvus[milvus_lite]' \
    docling \
    'langchain_replicate @ git+https://github.com/ibm-granite-community/langchain-replicate.git'
! echo "::endgroup::"

Etapa 2: Seleção dos modelos de IA

Registro

Para ver algumas informações de registro, podemos configurar o nível de log INFO.

OBSERVAÇÃO: não há problema em ignorar a execução dessa célula.

import logging

logging.basicConfig(level=logging.INFO)


Carregue os modelos Granite

Especifique o modelo de embedding a ser usado para gerar vetores de embedding de texto. Aqui, usaremos um dos modelos Granite Embeddings

Para usar um modelo de embedding diferente, substitua esta célula de código por uma desta receita de Modelo de embedding.

from langchain_huggingface import HuggingFaceEmbeddings
from transformers import AutoTokenizer

embeddings_model_path = “ibm-granite/granite-embedding-30m-english”
embeddings_model = HuggingFaceEmbeddings(
    model_name=embeddings_model_path,
)
embeddings_tokenizer = AutoTokenizer.from_pretrained(embeddings_model_path)

 

Especifique o MLLM a ser usado para compreensão da imagem. Usaremos o modelo Granite Vision. 

from ibm_granite_community.notebook_utils import get_env_var
from langchain_community.llms import Replicate
from transformers import AutoProcessor

vision_model_path = “ibm-granite/granite-vision-3.2-2b”
vision_model = Replicate(
    model=vision_model_path,
    replicate_api_token=get_env_var(“REPLICATE_API_TOKEN”),
    model_kwargs={
        “max_tokens”: embeddings_tokenizer.max_len_single_sentence, # Set the maximum number of tokens to generate as output.
        “min_tokens”: 100, # Set the minimum number of tokens to generate as output.
    },
)
vision_processor = AutoProcessor.from_pretrained(vision_model_path)

 

Especifique o modelo de linguagem a ser usado para a operação de geração de RAG.  Aqui, usamos o cliente Replicate LangChain para se conectar a um modelo Granite a partir da organização ibm-granite no Replicate.

Para configurar o Replicate, consulte Introdução ao Replicate. Para conectar a um modelo em um provedor que não seja o Replicate, substitua esta célula de código por uma da receita do componente LLM.

Para conectar a um modelo em um provedor que não seja o Replicate, substitua esta célula de código por uma da receita do componente LLM.

from langchain_replicate import ChatReplicate

model_path = "ibm-granite/granite-4.0-h-small"
model = ChatReplicate(
    model=model_path,
    replicate_api_token=get_env_var("REPLICATE_API_TOKEN"),
    model_kwargs={
        "max_tokens": 1000, # Set the maximum number of tokens to generate as output.
        "min_tokens": 100, # Set the minimum number of tokens to generate as output.
    },
)

Etapa 3: Prepare os documentos para o banco de dados de vetores

Neste exemplo, a partir de um conjunto de documentos de origem, usamos o Docling para converter os documentos em texto e imagens. O texto é, então, dividido em fragmentos. As imagens são processadas pelo MLLM para gerar resumos de imagens.

Use o Docling para baixar os documentos e converter em texto e imagens

O Docling fará download dos documentos em PDF e os processará para que possamos obter o texto e as imagens contidos nos documentos. No PDF, existem vários tipos de dados, incluindo texto, tabelas, gráficos e imagens.

from docling.document_converter import DocumentConverter, PdfFormatOption
from docling.datamodel.base_models import InputFormat
from docling.datamodel.pipeline_options import PdfPipelineOptions

pdf_pipeline_options = PdfPipelineOptions(
    do_ocr=False,
    generate_picture_images=True,
)
format_options = {
    InputFormat.PDF: PdfFormatOption(pipeline_options=pdf_pipeline_options),
}
converter = DocumentConverter(format_options=format_options)

sources = [
    “https://midwestfoodbank.org/images/AR_2020_WEB2.pdf”,
]
conversions = { source: converter.convert(source=source).document for source in sources }

 

Com os documentos processados, processamos ainda mais os elementos de texto nos documentos. Nós os fragmentamos em tamanhos apropriados para o modelo de embeddings que estamos usando. Uma lista de documentos do LangChain é criada a partir dos fragmentos de texto.

from docling_core.transforms.chunker.hybrid_chunker import HybridChunker
from docling_core.types.doc.document import TableItem
from langchain_core.documents import Document

doc_id = 0
texts: list[Document] = []
for source, docling_document in conversions.items():
    for chunk in HybridChunker(tokenizer=embeddings_tokenizer).chunk(docling_document):
        items = chunk.meta.doc_items
        if len(items) == 1 and isinstance(items[0], TableItem):
            continue # we will process tables later
        refs = “ “.join(map(lambda item: item.get_ref().cref, items))
        print(refs)
        text = chunk.text
        document = Document(
            page_content=text,
            metadata={
                “doc_id”: (doc_id:=doc_id+1),
                “source”: source,
                “ref”: refs,
            },
        )
        texts.append(document)

print(f”{len(texts)} text document chunks created”)

 

Em seguida, processamos todas as tabelas dos documentos. Convertemos os dados da tabela em formato de remarcação para passar para o modelo de idioma. Uma lista de documentos do LangChain é criada a partir das renderizações de remarcação da tabela.

from docling_core.types.doc.labels import DocItemLabel

doc_id = len(texts)
tables: list[Document] = []
for source, docling_document in conversions.items():
    for table in docling_document.tables:
        if table.label in [DocItemLabel.TABLE]:
            ref = table.get_ref().cref
            print(ref)
            text = table.export_to_markdown()
            document = Document(
                page_content=text,
                metadata={
                    “doc_id”: (doc_id:=doc_id+1),
                    “source”: source,
                    “ref”: ref
                },
            )
            tables.append(document)


print(f”{len(tables)} table documents created”)

 

Por fim, processamos todas as imagens dos documentos. Aqui, usamos o modelo de linguagem da visão para entender o conteúdo de uma imagem. Neste exemplo, estamos interessados em qualquer informação textual na imagem. Talvez você queira experimentar textos de prompt diferentes para ver como isso pode melhorar os resultados.

OBSERVAÇÃO: o processamento das imagens pode demorar muito, dependendo do número de imagens e do serviço que executa o modelo de linguagem de visão.

import base64
import io
import PIL.Image
import PIL.ImageOps
from IPython.display import display

def encode_image(image: PIL.Image.Image, format: str = “png”) -> str:
    image = PIL.ImageOps.exif_transpose(image) or image
    image = image.convert(“RGB”)

    buffer = io.BytesIO()
    image.save(buffer, format)
    encoding = base64.b64encode(buffer.getvalue()).decode(“utf-8”)
    uri = f”data:image/{format};base64,{encoding}”
    return uri

# Feel free to experiment with this prompt
image_prompt = “If the image contains text, explain the text in the image.”
conversation = [
    {
        “role”: “user”,
        “content”: [
            {“type”: “image”},
            {“type”: “text”, “text”: image_prompt},
        ],        
    },
]
vision_prompt = vision_processor.apply_chat_template(
    conversation=conversation,
    add_generation_prompt=True,
)
pictures: list[Document] = []
doc_id = len(texts) + len(tables)
for source, docling_document in conversions.items():
    for picture in docling_document.pictures:
        ref = picture.get_ref().cref
        print(ref)
        image = picture.get_image(docling_document)
        if image:
            text = vision_model.invoke(vision_prompt, image=encode_image(image))
            document = Document(
                page_content=text,
                metadata={
                    “doc_id”: (doc_id:=doc_id+1),
                    “source”: source,
                    “ref”: ref,
                },
            )
            pictures.append(document)

print(f”{len(pictures)} image descriptions created”)

 

Podemos, então, exibir os documentos do LangChain criados a partir dos documentos de entrada.

import itertools
from docling_core.types.doc.document import RefItem

# Print all created documents
for document in itertools.chain(texts, tables):
    print(f”Document ID: {document.metadata[‘doc_id’]}”)
    print(f”Source: {document.metadata[‘source’]}”)
    print(f”Content:\n{document.page_content}”)
    print(“=” * 80) # Separator for clarity

for document in pictures:
    print(f”Document ID: {document.metadata[‘doc_id’]}”)
    source = document.metadata[‘source’]
    print(f”Source: {source}”)
    print(f”Content:\n{document.page_content}”)
    docling_document = conversions[source]
    ref = document.metadata[‘ref’]
    picture = RefItem(cref=ref).resolve(docling_document)
    image = picture.get_image(docling_document)
    print(“Image:”)
    display(image)
    print(“=” * 80) # Separator for clarity

Preencha o banco de dados de vetores

Usando o modelo de embedding, carregamos os documentos dos fragmentos de texto e legendas de imagens geradas para um banco de dados de vetores. A criação desse banco de dados de vetores nos permite realizar facilmente uma pesquisa de similaridade semântica entre nossos documentos.

OBSERVAÇÃO: o preenchimento do banco de dados de vetores pode levar algum tempo dependendo do modelo de embedding e do serviço.

Escolha seu banco de dados de vetores

Especifique o banco de dados a ser utilizado para armazenar e recuperar vetores de embedding.

Para conectar a um banco de dados de vetores diferente do Milvus, substitua esta célula de código por uma desta receita do armazenamento de vetores.

import tempfile
from langchain_core.vectorstores import VectorStore
from langchain_milvus import Milvus

db_file = tempfile.NamedTemporaryFile(prefix=”vectorstore_”, suffix=”.db”, delete=False).name
print(f”The vector database will be saved to {db_file}”)

vector_db: VectorStore = Milvus(
    embedding_function=embeddings_model,
    connection_args={“uri”: db_file},
    auto_id=True,
    enable_dynamic_field=True,
    index_params={“index_type”: “AUTOINDEX”},
)

 

Agora adicionamos todos os documentos do LangChain para o texto, tabelas e descrições de imagem ao banco de dados de vetores.

import itertools
documents = list(itertools.chain(texts, tables, pictures))
ids = vector_db.add_documents(documents)
print(f”{len(ids)} documents added to the vector database”)

Etapa 4: RAG com o Granite

Agora que convertemos e vetorizamos com sucesso nossos documentos, podemos configurar o pipeline RAG.

Recupere fragmentos relevantes

Aqui testamos o banco de dados de vetores procurando fragmentos com informações relevantes para nossa consulta no espaço vetorial. Exibimos os documentos associados à descrição da imagem recuperada.

Fique à vontade para testar diferentes consultas.

query = "How much was spent on food distribution relative to the amount of food distributed?"
for doc in vector_db.as_retriever().invoke(query):
    print(doc)
    print("=" * 80) # Separator for clarity

 

O documento devolvido deve responder à consulta. Vamos em frente construir nosso pipeline de RAG.


Crie o pipeline de RAG para o Granite

Primeiro, criamos os prompts para que o Granite realize a consulta de RAG. Usamos o modelo de bate-papo Granite e fornecemos os valores do espaço reservado que o pipeline de RAG LangChain substituirá.

Em seguida, construímos o pipeline de RAG utilizando os modelos de prompt do Granite criados anteriormente.

from ibm_granite_community.langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_classic.chains.retrieval import create_retrieval_chain
from langchain_core.prompts import ChatPromptTemplate

# Create a Granite prompt for question-answering with the retrieved context
prompt_template = ChatPromptTemplate.from_template("{input}")

# Assemble the retrieval-augmented generation chain
combine_docs_chain = create_stuff_documents_chain(
    llm=model,
    prompt=prompt_template,
)
rag_chain = create_retrieval_chain(
    retriever=vector_db.as_retriever(),
    combine_docs_chain=combine_docs_chain,
)

Gere uma resposta com recuperação aprimorada a uma pergunta

O pipeline usa a consulta para localizar documentos do banco de dados de vetores e usá-los como contexto para a consulta.

from ibm_granite_community.notebook_utils import wrap_text

output = rag_chain.invoke({"input": query})

print(wrap_text(output['answer']))

Fantástico! Criamos uma aplicação de IA que pode aproveitar o conhecimento do texto e das imagens dos documentos de origem.

Próximas etapas

  • Explore fluxos de trabalho avançados de RAG para outros setores.
  • Experimente com outros tipos de documento e conjuntos de dados maiores.
  • Otimize a engenharia de prompts para obter respostas melhores do Granite.
Soluções relacionadas
IBM watsonx.ai

Treine, valide, ajuste e implemente recursos de IA generativa, modelos de base e recursos de aprendizado de máquina com o IBM watsonx.ai, um estúdio empresarial de última geração para construtores de IA. Crie aplicações de IA em uma fração do tempo com uma fração dos dados.

Conheça o watsonx.ai
Soluções de inteligência artificial

Use a IA a serviço de sua empresa com a experiência e o portfólio de soluções líder do setor da IBM à sua disposição.

Explore as soluções de IA
Serviços de IA

Reinvente os fluxos de trabalho e operações críticos adicionando IA para maximizar experiências, tomadas de decisão em tempo real e valor de negócios.

Explore os serviços de IA
Dê o próximo passo

Obtenha acesso completo aos recursos que abrangem o ciclo de vida do desenvolvimento da IA. Produza soluções poderosas de IA com interfaces fáceis de usar, fluxos de trabalhos e acesso a APIs e SDKs padrão do setor.

Explore o watsonx.ai Agende uma demonstração em tempo real