Jangada AIJangada AI

Observability

Com a chave do projeto no ambiente, cada chamada de LLM vira uma observation no dashboard (app.jangada.dev.br): provider, modelo, tokens, custo, latência, input, output, tool calls e capacidades usadas. O envio roda em background e nunca derruba a chamada.

Ativar

# .env
JANGADA_OBSERVABILITY=true
JANGADA_OBSERVABILITY_API_KEY=lobs_xxx   # chave do projeto (gerada no dashboard)
# opcional (padrão: plataforma oficial)
# JANGADA_OBSERVABILITY_ENDPOINT=https://api.jangada.dev.br
import { LLM } from "jangada-ai";
import "jangada-ai/node"; // carrega o .env e liga o flush no fim do processo

const llm = new LLM("openai", "gpt-4o-mini");
await llm.complete("Resuma: ..."); // já vai para o dashboard

A flag precisa ser 1, true, yes, on ou sim, e a chave precisa existir. Faltando uma das duas, nada é enviado. O endpoint precisa ser https:// (http:// só em localhost); outro endereço desliga o envio com um aviso no console.

Fora do Node (Workers, Vercel Edge), process.env pode não existir. Nesse caso passe a chave na sessão (ver Uma chave por empresa).

O que é enviado

MétodoObservationTokens e custo
complete, parsesimsim
streamCompletesim (stream_complete)sim
streamsim (stream)não
transcribesimcusto por minuto, quando a duração é conhecida
embedsim (contagem e dimensão dos vetores)sim

stream() só entrega texto, sem usage do provider, então a observation dele não tem tokens nem custo. Se precisa disso, use streamComplete(). Nos dois a latência conta desde o pedido até o último pedaço, incluindo a espera pelo 1º.

Cada observation tem status: OK, INCOMPLETE (resposta cortada por maxTokens, finishReason === "length") ou ERROR (todos os candidatos falharam, com tipo e mensagem do erro). Textos longos de input e output são truncados. Como no Python, ERROR sai de complete, parse e streamComplete (inclusive quando o stream cai depois do 1º evento); stream, transcribe e embed só reportam sucesso.

Capacidades detectadas: vision (images), documents (files), tools, mcp, structured_output (parse), guardrails, streaming, cache, audio e embeddings. No dashboard viram badges e filtro.

Encerramento

Os envios ficam pendentes em memória. Com import "jangada-ai/node", a lib espera os pendentes no beforeExit do processo. Em serverless (Lambda, Workers, rotas do Next), chame flushObservability() antes de responder, senão o último trace pode se perder:

import { flushObservability, LLM } from "jangada-ai";

export async function POST(req: Request): Promise<Response> {
  const llm = new LLM("openai", "gpt-4o-mini");
  const comp = await llm.complete(await req.text());
  await flushObservability(); // prazo padrão: 5000 ms; nunca lança
  return new Response(comp.text);
}

Acima de 1000 envios pendentes, os novos eventos são descartados; droppedCount() diz quantos.

Agrupar chamadas num trace

Sem sessão, cada chamada vira um trace próprio, com o nome do script de entrada (node resumo.ts vira resumo) ou, na falta dele, do método.

Para juntar várias chamadas no mesmo trace, crie uma sessão com observabilitySession() e passe em trace. Funciona em qualquer runtime:

import { LLM, observabilitySession } from "jangada-ai";

const llm = new LLM("openai", "gpt-4o-mini");
const sessao = observabilitySession({ name: "resumo+tradução", userId: "cliente-123" });

const r1 = await llm.complete("Resuma: ...", { trace: sessao });
const r2 = await llm.complete(`Traduza: ${r1.text}`, { trace: sessao });
console.log(sessao.id); // id do trace

transcribe(audio, { trace }) e embed(textos, { trace }) também aceitam a sessão.

No Node, runInSession de jangada-ai/node dispensa passar trace em cada chamada (usa AsyncLocalStorage). Vale também para chamadas feitas dentro de agentes, flows e tools:

import { runInSession } from "jangada-ai/node";

