gohumanloop 0.0.10__py3-none-any.whl → 0.0.11__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.
@@ -19,7 +19,7 @@ from inspect import iscoroutinefunction
19
19
  from contextlib import asynccontextmanager, contextmanager
20
20
  import logging
21
21
 
22
- from gohumanloop.utils import run_async_safely
22
+ from gohumanloop.utils import run_async_safely, generate_function_summary
23
23
  from gohumanloop.core.interface import (
24
24
  HumanLoopRequest,
25
25
  HumanLoopResult,
@@ -241,6 +241,12 @@ class HumanloopAdapter:
241
241
 
242
242
  @wraps(fn)
243
243
  async def async_wrapper(*args: Any, **kwargs: Any) -> R:
244
+ # Check if ret_key exists in function parameters
245
+ if ret_key not in fn.__code__.co_varnames:
246
+ raise ValueError(
247
+ f"Function {fn.__name__} must have parameter named {ret_key}"
248
+ )
249
+
244
250
  # Determine if callback is instance or factory function
245
251
  cb = None
246
252
  if callable(callback) and not isinstance(callback, HumanLoopCallback):
@@ -255,7 +261,8 @@ class HumanloopAdapter:
255
261
  conversation_id=conversation_id,
256
262
  loop_type=HumanLoopType.APPROVAL,
257
263
  context={
258
- "message": {
264
+ "message": generate_function_summary(fn, args, kwargs),
265
+ "function": {
259
266
  "function_name": fn.__name__,
260
267
  "function_signature": str(fn.__code__.co_varnames),
261
268
  "arguments": str(args),
@@ -289,7 +296,9 @@ class HumanloopAdapter:
289
296
  "error": result.error,
290
297
  }
291
298
 
292
- kwargs[ret_key] = approval_info
299
+ # Inject approval info into kwargs
300
+ kwargs[ret_key] = approval_info
301
+
293
302
  # Check approval result
294
303
  if isinstance(result, HumanLoopResult):
295
304
  # Handle based on approval status
@@ -426,6 +435,12 @@ class HumanloopAdapter:
426
435
 
427
436
  @wraps(fn)
428
437
  async def async_wrapper(*args: Any, **kwargs: Any) -> R:
438
+ # Check if ret_key exists in function parameters
439
+ if ret_key not in fn.__code__.co_varnames:
440
+ raise ValueError(
441
+ f"Function {fn.__name__} must have parameter named {ret_key}"
442
+ )
443
+
429
444
  # Determine if callback is instance or factory function
430
445
  cb = None
431
446
  state = args[0] if args else None
@@ -516,8 +531,8 @@ class HumanloopAdapter:
516
531
  "responded_at": result.responded_at,
517
532
  "error": result.error,
518
533
  }
519
-
520
- kwargs[ret_key] = conversation_info
534
+ # Inject conversation info into kwargs
535
+ kwargs[ret_key] = conversation_info
521
536
 
522
537
  if isinstance(result, HumanLoopResult):
523
538
  if iscoroutinefunction(fn):
@@ -631,6 +646,12 @@ class HumanloopAdapter:
631
646
 
632
647
  @wraps(fn)
633
648
  async def async_wrapper(*args: Any, **kwargs: Any) -> R:
649
+ # Check if ret_key exists in function parameters
650
+ if ret_key not in fn.__code__.co_varnames:
651
+ raise ValueError(
652
+ f"Function {fn.__name__} must have parameter named {ret_key}"
653
+ )
654
+
634
655
  # Determine if callback is an instance or factory function
635
656
  # callback: can be HumanLoopCallback instance or factory function
636
657
  # - If factory function: accepts state parameter and returns HumanLoopCallback instance
@@ -665,12 +686,11 @@ class HumanloopAdapter:
665
686
  provider_id=provider_id,
666
687
  blocking=True,
667
688
  )
668
-
669
- # 初始化审批结果对象为None
689
+ # Initialize response info object as None
670
690
  resp_info = None
671
691
 
672
692
  if isinstance(result, HumanLoopResult):
673
- # 如果结果是HumanLoopResult类型,则构建完整的审批信息
693
+ # If result is HumanLoopResult type, build complete response info
674
694
  resp_info = {
675
695
  "conversation_id": result.conversation_id,
676
696
  "request_id": result.request_id,
@@ -682,12 +702,12 @@ class HumanloopAdapter:
682
702
  "responded_at": result.responded_at,
683
703
  "error": result.error,
684
704
  }
705
+ # Inject approval info into kwargs
706
+ kwargs[ret_key] = resp_info
685
707
 
686
- kwargs[ret_key] = resp_info
687
-
688
- # 检查结果是否有效
708
+ # Check if result is valid
689
709
  if isinstance(result, HumanLoopResult):
690
- # 返回获取信息结果,由用户去判断是否使用
710
+ # Return the information result, let user decide whether to use it
691
711
  if iscoroutinefunction(fn):
692
712
  ret = await fn(*args, **kwargs)
693
713
  else:
@@ -701,7 +721,7 @@ class HumanloopAdapter:
701
721
  ret = run_async_safely(async_wrapper(*args, **kwargs))
702
722
  return cast(R, ret)
703
723
 
704
- # 根据被装饰函数类型返回对应的wrapper
724
+ # Return corresponding wrapper based on decorated function type
705
725
  if iscoroutinefunction(fn):
706
726
  return async_wrapper
707
727
  return sync_wrapper
@@ -1,7 +1,5 @@
1
1
  from .utils import run_async_safely, get_secret_from_env
2
+ from .context_formatter import generate_function_summary
2
3
 
3
4
 
4
- __all__ = [
5
- "run_async_safely",
6
- "get_secret_from_env",
7
- ]
5
+ __all__ = ["run_async_safely", "get_secret_from_env", "generate_function_summary"]
@@ -1,64 +1,136 @@
1
- from typing import Dict, Any
2
- import json
3
-
4
-
5
- class ContextFormatter:
6
- """上下文格式化工具"""
7
-
8
- @staticmethod
9
- def format_for_human(context: Dict[str, Any]) -> str:
10
- """将上下文格式化为人类可读的文本"""
11
- result = []
12
-
13
- # 添加标题(如果有)
14
- if "title" in context:
15
- result.append(f"# {context['title']}\n")
16
-
17
- # 添加描述(如果有)
18
- if "description" in context:
19
- result.append(f"{context['description']}\n")
20
-
21
- # 添加任务信息
22
- if "task" in context:
23
- result.append(f"## 任务\n{context['task']}\n")
24
-
25
- # 添加代理信息
26
- if "agent" in context:
27
- result.append(f"## 代理\n{context['agent']}\n")
28
-
29
- # 添加操作信息
30
- if "action" in context:
31
- result.append(f"## 请求的操作\n{context['action']}\n")
32
-
33
- # 添加原因
34
- if "reason" in context:
35
- result.append(f"## 原因\n{context['reason']}\n")
36
-
37
- # 添加其他键值对
38
- other_keys = [
39
- k
40
- for k in context.keys()
41
- if k not in ["title", "description", "task", "agent", "action", "reason"]
42
- ]
43
- if other_keys:
44
- result.append("## 附加信息\n")
45
- for key in other_keys:
46
- value = context[key]
47
- if isinstance(value, (dict, list)):
48
- value = json.dumps(value, ensure_ascii=False, indent=2)
49
- result.append(f"### {key}\n```\n{value}\n```\n")
50
-
51
- return "\n".join(result)
52
-
53
- @staticmethod
54
- def format_for_api(context: Dict[str, Any]) -> Dict[str, Any]:
55
- """将上下文格式化为API友好的格式"""
56
- # 复制上下文以避免修改原始数据
57
- formatted = context.copy()
58
-
59
- # 确保所有值都是可序列化的
60
- for key, value in formatted.items():
61
- if not isinstance(value, (str, int, float, bool, list, dict, type(None))):
62
- formatted[key] = str(value)
63
-
64
- return formatted
1
+ import inspect
2
+ from typing import Callable, Any, Dict, Union, cast
3
+
4
+
5
+ def _param_detail_zh(p: inspect.Parameter) -> str:
6
+ return (
7
+ f" {p.name}: "
8
+ + (f"类型[{p.annotation}] " if p.annotation != p.empty else "")
9
+ + (f"(默认值={p.default})" if p.default != p.empty else "")
10
+ )
11
+
12
+
13
+ def _param_detail_en(p: inspect.Parameter) -> str:
14
+ return (
15
+ f" {p.name}: "
16
+ + (f"Type[{p.annotation}] " if p.annotation != p.empty else "")
17
+ + (f"(default={p.default})" if p.default != p.empty else "")
18
+ )
19
+
20
+
21
+ def generate_function_summary(
22
+ fn: Callable,
23
+ *args: Any,
24
+ language: str = "zh",
25
+ **kwargs: Any,
26
+ ) -> str:
27
+ """生成支持中英文切换的函数说明模板
28
+
29
+ Args:
30
+ fn: 目标函数
31
+ *args: 位置参数示例
32
+ **kwargs: 关键字参数示例
33
+ language: 输出语言 ('zh'/'en')
34
+
35
+ Returns:
36
+ 纯文本格式的函数说明
37
+ """
38
+
39
+ # 定义翻译类型
40
+
41
+ # 语言配置
42
+ translations: Dict[
43
+ str, Dict[str, Union[str, Callable[[inspect.Parameter], str]]]
44
+ ] = {
45
+ "zh": {
46
+ "title": "函数说明",
47
+ "name": "函数名称",
48
+ "module": "所属模块",
49
+ "description": "功能描述",
50
+ "params": "参数列表",
51
+ "param_detail": _param_detail_zh,
52
+ "return": "返回值类型",
53
+ "current_input": "当前输入",
54
+ "positional_args": "位置参数",
55
+ "keyword_args": "关键字参数",
56
+ "usage": "调用方式",
57
+ "approval": "审批状态",
58
+ "no_doc": "无文档说明",
59
+ },
60
+ "en": {
61
+ "title": "Function Documentation",
62
+ "name": "Function Name",
63
+ "module": "Module",
64
+ "description": "Description",
65
+ "params": "Parameters",
66
+ "param_detail": _param_detail_en,
67
+ "return": "Return Type",
68
+ "current_input": "Current Input",
69
+ "positional_args": "Positional Args",
70
+ "keyword_args": "Keyword Args",
71
+ "usage": "Usage",
72
+ "approval": "Approval Status",
73
+ "no_doc": "No documentation",
74
+ },
75
+ }
76
+
77
+ lang = translations.get(language, translations["zh"])
78
+
79
+ # 明确告诉类型检查器这是一个可调用对象
80
+ param_detail_func = cast(Callable[[inspect.Parameter], str], lang["param_detail"])
81
+
82
+ # 获取函数信息
83
+ func_name = fn.__name__
84
+ module_obj = inspect.getmodule(fn)
85
+ module = module_obj.__name__ if module_obj is not None else str(lang["no_doc"])
86
+ doc = (fn.__doc__ or str(lang["no_doc"])).strip()
87
+ sig = inspect.signature(fn)
88
+
89
+ # 构建模板
90
+ template = (
91
+ f"""
92
+ - {lang['title']}: {func_name}
93
+ - {lang['name']}: {func_name}
94
+ - {lang['module']}: {module}
95
+
96
+ - {lang['description']}:
97
+ {doc}
98
+
99
+ - {lang['params']}:
100
+ """
101
+ + "\n".join(param_detail_func(p) for p in sig.parameters.values())
102
+ + f"""
103
+
104
+ - {lang['return']}: {sig.return_annotation if sig.return_annotation != sig.empty else lang['no_doc']}
105
+
106
+ - {lang['current_input']}:
107
+ {lang['positional_args']}: {args}
108
+ {lang['keyword_args']}: {kwargs}
109
+
110
+ - {lang['usage']}: {func_name}(*{args}, **{kwargs})
111
+
112
+ """
113
+ )
114
+ return template.strip()
115
+
116
+
117
+ if __name__ == "__main__":
118
+ # 示例函数
119
+ def calculate(a: int, b: float = 1.0) -> float:
120
+ """计算两个数的乘积/Calculate the product of two numbers"""
121
+ return a * b
122
+
123
+ # 中文输出
124
+ print("==== 中文版 ====")
125
+ print(generate_function_summary(calculate, 3, b=2.5))
126
+
127
+ # 英文输出
128
+ print("\n==== English Version ====")
129
+ print(
130
+ generate_function_summary(
131
+ calculate,
132
+ 3,
133
+ language="en",
134
+ b=2.5,
135
+ )
136
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gohumanloop
3
- Version: 0.0.10
3
+ Version: 0.0.11
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
@@ -1,7 +1,7 @@
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=G1Tw7uzBrJObDdVT2kxifotkGZIptnZh5GUqCx54rSs,32884
4
+ gohumanloop/adapters/base_adapter.py,sha256=v5oEeWhdnUI6QL9G-JziMIJmg30RxSozVttAaxZwmmo,33926
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
@@ -19,13 +19,13 @@ gohumanloop/providers/base.py,sha256=rOA9xRTh-rn-64RIZpzcIhWNar8LEzYrEXS5H5r7PpM
19
19
  gohumanloop/providers/email_provider.py,sha256=kQA6kG7dqpV6R1cJUeaanLmxMVk9pcIwweNZA4IC-2o,45947
20
20
  gohumanloop/providers/ghl_provider.py,sha256=NmOac2LR0d87pc91sMuYtvTYsKdp4wZrhfmSYuZL2ko,2350
21
21
  gohumanloop/providers/terminal_provider.py,sha256=aBNgGpspiu7fGigN6K-gqGlyjFDFvIqKMGp58PxGyPg,15447
22
- gohumanloop/utils/__init__.py,sha256=l0e3lnOVrt6HwaxidNGFXORX4awCKemhJZ_t3k-5u-s,124
23
- gohumanloop/utils/context_formatter.py,sha256=sAWrKJpxmsHga6gsyBwFBlAsHW8bjR1hkaGhk1PoE1M,2064
22
+ gohumanloop/utils/__init__.py,sha256=WoxyK2JXOp_JtKGTxyH3Drly8hpDXxCTV2-QoHY5N0s,199
23
+ gohumanloop/utils/context_formatter.py,sha256=2ybnESv4-f4F1VKPkm81sbWE6e5h3LHljm5CczdznXg,3783
24
24
  gohumanloop/utils/threadsafedict.py,sha256=9uyewnwmvS3u1fCx3SK0YWFxHMcyIwlye1Ev7WW7WHA,9588
25
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,,
26
+ gohumanloop-0.0.11.dist-info/licenses/LICENSE,sha256=-U5tuCcSpndQwSKWtZbFbazb-_AtZcZL2kQgHbSLg-M,1064
27
+ gohumanloop-0.0.11.dist-info/METADATA,sha256=F3dCnFSie_t_P7Ttw93XPCQCHeRvs-N4oPPQeXU_76w,11428
28
+ gohumanloop-0.0.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
+ gohumanloop-0.0.11.dist-info/entry_points.txt,sha256=wM6jqRRD8bQXkvIduRVCuAJIlbyWg_F5EDXo5OZ_PwY,88
30
+ gohumanloop-0.0.11.dist-info/top_level.txt,sha256=LvOXBqS6Mspmcuqp81uz0Vjx_m_YI0w06DOPCiI1BfY,12
31
+ gohumanloop-0.0.11.dist-info/RECORD,,