Saltar para o conteúdo
Todos os artigos
Tutorial12 min de leitura

Para uma base de conhecimento empresarial, comece com RAG e um conjunto de teste, e faça fine-tuning apenas para o formato

Uma tabela de decisão entre RAG e fine-tuning, e um teste de duas semanas que mostra se a recuperação sobre os seus próprios documentos responde suficientemente bem às suas próprias perguntas.

As equipas que querem um assistente sobre os seus próprios documentos tendem a fazer a mesma primeira pergunta: devemos treinar um modelo com os nossos documentos, ou ligar um modelo a eles? Para uma base de conhecimento, a resposta por omissão é ligá-lo, que é o que faz a geração aumentada por recuperação (RAG), e construir um conjunto de teste de perguntas reais antes de escolher qualquer outra coisa. Esta nota explica as duas abordagens em termos simples, apresenta uma tabela de decisão e depois percorre um teste de duas semanas que pode realizar com um engenheiro e uma pessoa que conheça os documentos.

RAG e fine-tuning em termos simples

Um modelo de linguagem sabe o que estava nos seus dados de treino. Não conhece a sua política de devoluções, a tabela de preços do mês passado nem o procedimento interno para aprovar um fornecedor.

O RAG resolve isso no momento da pergunta. Quando alguém faz uma pergunta, o sistema pesquisa nos seus documentos, pega no punhado de passagens que parecem mais relevantes e entrega-as ao modelo com uma instrução: responde a partir destas passagens e indica quais usaste. Os documentos ficam onde estão. Altere uma política, volte a indexá-la, e a resposta seguinte reflete a alteração. A técnica foi descrita num artigo de investigação de 2020 que juntava um gerador de texto a um índice de documentos pesquisável; a ideia central não mudou desde então.

O fine-tuning continua o treino de um modelo com exemplos de entradas e das saídas pretendidas. Muda os hábitos do modelo: tom, estrutura, vocabulário, a forma como trata uma tarefa restrita. É uma forma fraca de ensinar factos que mudam. Cada alteração na fonte exige um novo treino, o modelo não consegue apontar de onde veio uma resposta, e tudo o que aprendeu fica disponível para todos os que o podem usar.

A maioria das empresas já trabalha assim. O relatório da Menlo Ventures de dezembro de 2025 sobre IA generativa nas empresas concluiu que o desenho de prompts é a forma mais comum de as empresas adaptarem modelos, com o RAG em segundo lugar; o fine-tuning e a aprendizagem por reforço continuavam a ser nichos, usados sobretudo por equipas de ponta.

Quanto custa. O teste de duas semanas abaixo custa o tempo de um engenheiro e alguns dias de um especialista no assunto, mais um uso do modelo que é pequeno à escala de um conjunto de teste. Em produção, o custo de operação segue uma fórmula: perguntas por mês × tokens por pergunta × preço por token. A título ilustrativo: 10.000 perguntas por mês, cada uma a enviar cinco passagens de cerca de 400 tokens mais 500 tokens de instruções e pergunta, e a receber uma resposta de 300 tokens, dão 10.000 × 2.800 = 28 milhões de tokens. A um preço combinado ilustrativo de 5 USD por milhão de tokens, são 140 USD por mês. Use os preços atuais do seu fornecedor; o que importa é a fórmula. Se quiser o sistema construído e não apenas testado, o nosso intervalo publicado para um copiloto RAG interno é de 8k–18k USD em 4–8 semanas, e a página de preços mostra a tabela de tarifas por trás dele.

A tabela de decisão

