Compare commits

...

4 Commits

Author SHA1 Message Date
5db9799444 Create test_judge.py 2026-05-11 11:34:29 -03:00
4388dc98c5 Langfuse 2026-05-08 00:39:42 -03:00
0237fff63c Langfuse 2026-05-08 00:37:24 -03:00
dc58e8c19b Langfuse 2026-05-06 14:02:37 -03:00
10 changed files with 926 additions and 44 deletions

176
README.md
View File

@@ -940,6 +940,182 @@ Endpoint OTLP (para envio de traces):
![img_2.png](img_2.png) ![img_2.png](img_2.png)
### 10.2 Integração com Langfuse via OpenTelemetry
O projeto já possui suporte a tracing utilizando OpenTelemetry (OTEL), o que permite integrar facilmente plataformas de observabilidade como:
- Langfuse
- Phoenix
- Jaeger
- Grafana Tempo
- Elastic APM
A arquitetura atual já está desacoplada do backend de observabilidade, portanto a adaptação para o Langfuse exige apenas pequenas alterações.
---
#### Como funciona
O fluxo será:
```text
NeMo Guardrails
OpenTelemetry
OTLP Exporter
Langfuse
```
---
#### Código Atual
O projeto já possui um `tracing.py` semelhante a:
```python
exporter = OTLPSpanExporter(
endpoint=endpoint,
timeout=5
)
```
Atualmente ele publica para:
```python
http://localhost:6006/v1/traces
```
que normalmente corresponde ao Phoenix.
---
### Modificações Necessárias
#### 10.2.1 Adicionar autenticação do Langfuse
>**Nota:** O Langfuse exige autenticação Basic Auth no exporter OTLP.
Renomeie o **tracing_langfuse.py** para **tracing.py** caso deseje adotar o Langfuse como padrão.
---
#### 10.2.2 Adicionar variáveis de ambiente
Adicionar no `.env`:
```bash
ENABLE_TRACING=true
LANGFUSE_PUBLIC_KEY=pk-xxxxxxxx
LANGFUSE_SECRET_KEY=sk-xxxxxxxx
OTEL_EXPORTER_OTLP_ENDPOINT=https://cloud.langfuse.com/api/public/otel
```
---
#### 10.2.3 Explicação das Variáveis
| Variável | Descrição |
|---|---|
| `ENABLE_TRACING` | Ativa ou desativa o tracing |
| `LANGFUSE_PUBLIC_KEY` | Chave pública do projeto Langfuse |
| `LANGFUSE_SECRET_KEY` | Chave secreta do projeto Langfuse |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Endpoint OTLP do Langfuse |
---
#### 10.2.4 Alteração no tracing.py
**Código original**
```python
exporter = OTLPSpanExporter(
endpoint=endpoint,
timeout=5
)
```
---
#### 10.2.5 Código adaptado para Langfuse
```python
import base64
public_key = os.getenv(
"LANGFUSE_PUBLIC_KEY"
)
secret_key = os.getenv(
"LANGFUSE_SECRET_KEY"
)
headers = {}
if public_key and secret_key:
auth = base64.b64encode(
f"{public_key}:{secret_key}".encode()
).decode()
headers["Authorization"] = (
f"Basic {auth}"
)
exporter = OTLPSpanExporter(
endpoint=endpoint,
headers=headers,
timeout=5
)
```
---
#### 10.2.6 O que muda após isso
O projeto passará a enviar automaticamente:
- traces
- spans
- latência
- execução de rails
- execução de judges
- tool calls
- chamadas LLM
- erros
- retries
para o Langfuse.
---
#### 10.2.7 Exemplo de Trace Visualizado
```text
TRACE
├── input_guardrail
├── supervisor
├── llm_router
├── mcp_tool
├── judge_groundedness
└── final_response
```
#### 10.2.8 Adaptação para uso do Langfuse
A implementação atual utiliza o **tracing.py** como padrão de observabilidade, porém existe outro código montado para autenticação com **Langfuse** chamado **tracing_langfuse.py**. Bastando utilizar a variáveis de memórias adicionais de autenticação como informado acima.
Portanto a integração com Langfuse exige apenas:
- alteração do endpoint
- inclusão do Authorization header
- configuração das variáveis de ambiente
---
## 11. Como executar o projeto ## 11. Como executar o projeto
### 11.1 Teste demonstrável sem proxy ### 11.1 Teste demonstrável sem proxy

View File

@@ -5,9 +5,12 @@ OPENAI_API_KEY=dummy
OPENAI_MODEL=gpt-5 OPENAI_MODEL=gpt-5
# Tracing / Phoenix / OpenTelemetry # Tracing / Phoenix / OpenTelemetry
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:6006/v1/traces export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:8087/api/public/otel/v1/traces
OTEL_SERVICE_NAME=nemo-guardrails-demo export OTEL_SERVICE_NAME=nemo-guardrails-demo
ENABLE_TRACING=true export ENABLE_TRACING=true
export LANGFUSE_SECRET_KEY="sk-lf-0bb4d15d-6101-4874-991b-68780cf7b748"
export LANGFUSE_PUBLIC_KEY="pk-lf-1b51d3b4-af8d-4b8b-8ac4-e81e6006123f"
export LANGFUSE_BASE_URL="http://localhost:8087"
# Modo demo: usa cliente fake se o proxy não estiver disponível # Modo demo: usa cliente fake se o proxy não estiver disponível
USE_MOCK_LLM=false USE_MOCK_LLM=false

View File

@@ -1,64 +1,142 @@
import os import os
import base64
from contextlib import contextmanager from contextlib import contextmanager
_TRACING_ENABLED = os.getenv("ENABLE_TRACING", "false").lower() == "true" _TRACING_ENABLED = os.getenv(
"ENABLE_TRACING",
"false"
).lower() == "true"
tracer = None tracer = None
if _TRACING_ENABLED: if _TRACING_ENABLED:
try: try:
from opentelemetry import trace from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import (
from opentelemetry.sdk.trace.export import BatchSpanProcessor TracerProvider
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter )
from opentelemetry.sdk.resources import (
Resource
)
from opentelemetry.sdk.trace.export import (
BatchSpanProcessor
)
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
OTLPSpanExporter
)
endpoint = os.getenv( endpoint = os.getenv(
"OTEL_EXPORTER_OTLP_ENDPOINT", "OTEL_EXPORTER_OTLP_ENDPOINT",
"http://localhost:6006/v1/traces" "http://localhost:8087/api/public/otel/v1/traces"
) )
# ✅ Define metadata do serviço (IMPORTANTE pro Phoenix) # -----------------------------------
# LANGFUSE AUTH
# -----------------------------------
public_key = os.getenv(
"LANGFUSE_PUBLIC_KEY"
)
secret_key = os.getenv(
"LANGFUSE_SECRET_KEY"
)
headers = {}
if public_key and secret_key:
auth = base64.b64encode(
f"{public_key}:{secret_key}".encode()
).decode()
headers["Authorization"] = (
f"Basic {auth}"
)
# -----------------------------------
# RESOURCE
# -----------------------------------
resource = Resource.create({ resource = Resource.create({
"service.name": "nemo_guardrails_governed_project" "service.name":
"nemo_guardrails_governed_project"
}) })
provider = TracerProvider(resource=resource) provider = TracerProvider(
resource=resource
)
exporter = OTLPSpanExporter( exporter = OTLPSpanExporter(
endpoint=endpoint, endpoint=endpoint,
timeout=5 # evita travamentos headers=headers,
timeout=5
) )
span_processor = BatchSpanProcessor(exporter) span_processor = BatchSpanProcessor(
provider.add_span_processor(span_processor) exporter
)
# ✅ Evita sobrescrever provider existente provider.add_span_processor(
if not isinstance(trace.get_tracer_provider(), TracerProvider): span_processor
)
if not isinstance(
trace.get_tracer_provider(),
TracerProvider
):
trace.set_tracer_provider(provider) trace.set_tracer_provider(provider)
tracer = trace.get_tracer(__name__) tracer = trace.get_tracer(__name__)
print(
f"[Tracing Enabled] endpoint={endpoint}"
)
except Exception as e: except Exception as e:
print(f"[Tracing Disabled] Error initializing tracing: {e}")
print(
f"[Tracing Disabled] Error: {e}"
)
tracer = None tracer = None
@contextmanager @contextmanager
def span(name: str, **attrs): def span(name: str, **attrs):
if tracer is None: if tracer is None:
yield None yield None
return return
try: try:
with tracer.start_as_current_span(name) as sp: with tracer.start_as_current_span(name) as sp:
for k, v in attrs.items(): for k, v in attrs.items():
# ✅ mantém tipo quando possível
if isinstance(v, (str, int, float, bool)): if isinstance(
v,
(str, int, float, bool)
):
sp.set_attribute(k, v) sp.set_attribute(k, v)
else: else:
sp.set_attribute(k, str(v)) sp.set_attribute(k, str(v))
yield sp yield sp
except Exception as e: except Exception as e:
print(f"[Tracing Error] Span '{name}' failed: {e}")
yield None print(
f"[Tracing Error] Span '{name}' failed: {e}"
)
yield None

