Usar a API e testar a Tuiuiú
Duas coisas nesta página: como chamar a Tuiuiú de dentro do seu próprio programa (sem cadastro, sem chave) e como saber se ela está respondendo certo. Você não precisa saber programar para a segunda parte.
Usar a API, começando
Uma API é um jeito de um programa conversar com outro. A Tuiuiú tem uma: você manda uma pergunta em texto, ela devolve a resposta em texto, palavra por palavra, igual ao que acontece na tela de conversa. Tudo o que o site faz, o seu código também pode fazer.
https://ia-ollama.victorhugocampos.cloud/apiTodos os caminhos abaixo são relativos a ele. Não existe login nem chave, é só chamar.
Só existem duas rotas que importam para começar:
| Rota | Para quê |
|---|---|
GET/health | Saber se a API está no ar e o que está funcionando. |
POST/chat | Mandar uma pergunta e receber a resposta em streaming (palavra por palavra). |
Está no ar?, GET /health
Cole isto no terminal (Mac e Linux já têm o curl; no Windows use o PowerShell):
curl https://ia-ollama.victorhugocampos.cloud/api/health
Resposta:
{"status": "ok", "banco": "ok", "ollama": "ok"}
Se status vier "stub", a API está no modo demonstração (respostas de exemplo, poucos municípios). Se vier "erro", o campo que estiver diferente de "ok" diz o que caiu.
Conversar, POST /chat
Você envia um JSON com a sua pergunta. Os campos:
| Campo | Tipo | Obrigatório | O que é |
|---|---|---|---|
mensagem | texto | sim | A pergunta, do seu jeito. Ex.: "Cuiabá é Cerrado ou Pantanal?" |
historico | lista | não | As últimas mensagens da conversa, para ela lembrar o contexto. Cada item: {"papel": "user" | "assistant", "texto": "..."}. Mande no máximo 6. |
modo | texto | não | "conversa" (padrão) ou "quiz". |
A resposta não é um JSON único, é um fluxo de eventos (Server-Sent Events, ou SSE). Em vez de esperar a frase inteira, você recebe pedacinhos conforme ela escreve. É o que faz o chat parecer vivo.
curl -N https://ia-ollama.victorhugocampos.cloud/api/chat \
-H "Content-Type: application/json" \
-d '{"mensagem": "Cuiabá é Cerrado ou Pantanal?"}'
O -N manda o curl mostrar cada pedaço assim que chega. Você verá algo assim:
event: entidade
data: {"id": 5103403, "nome": "Cuiabá", "tipo": "municipio"}
event: token
data: {"texto": "Essa "}
event: token
data: {"texto": "é boa, "}
... (um evento por pedaço de texto) ...
event: citacao
data: {"fonte": "IBGE Biomas 2019", "url": null}
event: fim
data: {}
Os eventos, um a um
| Evento | Dados | O que fazer com ele |
|---|---|---|
token | {"texto": "..."} | Um pedaço da resposta. Vá concatenando na ordem em que chegam. |
entidade | {"id", "nome", "tipo"} | Um lugar que a resposta cita. O id é o código IBGE, no site, é o que acende o mapa. |
citacao | {"fonte", "url"} | De onde veio o dado. Mostre ao usuário, é a garantia de que não foi inventado. |
fim | {} | Acabou. Pode fechar a conexão. |
erro | {"mensagem": "..."} | Algo deu errado no meio. Mostre a mensagem e ofereça tentar de novo. |
: são batimentos (heartbeat) que a API manda a cada ~15 s enquanto pensa, para a conexão não cair. Ignore.Exemplos de código
JavaScript (navegador ou Node 18+)
EventSource só faz GET, e a Tuiuiú precisa de POST, então usamos fetch e lemos o fluxo na mão. É menos assustador do que parece:
async function perguntar(mensagem, aoReceber) {
const resp = await fetch("https://ia-ollama.victorhugocampos.cloud/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ mensagem }),
});
const leitor = resp.body.getReader(), dec = new TextDecoder();
let buf = "";
while (true) {
const { value, done } = await leitor.read();
if (done) break;
buf += dec.decode(value, { stream: true });
let i;
while ((i = buf.indexOf("\n\n")) >= 0) { // cada evento termina com linha em branco
const bloco = buf.slice(0, i); buf = buf.slice(i + 2);
const ev = (bloco.match(/^event: (.+)$/m) || [])[1] || "message";
const data = (bloco.match(/^data: (.+)$/m) || [])[1];
if (data) aoReceber(ev, JSON.parse(data));
}
}
}
// uso:
let texto = "";
perguntar("Qual a capital do Acre?", (ev, d) => {
if (ev === "token") { texto += d.texto; console.log(texto); }
if (ev === "citacao") console.log("fonte:", d.fonte);
});
Python (3.10+, com httpx)
# pip install httpx
import httpx, json
def perguntar(mensagem: str):
with httpx.stream("POST", "https://ia-ollama.victorhugocampos.cloud/api/chat",
json={"mensagem": mensagem}, timeout=120) as r:
ev = "message"
for linha in r.iter_lines():
if linha.startswith("event:"): ev = linha[6:].strip()
elif linha.startswith("data:"): yield ev, json.loads(linha[5:])
texto = ""
for ev, d in perguntar("Quais estados fazem divisa com Goiás?"):
if ev == "token": texto += d["texto"]; print(d["texto"], end="", flush=True)
if ev == "citacao": print(f"\n[fonte: {d['fonte']}]")
Mantendo o contexto
Para ela lembrar do que vocês falaram, mande as últimas mensagens em historico:
{
"mensagem": "E quantas pessoas moram lá?",
"historico": [
{ "papel": "user", "texto": "Cuiabá é Cerrado ou Pantanal?" },
{ "papel": "assistant", "texto": "Essa é boa. Cuiabá fica na divisa: ..." }
]
}
Limites e erros
| Código | Significa | O que fazer |
|---|---|---|
200 | Deu certo; o fluxo de eventos vem no corpo. | Ler até o fim. |
422 | Seu JSON está errado (falta mensagem, tipo trocado). | Confira os campos da tabela acima. |
429 | Muitas perguntas em pouco tempo. O limite em /chat é 10 por minuto por endereço IP, com rajada de 5. | Espere o tempo do cabeçalho x-retry-in e tente de novo. Só /chat tem limite; /health e a página não. |
502/503 | A API está fora ou reiniciando. | Confira /health; tente em alguns segundos. |
: heartbeat chegando, é normal.Em breve
Rotas que já estão desenhadas e entram nas próximas versões, os nomes não vão mudar:
GET/geo/municipio/{id} | Contorno do município em GeoJSON, para desenhar no mapa. |
POST/quiz/pergunta | Sorteia uma pergunta de múltipla escolha com dificuldade calibrada. |
POST/quiz/resposta | Corrige, dá dica antes da resposta e explica. |
Testar a IA, o que é “certo”
A Tuiuiú tem uma promessa incomum para uma IA: ela não pode inventar fato. Testar a Tuiuiú é, principalmente, tentar fazê-la quebrar essa promessa. Uma resposta boa tem estas quatro marcas:
- O fato está certo, e você consegue conferir, porque a fonte aparece.
- Nenhum número saiu do nada. Se ela citou população, área ou percentual, isso estava no dado consultado.
- Ela soa como gente, não como verbete: frases curtas, um gancho, uma pergunta de volta.
- Quando não sabe, ela diz, e oferece o que tem, em vez de preencher o buraco.
Uma resposta ruim costuma ser convincente. É por isso que o teste precisa de gabarito, não de impressão.
Roteiro de 6 testes (10 minutos)
Abra a conversa e faça estas perguntas na ordem. Ao lado, o que deve acontecer.
| # | Pergunte | Esperado | Está testando |
|---|---|---|---|
| 1 | Qual a capital do Acre? | “Rio Branco”, com fonte IBGE. Resposta quase instantânea. | Fato simples (Camada 0) |
| 2 | Qual a população de Salvador? | Um número com “Censo 2022” na fonte. Confira no IBGE, tem que bater. | Número real, não inventado |
| 3 | Quais estados fazem divisa com Goiás? | MG, BA, TO, MT, MS e DF, os seis, nem mais nem menos. | Consulta espacial |
| 4 | Cuiabá é Cerrado ou Pantanal? | Os dois, com percentual aproximado. Se ela responder só um, é erro. | Município em mais de um bioma |
| 5 | Me fala de Bom Jesus | Ela pergunta qual, existem vários (PI, RS, RN, PB, SC). Não pode chutar um. | Ambiguidade |
| 6 | Qual a população de Lisboa? | Recusa com bom humor: a especialidade dela é o Brasil. Nenhum número. | Fora de escopo |
Como pegar uma alucinação
“Alucinação” é quando a IA afirma algo falso com total confiança. Os três sinais mais comuns aqui:
- Número sem fonte. Toda resposta com número traz um 📎 com a origem. Número sem 📎 é suspeito.
- Especificidade demais. “Exatamente 2.347 espécies” para uma pergunta aberta sobre um bioma. Dado real de bioma costuma ser aproximado e vir da Wikipédia, e a fonte diz isso.
- Bicho errado no lugar errado. Crocodilo no Pantanal (são jacarés), pinguim no Nordeste. Modelos pequenos misturam. A arquitetura existe para impedir; se passou, é bug e queremos saber.
Reportar um erro
Achou uma resposta errada? Isso vale ouro. Abra uma issue no GitHub com:
- A pergunta exata que você fez (copie e cole).
- A resposta que veio, incluindo a fonte que apareceu (ou a ausência dela).
- O que você esperava, e onde conferiu (link do IBGE, da Wikipédia…).
Não precisa saber programar. Uma pergunta bem descrita vira um teste automático que impede o erro de voltar.
Para quem programa: testar de dentro
Se você clonou o repositório:
make chat # conversa pelo terminal, sem navegador
make eval # roda as 50 perguntas com gabarito e mede acerto, alucinação e recusa
make test # testes unitários
O make eval é o coração: 50 perguntas com resposta esperada, nas mesmas 6 categorias do roteiro acima. Ele imprime três números, acerto factual (meta ≥ 90%), alucinação numérica (meta 0%) e recusa correta (meta 100%), e roda em todo pull request. Um PR que faz a Tuiuiú voltar a inventar fica vermelho antes de chegar em produção. Detalhes em docs/05-AVALIACAO.md.