O que precisa RAG Fine-tuning
Conhecimento que muda semanal ou diariamente As alterações aparecem após a próxima reindexação Cada alteração exige um novo treino e uma nova avaliação
Uma ligação para a fonte em cada resposta Incluído: a resposta cita as passagens que usou Não há forma fiável de dizer de onde veio uma resposta
Pessoas diferentes autorizadas a ver documentos diferentes As passagens são filtradas pelas permissões do utilizador antes de o modelo as ver O que for treinado fica visível para todos os utilizadores do modelo
Um formato de saída fixo ou um tom da casa Possível com instruções e exemplos no prompt, por vezes inconsistente Boa adequação
Baixa latência Acrescenta um passo de pesquisa e um prompt mais longo Nenhum passo extra; um modelo afinado mais pequeno também pode ser mais rápido
Custo de atualização Reindexar os documentos alterados Preparar exemplos, voltar a treinar, voltar a avaliar
Dados necessários para começar Os documentos e um conjunto de teste de perguntas reais Exemplos de entradas com saídas ideais verificadas por pessoas, mais o mesmo conjunto de teste

Conte quais as linhas que importam no seu caso. Para uma base de conhecimento, as três primeiras costumam decidir: o conteúdo muda, as pessoas querem verificar a fonte, e nem todos podem ler tudo.

O fine-tuning justifica-se num conjunto mais restrito de casos: as respostas têm de seguir sempre uma estrutura rígida; uma tarefa de grande volume, como encaminhar tickets para categorias, poderia correr num modelo mais pequeno e mais barato; ou o tom tem de seguir um guia de estilo que os prompts não conseguem manter. Mesmo assim, costuma ficar por cima da recuperação em vez de a substituir, como um modelo afinado que continua a ler as passagens recuperadas.

Portanto, o ponto de partida é RAG mais um conjunto de avaliação. Faça fine-tuning mais tarde, para formato, tom ou uma tarefa restrita, e só se o conjunto de teste mostrar que os erros restantes são desse tipo.

O teste de duas semanas

O teste substitui opiniões por um número. No fim tem uma pontuação nas suas próprias perguntas e uma lista de falhas ordenadas por causa, que lhe diz se deve construir, o que corrigir e se o fine-tuning ajudaria de todo.

Passo 1: recolher perguntas reais

Use perguntas que as pessoas fizeram de facto: tickets de helpdesk, conversas no chat interno, registos de pesquisa da intranet, as perguntas a que o responsável pela política responde por e-mail. Aponte para 150 a 300. Inclua 10–15% a que os seus documentos não conseguem responder, porque recusar corretamente faz parte do trabalho.

Porquê tantas: com 200 perguntas e uma exatidão real de cerca de 80%, a margem de erro a 95% é de aproximadamente ±5,5 pontos percentuais (1,96 × √(0,8 × 0,2 / 200) ≈ 0,055). Um conjunto mais pequeno não consegue distinguir duas versões do sistema.

Para cada pergunta, uma pessoa que conheça a matéria escreve a resposta esperada e indica o documento de onde ela deve vir. Uma linha JSON por pergunta:

{"id": "q041", "question": "Who approves a new supplier above the purchasing threshold?", "expected": "Procurement lead and finance, both in writing.", "doc": "supplier-onboarding", "groups": ["procurement"], "answerable": true}

Cada resposta recebe então uma de quatro etiquetas:

  • correct: certa, completa, e cita o documento certo;
  • partly: certa mas incompleta, ou certa com uma citação errada ou em falta;
  • wrong: afirma algo falso, ou responde a uma pergunta a que os documentos não conseguem responder;
  • declined: diz que não consegue responder.

A recusa é uma etiqueta separada de propósito. Numa pergunta com resposta, é uma falha, mas uma falha mais barata do que uma resposta errada, e é o negócio que decide quantas de cada pode aceitar. Acorde esse limiar antes da primeira execução, da mesma forma que uma métrica é acordada antes de se treinar um modelo.

Um especialista do domínio classifica à mão as primeiras 50 respostas. Depois disso, um segundo modelo pode classificar as restantes face às respostas esperadas, desde que as suas etiquetas coincidam com as do especialista nessas 50. As duas práticas são comuns: no inquérito da LangChain a 1.340 profissionais que constroem agentes, realizado em novembro e dezembro de 2025, 59,8% usavam revisão humana, 53,3% usavam um modelo como juiz e 52,4% faziam avaliações offline com conjuntos de teste.

Passo 2: indexar passagens com as suas permissões

Divida os documentos pelos seus títulos em passagens de algumas centenas de palavras. Guarde com cada passagem o id do documento, o título, a ligação e os grupos de acesso. As permissões pertencem à passagem desde o início; acrescentá-las mais tarde significa reindexar tudo.

Passo 3: recuperação híbrida com reciprocal rank fusion

A pesquisa por palavras-chave (BM25) encontra termos exatos, como códigos de produto, números de formulários e siglas. A pesquisa por embeddings encontra paráfrases: «folga» quando a política diz «férias anuais». Cada uma falha o que a outra apanha, por isso use as duas e junte as classificações.

O reciprocal rank fusion (RRF) junta por posição e não por pontuação, pelo que as duas escalas de pontuação nunca precisam de ser calibradas: cada passagem recebe 1 / (k + rank) de cada lista, somados. O artigo de 2009 que o introduziu fixou k em 60 e concluiu que esse valor era quase ótimo, mas não crítico.

import numpy as np
from rank_bm25 import BM25Okapi

def tokenize(text):
    return text.lower().split()

class HybridIndex:
    def __init__(self, chunks, embed):
        # chunks: dicts with "text", "doc", "groups" (a set); embed: list[str] -> unit vectors
        self.chunks, self.embed = chunks, embed
        self.bm25 = BM25Okapi([tokenize(c["text"]) for c in chunks])
        self.vectors = np.asarray(embed([c["text"] for c in chunks]))

    def search(self, query, user_groups, k=5, depth=50, rrf_k=60):
        allowed = [i for i, c in enumerate(self.chunks) if c["groups"] & user_groups]
        lexical = self.bm25.get_scores(tokenize(query))
        semantic = self.vectors @ np.asarray(self.embed([query]))[0]
        fused = {}
        for scores in (lexical, semantic):
            ranked = sorted(allowed, key=lambda i: scores[i], reverse=True)[:depth]
            for rank, i in enumerate(ranked, start=1):
                fused[i] = fused.get(i, 0.0) + 1.0 / (rrf_k + rank)
        top = sorted(fused, key=fused.get, reverse=True)[:k]
        return [{**self.chunks[i], "similarity": float(semantic[i])} for i in top]

embed é qualquer função de embeddings, alojada ou open-source; trocá-la muda um argumento. O filtro de permissões corre antes da classificação, pelo que uma passagem que o utilizador não pode abrir nunca chega ao modelo.

Passo 4: responder com citações, ou recusar

Duas verificações impedem o sistema de inventar. Antes de chamar o modelo: se a melhor passagem não for suficientemente semelhante à pergunta, recusar sem gerar nada. Depois: cada resposta tem de citar passagens, e cada número citado tem de existir. Tudo o resto conta como recusa.

import re

PROMPT = """Answer the question using only the numbered passages below.
After each sentence, cite the passages you used, for example [2].
If the passages do not contain the answer, reply with exactly: NOT_FOUND

{passages}

Question: {question}"""

def answer(question, index, user_groups, llm, min_similarity=0.35):
    hits = index.search(question, user_groups)
    if not hits or max(h["similarity"] for h in hits) < min_similarity:
        return {"status": "declined", "text": None, "docs": []}
    passages = "\n\n".join(f"[{n}] ({h['doc']}) {h['text']}" for n, h in enumerate(hits, 1))
    text = llm(PROMPT.format(passages=passages, question=question)).strip()
    cited = {int(n) for n in re.findall(r"\[(\d+)\]", text)}
    if text == "NOT_FOUND" or not cited or not cited <= set(range(1, len(hits) + 1)):
        return {"status": "declined", "text": None, "docs": []}
    return {"status": "answered", "text": text, "docs": [hits[n - 1]["doc"] for n in sorted(cited)]}

