Skip to main content

Jangada AI

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.

PyPI Python License Downloads Open In Colab

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)

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-5 rejeita temperature (400) e exige max_completion_tokens; gemini-3.x descarta temperature/top_p/top_k e troca thinking_budget por thinking_level. A jangada normaliza o payload por modelo — você só troca o nome.
  • Nomes de parâmetro divergem entre SDKs. stop vira stop_sequences na Anthropic/Gemini; max_tokens vira max_output_tokens no 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 usage e cost estimado, e Flow/Graph agregam o total.
  • Structured output é diferente em cada um. OpenAI .parse, Groq json_schema, Gemini response_schema, Anthropic tool-forcing — uma só chamada parse() 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:

🧩 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 por scripts/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.5gemini-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:

  1. Retry no mesmo provider (com backoff exponencial + jitter) para erros transitórios: rate limit (429), timeout, conexão e 5xx.
  2. 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 um RAG do jangada_ai.rag) via Agent(..., memory=RAGMemory(rag))recall busca, remember indexa.
  • MCP como tools do agente: Agent(..., mcp_client=MCPClient(...)), disponível no arun (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 em GET /.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 threshold por modelo de embedding — a escala de similaridade varia bastante entre modelos (paráfrases ficam ~0.6 no text-embedding-3-small, mas ~0.9 no gemini-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

  • system templatizado é strict. Se você definir system="Aja como {{persona}}." no construtor, passe persona em toda chamada. Em fluxos, prefira system fixo 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; use extra={"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.
  • Flow para pipeline fixo; Graph quando há decisão ou paralelismo. Não use Graph se a ordem é sempre a mesma — Flow é mais simples de ler.
  • Em FastAPI use sempre as versões async (acomplete/aparse/astream e graph.arun) para não bloquear o event loop.
  • Cheque o custo em produção. Logue resp.cost e result.cost; sobrescreva a tabela de preços com os valores do seu contrato.
  • Endpoints compatíveis com OpenAI (Ollama, OpenRouter, vLLM) funcionam pelo provider openai passando base_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/base64 para 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.

  1. Abra uma issue descrevendo o problema ou a proposta antes de um PR grande.
  2. pip install -e ".[dev]" para o ambiente de testes.
  3. Rode a suíte local antes de abrir o PR.
  4. 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

jangada_ai-1.2.1.tar.gz (337.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jangada_ai-1.2.1-py3-none-any.whl (137.0 kB view details)

Uploaded Python 3

File details

Details for the file jangada_ai-1.2.1.tar.gz.

File metadata

  • Download URL: jangada_ai-1.2.1.tar.gz
  • Upload date:
  • Size: 337.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for jangada_ai-1.2.1.tar.gz
Algorithm Hash digest
SHA256 600bd86304e8785b1521de593aee47f07cc033bd54331bf0c1595950634286f5
MD5 8838011a7a8b3a8ea4c0bda4b0d1e746
BLAKE2b-256 ffc14385f49244763177185b6cb0366fb550d1942ce85cf47df53d89a02e47fb

See more details on using hashes here.

Provenance

The following attestation bundles were made for jangada_ai-1.2.1.tar.gz:

Publisher: publish.yml on nerigleston/jangada

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jangada_ai-1.2.1-py3-none-any.whl.

File metadata

  • Download URL: jangada_ai-1.2.1-py3-none-any.whl
  • Upload date:
  • Size: 137.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for jangada_ai-1.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2f5ff88adf2d27605b5adb5fba05dfb82159b5823f88366fe53e0766071f9c97
MD5 1fcbcc29155aa0d06481efd8d529e838
BLAKE2b-256 74ec68abf3a43ac1b42782f7aac81aee37cae9f7f06e29757c403bee5d2b3330

See more details on using hashes here.

Provenance

The following attestation bundles were made for jangada_ai-1.2.1-py3-none-any.whl:

Publisher: publish.yml on nerigleston/jangada

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page