Merge pull request #3735 from pipecat-ai/mb/fix-llm-tracing-error-handilng
Fix double execution of service functions on tracing errors
This commit is contained in:
1
changelog/3735.fixed.md
Normal file
1
changelog/3735.fixed.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
- Fixed tracing service decorators executing the wrapped function twice when the function itself raised an exception (e.g., LLM rate limit, TTS timeout).
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user