View File

@@ -0,0 +1,142 @@
import os
import base64
from contextlib import contextmanager
_TRACING_ENABLED = os.getenv(
"ENABLE_TRACING",
"false"
).lower() == "true"
tracer = None
if _TRACING_ENABLED:
try:
from opentelemetry import trace
from opentelemetry.sdk.trace import (
TracerProvider
)
from opentelemetry.sdk.resources import (
Resource
)
from opentelemetry.sdk.trace.export import (
BatchSpanProcessor
)
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
OTLPSpanExporter
)
endpoint = os.getenv(
"OTEL_EXPORTER_OTLP_ENDPOINT",
"http://localhost:8087/api/public/otel/v1/traces"
)
# -----------------------------------
# LANGFUSE AUTH
# -----------------------------------
public_key = os.getenv(
"LANGFUSE_PUBLIC_KEY"
)
secret_key = os.getenv(
"LANGFUSE_SECRET_KEY"
)
headers = {}
if public_key and secret_key:
auth = base64.b64encode(
f"{public_key}:{secret_key}".encode()
).decode()
headers["Authorization"] = (
f"Basic {auth}"
)
# -----------------------------------
# RESOURCE
# -----------------------------------
resource = Resource.create({
"service.name":
"nemo_guardrails_governed_project"
})
provider = TracerProvider(
resource=resource
)
exporter = OTLPSpanExporter(
endpoint=endpoint,
headers=headers,
timeout=5
)
span_processor = BatchSpanProcessor(
exporter
)
provider.add_span_processor(
span_processor
)
if not isinstance(
trace.get_tracer_provider(),
TracerProvider
):
trace.set_tracer_provider(provider)
tracer = trace.get_tracer(__name__)
print(
f"[Tracing Enabled] endpoint={endpoint}"
)
except Exception as e:
print(
f"[Tracing Disabled] Error: {e}"
)
tracer = None
@contextmanager
def span(name: str, **attrs):
if tracer is None:
yield None
return
try:
with tracer.start_as_current_span(name) as sp:
for k, v in attrs.items():
if isinstance(
v,
(str, int, float, bool)
):
sp.set_attribute(k, v)
else:
sp.set_attribute(k, str(v))
yield sp
except Exception as e:
print(
f"[Tracing Error] Span '{name}' failed: {e}"
)
yield None

View File

