gohumanloop 0.0.5__py3-none-any.whl → 0.0.6__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.
@@ -3,8 +3,9 @@ import asyncio
3
3
  import threading
4
4
 
5
5
 
6
- K = TypeVar('K')
7
- V = TypeVar('V')
6
+ K = TypeVar("K")
7
+ V = TypeVar("V")
8
+
8
9
 
9
10
  class ThreadSafeDict(Generic[K, V]):
10
11
  """
@@ -27,71 +28,71 @@ class ThreadSafeDict(Generic[K, V]):
27
28
  - 为每个键创建独立的锁,减少不同键之间的锁竞争
28
29
  - 使用全局锁 self._global_lock 保护键级别锁的创建和删除
29
30
  """
30
-
31
- def __init__(self):
32
- self._dict = {}
31
+
32
+ def __init__(self) -> None:
33
+ self._dict: dict[Any, Any] = {}
33
34
  # 使用 threading.RLock 支持同步操作的线程安全
34
35
  self._sync_lock = threading.RLock()
35
36
  # 使用 asyncio.Lock 支持异步操作的线程安全
36
37
  self._async_lock = asyncio.Lock()
37
38
  # 键级别锁字典
38
- self._key_locks = {}
39
+ self._key_locks: dict[Any, asyncio.Lock] = {}
39
40
  # 键级别锁的全局锁
40
41
  self._global_lock = asyncio.Lock()
41
-
42
- async def _get_key_lock(self, key):
42
+
43
+ async def _get_key_lock(self, key: K) -> asyncio.Lock:
43
44
  """获取指定键的锁,如果不存在则创建"""
44
45
  async with self._global_lock:
45
46
  if key not in self._key_locks:
46
47
  self._key_locks[key] = asyncio.Lock()
47
48
  return self._key_locks[key]
48
-
49
+
49
50
  # 同步方法 - 使用 threading.RLock 保证线程安全
50
- def __getitem__(self, key: K) -> V:
51
+ def __getitem__(self, key: K) -> Any:
51
52
  """获取值 - 同步方法,用于 dict[key] 语法"""
52
53
  with self._sync_lock:
53
54
  return self._dict[key]
54
-
55
+
55
56
  def __setitem__(self, key: K, value: V) -> None:
56
57
  """设置值 - 同步方法,用于 dict[key] = value 语法"""
57
58
  with self._sync_lock:
58
59
  self._dict[key] = value
59
-
60
+
60
61
  def __delitem__(self, key: K) -> None:
61
62
  """删除键 - 同步方法,用于 del dict[key] 语法"""
62
63
  with self._sync_lock:
63
64
  del self._dict[key]
64
-
65
+
65
66
  def __contains__(self, key: K) -> bool:
66
67
  """检查键是否存在 - 同步方法,用于 key in dict 语法"""
67
68
  with self._sync_lock:
68
69
  return key in self._dict
69
-
70
- def get(self, key: K, default: Optional[V] = None) -> Optional[V]:
70
+
71
+ def get(self, key: K, default: Optional[V] = None) -> Any:
71
72
  """获取值,如果不存在则返回默认值 - 同步方法"""
72
73
  with self._sync_lock:
73
74
  return self._dict.get(key, default)
74
-
75
+
75
76
  def __len__(self) -> int:
76
77
  """获取字典长度 - 同步方法,用于 len(dict) 语法"""
77
78
  with self._sync_lock:
78
79
  return len(self._dict)
79
-
80
- def keys(self):
80
+
81
+ def keys(self) -> list:
81
82
  """获取所有键 - 同步方法"""
82
83
  with self._sync_lock:
83
84
  return list(self._dict.keys())
84
-
85
- def values(self):
85
+
86
+ def values(self) -> list:
86
87
  """获取所有值 - 同步方法"""
87
88
  with self._sync_lock:
88
89
  return list(self._dict.values())
89
-
90
- def items(self):
90
+
91
+ def items(self) -> list:
91
92
  """获取所有键值对 - 同步方法"""
92
93
  with self._sync_lock:
93
94
  return list(self._dict.items())
