Files
2026-06-13 08:23:21 -03:00

4.9 KiB

Padrão de implementação para clients de API externa

Este documento define o padrão obrigatório para qualquer client novo que faça chamadas a APIs externas (HTTP ou outras). Ele garante que todas as requisições apareçam no Langfuse com endpoint, status code e response body, sem que o dev precise escrever código de tracing manual.

1. Use sempre traced_async_client / traced_sync_client

Nunca instancie httpx.AsyncClient (ou httpx.Client) diretamente em um client de API. Use os factories de src/utils/http.py:

from src.utils.http import traced_async_client

async with traced_async_client(timeout=self.timeout) as client:
    response = await client.get(url, headers=headers)
    # ...

O factory devolve um httpx.AsyncClient que automaticamente:

  • registra o endpoint real (URL completa, com path e query string) da request realizada — não depende de self.base_url ou self.url;
  • registra o status code retornado;
  • registra o response body (JSON ou texto truncado em 2000 chars);
  • registra metadata também em erros de rede (timeout, connect error) com status_code=None e o erro como body.

Argumentos comuns aceitos: timeout, verify_ssl (default settings.VERIFY_SSL), headers, base_url, response_sanitizer, e qualquer outro kwarg de httpx.AsyncClient.

2. Decore os métodos públicos com @trace_tool

Aplique @trace_tool em todo método que faz chamada externa, incluindo helpers privados de autenticação (_get_token, _refresh_xxx). Cada método decorado vira um span filho próprio no Langfuse, com nome NomeDaClasse.nome_do_metodo — então uma classe com múltiplas APIs (ex: SpeechAnalyticsClient._get_token vs .get_prediction vs .get_history) não perde a granularidade.

from src.utils.observer import trace_tool

class MyClient:
    @trace_tool
    async def get_something(self, ...) -> ...:
        ...

3. Convenções de nomenclatura

  • Métodos com verbo + recurso: get_history, post_prediction, open_service_request. O nome do método aparece no nome do span.
  • __init__ não precisa expor base_url para fins de tracing — o endpoint real vem da request. Mantenha self.base_url apenas se o client realmente precisa dela para construir URLs.

4. Sanitização de payloads sensíveis

Quando a resposta contém credenciais, tokens ou dados sensíveis que não devem ir para o Langfuse, passe um response_sanitizer ao factory. A função recebe o body já parseado e devolve a versão filtrada:

def _sanitize_token_response(body):
    if not isinstance(body, dict):
        return body
    return {
        "expires_in": body.get("expires_in"),
        "status": body.get("status"),
    }

async with traced_async_client(
    timeout=settings.SPEECH_TIMEOUT,
    response_sanitizer=_sanitize_token_response,
) as client:
    response = await client.post(url, data=...)

Exemplo real: SpeechAnalyticsClient._get_token.

5. Clients que não usam HTTP

Para clients que não falam HTTP (ex: TaisKbClient, que usa OCI SDK + oracledb), basta decorar o método público com @trace_tool. O decorator cai no fallback de logar o retorno da função como output do span. Não tente sintetizar um endpoint via set_tool_call_metadata — apenas adiciona ruído.

6. Exemplo mínimo

import httpx
import logging
from src.utils.observer import trace_tool
from src.utils.http import traced_async_client
from src.core.config import settings

logger = logging.getLogger(__name__)


class FooClient:
    def __init__(self) -> None:
        self.base_url = settings.FOO_API_BASE_URL
        self.token = settings.FOO_API_TOKEN
        self.timeout = settings.FOO_API_TIMEOUT

    @trace_tool
    async def get_foo(self, foo_id: str) -> dict:
        url = f"{self.base_url}/foo/{foo_id}"
        headers = {"Authorization": f"Bearer {self.token}"}
        try:
            async with traced_async_client(timeout=self.timeout) as client:
                response = await client.get(url, headers=headers)
                response.raise_for_status()
                return response.json()
        except httpx.HTTPStatusError as exc:
            logger.error(f"Foo API HTTP error {exc.response.status_code}")
            raise

Nada de set_tool_call_metadata espalhado pelo método. O span no Langfuse já vai conter endpoint, status_code e response automaticamente.

Checklist de revisão

Ao revisar um PR de novo client, valide:

  • Usa traced_async_client / traced_sync_client (não httpx.AsyncClient direto).
  • Métodos públicos têm @trace_tool.
  • Não há chamadas manuais a set_tool_call_metadata.
  • Respostas com dados sensíveis passam por response_sanitizer.
  • Nomes de método são verbo_recurso (legíveis como nome de span).