Fix double execution of service functions when tracing errors occur

The outer try/except in each service decorator caught both tracing
setup errors and application errors from the wrapped function. If the
function itself raised (e.g. LLM rate limit, TTS timeout), the
exception was caught and the function was called a second time.

Fix by tracking whether the original function was called via a
fn_called flag. If the function was already called, re-raise the
exception instead of falling back to untraced re-execution.
This commit is contained in:
Mark Backman
2026-02-12 22:03:55 -05:00
parent 3640c7a2dd
commit e50b138ab2

View File

@@ -230,19 +230,21 @@ def traced_tts(func: Optional[Callable] = None, *, name: Optional[str] = None) -
@functools.wraps(f) @functools.wraps(f)
async def gen_wrapper(self, text, *args, **kwargs): async def gen_wrapper(self, text, *args, **kwargs):
try: if not getattr(self, "_tracing_enabled", False):
# Check if tracing is enabled for this service instance async for item in f(self, text, *args, **kwargs):
if not getattr(self, "_tracing_enabled", False): yield item
async for item in f(self, text, *args, **kwargs): return
yield item
return
fn_called = False
try:
async with tracing_context(self, text): async with tracing_context(self, text):
fn_called = True
async for item in f(self, text, *args, **kwargs): async for item in f(self, text, *args, **kwargs):
yield item yield item
except Exception as e: except Exception as e:
if fn_called:
raise
logging.error(f"Error in TTS tracing (continuing without tracing): {e}") logging.error(f"Error in TTS tracing (continuing without tracing): {e}")
# If tracing fails, fall back to the original function
async for item in f(self, text, *args, **kwargs): async for item in f(self, text, *args, **kwargs):
yield item yield item
@@ -251,16 +253,18 @@ def traced_tts(func: Optional[Callable] = None, *, name: Optional[str] = None) -
@functools.wraps(f) @functools.wraps(f)
async def wrapper(self, text, *args, **kwargs): async def wrapper(self, text, *args, **kwargs):
try: if not getattr(self, "_tracing_enabled", False):
# Check if tracing is enabled for this service instance return await f(self, text, *args, **kwargs)
if not getattr(self, "_tracing_enabled", False):
return await f(self, text, *args, **kwargs)
fn_called = False
try:
async with tracing_context(self, text): async with tracing_context(self, text):
fn_called = True
return await f(self, text, *args, **kwargs) return await f(self, text, *args, **kwargs)
except Exception as e: except Exception as e:
if fn_called:
raise
logging.error(f"Error in TTS tracing (continuing without tracing): {e}") logging.error(f"Error in TTS tracing (continuing without tracing): {e}")
# If tracing fails, fall back to the original function
return await f(self, text, *args, **kwargs) return await f(self, text, *args, **kwargs)
return wrapper return wrapper
@@ -293,11 +297,11 @@ def traced_stt(func: Optional[Callable] = None, *, name: Optional[str] = None) -
def decorator(f): def decorator(f):
@functools.wraps(f) @functools.wraps(f)
async def wrapper(self, transcript, is_final, language=None): async def wrapper(self, transcript, is_final, language=None):
try: if not getattr(self, "_tracing_enabled", False):
# Check if tracing is enabled for this service instance return await f(self, transcript, is_final, language)
if not getattr(self, "_tracing_enabled", False):
return await f(self, transcript, is_final, language)
fn_called = False
try:
service_class_name = self.__class__.__name__ service_class_name = self.__class__.__name__
span_name = "stt" span_name = "stt"
@@ -332,14 +336,16 @@ def traced_stt(func: Optional[Callable] = None, *, name: Optional[str] = None) -
) )
# Call the original function # Call the original function
fn_called = True
return await f(self, transcript, is_final, language) return await f(self, transcript, is_final, language)
except Exception as e: except Exception as e:
# Log any exception but don't disrupt the main flow # Log any exception but don't disrupt the main flow
logging.warning(f"Error in STT transcription tracing: {e}") logging.warning(f"Error in STT transcription tracing: {e}")
raise raise
except Exception as e: except Exception as e:
if fn_called:
raise
logging.error(f"Error in STT tracing (continuing without tracing): {e}") logging.error(f"Error in STT tracing (continuing without tracing): {e}")
# If tracing fails, fall back to the original function
return await f(self, transcript, is_final, language) return await f(self, transcript, is_final, language)
return wrapper return wrapper
@@ -374,11 +380,11 @@ def traced_llm(func: Optional[Callable] = None, *, name: Optional[str] = None) -
def decorator(f): def decorator(f):
@functools.wraps(f) @functools.wraps(f)
async def wrapper(self, context, *args, **kwargs): async def wrapper(self, context, *args, **kwargs):
try: if not getattr(self, "_tracing_enabled", False):
# Check if tracing is enabled for this service instance return await f(self, context, *args, **kwargs)
if not getattr(self, "_tracing_enabled", False):
return await f(self, context, *args, **kwargs)
fn_called = False
try:
service_class_name = self.__class__.__name__ service_class_name = self.__class__.__name__
span_name = "llm" span_name = "llm"
@@ -525,6 +531,7 @@ def traced_llm(func: Optional[Callable] = None, *, name: Optional[str] = None) -
# Don't raise - let the function execute anyway # Don't raise - let the function execute anyway
# Run function with modified push_frame to capture the output # Run function with modified push_frame to capture the output
fn_called = True
result = await f(self, context, *args, **kwargs) result = await f(self, context, *args, **kwargs)
# Add aggregated output after function completes, if available # Add aggregated output after function completes, if available
@@ -550,8 +557,9 @@ def traced_llm(func: Optional[Callable] = None, *, name: Optional[str] = None) -
if ttfb is not None: if ttfb is not None:
current_span.set_attribute("metrics.ttfb", ttfb) current_span.set_attribute("metrics.ttfb", ttfb)
except Exception as e: except Exception as e:
if fn_called:
raise
logging.error(f"Error in LLM tracing (continuing without tracing): {e}") logging.error(f"Error in LLM tracing (continuing without tracing): {e}")
# If tracing fails, fall back to the original function
return await f(self, context, *args, **kwargs) return await f(self, context, *args, **kwargs)
return wrapper return wrapper
@@ -583,11 +591,11 @@ def traced_gemini_live(operation: str) -> Callable:
def decorator(func): def decorator(func):
@functools.wraps(func) @functools.wraps(func)
async def wrapper(self, *args, **kwargs): async def wrapper(self, *args, **kwargs):
try: if not getattr(self, "_tracing_enabled", False):
# Check if tracing is enabled for this service instance return await func(self, *args, **kwargs)
if not getattr(self, "_tracing_enabled", False):
return await func(self, *args, **kwargs)
fn_called = False
try:
service_class_name = self.__class__.__name__ service_class_name = self.__class__.__name__
span_name = f"{operation}" span_name = f"{operation}"
@@ -849,6 +857,7 @@ def traced_gemini_live(operation: str) -> Callable:
current_span.set_attribute("metrics.ttfb", ttfb) current_span.set_attribute("metrics.ttfb", ttfb)
# Run the original function # Run the original function
fn_called = True
result = await func(self, *args, **kwargs) result = await func(self, *args, **kwargs)
return result return result
@@ -859,8 +868,9 @@ def traced_gemini_live(operation: str) -> Callable:
raise raise
except Exception as e: except Exception as e:
if fn_called:
raise
logging.error(f"Error in Gemini Live tracing (continuing without tracing): {e}") logging.error(f"Error in Gemini Live tracing (continuing without tracing): {e}")
# If tracing fails, fall back to the original function
return await func(self, *args, **kwargs) return await func(self, *args, **kwargs)
return wrapper return wrapper
@@ -889,11 +899,11 @@ def traced_openai_realtime(operation: str) -> Callable:
def decorator(func): def decorator(func):
@functools.wraps(func) @functools.wraps(func)
async def wrapper(self, *args, **kwargs): async def wrapper(self, *args, **kwargs):
try: if not getattr(self, "_tracing_enabled", False):
# Check if tracing is enabled for this service instance return await func(self, *args, **kwargs)
if not getattr(self, "_tracing_enabled", False):
return await func(self, *args, **kwargs)
fn_called = False
try:
service_class_name = self.__class__.__name__ service_class_name = self.__class__.__name__
span_name = f"{operation}" span_name = f"{operation}"
@@ -1072,6 +1082,7 @@ def traced_openai_realtime(operation: str) -> Callable:
current_span.set_attribute("metrics.ttfb", ttfb) current_span.set_attribute("metrics.ttfb", ttfb)
# Run the original function # Run the original function
fn_called = True
result = await func(self, *args, **kwargs) result = await func(self, *args, **kwargs)
return result return result
@@ -1082,8 +1093,9 @@ def traced_openai_realtime(operation: str) -> Callable:
raise raise
except Exception as e: except Exception as e:
if fn_called:
raise
logging.error(f"Error in OpenAI Realtime tracing (continuing without tracing): {e}") logging.error(f"Error in OpenAI Realtime tracing (continuing without tracing): {e}")
# If tracing fails, fall back to the original function
return await func(self, *args, **kwargs) return await func(self, *args, **kwargs)
return wrapper return wrapper