94
-
95
+
95
96
  def update(self, key: K, updates: Dict[str, Any]) -> bool:
96
97
  """更新字典中的值 - 同步方法"""
97
98
  with self._sync_lock:
@@ -99,7 +100,7 @@ class ThreadSafeDict(Generic[K, V]):
99
100
  self._dict[key].update(updates)
100
101
  return True
101
102
  return False
102
-
103
+
103
104
  def update_item(self, key: K, item_key: Any, item_value: Any) -> bool:
104
105
  """更新字典中的单个项 - 同步方法"""
105
106
  with self._sync_lock:
@@ -107,20 +108,20 @@ class ThreadSafeDict(Generic[K, V]):
107
108
  self._dict[key][item_key] = item_value
108
109
  return True
109
110
  return False
110
-
111
+
111
112
  # 异步方法 - 使用 asyncio.Lock 保证线程安全
112
- async def aget(self, key: K, default: Optional[V] = None) -> Optional[V]:
113
+ async def aget(self, key: K, default: Optional[V] = None) -> Any:
113
114
  """安全地获取值 - 异步方法"""
114
115
  async with self._async_lock:
115
116
  return self._dict.get(key, default)
116
-
117
+
117
118
  async def aset(self, key: K, value: V) -> None:
118
119
  """安全地设置值 - 异步方法"""
119
120
  key_lock = await self._get_key_lock(key)
120
121
  async with key_lock:
121
122
  async with self._async_lock:
122
123
  self._dict[key] = value
123
-
124
+
124
125
  async def adelete(self, key: K) -> bool:
125
126
  """安全地删除键 - 异步方法"""
126
127
  key_lock = await self._get_key_lock(key)
@@ -134,7 +135,7 @@ class ThreadSafeDict(Generic[K, V]):
134
135
  del self._key_locks[key]
135
136
  return True
136
137
  return False
137
-
138
+
138
139
  async def aupdate(self, key: K, updates: Dict[str, Any]) -> bool:
139
140
  """安全地更新值 - 异步方法"""
140
141
  key_lock = await self._get_key_lock(key)
@@ -144,7 +145,7 @@ class ThreadSafeDict(Generic[K, V]):
144
145
  self._dict[key].update(updates)
145
146
  return True
146
147
  return False
147
-
148
+
148
149
  async def aupdate_item(self, key: K, item_key: Any, item_value: Any) -> bool:
149
150
  """安全地更新字典中的单个项 - 异步方法"""
150
151
  key_lock = await self._get_key_lock(key)
@@ -154,90 +155,97 @@ class ThreadSafeDict(Generic[K, V]):
154
155
  self._dict[key][item_key] = item_value
155
156
  return True
156
157
  return False
157
-
158
+
158
159
  async def acontains(self, key: K) -> bool:
159
160
  """安全地检查键是否存在 - 异步方法"""
160
161
  async with self._async_lock:
161
162
  return key in self._dict
162
-
163
+
163
164
  async def alen(self) -> int:
164
165
  """安全地获取字典长度 - 异步方法"""
165
166
  async with self._async_lock:
166
167
  return len(self._dict)
167
-
168
- async def akeys(self):
168
+
169
+ async def akeys(self) -> list:
169
170
  """安全地获取所有键 - 异步方法"""
170
171
  async with self._async_lock:
171
172
  return list(self._dict.keys())
172
-
173
- async def avalues(self):
173
+
174
+ async def avalues(self) -> list:
174
175
  """安全地获取所有值 - 异步方法"""
175
176
  async with self._async_lock:
176
177
  return list(self._dict.values())
177
-
178
- async def aitems(self):
178
+
179
+ async def aitems(self) -> list:
179
180
  """安全地获取所有键值对 - 异步方法"""
180
181
  async with self._async_lock:
181
182
  return list(self._dict.items())
182
183
 
184
+
183
185
  if __name__ == "__main__":
184
186
  # 测试同步方法
185
- def test_sync_methods():
187
+ def test_sync_methods() -> None:
186
188
  print("\n=== 测试同步方法 ===")
