xgae 0.1.5__py3-none-any.whl → 0.1.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of xgae might be problematic. Click here for more details.

@@ -5,9 +5,10 @@ import uuid
5
5
 
6
6
  from dataclasses import dataclass
7
7
  from datetime import datetime, timezone
8
- from typing import List, Dict, Any, Optional, AsyncGenerator, override
8
+ from typing import List, Dict, Any, Optional, AsyncGenerator, override, Literal
9
9
 
10
- from xgae.engine.responser.xga_responser_base import TaskResponseProcessor, ToolExecutionStrategy, XmlAddingStrategy, TaskResponseContext,TaskRunContinuousState
10
+ from xgae.utils import langfuse
11
+ from xgae.engine.responser.responser_base import TaskResponseProcessor, TaskResponserContext,TaskRunContinuousState,XmlAddingStrategy,ToolExecutionStrategy
11
12
  from xgae.utils.json_helpers import (
12
13
  ensure_dict, safe_json_parse,
13
14
  to_json_string, format_for_yield
@@ -55,7 +56,7 @@ class ProcessorConfig:
55
56
 
56
57
 
57
58
  class StreamTaskResponser(TaskResponseProcessor):
58
- def __init__(self, response_context: TaskResponseContext):
59
+ def __init__(self, response_context: TaskResponserContext):
59
60
  super().__init__(response_context)
60
61
 
61
62
  @override
@@ -129,14 +130,14 @@ class StreamTaskResponser(TaskResponseProcessor):
129
130
  # --- Save and Yield Start Events (only if not auto-continuing) ---
130
131
  if auto_continue_count == 0:
131
132
  start_content = {"status_type": "thread_run_start", "thread_run_id": thread_run_id}
132
- start_msg_obj = await self.add_message(
133
+ start_msg_obj = await self.add_response_message(
133
134
  type="status", content=start_content,
134
135
  is_llm_message=False, metadata={"thread_run_id": thread_run_id}
135
136
  )
136
137
  if start_msg_obj: yield format_for_yield(start_msg_obj)
137
138
 
138
139
  assist_start_content = {"status_type": "assistant_response_start"}
139
- assist_start_msg_obj = await self.add_message(
140
+ assist_start_msg_obj = await self.add_response_message(
140
141
  type="status", content=assist_start_content,
141
142
  is_llm_message=False, metadata={"thread_run_id": thread_run_id}
142
143
  )
@@ -204,7 +205,7 @@ class StreamTaskResponser(TaskResponseProcessor):
204
205
  __sequence += 1
205
206
  else:
206
207
  logging.info("XML tool call limit reached - not yielding more content chunks")
207
- self.trace.event(name="xml_tool_call_limit_reached", level="DEFAULT", status_message=(
208
+ langfuse.create_event(trace_context=self.trace_context, name="xml_tool_call_limit_reached", level="DEFAULT", status_message=(
208
209
  f"XML tool call limit reached - not yielding more content chunks"))
209
210
 
210
211
  # --- Process XML Tool Calls (if enabled and limit not reached) ---
@@ -226,8 +227,7 @@ class StreamTaskResponser(TaskResponseProcessor):
226
227
 
227
228
  if config.execute_tools and config.execute_on_stream:
228
229
  # Save and Yield tool_started status
229
- started_msg_obj = await self._yield_and_save_tool_started(context, thread_id,
230
- thread_run_id)
230
+ started_msg_obj = await self._add_tool_start_message(context)
231
231
  if started_msg_obj: yield format_for_yield(started_msg_obj)
232
232
  yielded_tool_indices.add(tool_index) # Mark status as yielded
233
233
 
@@ -304,8 +304,7 @@ class StreamTaskResponser(TaskResponseProcessor):
304
304
  )
305
305
 
306
306
  # Save and Yield tool_started status
307
- started_msg_obj = await self._yield_and_save_tool_started(context, thread_id,
308
- thread_run_id)
307
+ started_msg_obj = await self._add_tool_start_message(context)
309
308
  if started_msg_obj: yield format_for_yield(started_msg_obj)
310
309
  yielded_tool_indices.add(tool_index) # Mark status as yielded
311
310
 
@@ -318,7 +317,7 @@ class StreamTaskResponser(TaskResponseProcessor):
318
317
 
319
318
  if finish_reason == "xml_tool_limit_reached":
320
319
  logging.info("Stopping stream processing after loop due to XML tool call limit")
321
- self.trace.event(name="stopping_stream_processing_after_loop_due_to_xml_tool_call_limit",
320
+ langfuse.create_event(trace_context=self.trace_context, name="stopping_stream_processing_after_loop_due_to_xml_tool_call_limit",
322
321
  level="DEFAULT", status_message=(
323
322
  f"Stopping stream processing after loop due to XML tool call limit"))
324
323
  break
@@ -353,18 +352,18 @@ class StreamTaskResponser(TaskResponseProcessor):
353
352
  # f"🔥 Estimated tokens – prompt: {prompt_tokens}, "
354
353
  # f"completion: {completion_tokens}, total: {prompt_tokens + completion_tokens}"
355
354
  # )
356
- self.trace.event(name="usage_calculated_with_litellm_token_counter", level="DEFAULT",
355
+ langfuse.create_event(trace_context=self.trace_context, name="usage_calculated_with_litellm_token_counter", level="DEFAULT",
357
356
  status_message=(f"Usage calculated with litellm.token_counter"))
358
357
  except Exception as e:
359
358
  logging.warning(f"Failed to calculate usage: {str(e)}")
360
- self.trace.event(name="failed_to_calculate_usage", level="WARNING",
359
+ langfuse.create_event(trace_context=self.trace_context, name="failed_to_calculate_usage", level="WARNING",
361
360
  status_message=(f"Failed to calculate usage: {str(e)}"))
362
361
 
363
362
  # Wait for pending tool executions from streaming phase
364
363
  tool_results_buffer = [] # Stores (tool_call, result, tool_index, context)
365
364
  if pending_tool_executions:
366
365
  logging.info(f"Waiting for {len(pending_tool_executions)} pending streamed tool executions")
367
- self.trace.event(name="waiting_for_pending_streamed_tool_executions", level="DEFAULT", status_message=(
366
+ langfuse.create_event(trace_context=self.trace_context, name="waiting_for_pending_streamed_tool_executions", level="DEFAULT", status_message=(
368
367
  f"Waiting for {len(pending_tool_executions)} pending streamed tool executions"))
369
368
  # ... (asyncio.wait logic) ...
370
369
  pending_tasks = [execution["task"] for execution in pending_tool_executions]
@@ -388,24 +387,24 @@ class StreamTaskResponser(TaskResponseProcessor):
388
387
  if tool_name in ['ask', 'complete']:
389
388
  logging.info(
390
389
  f"Terminating tool '{tool_name}' completed during streaming. Setting termination flag.")
391
- self.trace.event(name="terminating_tool_completed_during_streaming",
390
+ langfuse.create_event(trace_context=self.trace_context, name="terminating_tool_completed_during_streaming",
392
391
  level="DEFAULT", status_message=(
393
392
  f"Terminating tool '{tool_name}' completed during streaming. Setting termination flag."))
394
393
  agent_should_terminate = True
395
394
 
396
395
  else: # Should not happen with asyncio.wait
397
396
  logging.warning(f"Task for tool index {tool_idx} not done after wait.")
398
- self.trace.event(name="task_for_tool_index_not_done_after_wait", level="WARNING",
397
+ langfuse.create_event(trace_context=self.trace_context, name="task_for_tool_index_not_done_after_wait", level="WARNING",
399
398
  status_message=(
400
399
  f"Task for tool index {tool_idx} not done after wait."))
401
400
  except Exception as e:
402
401
  logging.error(f"Error getting result for pending tool execution {tool_idx}: {str(e)}")
403
- self.trace.event(name="error_getting_result_for_pending_tool_execution", level="ERROR",
402
+ langfuse.create_event(trace_context=self.trace_context, name="error_getting_result_for_pending_tool_execution", level="ERROR",
404
403
  status_message=(
405
404
  f"Error getting result for pending tool execution {tool_idx}: {str(e)}"))
406
405
  context.error = e
407
406
  # Save and Yield tool error status message (even if started was yielded)
408
- error_msg_obj = await self._yield_and_save_tool_error(context, thread_id, thread_run_id)
407
+ error_msg_obj = await self._add_tool_error_message(context)
409
408
  if error_msg_obj: yield format_for_yield(error_msg_obj)
410
409
  continue # Skip further status yielding for this tool index
411
410
 
@@ -420,40 +419,39 @@ class StreamTaskResponser(TaskResponseProcessor):
420
419
  if tool_name in ['ask', 'complete']:
421
420
  logging.info(
422
421
  f"Terminating tool '{tool_name}' completed during streaming. Setting termination flag.")
423
- self.trace.event(name="terminating_tool_completed_during_streaming", level="DEFAULT",
422
+ langfuse.create_event(trace_context=self.trace_context, name="terminating_tool_completed_during_streaming", level="DEFAULT",
424
423
  status_message=(
425
424
  f"Terminating tool '{tool_name}' completed during streaming. Setting termination flag."))
426
425
  agent_should_terminate = True
427
426
 
428
427
  # Save and Yield tool completed/failed status
429
- completed_msg_obj = await self._yield_and_save_tool_completed(
430
- context, None, thread_id, thread_run_id
431
- )
428
+ completed_msg_obj = await self._add_tool_completed_message(
429
+ context, None)
432
430
  if completed_msg_obj: yield format_for_yield(completed_msg_obj)
433
431
  yielded_tool_indices.add(tool_idx)
434
432
  except Exception as e:
435
433
  logging.error(
436
434
  f"Error getting result/yielding status for pending tool execution {tool_idx}: {str(e)}")
437
- self.trace.event(name="error_getting_result_yielding_status_for_pending_tool_execution",
435
+ langfuse.create_event(trace_context=self.trace_context, name="error_getting_result_yielding_status_for_pending_tool_execution",
438
436
  level="ERROR", status_message=(
439
437
  f"Error getting result/yielding status for pending tool execution {tool_idx}: {str(e)}"))
440
438
  context.error = e
441
439
  # Save and Yield tool error status
442
- error_msg_obj = await self._yield_and_save_tool_error(context, thread_id, thread_run_id)
440
+ error_msg_obj = await self._add_tool_error_message(context)
443
441
  if error_msg_obj: yield format_for_yield(error_msg_obj)
444
442
  yielded_tool_indices.add(tool_idx)
445
443
 
446
444
  # Save and yield finish status if limit was reached
447
445
  if finish_reason == "xml_tool_limit_reached":
448
446
  finish_content = {"status_type": "finish", "finish_reason": "xml_tool_limit_reached"}
449
- finish_msg_obj = await self.add_message(
447
+ finish_msg_obj = await self.add_response_message(
450
448
  type="status", content=finish_content,
451
449
  is_llm_message=False, metadata={"thread_run_id": thread_run_id}
452
450
  )
453
451
  if finish_msg_obj: yield format_for_yield(finish_msg_obj)
454
452
  logging.info(
455
453
  f"Stream finished with reason: xml_tool_limit_reached after {xml_tool_call_count} XML tool calls")
456
- self.trace.event(name="stream_finished_with_reason_xml_tool_limit_reached_after_xml_tool_calls",
454
+ langfuse.create_event(trace_context=self.trace_context, name="stream_finished_with_reason_xml_tool_limit_reached_after_xml_tool_calls",
457
455
  level="DEFAULT", status_message=(
458
456
  f"Stream finished with reason: xml_tool_limit_reached after {xml_tool_call_count} XML tool calls"))
459
457
 
@@ -489,7 +487,7 @@ class StreamTaskResponser(TaskResponseProcessor):
489
487
  "tool_calls": complete_native_tool_calls or None
490
488
  }
491
489
 
492
- last_assistant_message_object = await self._add_message_with_agent_info(type="assistant", content=message_data,
490
+ last_assistant_message_object = await self.add_response_message(type="assistant", content=message_data,
493
491
  is_llm_message=True, metadata={"thread_run_id": thread_run_id}
494
492
  )
495
493
 
@@ -503,12 +501,12 @@ class StreamTaskResponser(TaskResponseProcessor):
503
501
  yield format_for_yield(yield_message)
504
502
  else:
505
503
  logging.error(f"Failed to save final assistant message for thread {thread_id}")
506
- self.trace.event(name="failed_to_save_final_assistant_message_for_thread", level="ERROR",
504
+ langfuse.create_event(trace_context=self.trace_context, name="failed_to_save_final_assistant_message_for_thread", level="ERROR",
507
505
  status_message=(f"Failed to save final assistant message for thread {thread_id}"))
508
506
  # Save and yield an error status
509
507
  err_content = {"role": "system", "status_type": "error",
510
508
  "message": "Failed to save final assistant message"}
511
- err_msg_obj = await self.add_message(
509
+ err_msg_obj = await self.add_response_message(
512
510
  type="status", content=err_content,
513
511
  is_llm_message=False, metadata={"thread_run_id": thread_run_id}
514
512
  )
@@ -571,7 +569,7 @@ class StreamTaskResponser(TaskResponseProcessor):
571
569
  # Populate from buffer if executed on stream
572
570
  if config.execute_on_stream and tool_results_buffer:
573
571
  logging.info(f"Processing {len(tool_results_buffer)} buffered tool results")
574
- self.trace.event(name="processing_buffered_tool_results", level="DEFAULT",
572
+ langfuse.create_event(trace_context=self.trace_context, name="processing_buffered_tool_results", level="DEFAULT",
575
573
  status_message=(f"Processing {len(tool_results_buffer)} buffered tool results"))
576
574
  for tool_call, result, tool_idx, context in tool_results_buffer:
577
575
  if last_assistant_message_object: context.assistant_message_id = last_assistant_message_object[
@@ -582,7 +580,7 @@ class StreamTaskResponser(TaskResponseProcessor):
582
580
  elif final_tool_calls_to_process and not config.execute_on_stream:
583
581
  logging.info(
584
582
  f"Executing {len(final_tool_calls_to_process)} tools ({config.tool_execution_strategy}) after stream")
585
- self.trace.event(name="executing_tools_after_stream", level="DEFAULT", status_message=(
583
+ langfuse.create_event(trace_context=self.trace_context, name="executing_tools_after_stream", level="DEFAULT", status_message=(
586
584
  f"Executing {len(final_tool_calls_to_process)} tools ({config.tool_execution_strategy}) after stream"))
587
585
  results_list = await self._execute_tools(final_tool_calls_to_process,
588
586
  config.tool_execution_strategy)
@@ -600,14 +598,14 @@ class StreamTaskResponser(TaskResponseProcessor):
600
598
  tool_results_map[current_tool_idx] = (tc, res, context)
601
599
  else:
602
600
  logging.warning(f"Could not map result for tool index {current_tool_idx}")
603
- self.trace.event(name="could_not_map_result_for_tool_index", level="WARNING",
601
+ langfuse.create_event(trace_context=self.trace_context, name="could_not_map_result_for_tool_index", level="WARNING",
604
602
  status_message=(f"Could not map result for tool index {current_tool_idx}"))
605
603
  current_tool_idx += 1
606
604
 
607
605
  # Save and Yield each result message
608
606
  if tool_results_map:
609
607
  logging.info(f"Saving and yielding {len(tool_results_map)} final tool result messages")
610
- self.trace.event(name="saving_and_yielding_final_tool_result_messages", level="DEFAULT",
608
+ langfuse.create_event(trace_context=self.trace_context, name="saving_and_yielding_final_tool_result_messages", level="DEFAULT",
611
609
  status_message=(
612
610
  f"Saving and yielding {len(tool_results_map)} final tool result messages"))
613
611
  for tool_idx in sorted(tool_results_map.keys()):
@@ -618,21 +616,19 @@ class StreamTaskResponser(TaskResponseProcessor):
618
616
 
619
617
  # Yield start status ONLY IF executing non-streamed (already yielded if streamed)
620
618
  if not config.execute_on_stream and tool_idx not in yielded_tool_indices:
621
- started_msg_obj = await self._yield_and_save_tool_started(context, thread_id, thread_run_id)
619
+ started_msg_obj = await self._add_tool_start_message(context)
622
620
  if started_msg_obj: yield format_for_yield(started_msg_obj)
623
621
  yielded_tool_indices.add(tool_idx) # Mark status yielded
624
622
 
625
623
  # Save the tool result message to DB
626
- saved_tool_result_object = await self._add_tool_result( # Returns full object or None
627
- thread_id, tool_call, result, config.xml_adding_strategy,
628
- context.assistant_message_id, context.parsing_details
629
- )
624
+ saved_tool_result_object = await self._add_tool_messsage(tool_call, result, config.xml_adding_strategy,
625
+ context.assistant_message_id, context.parsing_details
626
+ )
630
627
 
631
628
  # Yield completed/failed status (linked to saved result ID if available)
632
- completed_msg_obj = await self._yield_and_save_tool_completed(
629
+ completed_msg_obj = await self._add_tool_completed_message(
633
630
  context,
634
- saved_tool_result_object['message_id'] if saved_tool_result_object else None,
635
- thread_id, thread_run_id
631
+ saved_tool_result_object['message_id'] if saved_tool_result_object else None
636
632
  )
637
633
  if completed_msg_obj: yield format_for_yield(completed_msg_obj)
638
634
  # Don't add to yielded_tool_indices here, completion status is separate yield
@@ -644,7 +640,7 @@ class StreamTaskResponser(TaskResponseProcessor):
644
640
  else:
645
641
  logging.error(
646
642
  f"Failed to save tool result for index {tool_idx}, not yielding result message.")
647
- self.trace.event(name="failed_to_save_tool_result_for_index", level="ERROR",
643
+ langfuse.create_event(trace_context=self.trace_context, name="failed_to_save_tool_result_for_index", level="ERROR",
648
644
  status_message=(
649
645
  f"Failed to save tool result for index {tool_idx}, not yielding result message."))
650
646
  # Optionally yield error status for saving failure?
@@ -652,7 +648,7 @@ class StreamTaskResponser(TaskResponseProcessor):
652
648
  # --- Final Finish Status ---
653
649
  if finish_reason and finish_reason != "xml_tool_limit_reached":
654
650
  finish_content = {"status_type": "finish", "finish_reason": finish_reason}
655
- finish_msg_obj = await self.add_message(
651
+ finish_msg_obj = await self.add_response_message(
656
652
  type="status", content=finish_content,
657
653
  is_llm_message=False, metadata={"thread_run_id": thread_run_id}
658
654
  )
@@ -662,7 +658,7 @@ class StreamTaskResponser(TaskResponseProcessor):
662
658
  if agent_should_terminate:
663
659
  logging.info(
664
660
  "Agent termination requested after executing ask/complete tool. Stopping further processing.")
665
- self.trace.event(name="agent_termination_requested", level="DEFAULT",
661
+ langfuse.create_event(trace_context=self.trace_context, name="agent_termination_requested", level="DEFAULT",
666
662
  status_message="Agent termination requested after executing ask/complete tool. Stopping further processing.")
667
663
 
668
664
  # Set finish reason to indicate termination
@@ -670,7 +666,7 @@ class StreamTaskResponser(TaskResponseProcessor):
670
666
 
671
667
  # Save and yield termination status
672
668
  finish_content = {"status_type": "finish", "finish_reason": "agent_terminated"}
673
- finish_msg_obj = await self.add_message(
669
+ finish_msg_obj = await self.add_response_message(
674
670
  type="status", content=finish_content,
675
671
  is_llm_message=False, metadata={"thread_run_id": thread_run_id}
676
672
  )
@@ -714,7 +710,7 @@ class StreamTaskResponser(TaskResponseProcessor):
714
710
  if streaming_metadata.get("response_ms"):
715
711
  assistant_end_content["response_ms"] = streaming_metadata["response_ms"]
716
712
 
717
- await self.add_message(
713
+ await self.add_response_message(
718
714
  type="assistant_response_end",
719
715
  content=assistant_end_content,
720
716
  is_llm_message=False,
@@ -723,7 +719,7 @@ class StreamTaskResponser(TaskResponseProcessor):
723
719
  logging.info("Assistant response end saved for stream (before termination)")
724
720
  except Exception as e:
725
721
  logging.error(f"Error saving assistant response end for stream (before termination): {str(e)}")
726
- self.trace.event(name="error_saving_assistant_response_end_for_stream_before_termination",
722
+ langfuse.create_event(trace_context=self.trace_context, name="error_saving_assistant_response_end_for_stream_before_termination",
727
723
  level="ERROR", status_message=(
728
724
  f"Error saving assistant response end for stream (before termination): {str(e)}"))
729
725
 
@@ -770,7 +766,7 @@ class StreamTaskResponser(TaskResponseProcessor):
770
766
  if streaming_metadata.get("response_ms"):
771
767
  assistant_end_content["response_ms"] = streaming_metadata["response_ms"]
772
768
 
773
- await self.add_message(
769
+ await self.add_response_message(
774
770
  type="assistant_response_end",
775
771
  content=assistant_end_content,
776
772
  is_llm_message=False,
@@ -779,18 +775,18 @@ class StreamTaskResponser(TaskResponseProcessor):
779
775
  logging.info("Assistant response end saved for stream")
780
776
  except Exception as e:
781
777
  logging.error(f"Error saving assistant response end for stream: {str(e)}")
782
- self.trace.event(name="error_saving_assistant_response_end_for_stream", level="ERROR",
778
+ langfuse.create_event(trace_context=self.trace_context, name="error_saving_assistant_response_end_for_stream", level="ERROR",
783
779
  status_message=(f"Error saving assistant response end for stream: {str(e)}"))
784
780
 
785
781
  except Exception as e:
786
782
  logging.error(f"Error processing stream: {str(e)}", exc_info=True)
787
- self.trace.event(name="error_processing_stream", level="ERROR",
783
+ langfuse.create_event(trace_context=self.trace_context, name="error_processing_stream", level="ERROR",
788
784
  status_message=(f"Error processing stream: {str(e)}"))
789
785
  # Save and yield error status message
790
786
 
791
787
  err_content = {"role": "system", "status_type": "error", "message": str(e)}
792
788
  if (not "AnthropicException - Overloaded" in str(e)):
793
- err_msg_obj = await self.add_message(
789
+ err_msg_obj = await self.add_response_message(
794
790
  type="status", content=err_content,
795
791
  is_llm_message=False,
796
792
  metadata={"thread_run_id": thread_run_id if 'thread_run_id' in locals() else None}
@@ -798,12 +794,12 @@ class StreamTaskResponser(TaskResponseProcessor):
798
794
  if err_msg_obj: yield format_for_yield(err_msg_obj) # Yield the saved error message
799
795
  # Re-raise the same exception (not a new one) to ensure proper error propagation
800
796
  logging.critical(f"Re-raising error to stop further processing: {str(e)}")
801
- self.trace.event(name="re_raising_error_to_stop_further_processing", level="ERROR",
797
+ langfuse.create_event(trace_context=self.trace_context, name="re_raising_error_to_stop_further_processing", level="ERROR",
802
798
  status_message=(f"Re-raising error to stop further processing: {str(e)}"))
803
799
  else:
804
800
  logging.error(f"AnthropicException - Overloaded detected - Falling back to OpenRouter: {str(e)}",
805
801
  exc_info=True)
806
- self.trace.event(name="anthropic_exception_overloaded_detected", level="ERROR", status_message=(
802
+ langfuse.create_event(trace_context=self.trace_context, name="anthropic_exception_overloaded_detected", level="ERROR", status_message=(
807
803
  f"AnthropicException - Overloaded detected - Falling back to OpenRouter: {str(e)}"))
808
804
  raise # Use bare 'raise' to preserve the original exception with its traceback
809
805
 
@@ -818,7 +814,7 @@ class StreamTaskResponser(TaskResponseProcessor):
818
814
  # Save and Yield the final thread_run_end status (only if not auto-continuing and finish_reason is not 'length')
819
815
  try:
820
816
  end_content = {"status_type": "thread_run_end"}
821
- end_msg_obj = await self.add_message(
817
+ end_msg_obj = await self.add_response_message(
822
818
  type="status", content=end_content,
823
819
  is_llm_message=False,
824
820
  metadata={"thread_run_id": thread_run_id if 'thread_run_id' in locals() else None}
@@ -826,5 +822,5 @@ class StreamTaskResponser(TaskResponseProcessor):
826
822
  if end_msg_obj: yield format_for_yield(end_msg_obj)
827
823
  except Exception as final_e:
828
824
  logging.error(f"Error in finally block: {str(final_e)}", exc_info=True)
829
- self.trace.event(name="error_in_finally_block", level="ERROR",
825
+ langfuse.create_event(trace_context=self.trace_context, name="error_in_finally_block", level="ERROR",
830
826
  status_message=(f"Error in finally block: {str(final_e)}"))