💡 Exemplo completo em funcionamento disponível no GitHub:
document-to-markdown-rag-chunks-java

O Problema da Mesa de Sinistros: Por que a Ingestão RAG Falha em Arquivos Reais

Um pipeline de ingestão RAG on‑premise é uma camada de análise de documentos que transforma os arquivos que a equipe já possui em texto pesquisável e citável sem enviá‑los a terceiros. As operações de seguros mostram por que isso importa. Um operador de sinistros pergunta ao assistente interno “o que o ajustador disse sobre danos causados por água nesta apólice?”, e a resposta está em um e‑mail de março passado, dentro de um relatório PDF anexado a ele, sob um título chamado “Findings”. O assistente só consegue encontrá‑la se a ingestão ler o anexo, mantiver o título e lembrar de qual e‑mail o relatório veio.

A maioria dos trabalhos de ingestão erra um desses três pontos. Um extrator de texto simples devolve o corpo do e‑mail e ignora o anexo. Um divisor fixo de 512 tokens corta o relatório no meio de “Findings”. E um analisador hospedado resolve ambos, mas somente depois que o arquivo de sinistro, com seus detalhes pessoais e médicos, foi enviado para os servidores de outra pessoa e cobrado por página.

O que a equipe precisa é algo específico e concreto: ler arquivos Word, PDF, EML e ZIP dentro do serviço Java que já possui os dados, manter sua estrutura como Markdown, dividir nos títulos do autor e anexar a fonte e o pai a cada fragmento.

Market snapshot: os guias de 2026 sobre “document parsing for RAG” que revisamos comparam analisadores Python de código aberto com APIs de nuvem cobradas por página. Uma biblioteca Java rodando no seu próprio hardware está praticamente ausente dessa conversa.

Por que os Pipelines RAG Precisam de Markdown em vez de Texto Simples?

Markdown preserva as partes de um documento que indicam ao recuperador onde uma ideia termina e a próxima começa. Títulos tornam‑se linhas #, tabelas permanecem como linhas e colunas, e listas mantêm seus itens. Um fragmentador pode dividir nesses títulos, e um LLM que lê o fragmento vê o mesmo esboço que uma pessoa veria. Texto simples descarta esse esboço, deixando o tamanho como o único sinal para divisão.

Por que Soluções Genéricas Não Funcionam Aqui

As equipes geralmente tentam uma das três coisas antes de construir uma camada de ingestão adequada:

  • Extração de texto simples mais um divisor de tokens: rápido de configurar, mas os fragmentos misturam seções e perdem o título que você citaria.
  • Uma API de análise em nuvem: boa estrutura, mas os arquivos deixam a rede e a conta cresce a cada página reindexada.
  • Um analisador caseiro por formato: uma biblioteca para DOCX, outra para PDF, um analisador MIME para e‑mail e java.util.zip para arquivos compactados, cada um com seus próprios casos de borda.

GroupDocs.Parser for Java cobre todos os quatro formatos por trás de uma única classe Parser e roda in‑process.

A Solução: GroupDocs.Parser em um Fluxo de Trabalho de Ingestão de Sinistros

GroupDocs.Parser for Java lê um documento e pode devolver seu texto como Markdown através de getFormattedText. E‑mails e arquivos compactados são expostos como contêineres cujos itens abrem como novas instâncias Parser, de modo que um PDF anexado a um e‑mail é processado exatamente como um PDF no disco. O projeto de exemplo encapsula isso em seis métodos estáticos e grava o resultado como JSON Lines, que qualquer trabalho de incorporação ou repositório vetorial pode carregar. Nenhum framework RAG está envolvido; o formato de saída é o ponto de integração.

Para acompanhar, você precisa:

  • JDK 11 ou superior para o pom.xml incluído (o código em si se limita a recursos do Java 8)
  • GroupDocs.Parser for Java 26.10 (obter uma licença temporária)
  • Um conjunto de arquivos DOCX, PDF, EML ou ZIP

