From 7374149d656774d5b7d226348163327ddcaad837 Mon Sep 17 00:00:00 2001 From: AP Date: Tue, 13 Oct 2026 16:55:27 +0530 Subject: [PATCH] fix(huggingface_hub): stop swallowing stream iteration errors Split out of #7946 per review. Mid-stream provider exceptions raised by the text-generation and chat-completion iterators were swallowed by the outer capture_internal_exceptions(); they now bubble out to the caller while the span is still closed out. --- sentry_sdk/integrations/huggingface_hub.py | 115 ++++++++++++--------- 1 file changed, 66 insertions(+), 49 deletions(-) diff --git a/sentry_sdk/integrations/huggingface_hub.py b/sentry_sdk/integrations/huggingface_hub.py index b2e6b3ef71..8231749930 100644 --- a/sentry_sdk/integrations/huggingface_hub.py +++ b/sentry_sdk/integrations/huggingface_hub.py @@ -297,29 +297,36 @@ def new_details_iterator() -> "Iterable[Any]": response_text_buffer: "list[str]" = [] tokens_used = 0 - with capture_internal_exceptions(): + try: for chunk in res: - if ( - hasattr(chunk, "token") - and hasattr(chunk.token, "text") - and chunk.token.text is not None - ): - response_text_buffer.append(chunk.token.text) + with capture_internal_exceptions(): + if ( + hasattr(chunk, "token") + and hasattr(chunk.token, "text") + and chunk.token.text is not None + ): + response_text_buffer.append(chunk.token.text) - if hasattr(chunk, "details") and hasattr( - chunk.details, "finish_reason" - ): - finish_reason = chunk.details.finish_reason + if hasattr(chunk, "details") and hasattr( + chunk.details, "finish_reason" + ): + finish_reason = chunk.details.finish_reason - if ( - hasattr(chunk, "details") - and hasattr(chunk.details, "generated_tokens") - and chunk.details.generated_tokens is not None - ): - tokens_used = chunk.details.generated_tokens + if ( + hasattr(chunk, "details") + and hasattr(chunk.details, "generated_tokens") + and chunk.details.generated_tokens is not None + ): + tokens_used = chunk.details.generated_tokens yield chunk + except Exception: + exc_info = sys.exc_info() + with capture_internal_exceptions(): + span.__exit__(*exc_info) + reraise(*exc_info) + with capture_internal_exceptions(): if finish_reason is not None: set_data_normalized( span, @@ -360,44 +367,54 @@ def new_iterator() -> "Iterable[ChatCompletionStreamOutput]": tool_calls = None usage = None - with capture_internal_exceptions(): + try: for chunk in cast("Iterable[ChatCompletionStreamOutput]", res): - if hasattr(chunk, "model") and chunk.model is not None: - response_model = chunk.model - - if hasattr(chunk, "usage") and chunk.usage is not None: - usage = chunk.usage - - if isinstance(chunk, str): - if chunk is not None: - response_text_buffer.append(chunk) - - if hasattr(chunk, "choices") and chunk.choices is not None: - for choice in chunk.choices: - if ( - hasattr(choice, "delta") - and hasattr(choice.delta, "content") - and choice.delta.content is not None - ): - response_text_buffer.append( - choice.delta.content - ) + with capture_internal_exceptions(): + if hasattr(chunk, "model") and chunk.model is not None: + response_model = chunk.model - if ( - hasattr(choice, "finish_reason") - and choice.finish_reason is not None - ): - finish_reason = choice.finish_reason + if hasattr(chunk, "usage") and chunk.usage is not None: + usage = chunk.usage - if ( - hasattr(choice, "delta") - and hasattr(choice.delta, "tool_calls") - and choice.delta.tool_calls is not None - ): - tool_calls = choice.delta.tool_calls + if isinstance(chunk, str): + if chunk is not None: + response_text_buffer.append(chunk) + + if ( + hasattr(chunk, "choices") + and chunk.choices is not None + ): + for choice in chunk.choices: + if ( + hasattr(choice, "delta") + and hasattr(choice.delta, "content") + and choice.delta.content is not None + ): + response_text_buffer.append( + choice.delta.content + ) + + if ( + hasattr(choice, "finish_reason") + and choice.finish_reason is not None + ): + finish_reason = choice.finish_reason + + if ( + hasattr(choice, "delta") + and hasattr(choice.delta, "tool_calls") + and choice.delta.tool_calls is not None + ): + tool_calls = choice.delta.tool_calls yield chunk + except Exception: + exc_info = sys.exc_info() + with capture_internal_exceptions(): + span.__exit__(*exc_info) + reraise(*exc_info) + with capture_internal_exceptions(): if response_model is not None: _set_span_data_attribute( span, SPANDATA.GEN_AI_RESPONSE_MODEL, response_model