@@ -0,0 +1,64 @@
import os
from contextlib import contextmanager
_TRACING_ENABLED = os.getenv("ENABLE_TRACING", "false").lower() == "true"
tracer = None
if _TRACING_ENABLED:
try:
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
endpoint = os.getenv(
"OTEL_EXPORTER_OTLP_ENDPOINT",
"http://localhost:6006/v1/traces"
)
# ✅ Define metadata do serviço (IMPORTANTE pro Phoenix)
resource = Resource.create({
"service.name": "nemo_guardrails_governed_project"
})
provider = TracerProvider(resource=resource)
exporter = OTLPSpanExporter(
endpoint=endpoint,
timeout=5 # evita travamentos
)
span_processor = BatchSpanProcessor(exporter)
provider.add_span_processor(span_processor)
# ✅ Evita sobrescrever provider existente
if not isinstance(trace.get_tracer_provider(), TracerProvider):
trace.set_tracer_provider(provider)
tracer = trace.get_tracer(__name__)
except Exception as e:
print(f"[Tracing Disabled] Error initializing tracing: {e}")
tracer = None
@contextmanager
def span(name: str, **attrs):
if tracer is None:
yield None
return
try:
with tracer.start_as_current_span(name) as sp:
for k, v in attrs.items():
# ✅ mantém tipo quando possível
if isinstance(v, (str, int, float, bool)):
sp.set_attribute(k, v)
else:
sp.set_attribute(k, str(v))
yield sp
except Exception as e:
print(f"[Tracing Error] Span '{name}' failed: {e}")
yield None

View File

@@ -0,0 +1,132 @@
import os
from company_nemo_guardrails.judges import (
classificar_sentimento,
avaliar_alucinacao,
avaliar_qualidade_resposta,
avaliar_tom_de_voz
)
# ==========================================
# CONFIGURAÇÃO
# ==========================================
# MOCK = true -> usa mock local
# MOCK = false -> usa OCI/OpenAI real
os.environ["USE_MOCK_LLM"] = "false"
# Se quiser usar o proxy real:
#
# os.environ["USE_MOCK_LLM"] = "false"
# os.environ["OPENAI_BASE_URL"] = "http://localhost:8051/v1"
# os.environ["OPENAI_API_KEY"] = "dummy"
# os.environ["OPENAI_MODEL"] = "gpt-5"
# ==========================================
# HELPER
# ==========================================
def print_result(title, result):
print("\n" + "=" * 80)
print(f"🧪 TESTE: {title}")
print("=" * 80)
print(f"CODE : {result.code}")
print(f"ALLOWED : {result.allowed}")
print(f"REASON : {result.reason}")
print(f"MECHANISM : {result.mechanism}")
print(f"SANITIZED_TEXT : {result.sanitized_text}")
print("\nDATA:")
print(result.data)
print("=" * 80)
# ==========================================
# TESTE CSI
# ==========================================
def test_csi():
result = classificar_sentimento(
"""
Estou muito insatisfeito com o atendimento.
Quero cancelar meu plano imediatamente.
"""
)
print_result("CSI - Sentimento Negativo", result)
# ==========================================
# TESTE ALUCINAÇÃO
# ==========================================
def test_alucinacao():
result = avaliar_alucinacao(
resposta="""
O cliente pode cancelar em até 30 dias sem multa.
""",
dados_reais="""
O cliente pode cancelar em até 7 dias sem multa.
"""
)
print_result("ALUC - Alucinação Detectada", result)
# ==========================================
# TESTE QUALIDADE
# ==========================================
def test_qualidade():
result = avaliar_qualidade_resposta(
pergunta="""
Como faço para cancelar meu plano?
""",
resposta="""
Para cancelar seu plano,
acesse o portal do cliente,
vá até a seção Financeiro
e clique em Cancelamento.
"""
)
print_result("RQLT - Qualidade Resposta", result)
# ==========================================
# TESTE TOM DE VOZ
# ==========================================
def test_tom_voz():
result = avaliar_tom_de_voz(
"""
Se vira. Não posso fazer nada por você.
"""
)
print_result("VCTN - Tom Inadequado", result)
# ==========================================
# MAIN
# ==========================================
if __name__ == "__main__":
test_csi()
test_alucinacao()
test_qualidade()
test_tom_voz()

View File

