From c9d41f9bb9fc0308017a134fdbf76fa9e9927862 Mon Sep 17 00:00:00 2001 From: Viliam Gonda Date: Tue, 3 Nov 2026 16:19:12 +0100 Subject: [PATCH] fix(langchain): fallback to exception class name when error message is empty --- langfuse/langchain/CallbackHandler.py | 2 +- tests/unit/test_langchain.py | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/langfuse/langchain/CallbackHandler.py b/langfuse/langchain/CallbackHandler.py index dac76f4fc..bdc5bace6 100644 --- a/langfuse/langchain/CallbackHandler.py +++ b/langfuse/langchain/CallbackHandler.py @@ -343,7 +343,7 @@ def _get_error_level_and_status_message( if any(isinstance(error, t) for t in CONTROL_FLOW_EXCEPTION_TYPES): return "DEFAULT", str(error) or type(error).__name__ - return "ERROR", str(error) + return "ERROR", str(error) or type(error).__name__ def _get_observation_type_from_serialized( self, serialized: Optional[Dict[str, Any]], callback_type: str, **kwargs: Any diff --git a/tests/unit/test_langchain.py b/tests/unit/test_langchain.py index c55eaa3d2..95c81d547 100644 --- a/tests/unit/test_langchain.py +++ b/tests/unit/test_langchain.py @@ -487,6 +487,32 @@ class DummyControlFlowError(RuntimeError): ) +def test_error_without_message_uses_exception_type_as_status_message( + langfuse_memory_client, get_span +): + handler = CallbackHandler() + run_id = uuid4() + + handler.on_tool_start( + {"name": "failing_tool"}, + "{}", + run_id=run_id, + ) + handler.on_tool_error( + ValueError(), + run_id=run_id, + ) + + handler._langfuse_client.flush() + + span = get_span("failing_tool") + assert span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_LEVEL] == "ERROR" + assert ( + span.attributes[LangfuseOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE] + == "ValueError" + ) + + def test_streamed_llm_error_cleans_only_its_completion_start_time_memo( langfuse_memory_client, ):