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_urlouself.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=Nonee 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 exporbase_urlpara fins de tracing — o endpoint real vem da request. Mantenhaself.base_urlapenas 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ãohttpx.AsyncClientdireto). - 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).