gohumanloop 0.0.2__py3-none-any.whl → 0.0.4__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.
@@ -2,6 +2,7 @@ from typing import Dict, Any, Optional, Callable, Awaitable, TypeVar, Union, Lis
2
2
  from functools import wraps
3
3
  import asyncio
4
4
  import uuid
5
+ import time
5
6
  from inspect import iscoroutinefunction
6
7
  from contextlib import asynccontextmanager, contextmanager
7
8
 
@@ -209,7 +210,7 @@ class LangGraphAdapter:
209
210
  else:
210
211
  cb = callback
211
212
 
212
- result = await self.manager.request_humanloop(
213
+ result = await self.manager.async_request_humanloop(
213
214
  task_id=task_id,
214
215
  conversation_id=conversation_id,
215
216
  loop_type=HumanLoopType.APPROVAL,
@@ -374,12 +375,12 @@ class LangGraphAdapter:
374
375
  question_content = f"Please respond to the following information:\n{node_input}"
375
376
 
376
377
  # Check if conversation exists to determine whether to use request_humanloop or continue_humanloop
377
- conversation_requests = await self.manager.check_conversation_exist(task_id, conversation_id)
378
+ conversation_requests = await self.manager.async_check_conversation_exist(task_id, conversation_id)
378
379
 
379
380
  result = None
380
381
  if conversation_requests:
381
382
  # Existing conversation, use continue_humanloop
382
- result = await self.manager.continue_humanloop(
383
+ result = await self.manager.async_continue_humanloop(
383
384
  conversation_id=conversation_id,
384
385
  context={
385
386
  "message": {
@@ -400,7 +401,7 @@ class LangGraphAdapter:
400
401
  )
401
402
  else:
402
403
  # New conversation, use request_humanloop
403
- result = await self.manager.request_humanloop(
404
+ result = await self.manager.async_request_humanloop(
404
405
  task_id=task_id,
405
406
  conversation_id=conversation_id,
406
407
  loop_type=HumanLoopType.CONVERSATION,
@@ -543,7 +544,7 @@ class LangGraphAdapter:
543
544
  else:
544
545
  cb = callback
545
546
 
546
- result = await self.manager.request_humanloop(
547
+ result = await self.manager.async_request_humanloop(
547
548
  task_id=task_id,
548
549
  conversation_id=conversation_id,
549
550
  loop_type=HumanLoopType.INFORMATION,
@@ -608,37 +609,37 @@ class LangGraphHumanLoopCallback(HumanLoopCallback):
608
609
  def __init__(
609
610
  self,
610
611
  state: Any,
611
- on_update: Optional[Callable[[Any, HumanLoopProvider, HumanLoopResult], Awaitable[None]]] = None,
612
- on_timeout: Optional[Callable[[Any, HumanLoopProvider], Awaitable[None]]] = None,
613
- on_error: Optional[Callable[[Any, HumanLoopProvider, Exception], Awaitable[None]]] = None
612
+ async_on_update: Optional[Callable[[Any, HumanLoopProvider, HumanLoopResult], Awaitable[None]]] = None,
613
+ async_on_timeout: Optional[Callable[[Any, HumanLoopProvider], Awaitable[None]]] = None,
614
+ async_on_error: Optional[Callable[[Any, HumanLoopProvider, Exception], Awaitable[None]]] = None,
614
615
  ):
615
616
  self.state = state
616
- self.on_update = on_update
617
- self.on_timeout = on_timeout
618
- self.on_error = on_error
617
+ self.async_on_update = async_on_update
618
+ self.async_on_timeout = async_on_timeout
619
+ self.async_on_error = async_on_error
619
620
 
620
- async def on_humanloop_update(
621
+ async def async_on_humanloop_update(
621
622
  self,
622
623
  provider: HumanLoopProvider,
623
624
  result: HumanLoopResult
624
625
  ):
625
- if self.on_update:
626
- await self.on_update(self.state, provider, result)
626
+ if self.async_on_update:
627
+ await self.async_on_update(self.state, provider, result)
627
628
 
628
- async def on_humanloop_timeout(
629
+ async def async_on_humanloop_timeout(
629
630
  self,
630
631
  provider: HumanLoopProvider,
631
632
  ):
632
- if self.on_timeout:
633
- await self.on_timeout(self.state, provider)
633
+ if self.async_on_timeout:
634
+ await self.async_on_timeout(self.state, provider)
634
635
 
635
- async def on_humanloop_error(
636
+ async def async_on_humanloop_error(
636
637
  self,
637
638
  provider: HumanLoopProvider,
638
639
  error: Exception
639
640
  ):
640
- if self.on_error:
641
- await self.on_error(self.state, provider, error)
641
+ if self.async_on_error:
642
+ await self.async_on_error(self.state, provider, error)
642
643
 
643
644
 
644
645
  def default_langgraph_callback_factory(state: Any) -> LangGraphHumanLoopCallback:
@@ -661,7 +662,7 @@ def default_langgraph_callback_factory(state: Any) -> LangGraphHumanLoopCallback
661
662
 
662
663
  logger = logging.getLogger("gohumanloop.langgraph")
663
664
 
664
- async def on_update(state, provider: HumanLoopProvider, result: HumanLoopResult):
665
+ async def async_on_update(state, provider: HumanLoopProvider, result: HumanLoopResult):
665
666
  """Log human interaction update events"""
666
667
  logger.info(f"Provider ID: {provider.name}")
667
668
  logger.info(
@@ -673,8 +674,9 @@ def default_langgraph_callback_factory(state: Any) -> LangGraphHumanLoopCallback
673
674
  f"feedback={result.feedback}"
674
675
  )
675
676
 
677
+
676
678
 
677
- async def on_timeout(state, provider: HumanLoopProvider):
679
+ async def async_on_timeout(state, provider: HumanLoopProvider):
678
680
  """Log human interaction timeout events"""
679
681
 
680
682
  logger.info(f"Provider ID: {provider.name}")
@@ -685,7 +687,7 @@ def default_langgraph_callback_factory(state: Any) -> LangGraphHumanLoopCallback
685
687
 
686
688
  # Alert logic can be added here, such as sending notifications
687
689
 
688
- async def on_error(state, provider: HumanLoopProvider, error: Exception):
690
+ async def async_on_error(state, provider: HumanLoopProvider, error: Exception):
689
691
  """Log human interaction error events"""
690
692
 
691
693
  logger.info(f"Provider ID: {provider.name}")
@@ -695,9 +697,9 @@ def default_langgraph_callback_factory(state: Any) -> LangGraphHumanLoopCallback
695
697
 
696
698
  return LangGraphHumanLoopCallback(
697
699
  state=state,
698
- on_update=on_update,
699
- on_timeout=on_timeout,
700
- on_error=on_error
700
+ async_on_update=async_on_update,
701
+ async_on_timeout=async_on_timeout,
702
+ async_on_error=async_on_error
701
703
  )
702
704
 
703
705
  from gohumanloop.core.manager import DefaultHumanLoopManager
@@ -711,6 +713,8 @@ default_adapter = LangGraphAdapter(manager, default_timeout=60)
711
713
 
712
714
  default_conversation_id = str(uuid.uuid4())
713
715
 
716
+ _SKIP_NEXT_HUMANLOOP = False
717
+
714
718
  def interrupt(value: Any, lg_humanloop: LangGraphAdapter = default_adapter) -> Any:
715
719
  """
716
720
  Wraps LangGraph's interrupt functionality to pause graph execution and wait for human input
@@ -724,31 +728,32 @@ def interrupt(value: Any, lg_humanloop: LangGraphAdapter = default_adapter) -> A
724
728
  Returns:
725
729
  Input value provided by human user
726
730
  """
731
+
732
+ global _SKIP_NEXT_HUMANLOOP
733
+
727
734
  if not _SUPPORTS_INTERRUPT:
728
735
  raise RuntimeError(
729
736
  "LangGraph version too low, interrupt not supported. Please upgrade to version 0.2.57 or higher."
730
737
  "You can use: pip install --upgrade langgraph>=0.2.57"
731
738
  )
732
739
 
733
- # Get current event loop or create new one
734
- try:
735
- loop = asyncio.get_event_loop()
736
- except RuntimeError:
737
- # If no event loop exists, create a new one
738
- loop = asyncio.new_event_loop()
739
- asyncio.set_event_loop(loop)
740
-
741
- loop.create_task(lg_humanloop.manager.request_humanloop(
742
- task_id="lg_interrupt",
743
- conversation_id=default_conversation_id,
744
- loop_type=HumanLoopType.INFORMATION,
745
- context={
746
- "message": f"{value}",
747
- "question": "The execution has been interrupted. Please review the above information and provide your input to continue.",
748
- },
749
- blocking=False,
750
- ))
751
-
740
+ if not _SKIP_NEXT_HUMANLOOP:
741
+ # Get current event loop or create new one
742
+ lg_humanloop.manager.request_humanloop(
743
+ task_id="lg_interrupt",
744
+ conversation_id=default_conversation_id,
745
+ loop_type=HumanLoopType.INFORMATION,
746
+ context={
747
+ "message": f"{value}",
748
+ "question": "The execution has been interrupted. Please review the above information and provide your input to continue.",
749
+ },
750
+ blocking=False,
751
+ )
752
+ else:
753
+ # Reset flag to allow normal human intervention trigger next time
754
+ _SKIP_NEXT_HUMANLOOP = False
755
+
756
+
752
757
  # Return LangGraph's interrupt
753
758
  return _lg_interrupt(value)
754
759
  def create_resume_command(lg_humanloop: LangGraphAdapter = default_adapter) -> Any:
@@ -763,6 +768,9 @@ def create_resume_command(lg_humanloop: LangGraphAdapter = default_adapter) -> A
763
768
  Returns:
764
769
  Command object that can be used with graph.stream method
765
770
  """
771
+
772
+ global _SKIP_NEXT_HUMANLOOP
773
+
766
774
  if not _SUPPORTS_INTERRUPT:
767
775
  raise RuntimeError(
768
776
  "LangGraph version too low, Command feature not supported. Please upgrade to 0.2.57 or higher."
@@ -770,18 +778,19 @@ def create_resume_command(lg_humanloop: LangGraphAdapter = default_adapter) -> A
770
778
  )
771
779
 
772
780
  # Define async polling function
773
- async def poll_for_result():
781
+ def poll_for_result():
774
782
  poll_interval = 1.0 # Polling interval (seconds)
775
783
  while True:
776
- result = await lg_humanloop.manager.check_conversation_status(default_conversation_id)
784
+ result = lg_humanloop.manager.check_conversation_status(default_conversation_id)
777
785
  # If status is final state (not PENDING), return result
778
786
  if result.status != HumanLoopStatus.PENDING:
779
787
  return result.response
780
788
  # Wait before polling again
781
- await asyncio.sleep(poll_interval)
782
-
783
- # Wait for async result synchronously
784
- response = run_async_safely(poll_for_result())
789
+ time.sleep(poll_interval)
790
+
791
+ _SKIP_NEXT_HUMANLOOP = True
792
+
793
+ response = poll_for_result()
785
794
  return _lg_Command(resume=response)
786
795
 
787
796
  async def acreate_resume_command(lg_humanloop: LangGraphAdapter = default_adapter) -> Any:
@@ -796,6 +805,8 @@ async def acreate_resume_command(lg_humanloop: LangGraphAdapter = default_adapte
796
805
  Returns:
797
806
  Command object that can be used with graph.astream method
798
807
  """
808
+ global _SKIP_NEXT_HUMANLOOP
809
+
799
810
  if not _SUPPORTS_INTERRUPT:
800
811
  raise RuntimeError(
801
812
  "LangGraph version too low, Command feature not supported. Please upgrade to 0.2.57 or higher."
@@ -806,13 +817,15 @@ async def acreate_resume_command(lg_humanloop: LangGraphAdapter = default_adapte
806
817
  async def poll_for_result():
807
818
  poll_interval = 1.0 # Polling interval (seconds)
808
819
  while True:
809
- result = await lg_humanloop.manager.check_conversation_status(default_conversation_id)
820
+ result = await lg_humanloop.manager.async_check_conversation_status(default_conversation_id)
810
821
  # If status is final state (not PENDING), return result
811
822
  if result.status != HumanLoopStatus.PENDING:
812
823
  return result.response
813
824
  # Wait before polling again
814
825
  await asyncio.sleep(poll_interval)
815
826
 
827
+ _SKIP_NEXT_HUMANLOOP = True
828
+
816
829
  # Wait for async result directly
817
830
  response = await poll_for_result()
818
831
  return _lg_Command(resume=response)