187
- sync_dict = ThreadSafeDict()
188
-
189
+ sync_dict: ThreadSafeDict = ThreadSafeDict()
190
+
189
191
  # 测试基本的增删改查操作
190
192
  sync_dict["key1"] = "value1"
191
193
  print("设置并获取:", sync_dict["key1"]) # value1
192
194
  print("键存在性检查:", "key1" in sync_dict) # True
193
195
  print("获取默认值:", sync_dict.get("not_exist", "default")) # default
194
-
196
+
195
197
  # 测试字典长度
196
198
  print("字典长度:", len(sync_dict)) # 1
197
-
199
+
198
200
  # 测试字典方法
199
201
  sync_dict["key2"] = "value2"
200
202
  print("所有键:", sync_dict.keys()) # ['key1', 'key2']
201
203
  print("所有值:", sync_dict.values()) # ['value1', 'value2']
202
- print("所有键值对:", sync_dict.items()) # [('key1', 'value1'), ('key2', 'value2')]
203
-
204
+ print(
205
+ "所有键值对:", sync_dict.items()
206
+ ) # [('key1', 'value1'), ('key2', 'value2')]
207
+
204
208
  # 测试嵌套字典更新
205
209
  sync_dict["nested"] = {"a": 1}
206
210
  sync_dict.update("nested", {"b": 2})
207
211
  sync_dict.update_item("nested", "c", 3)
208
212
  print("嵌套字典:", sync_dict["nested"]) # {'a': 1, 'b': 2, 'c': 3}
209
-
213
+
210
214
  # 测试删除操作
211
215
  del sync_dict["key1"]
212
216
  print("删除后检查:", "key1" in sync_dict) # False
213
217
 
214
218
  # 测试异步方法
215
- async def test_async_methods():
219
+ async def test_async_methods() -> None:
216
220
  print("\n=== 测试异步方法 ===")
217
- async_dict = ThreadSafeDict()
218
-
221
+ async_dict: ThreadSafeDict = ThreadSafeDict()
222
+
219
223
  # 测试基本的异步增删改查
220
224
  await async_dict.aset("key1", "value1")
221
225
  print("异步获取:", await async_dict.aget("key1")) # value1
222
226
  print("异步键检查:", await async_dict.acontains("key1")) # True
223
-
227
+
224
228
  # 测试异步字典操作
225
229
  await async_dict.aset("key2", "value2")
226
230
  print("异步长度:", await async_dict.alen()) # 2
227
231
  print("异步所有键:", await async_dict.akeys()) # ['key1', 'key2']
228
232
  print("异步所有值:", await async_dict.avalues()) # ['value1', 'value2']
229
- print("异步所有键值对:", await async_dict.aitems()) # [('key1', 'value1'), ('key2', 'value2')]
230
-
233
+ print(
234
+ "异步所有键值对:", await async_dict.aitems()
235
+ ) # [('key1', 'value1'), ('key2', 'value2')]
236
+
231
237
  # 测试异步嵌套字典更新
232
238
  await async_dict.aset("nested", {"x": 1})
233
239
  await async_dict.aupdate("nested", {"y": 2})
234
240
  await async_dict.aupdate_item("nested", "z", 3)
235
- print("异步嵌套字典:", await async_dict.aget("nested")) # {'x': 1, 'y': 2, 'z': 3}
236
-
241
+ print(
242
+ "异步嵌套字典:", await async_dict.aget("nested")
243
+ ) # {'x': 1, 'y': 2, 'z': 3}
244
+
237
245
  # 测试异步删除
238
246
  await async_dict.adelete("key1")
239
247
  print("异步删除后检查:", await async_dict.acontains("key1")) # False
240
-
248
+
241
249
  # 运行测试
242
250
  test_sync_methods()
243
- asyncio.run(test_async_methods())
251
+ asyncio.run(test_async_methods())
@@ -1,13 +1,13 @@
1
1
  import asyncio
2
2
  import os
3
- from typing import Optional, Union
3
+ from typing import Awaitable, Any, Optional, Union
4
4
  from pydantic import SecretStr
