Observabilidade (automática)
A jangada envia as suas chamadas de LLM para a plataforma de observabilidade de
forma automática (zero-config): basta configurar o .env. Cada chamada vira
uma observation com provider, modelo, tokens, custo, latência, tool calls e as
capacidades de IA usadas — enviada em background, sem instrumentar o código.
Ativar (zero-config)
# .env
JANGADA_OBSERVABILITY=true
JANGADA_OBSERVABILITY_API_KEY=lobs_xxx # chave do projeto (dashboard)
# opcional (padrão é a plataforma oficial):
# JANGADA_OBSERVABILITY_ENDPOINT=https://api.jangada.dev.brfrom jangada_ai import LLM
llm = LLM("openai", "gpt-4o-mini")
resp = llm.complete("Resuma: ...") # já enviado à plataforma, sozinhoEmbeddings também são instrumentados automaticamente desde a v1.4.1:
embedder = LLM("openai", "text-embedding-3-small")
with observability_session(name="rag.documents.ingest"):
vectors = embedder.embed(["primeiro chunk", "segundo chunk"])A observation registra latência, tokens de entrada, custo estimado, quantidade e
dimensão dos vetores e a capability embeddings.
No Gemini, a contagem segue usage_metadata → count_tokens() com o mesmo
modelo/lote → estimativa local como último fallback. O preço vem do catálogo
jangada.dev.br/prices.json, nunca de um valor hardcoded no adapter. Os campos
usageSource e usageEstimated distinguem contagem real de estimativa.
A flag precisa ser "truthy" (1/true/yes/on/sim) e o token presente;
faltando qualquer um, o modo fica desligado e nada é enviado (custo zero). Falhas
de rede nunca derrubam a aplicação — o envio é best-effort numa thread daemon.
Encerramento (não perca o último trace)
Como o envio roda numa thread daemon, um script que termina logo após a última
chamada correria o risco de perder esse último trace: o interpretador mata as
threads daemon ao sair e o POST morreria no meio. Para evitar isso, a jangada
registra automaticamente um handler de atexit que espera os envios pendentes
terminarem antes de encerrar (com um prazo total de segurança — não trava o
processo se a rede estiver lenta). Você não precisa fazer nada.
A única exceção é o encerramento abrupto (os._exit(), um signal que não
passa pelo atexit, ou um sys.exit() em contexto que ignora handlers): aí,
chame o flush manualmente antes de sair.
from jangada_ai import LLM, flush_observability
llm = LLM("openai", "gpt-4o-mini")
resp = llm.complete("última pergunta")
flush_observability() # garante que o trace acima foi enviado
# ... encerramento abrupto ...Agrupar por lote
Por padrão cada chamada vira um trace próprio, nomeado pelo script de entrada
que a gerou (ex.: python examples/02_multi.py → 02_multi) — assim scripts
diferentes ficam distinguíveis no dashboard, em vez de uma parede de traces
iguais. Fora de um script nomeável (REPL, -c, -m, runners), o nome recua para
o método (complete, parse, stream, embed…); em nenhum caso aparece
"(sem nome)". Para agrupar várias chamadas de uma request no mesmo lote (mesmo
sendo enviadas uma a uma) e dar a ele um nome próprio, abra um escopo com
observability_session(name=...): um id de lote é gerado, todas as chamadas de
dentro o compartilham e o nome do escopo prevalece — o backend as agrupa no mesmo
trace.
from jangada_ai import LLM, observability_session
llm = LLM("openai", "gpt-4o-mini")
with observability_session(name="resumo+tradução", user_id="cliente-123"):
r1 = llm.complete("Resuma: ...") # observation no mesmo trace
r2 = llm.complete("Traduza: ...") # idem — agrupadas pelo id do loteobservability_session aceita id (reaproveita um id externo), name,
user_id, session_id e metadata, e devolve o id do lote. Funciona em código
sync e async (usa contextvars).
Feedback de produção
Capture a reação do usuário final (👍/👎 ou um score) e anexe ao trace com
feedback(), usando o id do lote devolvido por observability_session. É
best-effort (nunca derruba a app): True se enviou, False se faltou chave
ou deu erro de rede.
from jangada_ai import LLM, observability_session, feedback
llm = LLM("openai", "gpt-4o-mini")
with observability_session(name="suporte") as trace_id:
resp = llm.complete("Como emito uma NF?")
# mais tarde, quando o usuário avaliar:
feedback(trace_id, 1, comment="resolveu meu problema") # 👍
# feedback(trace_id, -1, comment="resposta errada") # 👎Vira um Score no trace (origem api), aparece no dashboard junto dos 👍/👎
humanos e fecha o loop: um 👎 pode ser promovido a exemplo de dataset e virar
caso de regressão nas evals.
O que é capturado
De cada chamada: provider, model, promptTokens/completionTokens (de
usage), costUsd (de cost), latência, o input (mensagens ou textos de
embedding; conteúdos muito longos são truncados), o output (texto da resposta,
ou quantidade/dimensão dos embeddings) e as tool calls que o modelo pediu
(tools: id/name/args).
O input preserva o histórico de ferramentas de forma auditável: cada
tool_call registra o nome e os argumentos ([tool_call consultar_estoque {"produto": "cabo HDMI"}]) e cada tool_result registra o conteúdo retornado
([tool_result] {"disponivel": 0, "previsao_dias": 12}), com marcação de erro
quando aplicável. Assim dá para conferir de onde saiu cada número que o modelo
afirmou — não fica só um marcador vazio. Args e resultados individualmente grandes
são truncados (teto por parte), além do teto global do input.
Cada observation tem um status: OK, ou INCOMPLETE quando a resposta
foi cortada por limite de tokens (finish_reason == "length"), junto do motivo
normalizado em finishReason. No dashboard vira um badge (âmbar) e entra no
filtro de status.
Capacidades (capabilities)
Cada observation registra quais capacidades de IA foram usadas — tools,
mcp, a2a, vision, audio, documents, rag, structured_output,
guardrails, cache, agents, embeddings. No dashboard viram badges,
filtro e a quebra Analytics → Uso por capacidade.
A detecção é automática a partir dos argumentos da chamada: images= → vision,
files= → documents, tools= → tools, mcp_servers= → mcp, parse() →
structured_output, guardrails → guardrails. tools também é derivado
quando o modelo pede tool calls.
embed()/aembed() registram embeddings diretamente, inclusive quando a
session contém apenas a ingestão RAG.
No dashboard
Em app.jangada.dev.br você acompanha tudo:
- Traces e detalhe — cada lote e suas observations (provider, modelo, tokens, custo, latência, tool calls e capacidades), em tabela ou waterfall.
- Analytics — custo, chamadas, tokens, taxa de erro e latência (p50/p95/p99), com quebra por modelo, provider e capacidade e série temporal por dia.
- Filtros e exportação — filtre por modelo, provider, erros, datas, userId/sessionId, custo mínimo e capacidade; exporte em CSV/JSON.
- Live tail — traces em tempo real, com pausar/retomar.
- Anomalias — avisos automáticos quando custo/latência/erro fogem do baseline de 7 dias.
- Alertas — regras de custo diário ou taxa de erro.
- Scores — avaliações por trace (feedback humano ou LLM-as-judge).
- Orçamento — teto de custo mensal por projeto, com acompanhamento e projeção.
Detalhes
- A
api_keyé a chave do projeto, gerada no dashboard e configurada no.env. - Reusar o mesmo id de lote (via
observability_session(id=...)) acrescenta observations ao mesmo trace de forma idempotente no backend. - Os campos de custo/tokens vêm de Custo e tokens.
O que mudou na 1.9.0
- Falhas também viram trace. Quando uma chamada esgota retry e fallback, a lib
envia uma observation com
status="ERROR"e o erro (tipo + mensagem, truncado) — antes só os sucessos apareciam no dashboard. Para reportar manualmente:jangada_ai.observability.auto_report_error(erro, provider=..., model=...). - Início real da chamada.
startedAtagora marca quando a chamada começou (não quando terminou);auto_report(..., started_at=...)aceita epoch oudatetime. - Stream e transcrição reportados.
stream/astreamenviam o texto acumulado ao final (capabilitystreaming);transcribetambém reporta (audio). Hits de cache ganham a capabilitycache. - Envio com fila limitada. Em vez de uma thread por chamada, há uma fila (1000
eventos) com poucos workers; se o endpoint ficar lento e a fila encher, os
eventos excedentes são descartados —
dropped_count()diz quantos.flush()e oatexitesperam a fila esvaziar com prazo. - Só HTTPS. O endpoint (e o do
feedback) precisa serhttps://(httpsó em localhost), para a chave e os prompts não trafegarem em claro. - Output truncado como o input, e
embednão envia mais todos os vetores (só dimensões e contagem acima de um teto). - Falhas de envio vão para o log
DEBUGdo loggerjangada_ai(nunca derrubam a chamada).