---
title: "RAG com Kotlin e Spring AI em 2026 | Kotlin Brasil"
url: "https://kotlin.dev.br/blog/kotlin-spring-ai-rag-2026/"
markdown_url: "https://kotlin.dev.br/blog/kotlin-spring-ai-rag-2026.MD"
description: "Aprenda como estruturar RAG com Kotlin e Spring AI em aplicações backend, com embeddings, vector store, prompts, testes e cuidados de produção."
date: "2026-05-21"
author: "Karina Melo"
---

# RAG com Kotlin e Spring AI em 2026 | Kotlin Brasil

Aprenda como estruturar RAG com Kotlin e Spring AI em aplicações backend, com embeddings, vector store, prompts, testes e cuidados de produção.


RAG, ou **Retrieval-Augmented Generation**, virou uma das formas mais pragmáticas de levar IA generativa para aplicações reais sem depender apenas da memória estatística de um modelo. Em vez de pedir para o LLM “saber tudo”, a aplicação busca documentos relevantes em uma base controlada, monta um contexto e só então chama o modelo para responder. Para times que já usam Kotlin no backend, o Spring AI torna esse fluxo mais familiar, porque encaixa embeddings, vector stores, prompts e clientes de modelo dentro do ecossistema Spring Boot.

Esse assunto interessa especialmente a quem trabalha com produto interno, suporte técnico, base de conhecimento, documentação, atendimento, busca semântica ou automação corporativa. Kotlin entra como uma ótima escolha quando o sistema de IA precisa ficar perto de regras de domínio, APIs existentes, autenticação, filas, bancos relacionais e observabilidade. Se você ainda está escolhendo a base do backend, leia também nosso guia de [Kotlin para backend](/guias/kotlin-para-backend/), o artigo sobre [Kotlin com Spring Boot](/blog/kotlin-spring-boot/) e a introdução a [Kotlin e IA/Machine Learning](/blog/kotlin-ia-machine-learning/).

## O que RAG resolve na prática

Um chatbot simples, sem recuperação de contexto, depende do conhecimento geral do modelo e do texto enviado diretamente no prompt. Isso funciona para perguntas genéricas, mas falha quando a resposta precisa refletir políticas internas, documentação privada, contratos, changelogs, manuais, catálogo de produtos ou estado atualizado do negócio.

RAG adiciona uma etapa intermediária. Quando o usuário pergunta algo, a aplicação transforma a pergunta em embedding, busca trechos semanticamente próximos em um vector store, seleciona os melhores resultados e injeta esses trechos no prompt. O modelo continua gerando linguagem natural, mas agora responde com base em material que a aplicação escolheu.

Esse padrão reduz alucinações, facilita atualização de conhecimento sem retreinar modelo e melhora rastreabilidade. Você consegue guardar quais documentos foram usados, mostrar fontes na resposta e auditar por que o sistema chegou a determinada conclusão. Não é mágica: se os documentos são ruins, desatualizados ou mal segmentados, a resposta também sofre. Mas o controle operacional é muito maior do que em um prompt solto.

## Arquitetura básica com Kotlin e Spring AI

Uma aplicação RAG em Kotlin costuma ter cinco peças:

1. **Pipeline de ingestão**: lê documentos, quebra em pedaços menores e gera embeddings.
2. **Vector store**: armazena embeddings e metadados para busca semântica.
3. **Serviço de pergunta**: recebe a consulta do usuário e busca trechos relevantes.
4. **Prompt template**: combina pergunta, contexto recuperado e instruções de resposta.
5. **Camada de resposta**: devolve texto, fontes, confiança operacional e logs.

Em Spring AI, essas responsabilidades aparecem como beans e serviços comuns de Spring Boot. O ponto importante é não colocar tudo dentro do controller. O controller deve validar entrada e chamar um serviço; o serviço coordena busca, prompt e modelo; a infraestrutura fica isolada atrás de interfaces. Essa separação conversa bem com [Clean Architecture em Kotlin](/guias/guia-clean-architecture-kotlin/) e facilita testes.

Um serviço simplificado pode ter esta cara:

