first commit

This commit is contained in:
2026-06-13 08:23:21 -03:00
commit 89c23fb0ed
439 changed files with 32801 additions and 0 deletions

View File

@@ -0,0 +1,137 @@
# 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`](../src/utils/http.py):
```python
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.
```python
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:
```python
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`](../src/components/clients/speech_analytics_client.py).
## 5. Clients que não usam HTTP
Para clients que não falam HTTP (ex: [`TaisKbClient`](../src/components/clients/tais_kb_client.py),
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
```python
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).