O pom.xml do projeto declara o repositório https://releases.groupdocs.com/java/repo/ e a única dependência, de modo que a compilação é um comando:

mvn compile exec:java

Implementando a Camada de Ingestão Passo a Passo

As etapas abaixo seguem um arquivo de sinistro da caixa de entrada até o índice. Cada etapa corresponde a um ou dois métodos em RagChunksDemo.java. Mantivemos‑as como métodos estáticos simples de propósito, para que você possa reutilizar qualquer um deles em um worker existente sem adotar o resto.

Etapa 1 - Abrir Cada Arquivo e Roteá‑lo

Cada arquivo enviado recebe seu próprio Parser. O nome do arquivo torna‑se o ID da fonte, e um arquivo de nível superior não tem pai.

// One call per uploaded claim document
String sourceId = new File(path).getName();
try (Parser parser = new Parser(path)) {
    return chunkParser(parser, sourceId, null, 0);  // null parent: top-level file
} catch (UnsupportedDocumentFormatException ex) {
    // An unknown format is logged and skipped, the batch keeps going
    System.out.println("skipped (unsupported format): " + sourceId);
    return new ArrayList<>();
}

chunkParser então extrai o corpo, fragmenta‑o e desce nos anexos enquanto a profundidade for menor que 2.

Etapa 2 - Solicitar Markdown, Aceitar Texto Simples

A etapa de extração verifica o que o formato suporta em vez de codificar uma lista fixa de extensões.

if (parser.getFeatures().isFormattedText()) {
    // Headings, lists and tables survive as Markdown
    FormattedTextOptions options =
            new FormattedTextOptions(FormattedTextMode.Markdown);
    try (TextReader reader = parser.getFormattedText(options)) {
        if (reader != null) {
            return new ExtractedText("markdown", reader.readToEnd());
        }
    } catch (IOException ex) {
        throw new RuntimeException(ex);
    }
}
try (TextReader reader = parser.getText()) {
    if (reader != null) {  // fallback for formats without formatted text
        return new ExtractedText("plain", reader.readToEnd());
    }
} catch (IOException ex) {
    throw new RuntimeException(ex);
}
return new ExtractedText("none", "");  // e.g. a photo with no OCR

Por que essas configurações são importantes para dados de sinistros:

  • FormattedTextMode.Markdown: o título “Findings” do ajustador chega ao fragmentador como ## Findings, podendo assim ser um ponto de divisão e uma citação.
  • isFormattedText(): na execução de exemplo o DOCX e o e‑mail retornam como Markdown, enquanto o PDF recai para texto simples, e o campo format registra o que ocorreu.
  • O resultado none: fotos de sinistros dentro de um ZIP não geram fragmentos sem OCR, o que é honesto e fácil de monitorar.

Etapa 3 - Dividir nos Títulos do Ajustador

O fragmentador mantém uma pilha do título atual em cada um dos seis níveis de Markdown. Um novo título fecha a seção em execução e redefine tudo abaixo do seu nível.

for (String line : markdown.split("\\r?\\n", -1)) {
    Matcher heading = HEADING.matcher(line);   // "#" to "######" + title
    if (heading.matches()) {
        // Close the previous section under the old path
        addSection(chunks, section.toString(), sourceId, parentId, path, format);
        section.setLength(0);
        int level = heading.group(1).length();
        stack[level - 1] = heading.group(2).trim();
        for (int i = level; i < stack.length; i++) {
            stack[i] = null;                     // forget deeper headings
        }
        path = joinPath(stack);                  // "Heading 1 > Heading 1.2"
    }
    section.append(line).append('\n');
}

Seções com mais de 1500 caracteres são cortadas em quebras de parágrafo, de modo que uma seção longa de conclusões se torne vários fragmentos que compartilham o mesmo caminho de título. No arquivo Word incluído isso produz caminhos como Heading 1 > Heading 1.2 > Heading 1.2.1.