```kotlin
@Service
class PerguntasRagService(
    private val chatClient: ChatClient,
    private val vectorStore: VectorStore,
) {
    fun responder(pergunta: String): RespostaRag {
        val documentos = vectorStore.similaritySearch(
            SearchRequest.query(pergunta).withTopK(5)
        )

        val contexto = documentos.joinToString("\n\n") { documento ->
            "Fonte: ${documento.metadata["fonte"]}\n${documento.content}"
        }

        val resposta = chatClient.prompt()
            .system("Responda em português brasileiro, usando apenas o contexto fornecido.")
            .user("""
                Contexto:
                $contexto

                Pergunta:
                $pergunta
            """.trimIndent())
            .call()
            .content()

        return RespostaRag(
            texto = resposta.orEmpty(),
            fontes = documentos.mapNotNull { it.metadata["fonte"]?.toString() }
        )
    }
}
```

O exemplo é curto de propósito. Em produção, você vai querer tratamento de erro, timeout, limites de tamanho, logging estruturado, cache quando fizer sentido e validação de permissões por documento. Ainda assim, a ideia central já aparece: o modelo não responde sozinho; ele responde com base no que a busca recuperou.

## Ingestão: onde muitos projetos erram

A parte mais subestimada de RAG não é chamar o LLM. É preparar os documentos. Um pipeline ruim cria chunks grandes demais, pequenos demais, sem metadados, sem versão e sem relação clara com a fonte original. Depois, quando a resposta sai incompleta, o time tenta “melhorar o prompt”, mas o problema estava na recuperação.

Para Kotlin com Spring AI, pense na ingestão como um processo explícito:

```kotlin
@Component
class IngestaoDocumentos(
    private val vectorStore: VectorStore,
    private val embeddingModel: EmbeddingModel,
) {
    fun indexar(documentos: List<DocumentoFonte>) {
        val chunks = documentos.flatMap { documento ->
            documento.texto.chunked(1_500).mapIndexed { indice, trecho ->
                Document(
                    trecho,
                    mapOf(
                        "fonte" to documento.url,
                        "titulo" to documento.titulo,
                        "chunk" to indice,
                        "versao" to documento.versao,
                    )
                )
            }
        }

        vectorStore.add(chunks)
    }
}
```

Na prática, `chunked(1_500)` é simplista. Um pipeline melhor respeita parágrafos, títulos, seções, tabelas e blocos de código. Documentação técnica, por exemplo, não deveria cortar uma função no meio. Políticas internas não deveriam separar exceção e regra principal em chunks desconectados. Se o conteúdo tem hierarquia, preserve essa hierarquia nos metadados.

Também vale manter versionamento. Quando um documento muda, você precisa saber se o vector store contém a versão nova ou uma versão antiga. Para bases pequenas, reindexar tudo pode ser aceitável. Para bases maiores, um processo incremental por hash de conteúdo evita custo desnecessário.

## Prompt: seja explícito sobre limites

Um bom prompt de RAG não precisa ser poético. Ele precisa ser operacional. Diga ao modelo para responder em português brasileiro, usar apenas o contexto, admitir quando não houver informação suficiente e listar fontes quando possível. Isso reduz respostas inventadas e cria uma experiência mais confiável para o usuário.

Exemplo de instrução de sistema:

```text
Você é um assistente técnico. Responda em português brasileiro.
Use apenas o contexto fornecido pela aplicação.
Se o contexto não contiver a resposta, diga que não há informação suficiente.
Não invente números, políticas, links ou nomes de responsáveis.
Quando usar uma fonte, mencione o título ou URL fornecido nos metadados.
```

Essa instrução não elimina alucinação por completo, mas estabelece um contrato. O contrato também deve ser reforçado na aplicação: limite temperatura quando o caso exigir precisão, recuse perguntas fora de escopo e registre quais fontes entraram no prompt. Se o sistema responde sobre documentos internos, autorização é parte do RAG. Não basta recuperar o documento semanticamente correto; o usuário precisa ter permissão para vê-lo.

## Testes para RAG em Kotlin