llm é qualquer função que recebe um prompt e devolve texto, por isso o modelo pode ser substituído sem tocar neste código. O limiar de 0,35 é provisório: as escalas de semelhança diferem entre modelos de embeddings. Defina-o no seu conjunto de teste, de modo a que as perguntas sem resposta sejam recusadas sem perder demasiadas perguntas com resposta.

Passo 5: o ciclo de pontuação

import json
from collections import Counter

def run_eval(path, index, llm, judge):
    items = [json.loads(line) for line in open(path)]
    labels, found = Counter(), 0
    for item in items:
        groups = set(item["groups"])
        if item["answerable"]:
            hits = index.search(item["question"], groups)
            found += item["doc"] in {h["doc"] for h in hits}
        result = answer(item["question"], index, groups, llm)
        if not item["answerable"]:
            label = "correct" if result["status"] == "declined" else "wrong"
        elif result["status"] == "declined":
            label = "declined"
        else:  # judge returns "correct", "partly" or "wrong"
            label = judge(item["question"], item["expected"], result["text"])
        labels[label] += 1
    answerable = sum(1 for item in items if item["answerable"])
    print({k: round(v / len(items), 3) for k, v in labels.items()})
    print("expected document in top 5:", round(found / answerable, 3))

O segundo número importa tanto como o primeiro. Se o documento certo não estava entre os cinco primeiros, o modelo nunca teve hipótese, e a correção está na recuperação ou nos documentos, não no modelo. Guarde as etiquetas de cada execução com a data e a alteração feita, para que cada alteração seja comparada com a mesma referência.

Semana 1 e semana 2

Semana 1: a referência.

  • Dias 1–2: recolher e escrever as perguntas com os responsáveis pelos documentos; acordar o limiar de aceitação.
  • Dia 3: indexar os documentos; correr só a pesquisa por palavras-chave, depois a híbrida, e registar a taxa de acerto no top 5 de ambas.
  • Dias 4–5: primeira execução completa; o especialista classifica 50 respostas; verificar o juiz face a elas; classificar as restantes.

O resultado da semana 1 é uma pontuação e uma lista de falhas ordenadas em quatro causas: o documento está em falta ou desatualizado; a recuperação não encontrou a passagem certa; o modelo tinha a passagem certa e mesmo assim respondeu mal; o sistema devia ter recusado e não recusou.

Semana 2: corrigir por ordem de dimensão.

  • Comece pela maior causa. Documentos em falta ou desatualizados são uma tarefa de conteúdo para os seus responsáveis. As falhas de recuperação costumam melhorar com um melhor chunking, títulos de documento e cabeçalhos acrescentados a cada passagem, ou sinónimos para o jargão interno. Más respostas a partir de boas passagens melhoram com alterações ao prompt e, por vezes, com outro modelo.
  • Volte a correr o conjunto completo após cada alteração. Mantenha uma alteração só se a pontuação variar mais do que a margem de erro.
  • Ajuste o limiar de recusa no fim, sobre a configuração final de recuperação.

No fim da semana 2, a decisão costuma ser clara. Se a taxa de respostas erradas estiver abaixo do limiar acordado, construa a versão de produção e mantenha o conjunto de teste como critério de lançamento. Se as falhas restantes forem de formato ou de tom enquanto os factos estão certos, é esse o caso em que vale a pena testar fine-tuning, e o conjunto de teste já existe para mostrar se ajudou. Se a maioria das falhas forem documentos em falta, o problema é a base de conhecimento, e nenhuma escolha de modelo o vai resolver.

É esta a ordem que seguimos quando construímos copilotos RAG: primeiro perguntas reais, depois a recuperação, depois uma pontuação que cada lançamento tem de superar.

Fontes

Tem um processo que custa demasiado?

Fale-nos dele. A primeira chamada é gratuita e, se a IA não for a resposta, dizemo-lo.

Falar sobre a sua tarefa