Etapa 4 - Seguir Anexos e Escrever JSON Lines

Esta é a etapa que a mesa de sinistros mais se importa. getContainer() lista anexos de e‑mail e entradas de ZIP; cada item abre como um documento filho e herda o ID do pai.

Iterable<ContainerItem> items = parser.getContainer();
if (items == null) {
    return chunks;                    // not an email or archive
}
for (ContainerItem item : items) {
    // "claim-0412.eml/report.pdf" style ids keep the lineage readable
    String childId = sourceId + "/" + item.getFilePath();
    try (Parser child = item.openParser()) {
        chunks.addAll(chunkParser(child, childId, sourceId, depth + 1));
    } catch (UnsupportedDocumentFormatException ex) {
        System.out.println("skipped (unsupported format): " + childId);
    }
}

writeJsonl então grava uma linha UTF‑8 por fragmento com id, source, parent, heading_path, format e text. Com os quatro arquivos de exemplo a execução produz 22 linhas. O fragmento do anexo do e‑mail de exemplo é email-with-attachment.eml/attachment.txt#0, e seu parent é email-with-attachment.eml. Essa combinação permite que o assistente diga “do relatório anexado ao e‑mail de 14 de março” em vez de “de algum lugar no arquivo”.

Fluxo de Trabalho Real: Reindexação Semanal de Sinistros

Imagine uma equipe de operações de sinistros que exporta as caixas de correio de sinistros fechados da semana toda sexta‑feira como arquivos EML e pacotes ZIP de documentos de apoio. Um worker Java já move essas exportações para o repositório de documentos. Adicionar ingestão significa chamar chunkDocument para cada arquivo e acrescentar a saída JSON Lines à fila de incorporação.

A Etapa 4 é o que mantém um relatório de reserva anexado ao e‑mail que o entregou. A Etapa 3 garante que, quando o assistente cita “Findings”, a citação nomeie o arquivo e o título em vez de apenas um número de fragmento. Minha regra prática para esse tipo de índice: se um fragmento não pode ser citado de volta a um arquivo e a um título, ele não deve estar no índice.

Impacto nos Negócios: O Que Isso Muda

Aspecto Configuração típica antes Com o pipeline GroupDocs.Parser
Onde os arquivos são analisados Endpoint de terceiros Dentro da sua própria JVM
Modelo de custo de análise Por página, a cada reindexação Sem taxa de nuvem por página
Limites dos fragmentos Janelas fixas de tokens Títulos do autor, limite de 1500 caracteres
Anexos de e‑mail Frequentemente ignorados ou achatados Analisados como documentos filhos
Dados de citação Nome do arquivo no máximo source, parent e heading_path
Dependências em tempo de execução SDK + acesso à rede Um artefato Maven

O efeito combinado é um índice que a equipe de conformidade pode aprovar e um assistente cujas respostas apontam para um documento e seção reais.

Outros Cenários de Seguros Onde o GroupDocs.Parser se Encaixa

Uma vez que a camada de ingestão exista, o mesmo código serve a outros trabalhos intensivos em documentos:

  • Busca em texto de apólices: documentos Word de apólices divididos por títulos de cláusulas, de modo que as respostas citem a seção exata.
  • Submissões de corretores: uploads ZIP com PDFs e planilhas misturados indexados item por item, cada um vinculado à submissão.
  • Tratamento de reclamações: e‑mails de clientes e seus anexos indexados juntos, com o e‑mail como pai de cada fragmento de anexo.

Começando no Seu Ambiente de Sinistros

Clone o exemplo, execute‑o contra alguns dos seus próprios e‑mails exportados e abra output/chunks.jsonl para verificar os caminhos de título e os IDs de pai antes de integrá‑lo ao seu trabalho de incorporação. As notas de arquitetura passo a passo estão no caso de uso de documentação para este pipeline, e a referência de saída Markdown lista quais formatações o modo preserva.

Recursos