@@ -5,9 +5,12 @@ OPENAI_API_KEY=dummy
OPENAI_MODEL=gpt-5 OPENAI_MODEL=gpt-5
# Tracing / Phoenix / OpenTelemetry # Tracing / Phoenix / OpenTelemetry
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:6006/v1/traces export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:8087/api/public/otel/v1/traces
OTEL_SERVICE_NAME=nemo-guardrails-demo export OTEL_SERVICE_NAME=nemo-guardrails-demo
ENABLE_TRACING=true export ENABLE_TRACING=true
export LANGFUSE_SECRET_KEY="sk-lf-0bb4d15d-6101-4874-991b-68780cf7b748"
export LANGFUSE_PUBLIC_KEY="pk-lf-1b51d3b4-af8d-4b8b-8ac4-e81e6006123f"
export LANGFUSE_BASE_URL="http://localhost:8087"
# Modo demo: usa cliente fake se o proxy não estiver disponível # Modo demo: usa cliente fake se o proxy não estiver disponível
USE_MOCK_LLM=false USE_MOCK_LLM=false

View File

@@ -1,64 +1,142 @@
import os import os
import base64
from contextlib import contextmanager from contextlib import contextmanager
_TRACING_ENABLED = os.getenv("ENABLE_TRACING", "false").lower() == "true" _TRACING_ENABLED = os.getenv(
"ENABLE_TRACING",
"false"
).lower() == "true"
tracer = None tracer = None
if _TRACING_ENABLED: if _TRACING_ENABLED:
try: try:
from opentelemetry import trace from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import (
from opentelemetry.sdk.trace.export import BatchSpanProcessor TracerProvider
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter )
from opentelemetry.sdk.resources import (
Resource
)
from opentelemetry.sdk.trace.export import (
BatchSpanProcessor
)
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
OTLPSpanExporter
)
endpoint = os.getenv( endpoint = os.getenv(
"OTEL_EXPORTER_OTLP_ENDPOINT", "OTEL_EXPORTER_OTLP_ENDPOINT",
"http://localhost:6006/v1/traces" "http://localhost:8087/api/public/otel/v1/traces"
) )
# ✅ Define metadata do serviço (IMPORTANTE pro Phoenix) # -----------------------------------
# LANGFUSE AUTH
# -----------------------------------
public_key = os.getenv(
"LANGFUSE_PUBLIC_KEY"
)
secret_key = os.getenv(
"LANGFUSE_SECRET_KEY"
)
headers = {}
if public_key and secret_key:
auth = base64.b64encode(
f"{public_key}:{secret_key}".encode()
).decode()
headers["Authorization"] = (
f"Basic {auth}"
)
# -----------------------------------
# RESOURCE
# -----------------------------------
resource = Resource.create({ resource = Resource.create({
"service.name": "nemo_guardrails_governed_project" "service.name":
"nemo_guardrails_governed_project"
}) })
provider = TracerProvider(resource=resource) provider = TracerProvider(
resource=resource
)
exporter = OTLPSpanExporter( exporter = OTLPSpanExporter(
endpoint=endpoint, endpoint=endpoint,
timeout=5 # evita travamentos headers=headers,
timeout=5
) )
span_processor = BatchSpanProcessor(exporter) span_processor = BatchSpanProcessor(
provider.add_span_processor(span_processor) exporter
)
# ✅ Evita sobrescrever provider existente provider.add_span_processor(
if not isinstance(trace.get_tracer_provider(), TracerProvider): span_processor
)
if not isinstance(
trace.get_tracer_provider(),
TracerProvider
):
trace.set_tracer_provider(provider) trace.set_tracer_provider(provider)
tracer = trace.get_tracer(__name__) tracer = trace.get_tracer(__name__)
print(
f"[Tracing Enabled] endpoint={endpoint}"
)
except Exception as e: except Exception as e:
print(f"[Tracing Disabled] Error initializing tracing: {e}")
print(
f"[Tracing Disabled] Error: {e}"
)
tracer = None tracer = None
@contextmanager @contextmanager
def span(name: str, **attrs): def span(name: str, **attrs):
if tracer is None: if tracer is None:
yield None yield None
return return
try: try:
with tracer.start_as_current_span(name) as sp: with tracer.start_as_current_span(name) as sp:
for k, v in attrs.items(): for k, v in attrs.items():
# ✅ mantém tipo quando possível
if isinstance(v, (str, int, float, bool)): if isinstance(
v,
(str, int, float, bool)
):
sp.set_attribute(k, v) sp.set_attribute(k, v)
else: else:
sp.set_attribute(k, str(v)) sp.set_attribute(k, str(v))
yield sp yield sp
except Exception as e: except Exception as e:
print(f"[Tracing Error] Span '{name}' failed: {e}")
yield None print(
f"[Tracing Error] Span '{name}' failed: {e}"
)
yield None

