fix: add namespace, model, and provider to langfuse metadata so we can filter (#565)
This commit is contained in:
parent
51497f174f
commit
2c50791642
|
|
@ -31,6 +31,7 @@ from .runtime import (
|
|||
effective_temperature,
|
||||
plan_attempt,
|
||||
resolve_runtime_model_config,
|
||||
update_current_langfuse_observation,
|
||||
)
|
||||
from .tool_loop import execute_tool_loop
|
||||
from .types import (
|
||||
|
|
@ -193,13 +194,19 @@ async def honcho_llm_call(
|
|||
current_attempt.set(1)
|
||||
|
||||
def _get_attempt_plan() -> AttemptPlan:
|
||||
return plan_attempt(
|
||||
plan = plan_attempt(
|
||||
runtime_model_config=runtime_model_config,
|
||||
attempt=current_attempt.get(),
|
||||
retry_attempts=retry_attempts,
|
||||
call_thinking_budget_tokens=thinking_budget_tokens,
|
||||
call_reasoning_effort=reasoning_effort,
|
||||
)
|
||||
update_current_langfuse_observation(
|
||||
plan.provider,
|
||||
plan.model,
|
||||
name=track_name,
|
||||
)
|
||||
return plan
|
||||
|
||||
async def _call_with_provider_selection() -> (
|
||||
HonchoLLMCallResponse[Any] | AsyncIterator[HonchoLLMCallStreamChunk]
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ from src.config import (
|
|||
ModelConfig,
|
||||
ModelTransport,
|
||||
resolve_model_config,
|
||||
settings,
|
||||
)
|
||||
|
||||
from .registry import backend_for_provider, client_for_model_config
|
||||
|
|
@ -32,6 +33,33 @@ logger = logging.getLogger(__name__)
|
|||
current_attempt: ContextVar[int] = ContextVar("current_attempt", default=0)
|
||||
|
||||
|
||||
def update_current_langfuse_observation(
|
||||
provider: ModelTransport,
|
||||
model: str,
|
||||
*,
|
||||
name: str | None = None,
|
||||
) -> None:
|
||||
"""Best-effort annotation of the current Langfuse span with LLM routing."""
|
||||
if not settings.LANGFUSE_PUBLIC_KEY:
|
||||
return
|
||||
|
||||
try:
|
||||
from langfuse import get_client
|
||||
|
||||
update_kwargs: dict[str, Any] = {
|
||||
"metadata": {
|
||||
"namespace": settings.NAMESPACE,
|
||||
"provider": provider,
|
||||
"model": model,
|
||||
}
|
||||
}
|
||||
if name is not None:
|
||||
update_kwargs["name"] = name
|
||||
get_client().update_current_span(**update_kwargs)
|
||||
except Exception as exc: # pragma: no cover - best-effort telemetry
|
||||
logger.debug("Failed to update Langfuse span metadata: %s", exc)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AttemptPlan:
|
||||
"""Per-attempt plan produced by `plan_attempt`.
|
||||
|
|
@ -204,4 +232,5 @@ __all__ = [
|
|||
"resolve_backend_for_plan",
|
||||
"resolve_runtime_model_config",
|
||||
"select_model_config_for_attempt",
|
||||
"update_current_langfuse_observation",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -23,7 +23,12 @@ from openai.types.chat.chat_completion_message import ChatCompletionMessage
|
|||
from openai.types.completion_usage import CompletionUsage
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from src.config import ConfiguredModelSettings, ModelConfig, ResolvedFallbackConfig
|
||||
from src.config import (
|
||||
ConfiguredModelSettings,
|
||||
ModelConfig,
|
||||
ResolvedFallbackConfig,
|
||||
settings,
|
||||
)
|
||||
from src.exceptions import LLMError, ValidationException
|
||||
from src.llm import (
|
||||
CLIENTS,
|
||||
|
|
@ -905,6 +910,44 @@ class TestMainLLMCallFunction:
|
|||
|
||||
assert response.content == "No retry response"
|
||||
|
||||
async def test_track_name_updates_langfuse_span_name(self):
|
||||
"""track_name should rename the top-level Langfuse span."""
|
||||
|
||||
mock_llm_client = AsyncMock(spec=AsyncAnthropic)
|
||||
mock_response = Mock()
|
||||
mock_response.content = [TextBlock(text="Named response", type="text")]
|
||||
mock_response.usage = Usage(input_tokens=5, output_tokens=5)
|
||||
mock_response.stop_reason = "stop"
|
||||
mock_llm_client.messages.create = AsyncMock(return_value=mock_response)
|
||||
|
||||
mock_langfuse_client = Mock()
|
||||
|
||||
with (
|
||||
patch.dict(CLIENTS, {"anthropic": mock_llm_client}),
|
||||
patch.object(settings, "LANGFUSE_PUBLIC_KEY", "test-public-key"),
|
||||
patch("langfuse.get_client", return_value=mock_langfuse_client),
|
||||
):
|
||||
response = await honcho_llm_call(
|
||||
model_config=ConfiguredModelSettings(
|
||||
model="claude-4-sonnet",
|
||||
transport="anthropic",
|
||||
),
|
||||
prompt="Hello",
|
||||
max_tokens=100,
|
||||
enable_retry=False,
|
||||
track_name="Dialectic Agent",
|
||||
)
|
||||
|
||||
assert response.content == "Named response"
|
||||
mock_langfuse_client.update_current_span.assert_called_once_with(
|
||||
name="Dialectic Agent",
|
||||
metadata={
|
||||
"namespace": settings.NAMESPACE,
|
||||
"provider": "anthropic",
|
||||
"model": "claude-4-sonnet",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class TestEdgeCases:
|
||||
"""Tests for edge cases and boundary conditions"""
|
||||
|
|
|
|||
Loading…
Reference in New Issue