5
- import warnings
6
5
  import logging
7
6
 
8
7
  logger = logging.getLogger(__name__)
9
8
 
10
- def run_async_safely(coro):
9
+
10
+ def run_async_safely(coro: Awaitable[Any]) -> Any:
11
11
  """
12
12
  Safely run async coroutines in synchronous environment
13
13
  Will raise RuntimeError if called in async environment
@@ -16,7 +16,7 @@ def run_async_safely(coro):
16
16
  loop = asyncio.get_running_loop()
17
17
  except RuntimeError: # No running event loop
18
18
  loop = None
19
-
19
+
20
20
  if loop is not None:
21
21
  raise RuntimeError(
22
22
  "Detected running event loop! "
@@ -24,19 +24,19 @@ def run_async_safely(coro):
24
24
  "If you really need to call sync code from async context, "
25
25
  "consider using asyncio.to_thread() or other proper methods."
26
26
  )
27
-
27
+
28
28
  # Handle synchronous environment
29
29
  try:
30
30
  loop = asyncio.get_event_loop()
31
- logger.info("Using existing event loop.")
31
+ logger.debug("Using existing event loop.")
32
32
  except RuntimeError:
33
33
  loop = asyncio.new_event_loop()
34
34
  asyncio.set_event_loop(loop)
35
35
  own_loop = True
36
- logger.info("Created new event loop.")
36
+ logger.debug("Created new event loop.")
37
37
  else:
38
38
  own_loop = False
39
-
39
+
40
40
  try:
41
41
  return loop.run_until_complete(coro)
42
42
  finally:
@@ -47,24 +47,24 @@ def run_async_safely(coro):
47
47
  def get_secret_from_env(
48
48
  key: Union[str, list, tuple],
49
49
  default: Optional[str] = None,
50
- error_message: Optional[str] = None
50
+ error_message: Optional[str] = None,
51
51
  ) -> Optional[SecretStr]:
52
- """Get a value from an environment variable."""
53
- if isinstance(key, (list, tuple)):
54
- for k in key:
55
- if k in os.environ:
56
- return SecretStr(os.environ[k])
57
- if isinstance(key, str) and key in os.environ:
58
- return SecretStr(os.environ[key])
59
- if isinstance(default, str):
60
- return SecretStr(default)
61
- if default is None:
62
- return None
63
- if error_message:
64
- raise ValueError(error_message)
65
- msg = (
66
- f"Did not find {key}, please add an environment variable"
67
- f" `{key}` which contains it, or pass"
68
- f" `{key}` as a named parameter."
69
- )
70
- raise ValueError(msg)
52
+ """Get a value from an environment variable."""
53
+ if isinstance(key, (list, tuple)):
54
+ for k in key:
55
+ if k in os.environ:
56
+ return SecretStr(os.environ[k])
57
+ if isinstance(key, str) and key in os.environ:
58
+ return SecretStr(os.environ[key])
59
+ if isinstance(default, str):
60
+ return SecretStr(default)
61
+ if default is None:
62
+ return None
63
+ if error_message:
64
+ raise ValueError(error_message)
65
+ msg = (
66
+ f"Did not find {key}, please add an environment variable"
67
+ f" `{key}` which contains it, or pass"
68
+ f" `{key}` as a named parameter."
69
+ )
70
+ raise ValueError(msg)
@@ -0,0 +1,259 @@
1
+ Metadata-Version: 2.4
2
+ Name: gohumanloop
3
+ Version: 0.0.6
4
+ Summary: Perfecting AI workflows with human intelligence
5
+ Author-email: gohumanloop authors <baird0917@163.com>
6
+ Project-URL: repository, https://github.com/ptonlix/gohumanloop
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: aiohttp>=3.11.16
11
+ Requires-Dist: click>=8.1.8
12
+ Requires-Dist: dotenv>=0.9.9
13
+ Requires-Dist: pydantic>=2.11.3
14
+ Requires-Dist: tomli>=2.2.1
15
+ Provides-Extra: email
16
+ Requires-Dist: imapclient>=3.0.1; extra == "email"
17
+ Provides-Extra: langgraph
18
+ Requires-Dist: langgraph>=0.3.30; extra == "langgraph"
19
+ Provides-Extra: apiservices
20
+ Requires-Dist: fastapi>=0.115.12; extra == "apiservices"
21
+ Requires-Dist: uvicorn>=0.34.2; extra == "apiservices"
22
+ Dynamic: license-file
23
+
24
+ <div align="center">
25
+
26
+ ![Wordmark Logo of HumanLayer](./docs/images/wordmark.png)
27
+ <b face="雅黑">Perfecting AI workflows with human intelligence</b>
28
+
29
+ </div>
30
+
31
+ **GoHumanLoop**: A Python library empowering AI agents to dynamically request human input (approval/feedback/conversation) at critical stages. Core features:
32
+
33
+ - `Human-in-the-loop control`: Lets AI agent systems pause and escalate decisions, enhancing safety and trust.
34
+ - `Multi-channel integration`: Supports Terminal, Email, API, and frameworks like LangGraph/CrewAI (soon).
35
+ - `Flexible workflows`: Combines automated reasoning with human oversight for reliable AI operations.
36
+
37
+ Ensures responsible AI deployment by bridging autonomous agents and human judgment.
38
+
39
+ <div align="center">
40
+ <img alt="Repostart" src="https://img.shields.io/github/stars/ptonlix/gohumanloop"/>
41
+ <img alt=" Python" src="https://img.shields.io/badge/Python-3.10%2B-blue"/>
42
+ <img alt="license" src="https://img.shields.io/badge/license-MIT-green"/>
43
+
44
+ [简体中文](README-zh.md) | English
45
+
46
+ </div>
47
+
48
+ ## Table of contents
49
+
50
+ - [Getting Started](#getting-started)
51
+ - [Why GoHumanloop?](#why-humanlayer)
52
+ - [Key Features](#key-features)
53
+ - [Examples](#examples)
54
+ - [Roadmap](#roadmap)
55
+ - [Contributing](#contributing)
56
+ - [License](#license)
57
+
58
+ ## 🎹 Getting Started
59
+
60
+ To get started, check out the following example or jump straight into one of the [Examples](./examples/):
61
+
62
+ - 🦜⛓️ [LangGraph](./examples/langgraph/)
63
+
64
+ ### Example
65
+
66
+ **GoHumanLoop** currently supports `Python`.
67
+
68
+ - Installation
69
+
70
+ ```shell
71
+ pip install gohumanloop
72
+ ```
73
+
74
+ - Example
75
+
76
+ The following example enhances [the official LangGraph example](https://langchain-ai.github.io/langgraph/tutorials/get-started/4-human-in-the-loop/#5-resume-execution) with `human-in-the-loop` functionality.
77
+
78
+ > 💡 By default, it uses `Terminal` as the `langgraph_adapter` for human interaction.
79
+
80
+ ```python
81
+ import os
82
+ from langchain.chat_models import init_chat_model
83
+ from typing import Annotated
84
+
85
+ from langchain_tavily import TavilySearch
86
+ from langchain_core.tools import tool
87
+ from typing_extensions import TypedDict
88
+
89
+ from langgraph.checkpoint.memory import MemorySaver
90
+ from langgraph.graph import StateGraph, START, END
91
+ from langgraph.graph.message import add_messages
92
+ from langgraph.prebuilt import ToolNode, tools_condition
93
+
94
+ # from langgraph.types import Command, interrupt # Don't use langgraph, use gohumanloop instead
95
+
96
+ from gohumanloop.adapters.langgraph_adapter import interrupt, create_resume_command
97
+
98
+ os.environ["DEEPSEEK_API_KEY"] = "sk-xxx"
99
+ os.environ["TAVILY_API_KEY"] = "tvly-xxx"
100
+
101
+ llm = init_chat_model("deepseek:deepseek-chat")
102
+
103
+ class State(TypedDict):
104
+ messages: Annotated[list, add_messages]
105
+
106
+ graph_builder = StateGraph(State)
107
+
108
+ @tool
109
+ def human_assistance(query: str) -> str:
110
+ """Request assistance from a human."""
111
+ human_response = interrupt({"query": query})
112
+ return human_response
113
+
114
+ tool = TavilySearch(max_results=2)
115
+ tools = [tool, human_assistance]
116
+ llm_with_tools = llm.bind_tools(tools)
117
+
118
+ def chatbot(state: State):
119
+ message = llm_with_tools.invoke(state["messages"])
120
+ # Because we will be interrupting during tool execution,
121
+ # we disable parallel tool calling to avoid repeating any
122
+ # tool invocations when we resume.
123
+ assert len(message.tool_calls) <= 1
124
+ return {"messages": [message]}
125
+
126
+ graph_builder.add_node("chatbot", chatbot)
127
+
128
+ tool_node = ToolNode(tools=tools)
129
+ graph_builder.add_node("tools", tool_node)
130
+
131
+ graph_builder.add_conditional_edges(
132
+ "chatbot",
133
+ tools_condition,
134
+ )
135
+ graph_builder.add_edge("tools", "chatbot")
136
+ graph_builder.add_edge(START, "chatbot")
137
+
138
+ memory = MemorySaver()
139
+
140
+ graph = graph_builder.compile(checkpointer=memory)
141
+
142
+ user_input = "I need some expert guidance for building an AI agent. Could you request assistance for me?"
143
+ config = {"configurable": {"thread_id": "1"}}
144
+
145
+ events = graph.stream(
146
+ {"messages": [{"role": "user", "content": user_input}]},
147
+ config,
148
+ stream_mode="values",
149
+ )
150
+ for event in events:
151
+ if "messages" in event:
152
+ event["messages"][-1].pretty_print()
153
+
154
+ # LangGraph code:
155
+ # human_response = (
156
+ # "We, the experts are here to help! We'd recommend you check out LangGraph to build your agent."
157
+ # "It's much more reliable and extensible than simple autonomous agents."
158
+ # )
159
+
160
+ # human_command = Command(resume={"data": human_response})
161
+
162
+ # GoHumanLoop code:
163
+ human_command = create_resume_command() # Use this command to resume the execution,instead of using the command above
164
+
165
+ events = graph.stream(human_command, config, stream_mode="values")
166
+ for event in events:
167
+ if "messages" in event:
168
+ event["messages"][-1].pretty_print()
169
+
170
+ ```
171
+
172
+ ## 🎵 Why GoHumanloop?
173
+
174
+ ### Human-in-the-loop
175
+
176
+ <div align="center">
177
+ <img height=240 src="http://cdn.oyster-iot.cloud/202505210851404.png"><br>
178
+ <b face="雅黑">Even with state-of-the-art agentic reasoning and prompt routing, LLMs are not sufficiently reliable to be given access to high-stakes functions without human oversight</b>
179
+ </div>
180
+ <br>
181
+
182
+ `Human-in-the-loop` is an AI system design philosophy that integrates human judgment and supervision into AI decision-making processes. This concept is particularly important in AI Agent systems:
183
+
184
+ - **Safety Assurance**: Allows human intervention and review at critical decision points to prevent potentially harmful AI decisions
185
+ - **Quality Control**: Improves accuracy and reliability of AI outputs through expert feedback
186
+ - **Continuous Learning**: AI systems can learn and improve from human feedback, creating a virtuous cycle
187
+ - **Clear Accountability**: Maintains ultimate human control over important decisions with clear responsibility
188
+
189
+ In practice, Human-in-the-loop can take various forms - from simple decision confirmation to deep human-AI collaborative dialogues - ensuring optimal balance between autonomy and human oversight to maximize the potential of AI Agent systems.
190
+
191
+ #### Typical Use Cases
192
+
193
+ <div align="center">
194
+ <img height=120 src="http://cdn.oyster-iot.cloud/tool-call-review.png"><br>
195
+ <b face="雅黑"> A human can review and edit the output from the agent before proceeding. This is particularly critical in applications where the tool calls requested may be sensitive or require human oversight.</b>
196
+ </div>
197
+ <br>
198
+
199
+ - 🛠️ Tool Call Review: Humans can review, edit or approve tool call requests initiated by LLMs before execution
200
+ - ✅ Model Output Verification: Humans can review, edit or approve content generated by LLMs (text, decisions, etc.)
201
+ - 💡 Context Provision: Allows LLMs to actively request human input for clarification, additional details or multi-turn conversation context
202
+
203
+ ### Secure and Efficient Go➡Humanloop
204
+
205
+ `GoHumanloop` provides a set of tools deeply integrated within AI Agents to ensure constant `Human-in-the-loop` oversight. It deterministically ensures high-risk function calls must undergo human review while also enabling human expert feedback, thereby improving AI system reliability and safety while reducing risks from LLM hallucinations.
206
+
207
+ <div align="center">
208
+ <img height=420 src="http://cdn.oyster-iot.cloud/202505210943862.png"><br>
209
+ <b face="雅黑"> The Outer-Loop and Inversion of Control</b>
210
+ </div>
211
+ <br>
212
+
213
+ Through `GoHumanloop`'s encapsulation, you can implement secure and efficient `Human-in-the-loop` when requesting tools, Agent nodes, MCP services and other Agents.
214
+
215
+ ## 📚 Key Features
216
+
217
+ <div align="center">
218
+ <img height=360 src="http://cdn.oyster-iot.cloud/202505211030197.png"><br>
219
+ <b face="雅黑"> GoHumanLoop Architecture</b>
220
+ </div>
221
+ <br>
222
+
223
+ `GoHumanloop` offers the following core capabilities:
224
+
225
+ - **Approval:** Requests human review or approval when executing specific tool calls or Agent nodes
226
+ - **Information:** Obtains critical human input during task execution to reduce LLM hallucination risks
227
+ - **Conversation:** Enables multi-turn interactions with humans through dialogue to acquire richer contextual information
228
+ - **Framework-specific Integration:** Provides specialized integration methods for specific Agent frameworks, such as `interrupt` and `resume` for `LangGraph`
229
+
230
+ ## 📅 Roadmap
231
+
232
+ | Feature | Status |
233
+ | ----------------- | ---------- |
234
+ | Approval | ⚙️ Beta |
235
+ | Information | ⚙️ Beta |
236
+ | Conversation | ⚙️ Beta |
237
+ | Email Provider | ⚙️ Beta |
238
+ | Terminal Provider | ⚙️ Beta |
239
+ | API Provider | ⚙️ Beta |
240
+ | Default Manager | ⚙️ Beta |
241
+ | GLH Manager | 🗓️ Planned |
242
+ | Langchain Support | ⚙️ Beta |
243
+ | CrewAI Support | 🗓️ Planned |
244
+
245
+ - 💡 GLH Manager - GoHumanLoop Manager will integrate with the upcoming GoHumanLoop Hub platform to provide users with more flexible management options.
246
+
247
+ ## 🤝 Contributing
248
+
249
+ The GoHumanLoop SDK and documentation are open source. We welcome contributions in the form of issues, documentation and PRs. For more details, please see [CONTRIBUTING.md](./CONTRIBUTING.md)
250
+
251
+ ## 📱 Contact
252
+
253
+ <img height=300 src="http://cdn.oyster-iot.cloud/202505231802103.png"/>
254
+
255
+ 🎉 If you're interested in this project, feel free to scan the QR code to contact the author.
256
+
257
+ ## 🌟 Star History
258
+
259
+ [![Star History Chart](https://api.star-history.com/svg?repos=gohumanloop/gohumanloop&type=Date)](https://www.star-history.com/#gohumanloop/gohumanloop&Date)
@@ -0,0 +1,30 @@
1
+ gohumanloop/__init__.py,sha256=oJqVOtRRDHNxwxZ2Blw7ycBRwcTBMTURUCx-XcL2YJ0,1969
2
+ gohumanloop/__main__.py,sha256=zdGKN92H9SgwZfL4xLqPkE1YaiRcHhVg_GqC-H1VurA,75
3
+ gohumanloop/adapters/__init__.py,sha256=cFXd9qzyR0oZg8mR912PShj2b_3BAM_RO_cIVMXVdWQ,390
4
+ gohumanloop/adapters/langgraph_adapter.py,sha256=il_zsQMdgbJrwIn_ucQiH1ku3JhozxIQ7RH82KqTLwA,36750
5
+ gohumanloop/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ gohumanloop/cli/main.py,sha256=Txjk31MlkiX9zeHZfFEXEu0s2IBESY8sxrxBEzZKk2U,767
7
+ gohumanloop/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ gohumanloop/core/interface.py,sha256=D59FR4Bj-7gCFXw_55pPcFBjG2bY-EI5sYZl46tBaB0,21476
9
+ gohumanloop/core/manager.py,sha256=xyJJItJvt5ZPudAa3kWy282jn-I5yCsLJYNPaJyIQLI,29906
10
+ gohumanloop/manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ gohumanloop/manager/ghl_manager.py,sha256=rmbfaS5mAb9c8RPr9Id24vO5DJT9ZC4D282dHZ26SJE,22695
12
+ gohumanloop/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ gohumanloop/models/api_model.py,sha256=1LtSQdx2ZVMzTdPrOVGe1JE1Cn_B0CnNQDp1-LlnqLw,2950
14
+ gohumanloop/models/glh_model.py,sha256=gix4f_57G4URkwXhCspWhU08Sf1MKu9FtUCx424Vf3A,940
15
+ gohumanloop/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ gohumanloop/providers/api_provider.py,sha256=kgduU2udtBUaaMHOjDic7v3bb5kTJeedmlLRpcaOgJM,25771
17
+ gohumanloop/providers/base.py,sha256=JHvkInxLIWkUvypR3fphRGgisSMOkutpl-fRnYSHdOw,21893
18
+ gohumanloop/providers/email_provider.py,sha256=n4-k4m3oQ8TocT-yxGLTwUMTXfgddFiGxEKMq9W6FGA,44704
19
+ gohumanloop/providers/ghl_provider.py,sha256=NmOac2LR0d87pc91sMuYtvTYsKdp4wZrhfmSYuZL2ko,2350
20
+ gohumanloop/providers/terminal_provider.py,sha256=6YiATcbc6TIYrmzT2BSd9U6gNnE6o8TX4HO1M5Ejxgc,14613
21
+ gohumanloop/utils/__init__.py,sha256=l0e3lnOVrt6HwaxidNGFXORX4awCKemhJZ_t3k-5u-s,124
22
+ gohumanloop/utils/context_formatter.py,sha256=sAWrKJpxmsHga6gsyBwFBlAsHW8bjR1hkaGhk1PoE1M,2064
23
+ gohumanloop/utils/threadsafedict.py,sha256=9uyewnwmvS3u1fCx3SK0YWFxHMcyIwlye1Ev7WW7WHA,9588
24
+ gohumanloop/utils/utils.py,sha256=-bti65OaVh5dlvc5BIgiTw6dqxmqdKnOQHE0V-LKjek,2107
25
+ gohumanloop-0.0.6.dist-info/licenses/LICENSE,sha256=-U5tuCcSpndQwSKWtZbFbazb-_AtZcZL2kQgHbSLg-M,1064
26
+ gohumanloop-0.0.6.dist-info/METADATA,sha256=waWXbFIFOXC9loVTl4OJUTB-UbjSae_TZcXLIrMek8k,10057
27
+ gohumanloop-0.0.6.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
28
+ gohumanloop-0.0.6.dist-info/entry_points.txt,sha256=wM6jqRRD8bQXkvIduRVCuAJIlbyWg_F5EDXo5OZ_PwY,88
29
+ gohumanloop-0.0.6.dist-info/top_level.txt,sha256=LvOXBqS6Mspmcuqp81uz0Vjx_m_YI0w06DOPCiI1BfY,12
30
+ gohumanloop-0.0.6.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.1)
2
+ Generator: setuptools (80.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5