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:

  1. Estrutura de Controle de Mensagens → onde variáveis e papéis são definidos.
  2. Estrutura de Dados (System, Controls, Documents e Tools) → onde o modelo decide o que usar na resposta.
  3. 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:


🔹 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:

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:

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ê:


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?

  1. System prompt sempre no topo → define o papel do modelo.
  2. Tools opcionais → aparecem só se definidos.
  3. Documents são injetados com identificadores e podem ser citados.
  4. User/assistant messages seguem o fluxo da conversa.
  5. 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

  1. System Prompt
    Sempre vem primeiro: <|start_of_role|>system<|end_of_role|>{{- $system }}<|end_of_text|>
  2. Tools (opcional)
    Se houver ferramentas ativas: <|start_of_role|>available_tools<|end_of_role|>[ ... ]<|end_of_text|>
  3. Documents (opcional)
    Cada documento já processado é injetado: {{ $documents }}
  4. Mensagens do usuário e assistente
    Todas as mensagens trocadas (menos system, control, document) são incluídas aqui.
  5. 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:

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.


Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *