fix: treat GraphInterrupt as default-level in @observe decorator#1687
Open
michaelxer wants to merge 1 commit into
Open
fix: treat GraphInterrupt as default-level in @observe decorator#1687michaelxer wants to merge 1 commit into
michaelxer wants to merge 1 commit into
Conversation
…d CallbackHandler LangGraph HITL interrupts (GraphInterrupt, NodeInterrupt) were logged as level=ERROR through the @observe decorator path. The CallbackHandler already handled this via CONTROL_FLOW_EXCEPTION_TYPES, but @observe did not. Extract shared control flow exception utility so both paths use the same logic. GraphBubbleUp subclasses now return level=DEFAULT consistently. Fixes #14034
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
LangGraph HITL interrupts (
GraphInterrupt,NodeInterrupt) are incorrectly logged aslevel="ERROR"when using the@observedecorator. TheCallbackHandleralready handles this correctly viaCONTROL_FLOW_EXCEPTION_TYPES, but@observedoes not check for control flow exceptions.Root Cause
In
observe.py, all 4 exception handlers unconditionally setlevel="ERROR":Fix
New shared utility
langfuse/_utils/control_flow.py: exportsget_error_level(error)that returns"DEFAULT"forGraphBubbleUpsubclasses,"ERROR"for everything elseModified
observe.py: all 4 handlers now uselevel=get_error_level(e)instead of hardcoded"ERROR"Modified
CallbackHandler.py: imports from shared utility instead of maintaining its own copyVerification
GraphInterrupt→level="DEFAULT"✓NodeInterrupt→level="DEFAULT"✓RuntimeError→level="ERROR"✓Fixes langfuse/langfuse#14034
Greptile Summary
This PR fixes
GraphInterruptandNodeInterrupt(LangGraph HITL control-flow exceptions) being incorrectly logged atlevel="ERROR"by the@observedecorator. It introduces a sharedget_error_level()utility inlangfuse/_utils/control_flow.pyand applies it to all four exception-handling paths inobserve.py, while also refactoringCallbackHandler.pyto use the same utility instead of its own inline copy.langfuse/_utils/control_flow.py: Exportsget_error_level(error)andCONTROL_FLOW_EXCEPTION_TYPES; safely optional-importsGraphBubbleUpfromlanggraph.observe.py: All four handlers (async, sync, sync-generator, async-generator) now callget_error_level(e)instead of hardcoding"ERROR".CallbackHandler.py:_get_error_level_and_status_messagedelegates toget_error_level;CONTROL_FLOW_EXCEPTION_TYPESis imported but no longer referenced.Confidence Score: 4/5
Safe to merge; the logic change is correct and well-scoped, with only minor housekeeping issues in CallbackHandler.py.
The core fix is straightforward and correct — get_error_level is properly shared across both code paths. The only issues are in CallbackHandler.py: an import placed after module-level constants instead of at the top of the file, and CONTROL_FLOW_EXCEPTION_TYPES being imported but never actually used after the refactor.
langfuse/langchain/CallbackHandler.py — the import placement and unused symbol should be cleaned up.
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Exception raised in decorated function] --> B{get_error_level} B --> C{Is instance of\nCONTROL_FLOW_EXCEPTION_TYPES?} C -- Yes\n e.g. GraphInterrupt\nNodeInterrupt --> D[level = DEFAULT] C -- No\n e.g. RuntimeError\nValueError --> E[level = ERROR] D --> F[span.update\nlevel=DEFAULT, status_message] E --> G[span.update\nlevel=ERROR, status_message] F --> H[Re-raise exception] G --> H subgraph control_flow.py B C end subgraph observe.py F G end subgraph CallbackHandler.py I[_get_error_level_and_status_message] --> B endPrompt To Fix All With AI
Reviews (1): Last reviewed commit: "fix: treat GraphInterrupt as default-lev..." | Re-trigger Greptile
Context used:
Learned From
langfuse/langfuse-python#1387