feat(otel): integrate OpenTelemetry for Langfuse monitoring and add configuration settings

This commit is contained in:
Davidson Gomes 2025-05-12 17:12:39 -03:00
parent f319b89806
commit ab1f528a34
6 changed files with 295 additions and 207 deletions

View File

@ -39,6 +39,10 @@ SENDGRID_API_KEY="your-sendgrid-api-key"
EMAIL_FROM="noreply@yourdomain.com"
APP_URL="https://yourdomain.com"
LANGFUSE_PUBLIC_KEY="your-langfuse-public-key"
LANGFUSE_SECRET_KEY="your-langfuse-secret-key"
OTEL_EXPORTER_OTLP_ENDPOINT="https://cloud.langfuse.com/api/public/otel"
# Server settings
HOST="0.0.0.0"
PORT=8000

View File

@ -49,6 +49,8 @@ dependencies = [
"jwcrypto==1.5.6",
"pyjwt[crypto]==2.9.0",
"langgraph==0.4.1",
"opentelemetry-sdk==1.33.0",
"opentelemetry-exporter-otlp==1.33.0",
]
[project.optional-dependencies]

View File

@ -84,6 +84,11 @@ class Settings(BaseSettings):
DEMO_PASSWORD: str = os.getenv("DEMO_PASSWORD", "demo123")
DEMO_CLIENT_NAME: str = os.getenv("DEMO_CLIENT_NAME", "Demo Client")
# Langfuse / OpenTelemetry settings
LANGFUSE_PUBLIC_KEY: str = os.getenv("LANGFUSE_PUBLIC_KEY", "")
LANGFUSE_SECRET_KEY: str = os.getenv("LANGFUSE_SECRET_KEY", "")
OTEL_EXPORTER_OTLP_ENDPOINT: str = os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "")
class Config:
env_file = ".env"
env_file_encoding = "utf-8"

View File

@ -7,6 +7,7 @@ from fastapi.staticfiles import StaticFiles
from src.config.database import engine, Base
from src.config.settings import settings
from src.utils.logger import setup_logger
from src.utils.otel import init_otel
# Necessary for other modules
from src.services.service_providers import session_service # noqa: F401
@ -85,6 +86,9 @@ app.include_router(session_router, prefix=API_PREFIX)
app.include_router(agent_router, prefix=API_PREFIX)
app.include_router(a2a_router, prefix=API_PREFIX)
# Inicializa o OpenTelemetry para Langfuse
init_otel()
@app.get("/")
def read_root():

View File

@ -11,6 +11,8 @@ from sqlalchemy.orm import Session
from typing import Optional, AsyncGenerator
import asyncio
import json
from src.utils.otel import get_tracer
from opentelemetry import trace
logger = setup_logger(__name__)
@ -25,6 +27,16 @@ async def run_agent(
db: Session,
session_id: Optional[str] = None,
timeout: float = 60.0,
):
tracer = get_tracer()
with tracer.start_as_current_span(
"run_agent",
attributes={
"agent_id": agent_id,
"external_id": external_id,
"session_id": session_id or f"{external_id}_{agent_id}",
"message": message,
},
):
exit_stack = None
try:
@ -110,7 +122,9 @@ async def run_agent(
if last_response:
await response_queue.put(last_response)
else:
await response_queue.put("Finished without specific response")
await response_queue.put(
"Finished without specific response"
)
execution_completed.set()
except Exception as e:
@ -128,7 +142,9 @@ async def run_agent(
p.cancel()
if not execution_completed.is_set():
logger.warning(f"Agent execution timed out after {timeout} seconds")
logger.warning(
f"Agent execution timed out after {timeout} seconds"
)
await response_queue.put(
"The response took too long and was interrupted."
)
@ -202,6 +218,18 @@ async def run_agent_stream(
db: Session,
session_id: Optional[str] = None,
) -> AsyncGenerator[str, None]:
tracer = get_tracer()
span = tracer.start_span(
"run_agent_stream",
attributes={
"agent_id": agent_id,
"external_id": external_id,
"session_id": session_id or f"{external_id}_{agent_id}",
"message": message,
},
)
try:
with trace.use_span(span, end_on_exit=True):
try:
logger.info(
f"Starting streaming execution of agent {agent_id} for external_id {external_id}"
@ -286,5 +314,9 @@ async def run_agent_stream(
logger.error(f"Error processing request: {str(e)}")
raise e
except Exception as e:
logger.error(f"Internal error processing request: {str(e)}", exc_info=True)
logger.error(
f"Internal error processing request: {str(e)}", exc_info=True
)
raise InternalServerError(str(e))
finally:
span.end()

41
src/utils/otel.py Normal file
View File

@ -0,0 +1,41 @@
import os
import base64
from src.config.settings import settings
from opentelemetry import trace
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
_otlp_initialized = False
def init_otel():
global _otlp_initialized
if _otlp_initialized:
return
if not (
settings.LANGFUSE_PUBLIC_KEY
and settings.LANGFUSE_SECRET_KEY
and settings.OTEL_EXPORTER_OTLP_ENDPOINT
):
return
langfuse_auth = base64.b64encode(
f"{settings.LANGFUSE_PUBLIC_KEY}:{settings.LANGFUSE_SECRET_KEY}".encode()
).decode()
os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"] = settings.OTEL_EXPORTER_OTLP_ENDPOINT
os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = f"Authorization=Basic {langfuse_auth}"
provider = TracerProvider(
resource=Resource.create({"service.name": "evo_ai_agent"})
)
exporter = OTLPSpanExporter()
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)
_otlp_initialized = True
def get_tracer(name: str = "evo_ai_agent"):
return trace.get_tracer(name)