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.
- gohumanloop/adapters/langgraph_adapter.py +65 -52
- gohumanloop/core/interface.py +342 -21
- gohumanloop/core/manager.py +302 -39
- gohumanloop/manager/ghl_manager.py +24 -24
- gohumanloop/providers/api_provider.py +50 -24
- gohumanloop/providers/base.py +170 -9
- gohumanloop/providers/email_provider.py +66 -22
- gohumanloop/providers/terminal_provider.py +95 -23
- gohumanloop/utils/utils.py +33 -3
- {gohumanloop-0.0.2.dist-info → gohumanloop-0.0.4.dist-info}/METADATA +1 -1
- {gohumanloop-0.0.2.dist-info → gohumanloop-0.0.4.dist-info}/RECORD +15 -15
- {gohumanloop-0.0.2.dist-info → gohumanloop-0.0.4.dist-info}/WHEEL +1 -1
- {gohumanloop-0.0.2.dist-info → gohumanloop-0.0.4.dist-info}/entry_points.txt +0 -0
- {gohumanloop-0.0.2.dist-info → gohumanloop-0.0.4.dist-info}/licenses/LICENSE +0 -0
- {gohumanloop-0.0.2.dist-info → gohumanloop-0.0.4.dist-info}/top_level.txt +0 -0
@@ -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.
|
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.
|
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.
|
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.
|
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.
|
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
|
-
|
612
|
-
|
613
|
-
|
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.
|
617
|
-
self.
|
618
|
-
self.
|
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
|
621
|
+
async def async_on_humanloop_update(
|
621
622
|
self,
|
622
623
|
provider: HumanLoopProvider,
|
623
624
|
result: HumanLoopResult
|
624
625
|
):
|
625
|
-
if self.
|
626
|
-
await self.
|
626
|
+
if self.async_on_update:
|
627
|
+
await self.async_on_update(self.state, provider, result)
|
627
628
|
|
628
|
-
async def
|
629
|
+
async def async_on_humanloop_timeout(
|
629
630
|
self,
|
630
631
|
provider: HumanLoopProvider,
|
631
632
|
):
|
632
|
-
if self.
|
633
|
-
await self.
|
633
|
+
if self.async_on_timeout:
|
634
|
+
await self.async_on_timeout(self.state, provider)
|
634
635
|
|
635
|
-
async def
|
636
|
+
async def async_on_humanloop_error(
|
636
637
|
self,
|
637
638
|
provider: HumanLoopProvider,
|
638
639
|
error: Exception
|
639
640
|
):
|
640
|
-
if self.
|
641
|
-
await self.
|
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
|
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
|
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
|
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
|
-
|
699
|
-
|
700
|
-
|
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
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
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
|
-
|
781
|
+
def poll_for_result():
|
774
782
|
poll_interval = 1.0 # Polling interval (seconds)
|
775
783
|
while True:
|
776
|
-
result =
|
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
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
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.
|
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)
|