const traceId = await runInSession({ name: "suporte", sessionId: "conversa-42" }, async (s) => {
  await llm.complete("Como emito uma NF?");
  await llm.complete("E para cancelar?");
  return s.id;
});

Opções da sessão: id (reaproveita um id externo; o backend acrescenta ao mesmo trace), name, userId, sessionId, metadata, apiKey e endpoint. Um trace passado na chamada vence a sessão do runInSession.

Uma chave por empresa

Num SaaS em que cada cliente tem o próprio projeto no dashboard, passe a chave do cliente na sessão. Só as chamadas dessa sessão vão para o projeto dele, e o envio liga mesmo sem JANGADA_OBSERVABILITY=true:

import { observabilitySession } from "jangada-ai";

async function atender(empresa: { obsKey: string; conversaId: string }, pergunta: string) {
  const sessao = observabilitySession({ apiKey: empresa.obsKey, sessionId: empresa.conversaId });
  return llm.complete(pergunta, { trace: sessao });
}

endpoint na sessão aponta para outra instalação da plataforma, com a mesma regra de https://.

Feedback

feedback(traceId, valor) anexa a reação do usuário final ao trace (1 para positivo, -1 ou 0 para negativo). Vira um score de origem api no dashboard. Devolve true se enviou e false se faltou chave ou a rede falhou; nunca lança.

import { feedback } from "jangada-ai";

await feedback(sessao.id, 1, { comment: "resolveu meu problema" });
await feedback(sessao.id, -1, { name: "precisao", comment: "valor errado" });

Opções: name (padrão "thumbs"), comment, apiKey e endpoint (chave do tenant dono do trace), timeoutMs e trace (uma sessão de observabilitySession(); usa a apiKey e o endpoint dela). Dentro de runInSession com apiKey, usa a chave da sessão sem precisar de trace.

Prompts versionados

O prompt registry guarda versões de prompt no dashboard. Usa a mesma chave (JANGADA_OBSERVABILITY_API_KEY) e o mesmo endpoint.

import { LLM, PromptVersion } from "jangada-ai";

// publica uma versão nova (imutável) e move a tag para ela
await PromptVersion.push("assistente-fiscal", "Você atende {{ cliente }}. Seja direto.", {
  tag: "production",
});

// busca a versão com a tag pedida; sem tag, "production"; sem ela, a mais nova
const p = await PromptVersion.pull("assistente-fiscal");
const llm = new LLM("openai", "gpt-4o-mini");
await llm.complete(p.render({ cliente: "ACME" }), { trace: p.trace() });

p.trace() devolve uma sessão marcada com o nome e a versão do prompt, e o trace registra promptName e promptVersion. Para herdar nome e usuário de uma sessão que já existe, use p.trace(sessao). Erros HTTP do registry viram LLMError (404 vira NotFoundError, 401 vira AuthError).

Experimentos

evaluate de jangada-ai/eval roda um dataset contra um alvo e agrega scores, custo e latência. Com push: true, o resultado vai para o dashboard como experimento:

import { Dataset, Evaluator, evaluate } from "jangada-ai/eval";

const dados = new Dataset([{ inputs: { pergunta: "2+2?" }, reference: "4" }], { name: "contas" });
const res = await evaluate(
  dados,
  (ex) => llm.complete(String(ex.inputs.pergunta)),
  [Evaluator.fn("acerto", (out, ref) => out.text.includes(ref))],
  { name: "gpt-4o-mini v1", push: true, targetInfo: { provider: "openai", model: "gpt-4o-mini" } },
);
console.log(res.aggregate, res.pushed, res.pushError);

O envio é best-effort: falha de rede fica em pushError e não derruba o resultado. Detalhes em Eval.

Variáveis de ambiente

VariávelUso
JANGADA_OBSERVABILITYliga o envio (true, 1, yes, on, sim)
JANGADA_OBSERVABILITY_API_KEYchave do projeto (lobs_...)
JANGADA_OBSERVABILITY_ENDPOINTendpoint alternativo (https://)
JANGADA_NO_OBSERVABILITY_SENDdesliga o envio mesmo com tudo configurado (testes)

Relacionado: Custo, cache e debug, observabilidade na lib Python.

On this page