gohumanloop 0.0.8__py3-none-any.whl → 0.0.10__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/base_adapter.py +10 -4
- gohumanloop/core/manager.py +21 -58
- gohumanloop/providers/api_provider.py +51 -14
- gohumanloop/providers/base.py +0 -52
- gohumanloop/providers/email_provider.py +53 -16
- gohumanloop/providers/terminal_provider.py +29 -12
- gohumanloop/utils/utils.py +1 -0
- {gohumanloop-0.0.8.dist-info → gohumanloop-0.0.10.dist-info}/METADATA +7 -6
- {gohumanloop-0.0.8.dist-info → gohumanloop-0.0.10.dist-info}/RECORD +13 -13
- {gohumanloop-0.0.8.dist-info → gohumanloop-0.0.10.dist-info}/WHEEL +0 -0
- {gohumanloop-0.0.8.dist-info → gohumanloop-0.0.10.dist-info}/entry_points.txt +0 -0
- {gohumanloop-0.0.8.dist-info → gohumanloop-0.0.10.dist-info}/licenses/LICENSE +0 -0
- {gohumanloop-0.0.8.dist-info → gohumanloop-0.0.10.dist-info}/top_level.txt +0 -0
@@ -434,11 +434,17 @@ class HumanloopAdapter:
|
|
434
434
|
else:
|
435
435
|
cb = callback
|
436
436
|
|
437
|
-
node_input
|
438
|
-
|
439
|
-
|
437
|
+
# First try to get node_input from kwargs using state_key
|
438
|
+
node_input = kwargs.get(state_key)
|
439
|
+
|
440
|
+
# If not found in kwargs, try to get from first argument (state)
|
441
|
+
if node_input is None and state and isinstance(state, dict):
|
440
442
|
node_input = state.get(state_key, {})
|
441
443
|
|
444
|
+
# If still not found, use empty dict as default
|
445
|
+
if node_input is None:
|
446
|
+
node_input = {}
|
447
|
+
|
442
448
|
# Compose question content
|
443
449
|
question_content = (
|
444
450
|
f"Please respond to the following information:\n{node_input}"
|
@@ -725,7 +731,7 @@ class AgentOpsHumanLoopCallback(HumanLoopCallback):
|
|
725
731
|
self._operation = operation
|
726
732
|
agentops.init(tags=self.session_tags)
|
727
733
|
else:
|
728
|
-
|
734
|
+
raise ImportError(
|
729
735
|
"AgentOps package not installed. Features disabled. "
|
730
736
|
"Please install with: pip install agentops"
|
731
737
|
)
|
gohumanloop/core/manager.py
CHANGED
@@ -170,12 +170,6 @@ class DefaultHumanLoopManager(HumanLoopManager):
|
|
170
170
|
pass
|
171
171
|
self._callbacks[(conversation_id, request_id)] = callback
|
172
172
|
|
173
|
-
# 如果设置了超时,创建超时任务
|
174
|
-
if timeout:
|
175
|
-
await self._async_create_timeout_task(
|
176
|
-
conversation_id, request_id, timeout, provider, callback
|
177
|
-
)
|
178
|
-
|
179
173
|
# 如果是阻塞模式,等待结果
|
180
174
|
if blocking:
|
181
175
|
return await self._async_wait_for_result(
|
@@ -308,12 +302,6 @@ class DefaultHumanLoopManager(HumanLoopManager):
|
|
308
302
|
pass
|
309
303
|
self._callbacks[(conversation_id, request_id)] = callback
|
310
304
|
|
311
|
-
# 如果设置了超时,创建超时任务
|
312
|
-
if timeout:
|
313
|
-
await self._async_create_timeout_task(
|
314
|
-
conversation_id, request_id, timeout, provider, callback
|
315
|
-
)
|
316
|
-
|
317
305
|
# 如果是阻塞模式,等待结果
|
318
306
|
if blocking:
|
319
307
|
return await self._async_wait_for_result(
|
@@ -429,22 +417,31 @@ class DefaultHumanLoopManager(HumanLoopManager):
|
|
429
417
|
|
430
418
|
try:
|
431
419
|
# 检查对话指定provider_id或默认provider_id最后一次请求的状态
|
432
|
-
|
433
|
-
|
434
|
-
# 处理检查对话状态过程中的异常
|
435
|
-
# 尝试找到与此对话关联的最后一个请求的回调
|
420
|
+
result = await provider.async_check_conversation_status(conversation_id)
|
421
|
+
|
436
422
|
if (
|
437
423
|
conversation_id in self._conversation_requests
|
438
424
|
and self._conversation_requests[conversation_id]
|
439
425
|
):
|
440
426
|
last_request_id = self._conversation_requests[conversation_id][-1]
|
441
427
|
callback = self._callbacks.get((conversation_id, last_request_id))
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
428
|
+
|
429
|
+
if callback and result.status not in [HumanLoopStatus.PENDING]:
|
430
|
+
# 如果有回调且状态不是等待或进行中,触发状态更新回调
|
431
|
+
await self._async_trigger_update_callback(
|
432
|
+
conversation_id, last_request_id, provider, result
|
433
|
+
)
|
434
|
+
|
435
|
+
return result
|
436
|
+
except Exception as e:
|
437
|
+
# 处理检查对话状态过程中的异常
|
438
|
+
# 尝试找到与此对话关联的最后一个请求的回调
|
439
|
+
if callback:
|
440
|
+
try:
|
441
|
+
await callback.async_on_humanloop_error(provider, e)
|
442
|
+
except Exception:
|
443
|
+
# 如果错误回调也失败,只能忽略
|
444
|
+
pass
|
448
445
|
raise # 重新抛出异常,让调用者知道发生了错误
|
449
446
|
|
450
447
|
def check_conversation_status(
|
@@ -632,42 +629,6 @@ class DefaultHumanLoopManager(HumanLoopManager):
|
|
632
629
|
|
633
630
|
return result
|
634
631
|
|
635
|
-
async def _async_create_timeout_task(
|
636
|
-
self,
|
637
|
-
conversation_id: str,
|
638
|
-
request_id: str,
|
639
|
-
timeout: int,
|
640
|
-
provider: HumanLoopProvider,
|
641
|
-
callback: Optional[HumanLoopCallback],
|
642
|
-
) -> None:
|
643
|
-
"""创建超时任务"""
|
644
|
-
|
645
|
-
async def timeout_task() -> None:
|
646
|
-
await asyncio.sleep(timeout)
|
647
|
-
# 检查当前状态
|
648
|
-
result = await self.async_check_request_status(
|
649
|
-
conversation_id, request_id, provider.name
|
650
|
-
)
|
651
|
-
|
652
|
-
# 只有当状态为PENDING时才触发超时回调
|
653
|
-
# INPROGRESS状态表示对话正在进行中,不应视为超时
|
654
|
-
if result.status == HumanLoopStatus.PENDING:
|
655
|
-
if callback:
|
656
|
-
await callback.async_on_humanloop_timeout(
|
657
|
-
provider=provider, result=result
|
658
|
-
)
|
659
|
-
# 如果状态是INPROGRESS,重置超时任务
|
660
|
-
elif result.status == HumanLoopStatus.INPROGRESS:
|
661
|
-
# 对于进行中的对话,我们可以选择延长超时时间
|
662
|
-
# 这里我们简单地重新创建一个超时任务,使用相同的超时时间
|
663
|
-
if (conversation_id, request_id) in self._timeout_tasks:
|
664
|
-
self._timeout_tasks[(conversation_id, request_id)].cancel()
|
665
|
-
new_task = asyncio.create_task(timeout_task())
|
666
|
-
self._timeout_tasks[(conversation_id, request_id)] = new_task
|
667
|
-
|
668
|
-
task = asyncio.create_task(timeout_task())
|
669
|
-
self._timeout_tasks[(conversation_id, request_id)] = task
|
670
|
-
|
671
632
|
async def _async_wait_for_result(
|
672
633
|
self,
|
673
634
|
conversation_id: str,
|
@@ -704,6 +665,8 @@ class DefaultHumanLoopManager(HumanLoopManager):
|
|
704
665
|
if callback:
|
705
666
|
try:
|
706
667
|
await callback.async_on_humanloop_update(provider, result)
|
668
|
+
if result.status == HumanLoopStatus.EXPIRED:
|
669
|
+
await callback.async_on_humanloop_timeout(provider, result)
|
707
670
|
# 如果状态是最终状态,可以考虑移除回调
|
708
671
|
if result.status not in [
|
709
672
|
HumanLoopStatus.PENDING,
|
@@ -267,14 +267,9 @@ class APIProvider(BaseProvider):
|
|
267
267
|
conversation_id,
|
268
268
|
request_id,
|
269
269
|
platform,
|
270
|
+
timeout,
|
270
271
|
)
|
271
272
|
|
272
|
-
# Create timeout task if timeout is set
|
273
|
-
if timeout:
|
274
|
-
await self._async_create_timeout_task(
|
275
|
-
conversation_id, request_id, timeout
|
276
|
-
)
|
277
|
-
|
278
273
|
return HumanLoopResult(
|
279
274
|
conversation_id=conversation_id,
|
280
275
|
request_id=request_id,
|
@@ -296,7 +291,11 @@ class APIProvider(BaseProvider):
|
|
296
291
|
)
|
297
292
|
|
298
293
|
def _run_async_poll_request_status(
|
299
|
-
self,
|
294
|
+
self,
|
295
|
+
conversation_id: str,
|
296
|
+
request_id: str,
|
297
|
+
platform: str,
|
298
|
+
timeout: Optional[int],
|
300
299
|
) -> None:
|
301
300
|
"""Run asynchronous API interaction in a separate thread"""
|
302
301
|
# Create new event loop
|
@@ -306,7 +305,9 @@ class APIProvider(BaseProvider):
|
|
306
305
|
try:
|
307
306
|
# Run interaction processing in the new event loop
|
308
307
|
loop.run_until_complete(
|
309
|
-
self.
|
308
|
+
self._async_poll_request_status_with_timeout(
|
309
|
+
conversation_id, request_id, platform, timeout
|
310
|
+
)
|
310
311
|
)
|
311
312
|
finally:
|
312
313
|
loop.close()
|
@@ -579,14 +580,9 @@ class APIProvider(BaseProvider):
|
|
579
580
|
conversation_id,
|
580
581
|
request_id,
|
581
582
|
platform,
|
583
|
+
timeout,
|
582
584
|
)
|
583
585
|
|
584
|
-
# Create timeout task if timeout is set
|
585
|
-
if timeout:
|
586
|
-
await self._async_create_timeout_task(
|
587
|
-
conversation_id, request_id, timeout
|
588
|
-
)
|
589
|
-
|
590
586
|
return HumanLoopResult(
|
591
587
|
conversation_id=conversation_id,
|
592
588
|
request_id=request_id,
|
@@ -606,6 +602,47 @@ class APIProvider(BaseProvider):
|
|
606
602
|
error=str(e),
|
607
603
|
)
|
608
604
|
|
605
|
+
async def _async_poll_request_status_with_timeout(
|
606
|
+
self,
|
607
|
+
conversation_id: str,
|
608
|
+
request_id: str,
|
609
|
+
platform: str,
|
610
|
+
timeout: Optional[int],
|
611
|
+
) -> None:
|
612
|
+
"""Poll request status with optional timeout
|
613
|
+
|
614
|
+
Args:
|
615
|
+
conversation_id: Conversation identifier
|
616
|
+
request_id: Request identifier
|
617
|
+
platform: Platform identifier
|
618
|
+
timeout: Optional timeout in seconds. If specified, polling will stop after timeout period
|
619
|
+
"""
|
620
|
+
|
621
|
+
try:
|
622
|
+
if timeout:
|
623
|
+
# 使用 wait_for 设置超时
|
624
|
+
await asyncio.wait_for(
|
625
|
+
self._async_poll_request_status(
|
626
|
+
conversation_id, request_id, platform
|
627
|
+
),
|
628
|
+
timeout=timeout,
|
629
|
+
)
|
630
|
+
else:
|
631
|
+
# 无超时限制
|
632
|
+
await self._async_poll_request_status(
|
633
|
+
conversation_id, request_id, platform
|
634
|
+
)
|
635
|
+
|
636
|
+
except asyncio.TimeoutError:
|
637
|
+
# 超时处理
|
638
|
+
request_info = self._get_request(conversation_id, request_id)
|
639
|
+
if request_info and request_info.get("status") == HumanLoopStatus.PENDING:
|
640
|
+
request_info["status"] = HumanLoopStatus.EXPIRED
|
641
|
+
request_info["error"] = "Request timed out"
|
642
|
+
logger.info(
|
643
|
+
f"\nRequest {request_id} has timed out after {timeout} seconds"
|
644
|
+
)
|
645
|
+
|
609
646
|
async def _async_poll_request_status(
|
610
647
|
self, conversation_id: str, request_id: str, platform: str
|
611
648
|
) -> None:
|
gohumanloop/providers/base.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
from abc import ABC
|
2
2
|
from typing import Dict, Any, Optional, List, Tuple
|
3
|
-
import asyncio
|
4
3
|
import json
|
5
4
|
import uuid
|
6
5
|
from datetime import datetime
|
@@ -33,8 +32,6 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
33
32
|
self._conversations: Dict[str, Dict[str, Any]] = {}
|
34
33
|
# For quick lookup of requests in conversations
|
35
34
|
self._conversation_requests: defaultdict[str, List[str]] = defaultdict(list)
|
36
|
-
# Store timeout tasks
|
37
|
-
self._timeout_tasks: Dict[Tuple[str, str], asyncio.Task] = {}
|
38
35
|
|
39
36
|
self.prompt_template = self.config.get("prompt_template", "{context}")
|
40
37
|
|
@@ -291,11 +288,6 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
291
288
|
bool: Whether cancellation was successful, True indicates success, False indicates failure
|
292
289
|
"""
|
293
290
|
|
294
|
-
# Cancel timeout task
|
295
|
-
if (conversation_id, request_id) in self._timeout_tasks:
|
296
|
-
self._timeout_tasks[(conversation_id, request_id)].cancel()
|
297
|
-
del self._timeout_tasks[(conversation_id, request_id)]
|
298
|
-
|
299
291
|
request_key = (conversation_id, request_id)
|
300
292
|
if request_key in self._requests:
|
301
293
|
# Update request status to cancelled
|
@@ -346,11 +338,6 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
346
338
|
HumanLoopStatus.INPROGRESS,
|
347
339
|
]:
|
348
340
|
self._requests[request_key]["status"] = HumanLoopStatus.CANCELLED
|
349
|
-
|
350
|
-
# Cancel the timeout task for this request
|
351
|
-
if request_key in self._timeout_tasks:
|
352
|
-
self._timeout_tasks[request_key].cancel()
|
353
|
-
del self._timeout_tasks[request_key]
|
354
341
|
else:
|
355
342
|
success = False
|
356
343
|
|
@@ -482,45 +469,6 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
482
469
|
|
483
470
|
return result
|
484
471
|
|
485
|
-
async def _async_create_timeout_task(
|
486
|
-
self, conversation_id: str, request_id: str, timeout: int
|
487
|
-
) -> None:
|
488
|
-
"""Create timeout task
|
489
|
-
|
490
|
-
Args:
|
491
|
-
conversation_id: Conversation ID
|
492
|
-
request_id: Request ID
|
493
|
-
timeout: Timeout duration in seconds
|
494
|
-
"""
|
495
|
-
|
496
|
-
async def timeout_task() -> None:
|
497
|
-
await asyncio.sleep(timeout)
|
498
|
-
|
499
|
-
# Check current status
|
500
|
-
request_info = self._get_request(conversation_id, request_id)
|
501
|
-
if not request_info:
|
502
|
-
return
|
503
|
-
|
504
|
-
current_status = request_info.get("status", HumanLoopStatus.PENDING)
|
505
|
-
|
506
|
-
# Only trigger timeout when status is PENDING
|
507
|
-
# INPROGRESS status means conversation is ongoing, should not be considered as timeout
|
508
|
-
if current_status == HumanLoopStatus.PENDING:
|
509
|
-
# Update request status to expired
|
510
|
-
request_info["status"] = HumanLoopStatus.EXPIRED
|
511
|
-
request_info["error"] = "Request timed out"
|
512
|
-
# If status is INPROGRESS, reset timeout task
|
513
|
-
elif current_status == HumanLoopStatus.INPROGRESS:
|
514
|
-
# For ongoing conversations, we can choose to extend the timeout
|
515
|
-
# Here we simply create a new timeout task with the same timeout duration
|
516
|
-
if (conversation_id, request_id) in self._timeout_tasks:
|
517
|
-
self._timeout_tasks[(conversation_id, request_id)].cancel()
|
518
|
-
new_task = asyncio.create_task(timeout_task())
|
519
|
-
self._timeout_tasks[(conversation_id, request_id)] = new_task
|
520
|
-
|
521
|
-
task = asyncio.create_task(timeout_task())
|
522
|
-
self._timeout_tasks[(conversation_id, request_id)] = task
|
523
|
-
|
524
472
|
def build_prompt(
|
525
473
|
self,
|
526
474
|
task_id: str,
|
@@ -213,6 +213,49 @@ class EmailProvider(BaseProvider):
|
|
213
213
|
logger.exception(f"Unknown error occurred while sending email: {str(e)}")
|
214
214
|
raise
|
215
215
|
|
216
|
+
async def _async_check_emails_with_timeout(
|
217
|
+
self,
|
218
|
+
conversation_id: str,
|
219
|
+
request_id: str,
|
220
|
+
recipient_email: str,
|
221
|
+
subject: str,
|
222
|
+
timeout: Optional[int],
|
223
|
+
) -> None:
|
224
|
+
"""Check email replies with timeout functionality
|
225
|
+
|
226
|
+
Args:
|
227
|
+
conversation_id: Conversation ID to identify a complete dialogue session
|
228
|
+
request_id: Request ID to identify a specific request
|
229
|
+
recipient_email: Recipient's email address
|
230
|
+
subject: Email subject line
|
231
|
+
timeout: Timeout duration in seconds, no timeout if None
|
232
|
+
"""
|
233
|
+
|
234
|
+
try:
|
235
|
+
if timeout:
|
236
|
+
# 使用 wait_for 设置超时
|
237
|
+
await asyncio.wait_for(
|
238
|
+
self._async_check_emails(
|
239
|
+
conversation_id, request_id, recipient_email, subject
|
240
|
+
),
|
241
|
+
timeout=timeout,
|
242
|
+
)
|
243
|
+
else:
|
244
|
+
# 无超时限制
|
245
|
+
await self._async_check_emails(
|
246
|
+
conversation_id, request_id, recipient_email, subject
|
247
|
+
)
|
248
|
+
|
249
|
+
except asyncio.TimeoutError:
|
250
|
+
# 超时处理
|
251
|
+
request_info = self._get_request(conversation_id, request_id)
|
252
|
+
if request_info and request_info.get("status") == HumanLoopStatus.PENDING:
|
253
|
+
request_info["status"] = HumanLoopStatus.EXPIRED
|
254
|
+
request_info["error"] = "Request timed out"
|
255
|
+
logger.info(
|
256
|
+
f"\nRequest {request_id} has timed out after {timeout} seconds"
|
257
|
+
)
|
258
|
+
|
216
259
|
async def _async_check_emails(
|
217
260
|
self, conversation_id: str, request_id: str, recipient_email: str, subject: str
|
218
261
|
) -> None:
|
@@ -488,11 +531,6 @@ class EmailProvider(BaseProvider):
|
|
488
531
|
}
|
489
532
|
)
|
490
533
|
|
491
|
-
# 取消超时任务
|
492
|
-
if request_key in self._timeout_tasks:
|
493
|
-
self._timeout_tasks[request_key].cancel()
|
494
|
-
del self._timeout_tasks[request_key]
|
495
|
-
|
496
534
|
def _format_email_body(
|
497
535
|
self, body: str, loop_type: HumanLoopType, subject: str
|
498
536
|
) -> Tuple[str, str]:
|
@@ -772,12 +810,9 @@ class EmailProvider(BaseProvider):
|
|
772
810
|
request_id,
|
773
811
|
recipient_email,
|
774
812
|
subject,
|
813
|
+
timeout,
|
775
814
|
)
|
776
815
|
|
777
|
-
# 如果设置了超时,创建超时任务
|
778
|
-
if timeout:
|
779
|
-
await self._async_create_timeout_task(conversation_id, request_id, timeout)
|
780
|
-
|
781
816
|
return HumanLoopResult(
|
782
817
|
conversation_id=conversation_id,
|
783
818
|
request_id=request_id,
|
@@ -786,7 +821,12 @@ class EmailProvider(BaseProvider):
|
|
786
821
|
)
|
787
822
|
|
788
823
|
def _run_email_check_task(
|
789
|
-
self,
|
824
|
+
self,
|
825
|
+
conversation_id: str,
|
826
|
+
request_id: str,
|
827
|
+
recipient_email: str,
|
828
|
+
subject: str,
|
829
|
+
timeout: Optional[int],
|
790
830
|
) -> None:
|
791
831
|
"""Run email check task in thread
|
792
832
|
|
@@ -803,8 +843,8 @@ class EmailProvider(BaseProvider):
|
|
803
843
|
try:
|
804
844
|
# Run email check in new event loop
|
805
845
|
loop.run_until_complete(
|
806
|
-
self.
|
807
|
-
conversation_id, request_id, recipient_email, subject
|
846
|
+
self._async_check_emails_with_timeout(
|
847
|
+
conversation_id, request_id, recipient_email, subject, timeout
|
808
848
|
)
|
809
849
|
)
|
810
850
|
except Exception as e:
|
@@ -987,12 +1027,9 @@ class EmailProvider(BaseProvider):
|
|
987
1027
|
request_id,
|
988
1028
|
recipient_email,
|
989
1029
|
subject,
|
1030
|
+
timeout,
|
990
1031
|
)
|
991
1032
|
|
992
|
-
# 如果设置了超时,创建超时任务
|
993
|
-
if timeout:
|
994
|
-
await self._async_create_timeout_task(conversation_id, request_id, timeout)
|
995
|
-
|
996
1033
|
return HumanLoopResult(
|
997
1034
|
conversation_id=conversation_id,
|
998
1035
|
request_id=request_id,
|
@@ -90,17 +90,36 @@ class TerminalProvider(BaseProvider):
|
|
90
90
|
self._terminal_input_tasks[
|
91
91
|
(conversation_id, request_id)
|
92
92
|
] = self._executor.submit(
|
93
|
-
self._run_async_terminal_interaction, conversation_id, request_id
|
93
|
+
self._run_async_terminal_interaction, conversation_id, request_id, timeout
|
94
94
|
)
|
95
95
|
|
96
|
-
# Create timeout task if timeout is specified
|
97
|
-
if timeout:
|
98
|
-
await self._async_create_timeout_task(conversation_id, request_id, timeout)
|
99
|
-
|
100
96
|
return result
|
101
97
|
|
98
|
+
async def _process_terminal_interaction_with_timeout(
|
99
|
+
self, conversation_id: str, request_id: str, timeout: Optional[int]
|
100
|
+
) -> None:
|
101
|
+
"""Process terminal interaction with timeout functionality"""
|
102
|
+
try:
|
103
|
+
if timeout:
|
104
|
+
# Set timeout using wait_for
|
105
|
+
await asyncio.wait_for(
|
106
|
+
self._process_terminal_interaction(conversation_id, request_id),
|
107
|
+
timeout=timeout,
|
108
|
+
)
|
109
|
+
else:
|
110
|
+
# No timeout limit
|
111
|
+
await self._process_terminal_interaction(conversation_id, request_id)
|
112
|
+
|
113
|
+
except asyncio.TimeoutError:
|
114
|
+
# Handle timeout
|
115
|
+
request_info = self._get_request(conversation_id, request_id)
|
116
|
+
if request_info and request_info.get("status") == HumanLoopStatus.PENDING:
|
117
|
+
request_info["status"] = HumanLoopStatus.EXPIRED
|
118
|
+
request_info["error"] = "Request timed out"
|
119
|
+
print(f"\nRequest {request_id} has timed out after {timeout} seconds")
|
120
|
+
|
102
121
|
def _run_async_terminal_interaction(
|
103
|
-
self, conversation_id: str, request_id: str
|
122
|
+
self, conversation_id: str, request_id: str, timeout: int | None
|
104
123
|
) -> None:
|
105
124
|
"""Run asynchronous terminal interaction in a separate thread"""
|
106
125
|
# Create new event loop
|
@@ -110,7 +129,9 @@ class TerminalProvider(BaseProvider):
|
|
110
129
|
try:
|
111
130
|
# Run interaction processing in the new event loop
|
112
131
|
loop.run_until_complete(
|
113
|
-
self.
|
132
|
+
self._process_terminal_interaction_with_timeout(
|
133
|
+
conversation_id, request_id, timeout
|
134
|
+
)
|
114
135
|
)
|
115
136
|
finally:
|
116
137
|
loop.close()
|
@@ -213,13 +234,9 @@ class TerminalProvider(BaseProvider):
|
|
213
234
|
self._terminal_input_tasks[
|
214
235
|
(conversation_id, request_id)
|
215
236
|
] = self._executor.submit(
|
216
|
-
self._run_async_terminal_interaction, conversation_id, request_id
|
237
|
+
self._run_async_terminal_interaction, conversation_id, request_id, timeout
|
217
238
|
)
|
218
239
|
|
219
|
-
# Create timeout task if timeout is specified
|
220
|
-
if timeout:
|
221
|
-
await self._async_create_timeout_task(conversation_id, request_id, timeout)
|
222
|
-
|
223
240
|
return result
|
224
241
|
|
225
242
|
async def _process_terminal_interaction(
|
gohumanloop/utils/utils.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: gohumanloop
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.10
|
4
4
|
Summary: Perfecting AI workflows with human intelligence
|
5
5
|
Author-email: gohumanloop authors <baird0917@163.com>
|
6
6
|
Project-URL: repository, https://github.com/ptonlix/gohumanloop
|
@@ -33,7 +33,7 @@ Dynamic: license-file
|
|
33
33
|
**GoHumanLoop**: A Python library empowering AI agents to dynamically request human input (approval/feedback/conversation) at critical stages. Core features:
|
34
34
|
|
35
35
|
- `Human-in-the-loop control`: Lets AI agent systems pause and escalate decisions, enhancing safety and trust.
|
36
|
-
- `Multi-channel integration`: Supports Terminal, Email, API, and frameworks like LangGraph/CrewAI
|
36
|
+
- `Multi-channel integration`: Supports Terminal, Email, API, and frameworks like LangGraph/CrewAI/...(soon)
|
37
37
|
- `Flexible workflows`: Combines automated reasoning with human oversight for reliable AI operations.
|
38
38
|
|
39
39
|
Ensures responsible AI deployment by bridging autonomous agents and human judgment.
|
@@ -59,9 +59,10 @@ Ensures responsible AI deployment by bridging autonomous agents and human judgme
|
|
59
59
|
|
60
60
|
## 🎹 Getting Started
|
61
61
|
|
62
|
-
To get started, check out the following example or jump straight into one of the [Examples](
|
62
|
+
To get started, check out the following example or jump straight into one of the [Examples Repo](https://github.com/ptonlix/gohumanloop-examples):
|
63
63
|
|
64
|
-
- 🦜⛓️ [LangGraph](
|
64
|
+
- 🦜⛓️ [LangGraph](https://github.com/ptonlix/gohumanloop-examples/tree/main/LangGraph)
|
65
|
+
- 🚣 [CrewAI](https://github.com/ptonlix/gohumanloop-examples/tree/main/CrewAI)
|
65
66
|
|
66
67
|
### Installation
|
67
68
|
|
@@ -206,7 +207,7 @@ Perform `human-in-the-loop` interaction by entering:
|
|
206
207
|
|
207
208
|
🚀🚀🚀 Completed successfully ~
|
208
209
|
|
209
|
-
➡️ Check out more examples in the [Examples
|
210
|
+
➡️ Check out more examples in the [Examples Repository](https://github.com/ptonlix/gohumanloop-examples) and we look foward to your contributions!
|
210
211
|
|
211
212
|
## 🎵 Why GoHumanloop?
|
212
213
|
|
@@ -254,7 +255,7 @@ Through `GoHumanloop`'s encapsulation, you can implement secure and efficient `H
|
|
254
255
|
## 📚 Key Features
|
255
256
|
|
256
257
|
<div align="center">
|
257
|
-
<img height=360 src="http://cdn.oyster-iot.cloud/
|
258
|
+
<img height=360 src="http://cdn.oyster-iot.cloud/202505291027894.png"><br>
|
258
259
|
<b face="雅黑"> GoHumanLoop Architecture</b>
|
259
260
|
</div>
|
260
261
|
<br>
|
@@ -1,31 +1,31 @@
|
|
1
1
|
gohumanloop/__init__.py,sha256=KiY2ncM6OQvkBYcz5rsbob_GuV8hQHOARA-EsxAylEU,2134
|
2
2
|
gohumanloop/__main__.py,sha256=zdGKN92H9SgwZfL4xLqPkE1YaiRcHhVg_GqC-H1VurA,75
|
3
3
|
gohumanloop/adapters/__init__.py,sha256=_XqJ6eaUBLJJJyjfBQJykBZ4X9HvC3VYOVTn2KnBlqo,484
|
4
|
-
gohumanloop/adapters/base_adapter.py,sha256=
|
4
|
+
gohumanloop/adapters/base_adapter.py,sha256=G1Tw7uzBrJObDdVT2kxifotkGZIptnZh5GUqCx54rSs,32884
|
5
5
|
gohumanloop/adapters/langgraph_adapter.py,sha256=AHJv_b6g8xjeq_9X2cGiW76pSGlfgCGBUvjrZQcbR9s,11621
|
6
6
|
gohumanloop/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
7
|
gohumanloop/cli/main.py,sha256=Txjk31MlkiX9zeHZfFEXEu0s2IBESY8sxrxBEzZKk2U,767
|
8
8
|
gohumanloop/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
9
|
gohumanloop/core/interface.py,sha256=uzV3xG08DDItgB9TgMvskcKVDSIFTx2qv8HXjVBtJcI,21810
|
10
|
-
gohumanloop/core/manager.py,sha256=
|
10
|
+
gohumanloop/core/manager.py,sha256=A54smLOrN6IgNey6L_zyYJMEX7uB9ZQ_2JDT2im423g,30207
|
11
11
|
gohumanloop/manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
12
12
|
gohumanloop/manager/ghl_manager.py,sha256=rmbfaS5mAb9c8RPr9Id24vO5DJT9ZC4D282dHZ26SJE,22695
|
13
13
|
gohumanloop/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
14
|
gohumanloop/models/api_model.py,sha256=1LtSQdx2ZVMzTdPrOVGe1JE1Cn_B0CnNQDp1-LlnqLw,2950
|
15
15
|
gohumanloop/models/glh_model.py,sha256=gix4f_57G4URkwXhCspWhU08Sf1MKu9FtUCx424Vf3A,940
|
16
16
|
gohumanloop/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
|
-
gohumanloop/providers/api_provider.py,sha256=
|
18
|
-
gohumanloop/providers/base.py,sha256=
|
19
|
-
gohumanloop/providers/email_provider.py,sha256=
|
17
|
+
gohumanloop/providers/api_provider.py,sha256=lp7mMbj01aIFZii_1YJDvUbv4kpPeANBB606S7E4VcI,27023
|
18
|
+
gohumanloop/providers/base.py,sha256=rOA9xRTh-rn-64RIZpzcIhWNar8LEzYrEXS5H5r7PpM,19554
|
19
|
+
gohumanloop/providers/email_provider.py,sha256=kQA6kG7dqpV6R1cJUeaanLmxMVk9pcIwweNZA4IC-2o,45947
|
20
20
|
gohumanloop/providers/ghl_provider.py,sha256=NmOac2LR0d87pc91sMuYtvTYsKdp4wZrhfmSYuZL2ko,2350
|
21
|
-
gohumanloop/providers/terminal_provider.py,sha256=
|
21
|
+
gohumanloop/providers/terminal_provider.py,sha256=aBNgGpspiu7fGigN6K-gqGlyjFDFvIqKMGp58PxGyPg,15447
|
22
22
|
gohumanloop/utils/__init__.py,sha256=l0e3lnOVrt6HwaxidNGFXORX4awCKemhJZ_t3k-5u-s,124
|
23
23
|
gohumanloop/utils/context_formatter.py,sha256=sAWrKJpxmsHga6gsyBwFBlAsHW8bjR1hkaGhk1PoE1M,2064
|
24
24
|
gohumanloop/utils/threadsafedict.py,sha256=9uyewnwmvS3u1fCx3SK0YWFxHMcyIwlye1Ev7WW7WHA,9588
|
25
|
-
gohumanloop/utils/utils.py,sha256
|
26
|
-
gohumanloop-0.0.
|
27
|
-
gohumanloop-0.0.
|
28
|
-
gohumanloop-0.0.
|
29
|
-
gohumanloop-0.0.
|
30
|
-
gohumanloop-0.0.
|
31
|
-
gohumanloop-0.0.
|
25
|
+
gohumanloop/utils/utils.py,sha256=O0zFtMFloAkWylmZE7WpmAjbDe385ZBfuUEh7NIwgGE,2148
|
26
|
+
gohumanloop-0.0.10.dist-info/licenses/LICENSE,sha256=-U5tuCcSpndQwSKWtZbFbazb-_AtZcZL2kQgHbSLg-M,1064
|
27
|
+
gohumanloop-0.0.10.dist-info/METADATA,sha256=kpmDmvASjoEsVlgRTtQt87g2ZpGfSeVeRuRVFShq334,11428
|
28
|
+
gohumanloop-0.0.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
29
|
+
gohumanloop-0.0.10.dist-info/entry_points.txt,sha256=wM6jqRRD8bQXkvIduRVCuAJIlbyWg_F5EDXo5OZ_PwY,88
|
30
|
+
gohumanloop-0.0.10.dist-info/top_level.txt,sha256=LvOXBqS6Mspmcuqp81uz0Vjx_m_YI0w06DOPCiI1BfY,12
|
31
|
+
gohumanloop-0.0.10.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|