17
dirty_json,
18
subagents,
19
)
20
+from python.helpers import extension
21
from python.helpers.print_style import PrintStyle
22
23
from langchain_core.prompts import (
31
from typing import Callable
32
from python.helpers.localization import Localization
33
from python.helpers.extension import call_extensions, extensible
33
-from python.helpers.errors import RepairableException
34
+from python.helpers.errors import RepairableException, InterventionException, HandledException
35
36
37
class AgentContextType(Enum):
241
self.task = self.communicate(UserMessage(self.agent0.read_prompt("fw.msg_nudge.md")))
242
return self.task
243
244
+ @extensible
245
def get_agent(self):
246
return self.streaming_agent or self.agent0
247
300
301
return response
302
except Exception as e:
301
- agent.handle_critical_exception(e)
303
+ exception_data = {"exception": e}
304
+ await extension.call_extensions("context_chain_exception", agent=agent, exception_data=exception_data)
305
+ if exception_data.get("exception"):
306
+ raise exception_data["exception"]
307
308
309
@dataclass
351
setattr(self, key, value)
352
353
349
-# intervention exception class - skips rest of message loop iteration
350
-class InterventionException(Exception):
351
- pass
352
-
353
-
354
-# killer exception class - not forwarded to LLM, cannot be fixed on its own, ends message loop
355
-
356
-
357
-class HandledException(Exception):
358
- pass
359
-
360
-
354
class Agent:
355
356
DATA_NAME_SUPERIOR = "_superior"
381
382
@extensible
383
async def monologue(self):
391
- error_retries = 0 # counter for critical error retries
384
while True:
385
try:
386
# loop data dictionary to pass to extensions
490
if tools_result: # final response of message loop available
491
return tools_result # break the execution if the task is done
492
501
- error_retries = 0 # reset retry counter on successful iteration
502
-
493
# exceptions inside message loop:
504
- except InterventionException as e:
505
- error_retries = 0 # reset retry counter on user intervention
506
- pass # intervention message has been handled in handle_intervention(), proceed with conversation loop
507
- except RepairableException as e:
508
- # Forward repairable errors to the LLM, maybe it can fix them
509
- msg = {"message": errors.format_error(e)}
510
- await self.call_extensions("error_format", msg=msg)
511
- self.hist_add_warning(msg["message"])
512
- PrintStyle(font_color="red", padding=True).print(msg["message"])
513
- self.context.log.log(type="warning", content=msg["message"])
494
except Exception as e:
515
- # Retry critical exceptions before failing
516
- error_retries = await self.retry_critical_exception(
517
- e, error_retries
518
- )
495
+ exception_data = { "exception": e }
496
+ await self.call_extensions("message_loop_exception", loop_data=self.loop_data, exception_data=exception_data)
497
+ if exception_data["exception"]:
498
+ raise exception_data["exception"]
499
500
finally:
501
# call message_loop_end extensions
507
508
509
# exceptions outside message loop:
530
- except InterventionException as e:
531
- error_retries = 0 # reset retry counter on user intervention
532
- pass # just start over
510
except Exception as e:
534
- # Retry critical exceptions before failing
535
- error_retries = await self.retry_critical_exception(
536
- e, error_retries
537
- )
511
+ exception_data = { "exception": e }
512
+ await self.call_extensions("monologue_exception", exception_data=exception_data)
513
+ if exception_data["exception"]:
514
+ raise exception_data["exception"]
515
finally:
516
self.context.streaming_agent = None # unset current streamer
517
# call monologue_end extensions
571
return full_prompt
572
573
@extensible
597
- async def retry_critical_exception(
598
- self, e: Exception, error_retries: int, delay: int = 3, max_retries: int = 1
599
- ) -> int:
600
- if error_retries >= max_retries:
601
- self.handle_critical_exception(e)
602
-
603
- error_message = errors.format_error(e)
604
-
605
- self.context.log.log(
606
- type="warning", heading="Critical error occurred, retrying...", content=error_message
607
- )
608
- PrintStyle(font_color="orange", padding=True).print(
609
- "Critical error occurred, retrying..."
610
- )
611
- await asyncio.sleep(delay)
612
- await self.handle_intervention()
613
- agent_facing_error = self.read_prompt(
614
- "fw.msg_critical_error.md", error_message=error_message
615
- )
616
- self.hist_add_warning(message=agent_facing_error)
617
- PrintStyle(font_color="orange", padding=True).print(
618
- agent_facing_error
619
- )
620
- return error_retries + 1
621
-
622
- @extensible
623
- def handle_critical_exception(self, exception: Exception):
624
- if isinstance(exception, HandledException):
625
- raise exception # Re-raise the exception to kill the loop
626
- elif isinstance(exception, asyncio.CancelledError):
627
- # Handling for asyncio.CancelledError
628
- PrintStyle(font_color="white", background_color="red", padding=True).print(
629
- f"Context {self.context.id} terminated during message loop"
630
- )
631
- raise HandledException(
632
- exception
633
- ) # Re-raise the exception to cancel the loop
634
- else:
635
- # Handling for general exceptions
636
- error_text = errors.error_text(exception)
637
- error_message = errors.format_error(exception)
638
-
639
- # Mask secrets in error messages
640
- PrintStyle(font_color="red", padding=True).print(error_message)
641
- self.context.log.log(
642
- type="error",
643
- content=error_message,
644
- )
645
- PrintStyle(font_color="red", padding=True).print(
646
- f"{self.agent_name}: {error_text}"
647
- )
648
-
649
- raise HandledException(exception) # Re-raise the exception to kill the loop
574
+ async def handle_critical_exception(self, exception: Exception):
575
+ pass
576
+ # exception_data = {"exception": exception}
577
+ # await self.call_extensions(
578
+ # "message_loop_exception", exception_data=exception_data
579
+ # )
580
+
581
+ # # If extensions cleared the exception, continue.
582
+ # if not exception_data.get("exception"):
583
+ # return
584
+
585
+ # # Backwards-compatible fallback (should normally be handled by _90 extension).
586
+ # exception = exception_data["exception"]
587
+ # if isinstance(exception, HandledException):
588
+ # raise exception
589
+ # elif isinstance(exception, asyncio.CancelledError):
590
+ # PrintStyle(font_color="white", background_color="red", padding=True).print(
591
+ # f"Context {self.context.id} terminated during message loop"
592
+ # )
593
+ # raise HandledException(exception)
594
+
595
+ # else:
596
+ # error_text = errors.error_text(exception)
597
+ # error_message = errors.format_error(exception)
598
+
599
+ # # Mask secrets in error messages
600
+ # PrintStyle(font_color="red", padding=True).print(error_message)
601
+ # self.context.log.log(
602
+ # type="error",
603
+ # content=error_message,
604
+ # )
605
+ # PrintStyle(font_color="red", padding=True).print(
606
+ # f"{self.agent_name}: {error_text}"
607
+ # )
608
+
609
+ # raise HandledException(exception) # Re-raise the exception to kill the loop
610
611
@extensible
612
async def get_system_prompt(self, loop_data: LoopData) -> list[str]:
818
819
@extensible
820
async def handle_intervention(self, progress: str = ""):
861
- while self.context.paused:
862
- await asyncio.sleep(0.1) # wait if paused
821
+ await self.wait_if_paused()
822
if (
823
self.intervention
824
): # if there is an intervention message, but not yet processed