Melhores práticas
Um apanhado de recomendações para tirar o máximo da jangada em produção. Cada item aponta para o guia detalhado da capacidade correspondente.
Provider e modelo
- Troque por configuração, não por código. Mantenha
provider,modeleapi_keyem variáveis de ambiente. A promessa da lib é trocar o provider sem mexer no resto — aproveite isso para alternar entre ambientes (dev/prod) e fazer testes A/B de modelo. - Use o modelo certo para cada tarefa. Um modelo forte para raciocínio/escrita
e um modelo barato (ex.:
llama-3.1-8b-instant) para classificação, triagem e judges de guardrail. Não pague por capacidade que a tarefa não exige. - Deixe os perfis normalizarem os params. Não escreva
if model == ...para ajustartemperature/max_tokens: a camada de perfis já adapta o payload por modelo (ver Parâmetros).
Sempre defina max_tokens explicitamente em extrações longas. Modelos com
thinking (ex.: Gemini 2.5) podem consumir o orçamento de saída e truncar o
JSON — um max_tokens folgado evita respostas cortadas.
Structured output
- Valide sempre contra o schema. Use
parse()/aparse()com um modelo Pydantic; não confie em parsing manual de texto. comp.parsedé confiável — a jangada coage automaticamente: se um SDK devolverparsed=Nonecom JSON válido em.text, a lib valida o texto pelo schema (semmodel_validate_jsonmanual).- JSON fora do schema entra no failover — vira
errors.OutputValidationErrore tenta o próximo modelo dowith_fallback(sem repetir o mesmo). O fallback cobre erro de API e saída malformada. - Campos opcionais com
default=Noneevitam que o modelo invente valores quando o dado não existe. Veja Structured output.
Guardrails
- Cheque o escopo na entrada, uma vez. Em agentes com loop de ferramentas,
não coloque o
ScopeGuardno LLM que itera — a cada passo o histórico cresce e a reavaliação pode recusar uma fala válida. Faça a checagem com um LLM "porteiro" antes de iniciar o agente. - Use
raise_on_block=Truequando quiser tratar a recusa no seu fluxo (ex.: responder com uma mensagem própria) em vez de devolver aCompletionde recusa. fail_closed=Trueem domínios sensíveis: se o judge falhar, barra (seguro). DeixeFalsequando disponibilidade importa mais que rigor.- Reserve a blocklist para termos óbvios (regex, custo zero) e deixe o judge decidir o escopo semântico. Veja Guardrails.
RAG
- Comece com busca híbrida (
mode="hybrid"): combina vetorial e BM25 por RRF e costuma superar só vetorial em perguntas com termos exatos. - Use
task="document"ao indexar etask="query"ao buscar — alguns providers (Gemini) diferenciam, e o orquestradorRAGjá faz isso por você. - Deixe um agente decidir quando recuperar. Nem toda mensagem precisa de RAG: exponha a busca como uma ferramenta e deixe o modelo chamá-la só quando a pergunta exigir contexto — saudações e conversa não devem disparar a base.
- Adicione um
reranker(Reranker.cohere()/voyage()): é o maior ganho de qualidade por esforço — o retriever garante recall, o reranker põe os trechos certos no topo. Combine comsemantic_chunker(emb)no índice (chunks que não cortam ideias). - Perguntas vagas? Use
strategy="multi_query"(o LLM gera variações e funde). Contexto longo?parent_chunk_size=+strategy="parent_document". Custo de tokens alto?compress=True. - Reindexe com
sync_document(incremental, dedup por hash) em vez deadd_documentquando a base é re-subida — só reembeda o que mudou. - Ajuste
k,min_scoreechunkao seu conteúdo. Veja RAG.
Retry e fallback
- Configure retry para erros transitórios (429, 5xx, timeouts) com backoff e
jitter=True. Não faça failover em erros de auth (401/403) ou bad request (400/422) — trocar de provider não resolve. - Encadeie um fallback barato → forte → alternativo com
with_fallback. O failover acontece antes do primeiro token, inclusive em streaming. - Veja Retry e fallback e Erros.
Custo e observabilidade
- Leia
usageecostem cada resposta e agregue por fluxo (Flow/Graphsomam automaticamente). Sobrescreva preços comregister_priceconforme seu contrato. - Agrupe chamadas num
Trace. Uma requisição do seu serviço = um lote de observações (detecção, extração, conferência...), facilitando auditoria e diagnóstico. Veja Custo e Observabilidade.
Agentes e ferramentas
- Descreva bem cada ferramenta. O docstring é o que o modelo lê para decidir quando chamá-la — diga o que faz e quando (não) usar.
- Force aritmética via tool. Para somas/contas, use
tool_choice="required"numa ferramenta de cálculo em vez de confiar na conta "de cabeça" do modelo. - Limite
max_iterationsem agentes para evitar loops longos, e prefiraSquadsequencial quando os papéis são claros (pesquisar → analisar → redigir). - Veja Tools e Agentes.
Cache (economia de tokens e latência)
-
Comece pelo
ExactCache. Para prompts que se repetem idênticos (FAQ, reprocessamento, retries do usuário) ele zera o custo da 2ª chamada em diante. Ajustemax_sizeettlao seu tráfego. -
Use
SemanticCachepara paráfrases. Quando perguntas têm o mesmo sentido com palavras diferentes, ele compara por embeddings e acerta o cache. Precisa de um LLM embedder e de umthreshold(similaridade mínima): comece em ~0.45e suba se vier resposta de cache "parecida demais" mas errada. -
Tenha um fallback de cache. Se o provider de embeddings não suportar embeddings (
UnsupportedError) ou faltar chave, caia paraExactCache— é o padrão doescritor-ia:try: cache = SemanticCache(embedder, threshold=0.45) except UnsupportedError: cache = ExactCache(max_size=512, ttl=3600) llm = LLM(provider, model, cache=cache) -
Veja Cache.
Orquestração: escolha a abstração certa
Flowpara um pipeline linear e previsível (limpar → estruturar → resumir). Cadastepvira variável{{ }}do próximo; um step pode terschema=para sair tipado. Custos somam automaticamente.Graphquando há roteamento condicional (ex.: classificar e mandar para o ramo técnico ou geral). Use quando o caminho depende do conteúdo.Agent/Squadquando o próprio modelo deve decidir os passos e quando chamar ferramentas. Não use agente para um pipeline fixo —Flowé mais barato e determinístico.- Veja Flows e Agentes.
Transcrição (áudio e vídeo)
- O Whisper aceita vídeo, mas tem teto de tamanho. Os endpoints de STT
(OpenAI/Groq) transcrevem
mp4direto — extraem a faixa de áudio sozinhos —, porém há um limite de ~25 MB por arquivo. Um vídeo de reunião estoura isso com facilidade. - Pré-processe a mídia no seu serviço, não na lib. Antes de transcrever,
extraia só o áudio e normalize para algo leve (mono, 16 kHz, comprimido) com
ffmpeg. Isso cabe no limite, acelera o upload e padroniza o container. Mantenha essa etapa no seu backend: a jangada é fina de propósito e não embute dependências de sistema (comoffmpeg) — ela só repassa os bytes ao provider viaAudio.from_bytes(dados, mime, name=...). - Preserve a extensão no
name. O Whisper usa o nome do arquivo para inferir o container; ao reprocessar, devolva algo comoreuniao.mp3. - Tenha um fallback. Se o
ffmpegnão estiver disponível (ou falhar num codec exótico), mande o arquivo original — ummp4pequeno ainda transcreve. - Veja Transcrição de áudio.
Async e FastAPI
- Use
acomplete/aparse/astreamem handlers async. Para o pipeline síncrono (parsing de arquivos, chamadas em lote), rode em thread (anyio.to_thread.run_sync) para não bloquear o event loop. - Em streaming, devolva um
StreamingResponseconsumindo oastream. Veja Streaming.
Não exponha suas chaves no front-end. O navegador deve falar com o seu
backend; é o backend que detém as api_key dos providers.
Pontos de atenção (como a lib se comporta)
Não são armadilhas — são contratos da lib: alguns defaults a ajustar, outras formas canônicas de uso, e uma fronteira clara de responsabilidade. Tudo verificado no comportamento atual:
| # | Ponto | Como lidar |
|---|---|---|
| 1 | max_tokens default = 8192. É alto, mas extrações maiores ainda podem truncar. | Em structured output a lib levanta errors.TruncatedError ("aumente max_tokens") antes do erro confuso de JSON. Ajuste conforme o modelo e o caso — ver Parâmetros. |
| 2 | parse/aparse retornam Completion, não a instância Pydantic. | Leia sempre resp.parsed (ver Structured output). |
| 3 | images= põe o texto primeiro e as imagens depois (não intercala por padrão). | Para rotular cada imagem, passe tuplas: images=[("frente", img1), ("verso", img2)]. Para controle total, monte history=[Message("user", [...])] — ver Mensagens e multimodal. |
| 4 | Templates {{ }} só renderizam quando você passa kwargs de variável. | Sem kwargs, o prompt passa intacto. E { } simples (JSON literal) nunca é tocado, mesmo com kwargs — JSON no prompt é seguro. |
| 5 | Params específicos do SDK vão por extra= (construtor) ou params= (chamada). | Para o thinking do Gemini, passe thinking_budget/thinking_level e a lib adapta à versão — ver Gemini. Reserve extra= para params sem nome canônico nem perfil. |
| 6 | Fallback cobre resposta malformada. Saída que não casa com o schema vira OutputValidationError e o with_fallback tenta o próximo modelo (além dos erros de API — timeout, rate limit, 5xx). | O fallback não repete o mesmo modelo (seria o mesmo JSON). Onde você faz parsing manual de texto cru, mantenha um loop/validação próprios. |
| 7 | detect_objects/adetect_objects devolvem só Detection (sem custo). | Use detect_objects_full/adetect_objects_full: devolvem um DetectionResult com .detections e .completion/.cost/.usage para fechar o acumulador por job. |
Custo multimodal: imagens já entram no custo como input tokens (os
providers contam assim) — nada a configurar além de ter o modelo na tabela de
preços. Áudio (transcrição Whisper/gpt-4o-transcribe) é cobrado por
minuto: o custo só aparece se a transcrição expuser a duração — passe
response_format="verbose_json" ou informe Audio.from_bytes(..., duration=...).
Ver Custo.