mirror of
https://github.com/hoshikawa2/compass_backoffice.git
synced 2026-07-10 14:14:21 +00:00
first commit
This commit is contained in:
112
src/providers/langfuse_provider.py
Normal file
112
src/providers/langfuse_provider.py
Normal file
@@ -0,0 +1,112 @@
|
||||
"""
|
||||
Local Langfuse client wrapper with enriched error handling.
|
||||
|
||||
The get_prompt_from_langfuse() method from agent_framework.prompts silently
|
||||
captures all exceptions without indicating the root cause of the failure.
|
||||
This module replaces that function with a wrapper that:
|
||||
- Distinguishes between authentication errors, prompt not found, and network errors.
|
||||
- Logs the root cause in a structured way.
|
||||
- Supports retrieval by label (e.g. "production") instead of version "latest".
|
||||
"""
|
||||
|
||||
import langfuse
|
||||
from typing import Optional, Tuple
|
||||
from src.core.logging import get_logger
|
||||
from src.core.config import settings
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def _build_httpx_client():
|
||||
"""Mirror setup_observer's behavior: honor settings.OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE."""
|
||||
cert_path = settings.OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE
|
||||
if not cert_path:
|
||||
return None
|
||||
try:
|
||||
import httpx
|
||||
return httpx.Client(verify=cert_path)
|
||||
except Exception as exc:
|
||||
logger.warning("Failed to build httpx client for Langfuse provider: %s", exc)
|
||||
return None
|
||||
|
||||
|
||||
def get_prompt_with_config_from_langfuse(
|
||||
prompt_name: str,
|
||||
label: str = "production",
|
||||
) -> Tuple[Optional[str], dict]:
|
||||
"""
|
||||
Fetches a text prompt + its `config` dict from Langfuse.
|
||||
|
||||
Langfuse permite anexar um objeto JSON `config` ao lado do corpo do
|
||||
prompt (editável pela mesma UI). Usamos isso para guardar tuning
|
||||
parameters que o squad de prompt-engineering pode ajustar sem deploy
|
||||
(ex.: tamanho de janelas de histórico, thresholds, flags).
|
||||
|
||||
Args:
|
||||
prompt_name: Nome do prompt registrado no Langfuse.
|
||||
label: Label a recuperar (default: "production").
|
||||
|
||||
Returns:
|
||||
Tupla `(content, config)`. `content=None` quando o prompt não foi
|
||||
encontrado ou houve erro de rede/auth — nesses casos o caller deve
|
||||
cair em fallback local. `config` é `{}` quando o prompt não tem
|
||||
config setado.
|
||||
"""
|
||||
|
||||
try:
|
||||
client_kwargs = {
|
||||
"public_key": settings.LANGFUSE_PUBLIC_KEY,
|
||||
"secret_key": settings.LANGFUSE_SECRET_KEY,
|
||||
"host": settings.LANGFUSE_BASE_URL,
|
||||
}
|
||||
httpx_client = _build_httpx_client()
|
||||
if httpx_client is not None:
|
||||
client_kwargs["httpx_client"] = httpx_client
|
||||
client = langfuse.Langfuse(**client_kwargs)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Failed to initialize the Langfuse client. Check LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY and LANGFUSE_BASE_URL. "
|
||||
f"Error: {e}"
|
||||
)
|
||||
return None, {}
|
||||
|
||||
try:
|
||||
prompt_obj = client.get_prompt(name=prompt_name, label=label)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Unexpected error while fetching prompt '{prompt_name}' from Langfuse: "
|
||||
f"{type(e).__name__}: {e}"
|
||||
)
|
||||
return None, {}
|
||||
|
||||
# The Langfuse v3 SDK returns different types depending on the prompt type.
|
||||
# For text prompts (type="text"), the content is in the .prompt attribute.
|
||||
if prompt_obj is None:
|
||||
logger.warning(f"Langfuse returned None for prompt '{prompt_name}'.")
|
||||
return None, {}
|
||||
|
||||
# Try both known SDK attributes to ensure version compatibility.
|
||||
content = getattr(prompt_obj, "prompt", None) or getattr(prompt_obj, "content", None)
|
||||
|
||||
if not content:
|
||||
logger.warning(
|
||||
f"Prompt '{prompt_name}' found but has no content. "
|
||||
f"Available attributes: {[a for a in dir(prompt_obj) if not a.startswith('_')]}"
|
||||
)
|
||||
return None, {}
|
||||
|
||||
# `config` é opcional no Langfuse. Normalizamos para dict para que o
|
||||
# caller possa sempre fazer `config.get(...)` sem checar tipo.
|
||||
raw_config = getattr(prompt_obj, "config", None)
|
||||
config = raw_config if isinstance(raw_config, dict) else {}
|
||||
|
||||
return content, config
|
||||
|
||||
|
||||
def get_prompt_from_langfuse(
|
||||
prompt_name: str,
|
||||
label: str = "production",
|
||||
) -> Optional[str]:
|
||||
"""Backwards-compatible wrapper que ignora o `config` do prompt."""
|
||||
content, _config = get_prompt_with_config_from_langfuse(prompt_name, label)
|
||||
return content
|
||||
Reference in New Issue
Block a user