jangada 🛶
Camada fina PT-BR sobre os SDKs de LLM: troque de provider sem mudar o código, com observability nativa em 1 linha no
.env.
Uma camada fina e adaptável sobre os SDKs de LLM. Uma jangada leve que te leva
entre Anthropic, OpenAI, Groq, Gemini e Mistral sem trocar o leme: você muda provider / model / api_key e o resto do código continua igual.
pip install "jangada-ai[anthropic]" # ou [openai] / [groq] / [gemini] / [mistral] / [all]
from jangada_ai import LLM
llm = LLM("anthropic", "claude-opus-4-8")
print(llm.complete("Explique {{tema}} em 2 frases.", tema="MCP").text)
O pitch em 4 linhas — troque o provider, o resto do código não muda:
LLM("openai", "gpt-4o-mini") # mesma chamada .complete()/.parse()/.stream()
LLM("groq", "llama-3.3-70b-versatile")
LLM("gemini", "gemini-2.5-flash")
LLM("anthropic", "claude-opus-4-8").with_fallback(LLM("openai", "gpt-4o-mini"))
A mesma API (complete/parse/stream, sync e async) vale para os 4 — com
templates {{ }}, structured output, vision, tools/MCP, retry e fallback.
Onde a jangada se encaixa
Existem ótimas libs de abstração de LLM por aí — esta tabela é uma comparação de alto nível, não uma auditoria completa de cada projeto. Use como ponto de partida, não como veredito final.
| jangada | LiteLLM | LangChain | |
|---|---|---|---|
Normaliza parâmetros por modelo específico (ex: gpt-5 vs gpt-4o), não só por provider |
✅ | ⚠️ parcial | ⚠️ parcial |
| Retry + fallback com semântica de erro clara | ✅ | ✅ | ⚠️ requer RunnableWithFallbacks |
| Custo estimado em toda resposta | ✅ | ✅ | ⚠️ requer callback próprio |
| RAG híbrido (BM25 + vetorial + RRF) nativo | ✅ | ❌ | ✅ (via integrações) |
Observability em 1 linha no .env, sem instrumentar código |
✅ | ⚠️ requer proxy/gateway | ⚠️ requer LangSmith |
| Documentação em PT-BR | ✅ | ❌ | ❌ |
| MCP nativo (server-side + cliente/agente próprio) | ✅ | ⚠️ parcial | ⚠️ via LangGraph |
| Multi-agente (handoff sequencial + delegação hierárquica) | ✅ (Squad, leve) |
❌ | ✅ (LangGraph, mais robusto) |
| Guardrails de escopo (blocklist + LLM-judge) em 1 linha | ✅ | ⚠️ via proxy | ⚠️ requer integração externa |
| Cache de resposta (exato + semântico) nativo | ✅ | ⚠️ via proxy | ⚠️ via integrações |
| Foco | lib leve, embutida no seu código | gateway/proxy de produção | orquestração de agentes |
Se você precisa de um gateway self-hosted com múltiplos times, budgets e virtual keys, o LiteLLM Proxy provavelmente serve melhor. Se você quer grafos de estado complexos e um ecossistema grande de integrações prontas, o LangChain/LangGraph tem mais peças. A jangada mira no meio-termo: uma lib fina que você importa direto no seu backend — sem subir um serviço extra — que já cobre fallback, cache, guardrails e multi-agente básico com as nuances de cada modelo resolvidas.
Sumário
- Por que existe (as nuances que ela resolve)
- Cookbook
- Documentação
- Instalação
- Chaves de API e
.env - Parâmetros de geração
- Structured output
- Vision
- Tools (function calling)
- MCP
- Detecção de objetos
- RAG
- Transcrição de áudio
- Documentos (docx, pdf, csv, xlsx)
- Streaming
- Retry + fallback
- Custo e tokens na resposta
- Fluxos (sequencial)
- Orquestração (Graph)
- Agentes & times (Agent/Squad)
- Guardrails (escopo)
- Cache de respostas
- Servindo a jangada como servidor MCP
- Versionamento de prompts
- Avaliação (evals)
- Tools prontas (prebuilt)
- Debug passo a passo
- Boas práticas e nuances
- Erros
- Estendendo (novo provider)
- Versionamento e estabilidade
- Contribuindo
Por que existe (as nuances que ela resolve)
Wrappers "genéricos" costumam quebrar em produção por detalhes que só aparecem quando você troca de modelo. A jangada resolve estes:
- Modelos do mesmo provider têm contratos diferentes.
gpt-5rejeitatemperature(400) e exigemax_completion_tokens;gemini-3.xdescartatemperature/top_p/top_ke trocathinking_budgetporthinking_level. A jangada normaliza o payload por modelo — você só troca o nome. - Nomes de parâmetro divergem entre SDKs.
stopvirastop_sequencesna Anthropic/Gemini;max_tokensviramax_output_tokensno Gemini. Você usa sempre o nome canônico. - Erros são heterogêneos. Cada SDK levanta exceções diferentes; aqui tudo
vira um conjunto único, com
status_code, que alimenta retry e fallback. - Falhas transitórias. Rate limit e 5xx ganham retry com backoff no mesmo provider antes de cair pro fallback (outro modelo/provider).
- Custo é invisível por padrão. Cada resposta volta com
usageecostestimado, eFlow/Graphagregam o total. - Structured output é diferente em cada um. OpenAI
.parse, Groqjson_schema, Geminiresponse_schema, Anthropic tool-forcing — uma só chamadaparse()cuida disso. - Detecção de objetos sem amarrar a um provider.
detect_objects()devolve bounding boxes em pixels e funciona em qualquer modelo com visão (vision + structured output), não só no Gemini. - Transcrição de áudio onde dá.
transcribe()cobre OpenAI, Groq, Gemini e Mistral/Voxtral (Anthropic não aceita áudio na API) — com a mesma interface e o mesmo fallback das outras chamadas. A Mistral ainda faz OCR/Document AI (ocr()): PDF/imagem → markdown por página + bounding boxes. - Documentos nem sempre precisam de vision. docx/pdf/csv/xlsx têm o texto embutido: a jangada extrai localmente (mais barato, roda em modelo sem visão) e só usa vision quando você pede ou quando o PDF é escaneado.
Imports são preguiçosos: import jangada_ai funciona sem nenhum SDK instalado.
Cookbook (receitas prontas)
Casos reais rodáveis em examples/cookbook/: extração de
nota fiscal (vision+structured), agente MCP, chatbot RAG, fallback multi-provider,
transcrição+resumo e observability. Cada um é script + explicação — pra ler e rodar.
Documentação
A documentação completa fica no repositório público jangada-docs:
- 📚 Guias por tema: https://github.com/nerigleston/jangada-docs/tree/main/docs
- 🌐 Site: https://jangada.dev.br
- 📖 Docs online: https://docs.jangada.dev.br
- 🤖 Para LLMs — índice:
llms.txt - 🤖 Para LLMs — completo:
llms-full.txt - 📦 Pacote no PyPI: https://pypi.org/project/jangada-ai/
🧩 Doc no seu editor (MCP) — para desenvolvedores
Pluga toda a doc do jangada no Claude Code / Claude Desktop / Cursor via MCP,
pra o assistente codar com a API atual (sem chutar). Sem clonar nada, com uvx:
claude mcp add jangada-docs -- uvx --from git+https://github.com/nerigleston/jangada-docs-mcp jangada-docs-mcp
Ou via endpoint hospedado (HTTP):
{
"mcpServers": {
"jangada-mcp": {
"url": "https://mcp.jangada.dev.br/mcp/"
}
}
}
Repo: jangada-docs-mcp (escopo global ou por projeto — ver o README de lá).
A fonte dos docs está aqui em
docs/; o repo público é espelhado porscripts/sync-docs.sh.
Instalação
pip install -e ".[anthropic]" # só Claude
pip install -e ".[openai,groq]" # OpenAI + Groq
pip install -e ".[all]" # todos
pip install -e ".[files]" # ler docx/pdf/csv/xlsx
pip install -e ".[dev]" # ambiente de testes (pytest + libs)
Atenção: o extra [all] traz todos os providers + files, mas não inclui rag nem mcp — esses entram à parte, ex.: pip install "jangada-ai[all,rag,mcp]".
Chaves de API e .env
Precedência: api_key= explícito > variável de ambiente > arquivo .env.
LLM("openai", "gpt-4o", api_key="sk-...") # explícito
LLM("openai", "gpt-4o") # lê OPENAI_API_KEY (ambiente ou .env)
O .env é detectado na importação, subindo a partir do diretório atual, de
forma não-destrutiva (não sobrescreve variáveis já definidas). Usa python-dotenv se instalado, ou um parser embutido. Desligue com JANGADA_NO_DOTENV=1, ou carregue manualmente: jangada.load_env("caminho/.env").
Variáveis lidas: ANTHROPIC_API_KEY, OPENAI_API_KEY, GROQ_API_KEY, GEMINI_API_KEY (ou GOOGLE_API_KEY).
Parâmetros de geração
Os params comuns são argumentos nomeados de primeira classe, traduzidos para cada SDK e descartados quando o modelo não os aceita:
llm = LLM("openai", "gpt-4o",
temperature=0.2, max_tokens=800, top_p=0.9,
top_k=40, stop=["\n\n"], seed=42)
| canônico | OpenAI / Groq | Anthropic | Gemini |
|---|---|---|---|
temperature |
temperature | temperature | temperature |
max_tokens |
max_tokens¹ | max_tokens | max_output_tokens |
top_p |
top_p | top_p | top_p |
top_k |
(descartado) | top_k | top_k |
stop |
stop | stop_sequences | stop_sequences |
seed |
seed | (descartado) | seed |
¹ vira max_completion_tokens em modelos de raciocínio (gpt-5).
Params específicos vão em extra={...} (ex.: reasoning_effort, thinking_level, verbosity). Override por chamada: llm.complete("...", params={"temperature": 0.7}).
Diferenças entre modelos (perfis automáticos)
Você troca o nome do modelo e segue — a jangada ajusta o payload:
LLM("openai", "gpt-4o", temperature=0.3, max_tokens=500) # vai como está
LLM("openai", "gpt-5.2", temperature=0.3, max_tokens=500) # temperature removido; max_tokens -> max_completion_tokens
LLM("gemini", "gemini-2.5-flash", temperature=0.5) # vai como está
LLM("gemini", "gemini-3.5-flash", temperature=0.5) # sampling descartado (Gemini 3.x usa defaults)
Registre regras para modelos novos:
from jangada_ai import Profile, register_profile
register_profile("openai", r"^modelo-novo", Profile(drop=("temperature",), note="..."))
Nuance de function calling no Gemini 3.x. A API valida thought signatures de forma estrita: reconstruir o histórico de tools (em vez de devolver o histórico completo e deixar o SDK cuidar) causa 400. Foi isso que quebrou conversores genéricos ao migrar
gemini-2.5→gemini-3.5. A jangada não reconstrói histórico de tools (structured output é single-turn), então não esbarra nisso — mas guarde a regra se for montar loops agênticos.
Structured output
A mesma parse() em qualquer provider (por baixo: OpenAI .parse, Groq json_schema, Gemini response_schema, Anthropic tool-forcing):
from pydantic import BaseModel
class Fatura(BaseModel):
fornecedor: str
total: float
itens: list[str]
fatura = llm.parse("Extraia a fatura:\n{{t}}", Fatura, t=texto).parsed # instância validada
Async: await llm.aparse(...). Em fluxos/grafos, um passo com schema= guarda
o .parsed em result.parsed("passo").
Vision
from jangada_ai import Image
llm.complete("O que aparece aqui?", images=["foto.jpg"])
img = Image.from_bytes(upload_bytes, "image/png") # ou from_base64
recibo = llm.parse("Extraia o total.", Recibo, images=[img]).parsed
Imagens são bytes (path/bytes/base64) e viram o formato nativo de cada SDK. Use sempre um modelo com visão.
Tools (function calling)
from jangada_ai import LLM, Message
def get_weather(city: str) -> str:
"""Retorna o clima atual de uma cidade."""
return "25°C, ensolarado"
llm = LLM("openai", "gpt-4o-mini")
comp = llm.complete("Tempo em Recife?", tools=[get_weather]) # OpenAI/Groq/Anthropic/Gemini
for call in comp.tool_calls: # você executa
out = get_weather(**call.args)
final = llm.complete("Tempo em Recife?",
history=[comp.assistant_message(), Message.tool_results(call.result(out))],
tools=[get_weather])
Baixo nível (você executa e reenvia). tools= aceita função/Pydantic/dict.
Tools prontas em jangada_ai.prebuilt (ex.: tavily_search). Veja docs/tools.md.
MCP (Model Context Protocol)
from jangada_ai import LLM, MCPServer
# remoto por URL — o provider executa as tools (Anthropic/OpenAI/Groq)
llm = LLM("anthropic", "claude-opus-4-8")
llm.complete("Liste as issues.", mcp_servers=[
MCPServer(url="https://mcp.exemplo.com/sse", name="github", authorization_token="TOKEN")])
# Gemini é client-side (sessão), só no async: await llm.acomplete(..., mcp_servers=[session])
MCP nativo (server-side) — dois modelos conforme o SDK: remoto/URL (Anthropic/OpenAI/Groq) e sessão (Gemini, async).
Ou use o cliente + agente próprios (jangada-ai[mcp]), que conecta no
servidor e roda o loop sozinho em qualquer provider:
from jangada_ai import LLM, MCPClient, run_agent
async with MCPClient("https://meu-mcp/mcp/") as mcp: # ou stdio (command=/args=)
ans = await run_agent(LLM("openai", "gpt-4o-mini"), "Role uns dados", client=mcp)
print(ans.text)
Veja docs/mcp.md.
Detecção de objetos
from jangada_ai import LLM, detect_objects
llm = LLM("gemini", "gemini-2.5-flash") # funciona em qualquer modelo com visão
for d in detect_objects(llm, "foto.png"):
print(d.label, d.box) # box = [x1, y1, x2, y2] em pixels
# acrescente contexto sem perder o formato de saída:
detect_objects(llm, "foto.png", target="todos os carros",
instructions="Ignore os desfocados; rotule em inglês.")
Bounding boxes em pixels absolutos. É vision + structured output, então roda em todos os providers (o Gemini é o mais preciso). Veja docs/detect.md.
RAG (embeddings + busca vetorial/híbrida)
from jangada_ai import LLM
from jangada_ai.rag import RAG, vector_store # pip install "jangada-ai[rag]"
emb = LLM("openai", "text-embedding-3-small") # embed: OpenAI ou Gemini
store = vector_store("postgresql://...") # ou "mongodb+srv://..." (pela conn string)
rag = RAG(emb, store, chat=LLM("openai", "gpt-4o-mini"))
rag.add_document("manual.pdf")
print(rag.ask("Como faço backup?", mode="hybrid").text) # vetorial + texto (RRF)
embed() está em OpenAI/Gemini (Anthropic/Groq não têm). O vector store é
escolhido pela string de conexão (pgvector ou Mongo) e cria tabelas/índices
sozinho. Veja docs/rag.md.
Transcrição de áudio
from jangada_ai import LLM
LLM("openai", "gpt-4o-transcribe").transcribe("entrevista.mp3").text
LLM("groq", "whisper-large-v3-turbo").transcribe("entrevista.mp3", language="pt").text
LLM("gemini", "gemini-2.5-flash").transcribe("entrevista.mp3").text
Suportado em OpenAI, Groq e Gemini; o Anthropic não aceita áudio na API
(levanta UnsupportedError). Honra retry e fallback como qualquer chamada. Veja docs/audio.md.
Documentos (docx, pdf, csv, xlsx)
from jangada_ai import Document
# Por padrão EXTRAI O TEXTO localmente (não usa vision): mais barato e
# funciona em qualquer modelo. Tabelas viram markdown.
llm.complete("Resuma:", files=["relatorio.pdf", "contrato.docx"])
# xlsx: todas as abas entram, cada uma rotulada (## Aba: ...).
# max_rows limita planilhas gigantes para economizar tokens.
llm.complete("Maior total?", files=[Document("vendas.xlsx", max_rows=200)])
# Bytes em memória (upload/fila) — informe o nome para detectar o tipo.
llm.parse("Há duplicadas?", Relatorio, files=[Document(blob, name="x.csv")])
# Forçar vision (PDF escaneado, sem camada de texto / quando o layout importa):
llm.complete("Transcreva:", files=[Document("scan.pdf", mode="vision")])
Regra do mode="auto" (padrão): csv/xlsx/docx e PDF com camada de texto são
extraídos como texto; imagens vão para vision. Um PDF sem texto (escaneado)
levanta DocumentError sugerindo mode="vision" — nunca devolve um bloco vazio
em silêncio. files= existe em complete/parse/stream (sync e async) e convive
com images=.
Requer o extra: pip install "jangada[files]" (pypdf, python-docx, openpyxl).
Streaming
for token in llm.stream("Conte sobre {{x}}", x="João Pessoa"):
print(token, end="")
async for token in llm.astream("..."): # FastAPI
...
Retry + fallback
Duas camadas, com semântica clara:
- Retry no mesmo provider (com backoff exponencial + jitter) para erros transitórios: rate limit (429), timeout, conexão e 5xx.
- Fallback para o próximo candidato quando o retry esgota — pode ser outro modelo ou outro provider.
llm = LLM(
"groq", "llama-3.3-70b-versatile",
max_retries=3, backoff_base=0.5, backoff_max=8.0, jitter=True,
).with_fallback(
LLM("openai", "gpt-5.2"),
LLM("anthropic", "claude-opus-4-8"),
)
resp = llm.complete("...")
print(resp.provider) # quem de fato respondeu
O que não dispara retry nem fallback por padrão: AuthError (401/403) e BadRequestError (400/422) — trocar de provider ou repetir não resolveria. NotFoundError (modelo inexistente) não faz retry, mas faz fallback
(ótimo para "modelo de reserva"). Tudo configurável via retry_on= (failover)
e backoff_on= (quais erros repetem). Vale em sync, async e streaming (o
failover de stream ocorre antes do 1º token).
Custo e tokens (na resposta)
Toda resposta volta com usage e cost (USD estimado):
r = llm.complete("...")
r.usage # {'input_tokens': 120, 'output_tokens': 84}
r.cost # 0.00042 (None se o modelo não estiver na tabela)
Flow e Graph agregam o total da execução:
res = flow.run(...)
res.usage # soma de todos os passos
res.cost # custo total estimado
Preços mudam direto. A tabela embutida é um retrato aproximado — verifique a página de preços e sobrescreva quando precisar de exatidão:
jangada.register_price(r"gpt-5\.2", 1.75, 14.00)(USD por 1M tokens).
Fluxos (sequencial)
A saída de cada passo vira variável {{ }} dos próximos:
from jangada_ai import Flow
flow = (
Flow(llm)
.step("resumo", "Resuma:\n{{texto}}")
.step("traducao", "Traduza para {{idioma}}:\n{{resumo}}")
)
r = flow.run(texto="...", idioma="inglês")
r["traducao"]; r.cost; r.usage
Cada passo pode ter llm= próprio (misturar providers) e schema= (parse).
Orquestração (Graph: roteamento + paralelo)
Quando o Flow linear não basta — um agente decide o próximo, ou vários rodam
em paralelo:
from jangada_ai import Graph
# roteamento condicional
g = Graph()
g.node("triagem", clf, "Responda 'tecnico' ou 'geral': {{pergunta}}")
g.node("tecnico", tec, "Responda técnico: {{pergunta}}")
g.node("geral", ger, "Responda simples: {{pergunta}}")
g.route("triagem", lambda ctx: "tecnico" if "tecnico" in ctx["triagem"].lower() else "geral")
r = g.run("triagem", pergunta="...")
r.path # ['triagem', 'tecnico']
# paralelo + junção
g = Graph()
g.parallel("pesquisas", {
"mercado": (llm_a, "Mercado de: {{tema}}"),
"tecnica": (llm_b, "Viabilidade de: {{tema}}"),
}, join="sintese")
g.node("sintese", llm_s, "Combine:\n{{mercado}}\n{{tecnica}}")
r = g.run("pesquisas", tema="...")
Compõe nos dois sentidos (rota → paralelo, ou paralelo → rota). O núcleo é
async: em FastAPI use await g.arun(...); fora de loop, g.run(...).
Agentes & times (Agent/Squad)
Camada de composição em cima do que já existe (tools, MCPClient, RAG) — sem infraestrutura nova. Agent é um LLM com papel/objetivo e um loop de tool calling; Squad orquestra vários agentes, em sequência (handoff) ou hierarquia (um gerente delega).
from jangada_ai import LLM, Agent, Squad
def clima(cidade: str) -> str:
"""Retorna o clima de uma cidade."""
return f"ensolarado em {cidade}"
pesquisador = Agent(LLM("openai", "gpt-4o-mini"), role="Meteorologista",
goal="informar o clima", tools=[clima])
escritor = Agent(LLM("openai", "gpt-4o-mini"), role="Redator",
goal="resumir em 1 frase")
# sequencial: a saída de um vira contexto do próximo
squad = Squad([pesquisador, escritor])
print(squad.run("Como está o clima em Recife?").text)
# hierárquico: um gerente delega via tools geradas automaticamente
squad = Squad([pesquisador, escritor], manager=Agent(LLM("openai", "gpt-4o-mini"), role="Gerente"))
- Memória de longo prazo:
RAGMemory(sobre umRAGdojangada_ai.rag) viaAgent(..., memory=RAGMemory(rag))—recallbusca,rememberindexa. - MCP como tools do agente:
Agent(..., mcp_client=MCPClient(...)), disponível noarun(async). - Planejamento:
plan(llm, goal)decompõe um objetivo em lista de tarefas (structured output). - A2A:
agent.card()gera os metadados no vocabulário do Agent Card A2A (name,skills,capabilities...) prontos para servir emGET /.well-known/agent.json— a lib não fala o protocolo A2A por HTTP, só entrega os metadados.
Veja docs/agents.md.
Guardrails (escopo)
Mantém a LLM dentro de um domínio e barra falas fora do escopo — intercepta a chamada antes (input) e/ou depois (output) do modelo principal, sem infra nova.
from jangada_ai import LLM, ScopeGuard
guard = ScopeGuard(
"Suporte do e-Gestor: notas fiscais, financeiro, cadastros; NÃO responde outros assuntos",
judge=LLM("openai", "gpt-4o-mini"), # modelo barato pra classificar (recomendado)
block=[r"senha", r"cart[aã]o de cr[eé]dito"], # blocklist: barra na hora, sem LLM
check="both", # "input" (padrão) | "output" | "both"
)
llm = LLM("openai", "gpt-4o", guardrails=[guard])
Dois mecanismos, do barato ao robusto: blocklist (regex/termos, sem custo) e classificador de escopo (LLM-as-judge, via structured output). Quando barra, devolve um Completion com a mensagem de recusa (curto-circuita antes de gastar o modelo principal); com raise_on_block=True, levanta GuardrailError. fail_closed controla o comportamento se o judge falhar.
Cache de respostas
Plugado via LLM(..., cache=...). Consulta antes de chamar o provider, popula depois de uma resposta bem-sucedida.
from jangada_ai import LLM, ExactCache, SemanticCache
# exato: só acerta requisição idêntica (hash de método+escopo+mensagens)
llm = LLM("openai", "gpt-4o-mini", cache=ExactCache(max_size=512, ttl=3600))
# semântico: acerta por similaridade de cosseno (reusa embed + vector_store do RAG)
llm = LLM("openai", "gpt-4o-mini", cache=SemanticCache(
embedder=LLM("openai", "text-embedding-3-small"), threshold=0.85,
))
Calibre o
thresholdpor modelo de embedding — a escala de similaridade varia bastante entre modelos (paráfrases ficam ~0.6 notext-embedding-3-small, mas ~0.9 nogemini-embedding-001). Meça paráfrases vs. perguntas distintas no seu modelo antes de confiar no padrão.
Servindo a jangada como servidor MCP
Além de consumir MCP (MCPServer/MCPClient), a jangada também serve: exponha suas funções ou um Agent inteiro como servidor MCP pra Claude Desktop, Cursor ou outro agente consumirem.
from jangada_ai import serve_mcp
def soma(a: int, b: int) -> int:
"""Soma dois números."""
return a + b
serve_mcp("minha-calc", tools=[soma]) # transporte stdio
# ou exponha um Agent inteiro como uma ferramenta `ask`:
from jangada_ai import Agent, LLM
agente = Agent(LLM("openai", "gpt-4o-mini"), role="suporte", tools=[soma])
serve_mcp("suporte", agent=agente)
Construído sobre o Server low-level do SDK oficial mcp (extra jangada-ai[mcp]) — o protocolo, não um framework como FastMCP. É o lado servidor equivalente ao MCPClient.
Versionamento de prompts
Registry opt-in pra versionar prompts fora do código e referenciá-los pelo nome — convive com prompt hardcoded, que continua funcionando normalmente.
from jangada_ai import LLM, PromptVersion
p = PromptVersion.pull("assistente-fiscal") # puxa a versão de produção
LLM("openai", "gpt-4o-mini").complete(p.render(cliente="ACME"))
# publicar uma nova versão por código (ou pelo painel):
PromptVersion.push("assistente-fiscal", "Você é... {{ cliente }}", tag="production")
Usa a mesma config da observability (JANGADA_OBSERVABILITY_API_KEY/_TOKEN + endpoint).
Avaliação (evals)
A régua de qualidade sobre a observabilidade: roda um target sobre um Dataset, dá notas com Evaluators (heurística e/ou LLM-juiz), agrega score/custo/latência. Funciona offline.
from jangada_ai import LLM
from jangada_ai.eval import Dataset, Evaluator, evaluate
ds = Dataset.from_jsonl("casos.jsonl")
exato = Evaluator.fn("exato", lambda out, ref: out.text.strip() == ref)
juiz = Evaluator.judge("util", "A resposta é útil? score 0..1.", judge=LLM("openai", "gpt-4o-mini"))
res = evaluate(ds, target=lambda ex: LLM("openai", "gpt-4o-mini").complete(ex.inputs["q"]),
evaluators=[exato, juiz], name="baseline")
print(res.summary())
Tools prontas (prebuilt)
Funções já prontas pra passar em tools=[...] — o schema sai da assinatura, você executa e reenvia como qualquer tool.
from jangada_ai.prebuilt import calculator, current_datetime, fetch_url, wikipedia_search, tavily_search, brave_search, openweather
Sem chave: calculator, current_datetime, fetch_url, wikipedia_search, http_request. Com chave (via env): tavily_search/tavily_tool, brave_search, openweather.
Debug passo a passo (por agente)
debug=True narra a cadeia: prompt, params, resposta (tempo, tokens, custo),
retries, erros e trocas de fallback. Cada LLM tem seu debug e name, então em
fluxos/grafos o trace sai por agente.
┌─ [resumidor] groq/llama-3.3-70b-versatile
│ user: Resuma: ...
│ params: temperature=0.2, max_tokens=1024
│ ↻ tentativa 1 após 0.5s (RateLimitError)
│ ✗ RateLimitError (429): quota exceeded
│ ↪ fallback → anthropic/claude-opus-4-8
┌─ [resumidor] anthropic/claude-opus-4-8
│ ← 412ms · ↑12 ↓84 tok · $0.006330: Resumo do texto...
└─
Para mandar pro log: llm.debug.sink = logging.getLogger("jangada").info.
Boas práticas e nuances
systemtemplatizado é strict. Se você definirsystem="Aja como {{persona}}."no construtor, passepersonaem toda chamada. Em fluxos, prefirasystemfixo no LLM e ponha a parte variável nos prompts dos passos.- Modelos de raciocínio (gpt-5, gemini-3.x) ignoram
temperature. Não adianta ajustar sampling; useextra={"reasoning_effort": "..."}/extra={"thinking_level": "..."}. A jangada já descarta o que não cabe. - Cadeia de fallback barato → forte. Coloque o modelo rápido/barato como primário e os caros como reserva; o retry segura picos de 429 sem trocar.
NotFoundErroré seu amigo no fallback de modelo. Aponte um modelo novo como primário e um estável como fallback: se o nome ainda não existir na sua região, ele cai pro estável sem quebrar.Flowpara pipeline fixo;Graphquando há decisão ou paralelismo. Não useGraphse a ordem é sempre a mesma —Flowé mais simples de ler.- Em FastAPI use sempre as versões async (
acomplete/aparse/astreamegraph.arun) para não bloquear o event loop. - Cheque o custo em produção. Logue
resp.costeresult.cost; sobrescreva a tabela de preços com os valores do seu contrato. - Endpoints compatíveis com OpenAI (Ollama, OpenRouter, vLLM) funcionam pelo
provider
openaipassandobase_url:LLM("openai", "llama3", base_url="http://localhost:11434/v1", api_key="ollama"). - Vision é só bytes. URLs remotas não são baixadas automaticamente; carregue
com
Image.from_path/bytes/base64para uniformidade entre os 4 providers.
Erros
Em jangada.errors, todos com .provider, .status_code e .original: RateLimitError, APITimeoutError, APIConnectionError, ServerError, OverloadedError, AuthError, BadRequestError, NotFoundError, ProviderError. Conjuntos prontos: TRANSIENT (retry) e DEFAULT_FAILOVER.
Estendendo (novo provider)
Herde de jangada.Provider, implemente os 6 métodos (complete/acomplete/
parse/aparse/stream/astream) + os dois _build_*_client, e registre com jangada.register("nome", lambda: SuaClasse). Veja CLAUDE.md para as
invariantes (imports preguiçosos, tradução de erro, ordem translate→profile).
Providers prontos: anthropic, openai, groq, gemini, openrouter, azure (Azure OpenAI), bedrock (AWS Bedrock, Converse API) e vertex (Gemini no Google Cloud).
Versionamento e estabilidade
A jangada segue SemVer. Na fase 0.x, mudanças
incompatíveis podiam ocorrer em releases minor (0.X.0) — sempre documentadas
no CHANGELOG.md. A partir do 1.0 (versão atual), SemVer pleno: sem breaking change em minor/patch.
A API pública é o que está em jangada_ai.__all__; submódulos internos e nomes
com _ podem mudar a qualquer momento. O que é estável × experimental está em docs/estabilidade.md; as regras de versionamento e
deprecação em docs/semver.md.
Contribuindo
Contribuições são bem-vindas — issues, PRs de bugfix, novos providers ou melhorias na doc.
- Abra uma issue descrevendo o problema ou a proposta antes de um PR grande.
pip install -e ".[dev]"para o ambiente de testes.- Rode a suíte local antes de abrir o PR.
- PRs de doc podem ir direto pro repositório jangada-docs.
Dúvidas rápidas? Abra uma issue — é o canal mais rápido de resposta.
Feito com 🛶 em PT-BR · jangada.dev.br · MIT License
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jangada_ai-1.4.3.tar.gz.
File metadata
- Download URL: jangada_ai-1.4.3.tar.gz
- Upload date:
- Size: 354.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3f1e713ebe8c6160be6e14ef62625af2dcfb9323758c018a02b5c883b411d8bb
|
|
| MD5 |
b9d2ca7c584425ba13308854f4735771
|
|
| BLAKE2b-256 |
bafe570e46b9fd02f9afc0d6a4a75c29efaea9c784b962a0673694ae70266192
|
Provenance
The following attestation bundles were made for jangada_ai-1.4.3.tar.gz:
Publisher:
publish.yml on nerigleston/jangada
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jangada_ai-1.4.3.tar.gz -
Subject digest:
3f1e713ebe8c6160be6e14ef62625af2dcfb9323758c018a02b5c883b411d8bb - Sigstore transparency entry: 2195409226
- Sigstore integration time:
-
Permalink:
nerigleston/jangada@7d207f38e36d63da18604e5adf68582972c7c19c -
Branch / Tag:
refs/heads/master - Owner: https://github.com/nerigleston
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7d207f38e36d63da18604e5adf68582972c7c19c -
Trigger Event:
push
-
Statement type:
File details
Details for the file jangada_ai-1.4.3-py3-none-any.whl.
File metadata
- Download URL: jangada_ai-1.4.3-py3-none-any.whl
- Upload date:
- Size: 142.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5f4f2bf2183cf40aae030cdb8ee6872e9433ceb44d98618e95afeb3180b9eba6
|
|
| MD5 |
06dc10aaeb974e723af8225e813ea918
|
|
| BLAKE2b-256 |
bb7ee12030773008fa9a585d5803ddce1b22e7403cebbc89bdbaae3c4c732fba
|
Provenance
The following attestation bundles were made for jangada_ai-1.4.3-py3-none-any.whl:
Publisher:
publish.yml on nerigleston/jangada
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jangada_ai-1.4.3-py3-none-any.whl -
Subject digest:
5f4f2bf2183cf40aae030cdb8ee6872e9433ceb44d98618e95afeb3180b9eba6 - Sigstore transparency entry: 2195409235
- Sigstore integration time:
-
Permalink:
nerigleston/jangada@7d207f38e36d63da18604e5adf68582972c7c19c -
Branch / Tag:
refs/heads/master - Owner: https://github.com/nerigleston
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7d207f38e36d63da18604e5adf68582972c7c19c -
Trigger Event:
push
-
Statement type: