Introdução aos Templates do Ollama
O Ollama é uma plataforma que facilita o uso e a personalização de Modelos de Linguagem de Grande Porte (LLMs) em máquinas locais. Diferente de apenas executar um modelo pré-treinado, o Ollama permite definir templates que controlam como as mensagens (inputs do usuário, system prompt, documentos e controles adicionais) são processadas antes de serem enviadas ao modelo.
Um template é, essencialmente, um arquivo de instruções em Go template que dita como o texto final de entrada é construído. Isso inclui desde mensagens do sistema até documentos e instruções especiais, como obrigar o modelo a citar fontes ou indicar quando algo é uma alucinação.
No caso do modelo Granite 3.3 8b da IBM, o template mostrado é um bom exemplo de como estruturar e controlar a geração de saída. Ele organiza as mensagens recebidas, separa documentos, aplica controles de estilo e define como a resposta final deve ser construída.
Para entender isso de forma didática, vamos dividir a explicação em três grandes blocos que aparecem no template:
- Estrutura de Controle de Mensagens → onde variáveis e papéis são definidos.
- Estrutura de Dados (System, Controls, Documents e Tools) → onde o modelo decide o que usar na resposta.
- Expansão Final do Template → onde o prompt é montado e enviado ao modelo.
Nesta primeira seção, o objetivo é entender por que os templates existem: eles permitem que você mantenha consistência na forma como o modelo responde, adicionando camadas de lógica sobre os prompts, algo que vai além de simplesmente enviar mensagens cruas.
Estrutura de Controle de Mensagens (com exemplos práticos)
Como vimos, o Ollama usa Go Templates para montar o prompt que será enviado ao modelo. Isso significa que temos variáveis, condições, e loops que funcionam de maneira muito semelhante a linguagens de programação.
A ideia é que, ao receber mensagens (sejam do usuário, do sistema, ou de documentos), o template saiba onde armazenar cada informação.
🔹 Declaração de Variáveis
Logo no início do template, vemos algo assim:
{{- $system := "" }}
{{- $documents := "" }}
{{- $documentCounter := 0 }}
{{- $thinking := false }}
{{- $citations := false }}
{{- $hallucinations := false }}
{{- $length := "" }}
{{- $originality := "" }}
Aqui criamos variáveis que vão ser preenchidas depois.
Por exemplo:
$system
começa vazio, mas se houver mensagens de role: system, ele será atualizado.$citations
éfalse
até que o usuário ou aplicação mande uma instruçãocontrol citations
.
🔹 Exemplo 1: Capturando mensagens do sistema
Se o usuário manda algo assim:
{ "role": "system", "content": "Você é um tutor de programação em C." }
O trecho do template responsável faz isso:
{{- if (eq .Role "system")}}
{{- if (ne $system "") }}
{{- $system = print $system "\n\n" }}
{{- end}}
{{- $system = print $system .Content }}
{{- end}}
Resultado:
- Antes:
$system = ""
- Depois:
$system = "Você é um tutor de programação em C."
Se o sistema mandar várias mensagens, o template as concatena com \n\n
.
🔹 Exemplo 2: Ativando controles
Se você quiser que o modelo mostre seu raciocínio antes da resposta final, pode mandar:
{ "role": "control", "content": "thinking" }
O template captura isso assim:
{{- if (eq .Content "thinking")}}{{- $thinking = true }}{{- end}}
Agora a variável $thinking
vale true
, e mais tarde, no prompt final, isso vai adicionar instruções como:
Respond to every user query in a comprehensive and detailed way.
You can write down your thoughts between <think></think>...
🔹 Exemplo 3: Adicionando documentos
Se você quiser passar um texto de apoio ao modelo, envia:
{ "role": "document", "content": "A linguagem C foi criada em 1972 por Dennis Ritchie." }
O trecho do template que trata documentos faz isto:
{{- if (and (ge (len .Role) 8) (eq (slice .Role 0 8) "document")) }}
{{- if (ne $documentCounter 0)}}
{{- $documents = print $documents "\n\n"}}
{{- end}}
{{- $identifier := ""}}
{{- if (ge (len .Role) 9) }}
{{- $identifier = (slice .Role 9)}}
{{- end}}
{{- if (eq $identifier "") }}
{{- $identifier := print $documentCounter}}
{{- end}}
{{- $documents = print $documents "<|start_of_role|>document {\"document_id\": \"" $identifier "\"}<|end_of_role|>\n" .Content "<|end_of_text|>"}}
{{- $documentCounter = len (printf "a%*s" $documentCounter "")}}
{{- end}}
Resultado final armazenado em $documents
:
<|start_of_role|>document {"document_id": "0"}<|end_of_role|>
A linguagem C foi criada em 1972 por Dennis Ritchie.<|end_of_text|>
🔹 Exemplo 4: Controle de tamanho e originalidade
Você pode instruir o modelo a responder de forma curta, média ou longa, e também a variar a originalidade.
Exemplo de mensagem:
{ "role": "control", "content": "length short" }
{ "role": "control", "content": "originality high" }
O template extrai isso:
{{- if (and (ge (len .Content) 7) (eq (slice .Content 0 7) "length "))}}
{{- $length = slice .Content 7 }}
{{- end}}
{{- if (and (ge (len .Content) 12) (eq (slice .Content 0 12) "originality "))}}
{{- $originality = slice .Content 12 }}
{{- end}}
Agora temos:
$length = "short"
$originality = "high"
Isso será injetado na resposta final assim:
<|start_of_role|>assistant {"length": "short", "originality": "high"}<|end_of_role|>
Ou seja, você consegue modular a saída do modelo sem alterar o prompt manualmente.
🔹 Por que isso é útil?
Esses controles permitem que você:
- Automatize a forma como o modelo responde (sempre citar fontes, nunca inventar, etc.).
- Padronize saídas em sistemas que dependem de consistência (por exemplo: geração de relatórios técnicos).
- Ajuste dinamicamente o estilo do modelo sem precisar reescrever o template.
Estrutura de Dados (System, Controls, Documents e Tools)
Depois de percorrer todas as mensagens, o template verifica se o usuário forneceu um system prompt.
Se não houver, ele cria um padrão:
{{- if eq $system "" }}
{{- $system = "Knowledge Cutoff Date: April 2024.\nYou are Granite, developed by IBM."}}
{{- end}}
Isso significa que, se você não mandar nada, o modelo já vem com uma instrução básica de contexto.
🔹 Montando o System Prompt
O system prompt é sempre colocado no início do prompt final:
<|start_of_role|>system<|end_of_role|>{{- $system }}<|end_of_text|>
Se você mandou um system assim:
{ "role": "system", "content": "Você é um professor de eletrônica que explica com exemplos práticos." }
O que o modelo vai receber é:
<|start_of_role|>system<|end_of_role|>
Você é um professor de eletrônica que explica com exemplos práticos.<|end_of_text|>
🔹 Inserindo Ferramentas (Tools)
Se o modelo tiver acesso a ferramentas externas (exemplo: uma API de busca ou um banco de dados), o Ollama adiciona a lista assim:
<|start_of_role|>available_tools<|end_of_role|>[
{"name": "calculator"},
{"name": "web_search"}
]<|end_of_text|>
Isso é controlado por .Tools
.
Se não houver ferramentas definidas, esse bloco simplesmente não aparece.
🔹 Inserindo Documentos
Se $documents
tiver conteúdo, ele é injetado logo depois do system e tools:
{{- if $documents }}
{{ $documents }}
{{- end}}
Exemplo: se você enviou dois documentos:
{ "role": "document", "content": "Ohm publicou sua lei em 1827." }
{ "role": "document", "content": "James Clerk Maxwell formulou suas equações em 1865." }
No prompt final, o modelo recebe:
<|start_of_role|>document {"document_id": "0"}<|end_of_role|>
Ohm publicou sua lei em 1827.<|end_of_text|>
<|start_of_role|>document {"document_id": "1"}<|end_of_role|>
James Clerk Maxwell formulou suas equações em 1865.<|end_of_text|>
🔹 Mensagens Padrão (Usuário e Assistente)
Por fim, o template adiciona todas as mensagens de usuário e assistente, menos os controles e documentos.
Exemplo:
{ "role": "user", "content": "Explique a Lei de Ohm." }
Vira:
<|start_of_role|>user<|end_of_role|>
Explique a Lei de Ohm.<|end_of_text|>
E a resposta do assistente vem marcada assim:
<|start_of_role|>assistant<|end_of_role|>
...
<|end_of_text|>
🔹 Exemplo completo de prompt montado
Se você mandou essas mensagens:
{ "role": "system", "content": "Você é um tutor de eletrônica." }
{ "role": "control", "content": "thinking" }
{ "role": "document", "content": "A Lei de Ohm relaciona tensão, corrente e resistência." }
{ "role": "user", "content": "Explique a Lei de Ohm com exemplo." }
O template gera algo assim:
<|start_of_role|>system<|end_of_role|>
Você é um tutor de eletrônica.<|end_of_text|>
<|start_of_role|>document {"document_id": "0"}<|end_of_role|>
A Lei de Ohm relaciona tensão, corrente e resistência.<|end_of_text|>
<|start_of_role|>user<|end_of_role|>
Explique a Lei de Ohm com exemplo.<|end_of_text|>
<|start_of_role|>assistant {"length": "", "originality": ""}<|end_of_role|>
E como você ativou thinking, a resposta virá estruturada:
<think>
O modelo começa a organizar o raciocínio: Lei de Ohm, fórmula V=R*I, exemplo prático.
</think>
<response>
A Lei de Ohm diz que a tensão (V) em um resistor é igual à resistência (R) multiplicada pela corrente (I).
Por exemplo, se um resistor de 10 Ω recebe uma corrente de 2 A, a tensão será V = 10 × 2 = 20 V.
</response>
🔹 O que aprendemos aqui?
- System prompt sempre no topo → define o papel do modelo.
- Tools opcionais → aparecem só se definidos.
- Documents são injetados com identificadores e podem ser citados.
- User/assistant messages seguem o fluxo da conversa.
- Controls não aparecem no prompt final, mas mudam o comportamento da saída.
Expansão Final do Template
Depois que todas as mensagens e controles foram processados, o template precisa formatar a conversa inteira em um formato que o modelo entenda. Isso é chamado de expansão final.
No Granite 3.3 8b, essa expansão segue uma ordem bem definida:
🔹 Ordem da Expansão
- System Prompt
Sempre vem primeiro:<|start_of_role|>system<|end_of_role|>{{- $system }}<|end_of_text|>
- Tools (opcional)
Se houver ferramentas ativas:<|start_of_role|>available_tools<|end_of_role|>[ ... ]<|end_of_text|>
- Documents (opcional)
Cada documento já processado é injetado:{{ $documents }}
- Mensagens do usuário e assistente
Todas as mensagens trocadas (menossystem
,control
,document
) são incluídas aqui. - Resposta do assistente
Sempre é aberta no final, já com parâmetros opcionais:<|start_of_role|>assistant {{- if and (ne $length "") (ne $originality "") }} {"length": "{{ $length }}", "originality": "{{ $originality }}"} {{- else if ne $length "" }} {"length": "{{ $length }}"} {{- else if ne $originality "" }} {"originality": "{{ $originality }}"} {{- end }}<|end_of_role|>
🔹 Exemplo 1: Resposta curta e factual
Mensagens enviadas:
{ "role": "system", "content": "Você é um professor de matemática." }
{ "role": "control", "content": "length short" }
{ "role": "user", "content": "Explique a regra de três simples." }
Prompt final gerado:
<|start_of_role|>system<|end_of_role|>
Você é um professor de matemática.<|end_of_text|>
<|start_of_role|>user<|end_of_role|>
Explique a regra de três simples.<|end_of_text|>
<|start_of_role|>assistant {"length": "short"}<|end_of_role|>
Resposta do modelo (curta, objetiva):
A regra de três simples relaciona duas razões. Exemplo: se 2 maçãs custam 4 reais, 5 maçãs custarão 10 reais.
🔹 Exemplo 2: Resposta com citações
Mensagens enviadas:
{ "role": "system", "content": "Você é um historiador." }
{ "role": "control", "content": "citations" }
{ "role": "document", "content": "A Revolução Francesa começou em 1789." }
{ "role": "user", "content": "Quando começou a Revolução Francesa?" }
Prompt final:
<|start_of_role|>system<|end_of_role|>
Você é um historiador. Write the response to the user's input by strictly aligning with the facts in the provided documents.
Use the symbols <|start_of_cite|> and <|end_of_cite|> to indicate when a fact comes from a document.<|end_of_text|>
<|start_of_role|>document {"document_id": "0"}<|end_of_role|>
A Revolução Francesa começou em 1789.<|end_of_text|>
<|start_of_role|>user<|end_of_role|>
Quando começou a Revolução Francesa?<|end_of_text|>
<|start_of_role|>assistant<|end_of_role|>
Resposta do modelo:
A Revolução Francesa começou em <|start_of_cite|>{"document_id": "0"}1789<|end_of_cite|>.
🔹 Exemplo 3: Resposta com detecção de alucinações
Mensagens enviadas:
{ "role": "system", "content": "Você é um cientista que só pode responder com base nos documentos." }
{ "role": "control", "content": "hallucinations" }
{ "role": "document", "content": "A água ferve a 100 °C ao nível do mar." }
{ "role": "user", "content": "A água ferve a 150 °C?" }
Prompt final:
<|start_of_role|>system<|end_of_role|>
Você é um cientista que só pode responder com base nos documentos.
Finally, after the response is written, include a numbered list of sentences from the response with a corresponding risk value that are hallucinated.<|end_of_text|>
<|start_of_role|>document {"document_id": "0"}<|end_of_role|>
A água ferve a 100 °C ao nível do mar.<|end_of_text|>
<|start_of_role|>user<|end_of_role|>
A água ferve a 150 °C?<|end_of_text|>
<|start_of_role|>assistant<|end_of_role|>
Resposta do modelo:
Não, a água ferve a 100 °C ao nível do mar, conforme o documento.
[Hallucinations]
1. "A água ferve a 150 °C." → risco: 90%
🔹 Por que isso é poderoso?
Essa expansão final garante que:
- O system prompt sempre vem no início, mantendo consistência.
- O modelo pode ser forçado a citar documentos.
- Você pode identificar alucinações automaticamente.
- Dá para ajustar a extensão e originalidade da resposta sem mudar o template.
Ou seja: um único template bem escrito permite criar modelos seguros, consistentes e flexíveis, sem necessidade de ficar reescrevendo prompts manualmente a cada uso.