View File

@@ -0,0 +1,142 @@
import os
import base64
from contextlib import contextmanager
_TRACING_ENABLED = os.getenv(
"ENABLE_TRACING",
"false"
).lower() == "true"
tracer = None
if _TRACING_ENABLED:
try:
from opentelemetry import trace
from opentelemetry.sdk.trace import (
TracerProvider
)
from opentelemetry.sdk.resources import (
Resource
)
from opentelemetry.sdk.trace.export import (
BatchSpanProcessor
)
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
OTLPSpanExporter
)
endpoint = os.getenv(
"OTEL_EXPORTER_OTLP_ENDPOINT",
"http://localhost:8087/api/public/otel/v1/traces"
)
# -----------------------------------
# LANGFUSE AUTH
# -----------------------------------
public_key = os.getenv(
"LANGFUSE_PUBLIC_KEY"
)
secret_key = os.getenv(
"LANGFUSE_SECRET_KEY"
)
headers = {}
if public_key and secret_key:
auth = base64.b64encode(
f"{public_key}:{secret_key}".encode()
).decode()
headers["Authorization"] = (
f"Basic {auth}"
)
# -----------------------------------
# RESOURCE
# -----------------------------------
resource = Resource.create({
"service.name":
"nemo_guardrails_governed_project"
})
provider = TracerProvider(
resource=resource
)
exporter = OTLPSpanExporter(
endpoint=endpoint,
headers=headers,
timeout=5
)
span_processor = BatchSpanProcessor(
exporter
)
provider.add_span_processor(
span_processor
)
if not isinstance(
trace.get_tracer_provider(),
TracerProvider
):
trace.set_tracer_provider(provider)
tracer = trace.get_tracer(__name__)
print(
f"[Tracing Enabled] endpoint={endpoint}"
)
except Exception as e:
print(
f"[Tracing Disabled] Error: {e}"
)
tracer = None
@contextmanager
def span(name: str, **attrs):
if tracer is None:
yield None
return
try:
with tracer.start_as_current_span(name) as sp:
for k, v in attrs.items():
if isinstance(
v,
(str, int, float, bool)
):
sp.set_attribute(k, v)
else:
sp.set_attribute(k, str(v))
yield sp
except Exception as e:
print(
f"[Tracing Error] Span '{name}' failed: {e}"
)
yield None

View File

@@ -0,0 +1,64 @@
import os
from contextlib import contextmanager
_TRACING_ENABLED = os.getenv("ENABLE_TRACING", "false").lower() == "true"
tracer = None
if _TRACING_ENABLED:
try:
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
endpoint = os.getenv(
"OTEL_EXPORTER_OTLP_ENDPOINT",
"http://localhost:6006/v1/traces"
)
# ✅ Define metadata do serviço (IMPORTANTE pro Phoenix)
resource = Resource.create({
"service.name": "nemo_guardrails_governed_project"
})
provider = TracerProvider(resource=resource)
exporter = OTLPSpanExporter(
endpoint=endpoint,
timeout=5 # evita travamentos
)
span_processor = BatchSpanProcessor(exporter)
provider.add_span_processor(span_processor)
# ✅ Evita sobrescrever provider existente
if not isinstance(trace.get_tracer_provider(), TracerProvider):
trace.set_tracer_provider(provider)
tracer = trace.get_tracer(__name__)
except Exception as e:
print(f"[Tracing Disabled] Error initializing tracing: {e}")
tracer = None
@contextmanager
def span(name: str, **attrs):
if tracer is None:
yield None
return
try:
with tracer.start_as_current_span(name) as sp:
for k, v in attrs.items():
# ✅ mantém tipo quando possível
if isinstance(v, (str, int, float, bool)):
sp.set_attribute(k, v)
else:
sp.set_attribute(k, str(v))
yield sp
except Exception as e:
print(f"[Tracing Error] Span '{name}' failed: {e}")
yield None