Testar RAG exige uma mentalidade diferente de testes unitários tradicionais. Você não deve comparar a resposta inteira caractere por caractere, porque modelos podem variar. Em vez disso, teste contratos observáveis.

Alguns testes úteis:

- quando a pergunta menciona um tema existente, a busca recupera documentos esperados;
- quando não há contexto suficiente, o serviço não inventa resposta;
- fontes usadas na resposta aparecem no objeto retornado;
- documentos sem permissão não entram no contexto;
- prompts respeitam limite máximo de tokens ou caracteres;
- erros do provedor de IA viram respostas controladas, não stack traces para o usuário.

Você pode combinar testes unitários com mocks para o `VectorStore` e o `ChatClient`, além de testes de integração com uma base pequena e determinística. O artigo sobre [JUnit 5 e MockK em Kotlin](/blog/kotlin-testes-junit5-mockk-guia/) complementa bem essa etapa, e o guia de [CI/CD para Kotlin](/guias/guia-kotlin-ci-cd/) mostra como levar essas verificações para o pipeline.

## Observabilidade e custo

RAG coloca uma dependência cara e variável no caminho da requisição. Cada pergunta pode consumir embeddings, busca vetorial, tokens de prompt, tokens de resposta e chamadas externas. Por isso, observe desde o início: latência da busca, tamanho do contexto, documentos recuperados, provedor chamado, custo estimado, taxa de erro e perguntas sem resposta.

Se o time já usa OpenTelemetry, trace a operação inteira: controller, busca no vector store, chamada ao modelo e pós-processamento. Em aplicações com agentes ou fluxos mais complexos, vale ler também o artigo sobre [Tracy para observabilidade de IA em Kotlin](/blog/tracy-observabilidade-ia-kotlin-2026/). A regra é simples: se você não consegue explicar por que a resposta ficou lenta, cara ou errada, ainda não tem uma operação madura.

## Quando Spring AI faz sentido

Spring AI é uma boa escolha quando sua aplicação já está no mundo Spring Boot ou quando o time quer padronizar integração com modelos, embeddings e vector stores sem criar uma camada própria do zero. Ele combina especialmente com backend Kotlin, APIs REST, autenticação via Spring Security, jobs de ingestão, mensageria e bancos já existentes.

Se o projeto é um protótipo rápido de ciência de dados, Python ainda pode ser mais direto. O ecossistema <a href="https://python.dev.br/" target="_blank" rel="noopener noreferrer" onclick="umami.track('portfolio-site-click', { destination: 'python.dev.br' })">Python</a> domina notebooks, avaliação de modelos e experimentação com IA. Mas quando a IA precisa virar produto backend integrado a sistemas JVM, Kotlin ganha força. Para serviços pequenos e binários simples, <a href="https://golang.com.br/" target="_blank" rel="noopener noreferrer" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go</a> também é uma alternativa forte; para componentes de performance crítica, <a href="https://rustlang.com.br/" target="_blank" rel="noopener noreferrer" onclick="umami.track('portfolio-site-click', { destination: 'rustlang.com.br' })">Rust</a> pode complementar a arquitetura.

## Checklist para colocar em produção

Antes de publicar um recurso RAG para usuários reais, revise este checklist:

- documentos têm fonte, versão, data e permissões;
- chunks preservam sentido e não cortam trechos críticos;
- busca vetorial foi avaliada com perguntas reais;
- prompt manda admitir falta de contexto;
- resposta retorna fontes quando possível;
- logs não vazam dados sensíveis;
- custos e latência estão monitorados;
- testes cobrem recuperação, autorização e fallback;
- existe processo para reindexar conteúdo atualizado.

RAG com Kotlin e Spring AI não é apenas “colocar IA no backend”. É um projeto de engenharia de informação. O valor aparece quando documentos bons, busca semântica, prompts objetivos, testes e observabilidade trabalham juntos. Para times Kotlin, essa combinação permite entregar IA generativa com mais controle, mantendo a aplicação próxima do ecossistema JVM que já sustenta APIs, jobs, regras de negócio e integrações críticas.
