gohumanloop 0.0.4__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.
- gohumanloop/__init__.py +15 -9
- gohumanloop/adapters/__init__.py +4 -4
- gohumanloop/adapters/langgraph_adapter.py +365 -220
- gohumanloop/cli/main.py +4 -1
- gohumanloop/core/interface.py +181 -215
- gohumanloop/core/manager.py +341 -361
- gohumanloop/manager/ghl_manager.py +223 -185
- gohumanloop/models/api_model.py +32 -7
- gohumanloop/models/glh_model.py +15 -11
- gohumanloop/providers/api_provider.py +233 -189
- gohumanloop/providers/base.py +179 -172
- gohumanloop/providers/email_provider.py +386 -325
- gohumanloop/providers/ghl_provider.py +19 -17
- gohumanloop/providers/terminal_provider.py +111 -92
- gohumanloop/utils/__init__.py +7 -1
- gohumanloop/utils/context_formatter.py +20 -15
- gohumanloop/utils/threadsafedict.py +64 -56
- gohumanloop/utils/utils.py +28 -28
- gohumanloop-0.0.6.dist-info/METADATA +259 -0
- gohumanloop-0.0.6.dist-info/RECORD +30 -0
- {gohumanloop-0.0.4.dist-info → gohumanloop-0.0.6.dist-info}/WHEEL +1 -1
- gohumanloop-0.0.4.dist-info/METADATA +0 -35
- gohumanloop-0.0.4.dist-info/RECORD +0 -30
- {gohumanloop-0.0.4.dist-info → gohumanloop-0.0.6.dist-info}/entry_points.txt +0 -0
- {gohumanloop-0.0.4.dist-info → gohumanloop-0.0.6.dist-info}/licenses/LICENSE +0 -0
- {gohumanloop-0.0.4.dist-info → gohumanloop-0.0.6.dist-info}/top_level.txt +0 -0
@@ -1,17 +1,18 @@
|
|
1
1
|
import os
|
2
2
|
from typing import Dict, Any, Optional
|
3
|
-
from pydantic import BaseModel, Field, field_validator, SecretStr
|
4
3
|
|
5
4
|
from gohumanloop.models.glh_model import GoHumanLoopConfig
|
6
5
|
from gohumanloop.providers.api_provider import APIProvider
|
7
6
|
from gohumanloop.utils import get_secret_from_env
|
8
7
|
|
8
|
+
|
9
9
|
class GoHumanLoopProvider(APIProvider):
|
10
10
|
"""
|
11
11
|
GoHumanLoop platform provider class.
|
12
12
|
This class is a concrete implementation of the `APIProvider` class.
|
13
13
|
The `GoHumanLoopProvider` class is responsible for interacting with the GoHumanLoop platform.
|
14
14
|
"""
|
15
|
+
|
15
16
|
def __init__(
|
16
17
|
self,
|
17
18
|
name: str,
|
@@ -19,14 +20,12 @@ class GoHumanLoopProvider(APIProvider):
|
|
19
20
|
poll_interval: int = 5,
|
20
21
|
max_retries: int = 3,
|
21
22
|
default_platform: Optional[str] = "GoHumanLoop",
|
22
|
-
config: Optional[Dict[str, Any]] = None
|
23
|
+
config: Optional[Dict[str, Any]] = None,
|
23
24
|
):
|
24
25
|
"""Initialize GoHumanLoop provider
|
25
|
-
|
26
|
+
|
26
27
|
Args:
|
27
28
|
name: Provider name
|
28
|
-
api_key: GoHumanLoop API key, if not provided will be fetched from environment variables
|
29
|
-
api_base_url: GoHumanLoop API base URL, if not provided will use default value
|
30
29
|
default_platform: Default platform, e.g. "wechat", "feishu" etc.
|
31
30
|
request_timeout: API request timeout in seconds
|
32
31
|
poll_interval: Polling interval in seconds
|
@@ -35,16 +34,18 @@ class GoHumanLoopProvider(APIProvider):
|
|
35
34
|
"""
|
36
35
|
# Get API key from environment variables (if not provided)
|
37
36
|
api_key = get_secret_from_env("GOHUMANLOOP_API_KEY")
|
38
|
-
|
37
|
+
|
38
|
+
if api_key is None:
|
39
|
+
raise ValueError("GOHUMANLOOP_API_KEY environment variable is not set!")
|
40
|
+
|
39
41
|
# Get API base URL from environment variables (if not provided)
|
40
|
-
api_base_url = os.environ.get(
|
41
|
-
|
42
|
-
# Validate configuration using pydantic model
|
43
|
-
ghl_config = GoHumanLoopConfig(
|
44
|
-
api_key=api_key,
|
45
|
-
api_base_url=api_base_url
|
42
|
+
api_base_url = os.environ.get(
|
43
|
+
"GOHUMANLOOP_API_BASE_URL", "https://www.gohumanloop.com"
|
46
44
|
)
|
47
|
-
|
45
|
+
|
46
|
+
# Validate configuration using pydantic model
|
47
|
+
ghl_config = GoHumanLoopConfig(api_key=api_key, api_base_url=api_base_url)
|
48
|
+
|
48
49
|
super().__init__(
|
49
50
|
name=name,
|
50
51
|
api_base_url=ghl_config.api_base_url,
|
@@ -53,12 +54,13 @@ class GoHumanLoopProvider(APIProvider):
|
|
53
54
|
request_timeout=request_timeout,
|
54
55
|
poll_interval=poll_interval,
|
55
56
|
max_retries=max_retries,
|
56
|
-
config=config
|
57
|
+
config=config,
|
57
58
|
)
|
58
|
-
|
59
|
+
|
59
60
|
def __str__(self) -> str:
|
60
61
|
"""Returns a string description of this instance"""
|
61
62
|
base_str = super().__str__()
|
62
|
-
ghl_info =
|
63
|
+
ghl_info = (
|
64
|
+
"- GoHumanLoop Provider: Connected to GoHumanLoop Official Platform\n"
|
65
|
+
)
|
63
66
|
return f"{ghl_info}{base_str}"
|
64
|
-
|
@@ -1,21 +1,22 @@
|
|
1
1
|
import asyncio
|
2
|
-
from concurrent.futures import ThreadPoolExecutor
|
3
|
-
from typing import Dict, Any, Optional
|
2
|
+
from concurrent.futures import ThreadPoolExecutor, Future
|
3
|
+
from typing import Dict, Any, Optional, Tuple
|
4
4
|
from datetime import datetime
|
5
5
|
|
6
|
-
from gohumanloop.core.interface import
|
6
|
+
from gohumanloop.core.interface import HumanLoopResult, HumanLoopStatus, HumanLoopType
|
7
7
|
from gohumanloop.providers.base import BaseProvider
|
8
8
|
|
9
|
+
|
9
10
|
class TerminalProvider(BaseProvider):
|
10
11
|
"""Terminal-based human-in-the-loop provider implementation
|
11
|
-
|
12
|
+
|
12
13
|
This provider interacts with users through command line interface, suitable for testing and simple scenarios.
|
13
14
|
Users can respond to requests via terminal input, supporting approval, information collection and conversation type interactions.
|
14
15
|
"""
|
15
|
-
|
16
|
+
|
16
17
|
def __init__(self, name: str, config: Optional[Dict[str, Any]] = None):
|
17
18
|
"""Initialize terminal provider
|
18
|
-
|
19
|
+
|
19
20
|
Args:
|
20
21
|
name: Provider name
|
21
22
|
config: Configuration options, may include:
|
@@ -23,11 +24,11 @@ class TerminalProvider(BaseProvider):
|
|
23
24
|
super().__init__(name, config)
|
24
25
|
|
25
26
|
# Store running terminal input tasks
|
26
|
-
self._terminal_input_tasks = {}
|
27
|
+
self._terminal_input_tasks: Dict[Tuple[str, str], Future] = {}
|
27
28
|
# Create thread pool for background service execution
|
28
29
|
self._executor = ThreadPoolExecutor(max_workers=10)
|
29
30
|
|
30
|
-
def __del__(self):
|
31
|
+
def __del__(self) -> None:
|
31
32
|
"""Destructor to ensure thread pool is properly closed"""
|
32
33
|
self._executor.shutdown(wait=False)
|
33
34
|
|
@@ -37,9 +38,11 @@ class TerminalProvider(BaseProvider):
|
|
37
38
|
|
38
39
|
def __str__(self) -> str:
|
39
40
|
base_str = super().__str__()
|
40
|
-
terminal_info =
|
41
|
+
terminal_info = (
|
42
|
+
"- Terminal Provider: Terminal-based human-in-the-loop implementation\n"
|
43
|
+
)
|
41
44
|
return f"{terminal_info}{base_str}"
|
42
|
-
|
45
|
+
|
43
46
|
async def async_request_humanloop(
|
44
47
|
self,
|
45
48
|
task_id: str,
|
@@ -47,10 +50,10 @@ class TerminalProvider(BaseProvider):
|
|
47
50
|
loop_type: HumanLoopType,
|
48
51
|
context: Dict[str, Any],
|
49
52
|
metadata: Optional[Dict[str, Any]] = None,
|
50
|
-
timeout: Optional[int] = None
|
53
|
+
timeout: Optional[int] = None,
|
51
54
|
) -> HumanLoopResult:
|
52
55
|
"""Request human-in-the-loop interaction through terminal
|
53
|
-
|
56
|
+
|
54
57
|
Args:
|
55
58
|
task_id: Task identifier
|
56
59
|
conversation_id: Conversation ID for multi-turn dialogs
|
@@ -58,13 +61,13 @@ class TerminalProvider(BaseProvider):
|
|
58
61
|
context: Context information provided to human
|
59
62
|
metadata: Additional metadata
|
60
63
|
timeout: Request timeout in seconds
|
61
|
-
|
64
|
+
|
62
65
|
Returns:
|
63
66
|
HumanLoopResult: Result object containing request ID and initial status
|
64
67
|
"""
|
65
68
|
# Generate request ID
|
66
69
|
request_id = self._generate_request_id()
|
67
|
-
|
70
|
+
|
68
71
|
# Store request information
|
69
72
|
self._store_request(
|
70
73
|
conversation_id=conversation_id,
|
@@ -73,36 +76,42 @@ class TerminalProvider(BaseProvider):
|
|
73
76
|
loop_type=loop_type,
|
74
77
|
context=context,
|
75
78
|
metadata=metadata or {},
|
76
|
-
timeout=timeout
|
79
|
+
timeout=timeout,
|
77
80
|
)
|
78
|
-
|
81
|
+
|
79
82
|
# Create initial result object
|
80
83
|
result = HumanLoopResult(
|
81
84
|
conversation_id=conversation_id,
|
82
85
|
request_id=request_id,
|
83
86
|
loop_type=loop_type,
|
84
|
-
status=HumanLoopStatus.PENDING
|
87
|
+
status=HumanLoopStatus.PENDING,
|
85
88
|
)
|
86
|
-
|
87
89
|
|
88
|
-
self._terminal_input_tasks[
|
90
|
+
self._terminal_input_tasks[
|
91
|
+
(conversation_id, request_id)
|
92
|
+
] = self._executor.submit(
|
93
|
+
self._run_async_terminal_interaction, conversation_id, request_id
|
94
|
+
)
|
89
95
|
|
90
96
|
# Create timeout task if timeout is specified
|
91
97
|
if timeout:
|
92
98
|
await self._async_create_timeout_task(conversation_id, request_id, timeout)
|
93
|
-
|
94
|
-
return result
|
95
99
|
|
100
|
+
return result
|
96
101
|
|
97
|
-
def _run_async_terminal_interaction(
|
102
|
+
def _run_async_terminal_interaction(
|
103
|
+
self, conversation_id: str, request_id: str
|
104
|
+
) -> None:
|
98
105
|
"""Run asynchronous terminal interaction in a separate thread"""
|
99
106
|
# Create new event loop
|
100
107
|
loop = asyncio.new_event_loop()
|
101
108
|
asyncio.set_event_loop(loop)
|
102
|
-
|
109
|
+
|
103
110
|
try:
|
104
111
|
# Run interaction processing in the new event loop
|
105
|
-
loop.run_until_complete(
|
112
|
+
loop.run_until_complete(
|
113
|
+
self._process_terminal_interaction(conversation_id, request_id)
|
114
|
+
)
|
106
115
|
finally:
|
107
116
|
loop.close()
|
108
117
|
# Remove from task dictionary
|
@@ -110,16 +119,14 @@ class TerminalProvider(BaseProvider):
|
|
110
119
|
del self._terminal_input_tasks[(conversation_id, request_id)]
|
111
120
|
|
112
121
|
async def async_check_request_status(
|
113
|
-
self,
|
114
|
-
conversation_id: str,
|
115
|
-
request_id: str
|
122
|
+
self, conversation_id: str, request_id: str
|
116
123
|
) -> HumanLoopResult:
|
117
124
|
"""Check request status
|
118
|
-
|
125
|
+
|
119
126
|
Args:
|
120
127
|
conversation_id: Conversation identifier
|
121
128
|
request_id: Request identifier
|
122
|
-
|
129
|
+
|
123
130
|
Returns:
|
124
131
|
HumanLoopResult: Result object containing current status
|
125
132
|
"""
|
@@ -130,9 +137,9 @@ class TerminalProvider(BaseProvider):
|
|
130
137
|
request_id=request_id,
|
131
138
|
loop_type=HumanLoopType.CONVERSATION,
|
132
139
|
status=HumanLoopStatus.ERROR,
|
133
|
-
error=f"Request '{request_id}' not found in conversation '{conversation_id}'"
|
140
|
+
error=f"Request '{request_id}' not found in conversation '{conversation_id}'",
|
134
141
|
)
|
135
|
-
|
142
|
+
|
136
143
|
# Build result object
|
137
144
|
result = HumanLoopResult(
|
138
145
|
conversation_id=conversation_id,
|
@@ -143,11 +150,11 @@ class TerminalProvider(BaseProvider):
|
|
143
150
|
feedback=request_info.get("feedback", {}),
|
144
151
|
responded_by=request_info.get("responded_by", None),
|
145
152
|
responded_at=request_info.get("responded_at", None),
|
146
|
-
error=request_info.get("error", None)
|
153
|
+
error=request_info.get("error", None),
|
147
154
|
)
|
148
|
-
|
155
|
+
|
149
156
|
return result
|
150
|
-
|
157
|
+
|
151
158
|
async def async_continue_humanloop(
|
152
159
|
self,
|
153
160
|
conversation_id: str,
|
@@ -156,13 +163,13 @@ class TerminalProvider(BaseProvider):
|
|
156
163
|
timeout: Optional[int] = None,
|
157
164
|
) -> HumanLoopResult:
|
158
165
|
"""Continue human-in-the-loop interaction for multi-turn conversations
|
159
|
-
|
166
|
+
|
160
167
|
Args:
|
161
168
|
conversation_id: Conversation identifier
|
162
169
|
context: Context information provided to human
|
163
170
|
metadata: Additional metadata
|
164
171
|
timeout: Request timeout in seconds
|
165
|
-
|
172
|
+
|
166
173
|
Returns:
|
167
174
|
HumanLoopResult: Result object containing request ID and status
|
168
175
|
"""
|
@@ -174,15 +181,15 @@ class TerminalProvider(BaseProvider):
|
|
174
181
|
request_id="",
|
175
182
|
loop_type=HumanLoopType.CONVERSATION,
|
176
183
|
status=HumanLoopStatus.ERROR,
|
177
|
-
error=f"Conversation '{conversation_id}' not found"
|
184
|
+
error=f"Conversation '{conversation_id}' not found",
|
178
185
|
)
|
179
|
-
|
186
|
+
|
180
187
|
# Generate new request ID
|
181
188
|
request_id = self._generate_request_id()
|
182
|
-
|
189
|
+
|
183
190
|
# Get task ID
|
184
191
|
task_id = conversation_info.get("task_id", "unknown_task")
|
185
|
-
|
192
|
+
|
186
193
|
# Store request information
|
187
194
|
self._store_request(
|
188
195
|
conversation_id=conversation_id,
|
@@ -191,30 +198,36 @@ class TerminalProvider(BaseProvider):
|
|
191
198
|
loop_type=HumanLoopType.CONVERSATION, # Default to conversation type for continued dialog
|
192
199
|
context=context,
|
193
200
|
metadata=metadata or {},
|
194
|
-
timeout=timeout
|
201
|
+
timeout=timeout,
|
195
202
|
)
|
196
|
-
|
203
|
+
|
197
204
|
# Create initial result object
|
198
205
|
result = HumanLoopResult(
|
199
206
|
conversation_id=conversation_id,
|
200
207
|
request_id=request_id,
|
201
208
|
loop_type=HumanLoopType.CONVERSATION,
|
202
|
-
status=HumanLoopStatus.PENDING
|
209
|
+
status=HumanLoopStatus.PENDING,
|
203
210
|
)
|
204
|
-
|
211
|
+
|
205
212
|
# Start async task to process user input
|
206
|
-
self._terminal_input_tasks[
|
207
|
-
|
213
|
+
self._terminal_input_tasks[
|
214
|
+
(conversation_id, request_id)
|
215
|
+
] = self._executor.submit(
|
216
|
+
self._run_async_terminal_interaction, conversation_id, request_id
|
217
|
+
)
|
218
|
+
|
208
219
|
# Create timeout task if timeout is specified
|
209
220
|
if timeout:
|
210
221
|
await self._async_create_timeout_task(conversation_id, request_id, timeout)
|
211
|
-
|
222
|
+
|
212
223
|
return result
|
213
|
-
|
214
|
-
async def _process_terminal_interaction(
|
224
|
+
|
225
|
+
async def _process_terminal_interaction(
|
226
|
+
self, conversation_id: str, request_id: str
|
227
|
+
) -> None:
|
215
228
|
request_info = self._get_request(conversation_id, request_id)
|
216
229
|
if not request_info:
|
217
|
-
return
|
230
|
+
return
|
218
231
|
|
219
232
|
prompt = self.build_prompt(
|
220
233
|
task_id=request_info["task_id"],
|
@@ -223,33 +236,40 @@ class TerminalProvider(BaseProvider):
|
|
223
236
|
loop_type=request_info["loop_type"],
|
224
237
|
created_at=request_info.get("created_at", ""),
|
225
238
|
context=request_info["context"],
|
226
|
-
metadata=request_info.get("metadata")
|
239
|
+
metadata=request_info.get("metadata"),
|
227
240
|
)
|
228
241
|
|
229
242
|
loop_type = request_info["loop_type"]
|
230
|
-
|
243
|
+
|
231
244
|
# Display prompt message
|
232
245
|
print(prompt)
|
233
|
-
|
246
|
+
|
234
247
|
# Handle different interaction types based on loop type
|
235
248
|
if loop_type == HumanLoopType.APPROVAL:
|
236
|
-
await self._async_handle_approval_interaction(
|
249
|
+
await self._async_handle_approval_interaction(
|
250
|
+
conversation_id, request_id, request_info
|
251
|
+
)
|
237
252
|
elif loop_type == HumanLoopType.INFORMATION:
|
238
|
-
await self._async_handle_information_interaction(
|
253
|
+
await self._async_handle_information_interaction(
|
254
|
+
conversation_id, request_id, request_info
|
255
|
+
)
|
239
256
|
else: # HumanLoopType.CONVERSATION
|
240
|
-
await self._async_handle_conversation_interaction(
|
257
|
+
await self._async_handle_conversation_interaction(
|
258
|
+
conversation_id, request_id, request_info
|
259
|
+
)
|
241
260
|
|
242
|
-
|
243
|
-
|
261
|
+
async def _async_handle_approval_interaction(
|
262
|
+
self, conversation_id: str, request_id: str, request_info: Dict[str, Any]
|
263
|
+
) -> None:
|
244
264
|
"""Handle approval type interaction
|
245
|
-
|
265
|
+
|
246
266
|
Args:
|
247
267
|
conversation_id: Conversation ID
|
248
268
|
request_id: Request ID
|
249
269
|
request_info: Request information
|
250
270
|
"""
|
251
271
|
print("\nPlease enter your decision (approve/reject):")
|
252
|
-
|
272
|
+
|
253
273
|
# Execute blocking input() call in thread pool using run_in_executor
|
254
274
|
loop = asyncio.get_event_loop()
|
255
275
|
response = await loop.run_in_executor(None, input)
|
@@ -266,53 +286,59 @@ class TerminalProvider(BaseProvider):
|
|
266
286
|
else:
|
267
287
|
print("\nInvalid input, please enter 'approve' or 'reject'")
|
268
288
|
# Recursively handle approval interaction
|
269
|
-
await self._async_handle_approval_interaction(
|
289
|
+
await self._async_handle_approval_interaction(
|
290
|
+
conversation_id, request_id, request_info
|
291
|
+
)
|
270
292
|
return
|
271
|
-
|
293
|
+
|
272
294
|
# Update request information
|
273
295
|
request_info["status"] = status
|
274
296
|
request_info["response"] = response_data
|
275
297
|
request_info["responded_by"] = "terminal_user"
|
276
298
|
request_info["responded_at"] = datetime.now().isoformat()
|
277
|
-
|
299
|
+
|
278
300
|
print(f"\nYour decision has been recorded: {status.value}")
|
279
|
-
|
280
|
-
async def _async_handle_information_interaction(
|
301
|
+
|
302
|
+
async def _async_handle_information_interaction(
|
303
|
+
self, conversation_id: str, request_id: str, request_info: Dict[str, Any]
|
304
|
+
) -> None:
|
281
305
|
"""Handle information collection type interaction
|
282
|
-
|
306
|
+
|
283
307
|
Args:
|
284
308
|
conversation_id: Conversation ID
|
285
309
|
request_id: Request ID
|
286
310
|
request_info: Request information
|
287
311
|
"""
|
288
312
|
print("\nPlease provide the required information:")
|
289
|
-
|
313
|
+
|
290
314
|
# Execute blocking input() call in thread pool using run_in_executor
|
291
315
|
loop = asyncio.get_event_loop()
|
292
316
|
response = await loop.run_in_executor(None, input)
|
293
|
-
|
317
|
+
|
294
318
|
# Update request information
|
295
319
|
request_info["status"] = HumanLoopStatus.COMPLETED
|
296
320
|
request_info["response"] = response
|
297
321
|
request_info["responded_by"] = "terminal_user"
|
298
322
|
request_info["responded_at"] = datetime.now().isoformat()
|
299
|
-
|
323
|
+
|
300
324
|
print("\nYour information has been recorded")
|
301
|
-
|
302
|
-
async def _async_handle_conversation_interaction(
|
325
|
+
|
326
|
+
async def _async_handle_conversation_interaction(
|
327
|
+
self, conversation_id: str, request_id: str, request_info: Dict[str, Any]
|
328
|
+
) -> None:
|
303
329
|
"""Handle conversation type interaction
|
304
|
-
|
330
|
+
|
305
331
|
Args:
|
306
332
|
conversation_id: Conversation ID
|
307
333
|
request_id: Request ID
|
308
334
|
request_info: Request information
|
309
335
|
"""
|
310
336
|
print("\nPlease enter your response (type 'exit' to end conversation):")
|
311
|
-
|
337
|
+
|
312
338
|
# Execute blocking input() call in thread pool using run_in_executor
|
313
339
|
loop = asyncio.get_event_loop()
|
314
340
|
response = await loop.run_in_executor(None, input)
|
315
|
-
|
341
|
+
|
316
342
|
# Process response
|
317
343
|
if response.strip().lower() in ["exit", "quit", "结束", "退出"]:
|
318
344
|
status = HumanLoopStatus.COMPLETED
|
@@ -325,20 +351,16 @@ class TerminalProvider(BaseProvider):
|
|
325
351
|
request_info["response"] = response
|
326
352
|
request_info["responded_by"] = "terminal_user"
|
327
353
|
request_info["responded_at"] = datetime.now().isoformat()
|
328
|
-
|
354
|
+
|
329
355
|
print("\nYour response has been recorded")
|
330
356
|
|
331
|
-
async def async_cancel_request(
|
332
|
-
self,
|
333
|
-
conversation_id: str,
|
334
|
-
request_id: str
|
335
|
-
) -> bool:
|
357
|
+
async def async_cancel_request(self, conversation_id: str, request_id: str) -> bool:
|
336
358
|
"""Cancel human-in-the-loop request
|
337
|
-
|
359
|
+
|
338
360
|
Args:
|
339
361
|
conversation_id: Conversation identifier for multi-turn dialogues
|
340
362
|
request_id: Request identifier for specific interaction request
|
341
|
-
|
363
|
+
|
342
364
|
Return:
|
343
365
|
bool: Whether cancellation was successful, True indicates successful cancellation,
|
344
366
|
False indicates cancellation failed
|
@@ -347,19 +369,16 @@ class TerminalProvider(BaseProvider):
|
|
347
369
|
if request_key in self._terminal_input_tasks:
|
348
370
|
self._terminal_input_tasks[request_key].cancel()
|
349
371
|
del self._terminal_input_tasks[request_key]
|
350
|
-
|
372
|
+
|
351
373
|
# 调用父类方法取消请求
|
352
374
|
return await super().async_cancel_request(conversation_id, request_id)
|
353
|
-
|
354
|
-
async def async_cancel_conversation(
|
355
|
-
self,
|
356
|
-
conversation_id: str
|
357
|
-
) -> bool:
|
375
|
+
|
376
|
+
async def async_cancel_conversation(self, conversation_id: str) -> bool:
|
358
377
|
"""Cancel the entire conversation
|
359
|
-
|
378
|
+
|
360
379
|
Args:
|
361
380
|
conversation_id: Conversation identifier
|
362
|
-
|
381
|
+
|
363
382
|
Returns:
|
364
383
|
bool: Whether cancellation was successful
|
365
384
|
"""
|
@@ -369,6 +388,6 @@ class TerminalProvider(BaseProvider):
|
|
369
388
|
if request_key in self._terminal_input_tasks:
|
370
389
|
self._terminal_input_tasks[request_key].cancel()
|
371
390
|
del self._terminal_input_tasks[request_key]
|
372
|
-
|
391
|
+
|
373
392
|
# 调用父类方法取消对话
|
374
|
-
return await super().async_cancel_conversation(conversation_id)
|
393
|
+
return await super().async_cancel_conversation(conversation_id)
|
gohumanloop/utils/__init__.py
CHANGED
@@ -1,40 +1,45 @@
|
|
1
|
-
from typing import Dict, Any
|
1
|
+
from typing import Dict, Any
|
2
2
|
import json
|
3
3
|
|
4
|
+
|
4
5
|
class ContextFormatter:
|
5
6
|
"""上下文格式化工具"""
|
6
|
-
|
7
|
+
|
7
8
|
@staticmethod
|
8
9
|
def format_for_human(context: Dict[str, Any]) -> str:
|
9
10
|
"""将上下文格式化为人类可读的文本"""
|
10
11
|
result = []
|
11
|
-
|
12
|
+
|
12
13
|
# 添加标题(如果有)
|
13
14
|
if "title" in context:
|
14
15
|
result.append(f"# {context['title']}\n")
|
15
|
-
|
16
|
+
|
16
17
|
# 添加描述(如果有)
|
17
18
|
if "description" in context:
|
18
19
|
result.append(f"{context['description']}\n")
|
19
|
-
|
20
|
+
|
20
21
|
# 添加任务信息
|
21
22
|
if "task" in context:
|
22
23
|
result.append(f"## 任务\n{context['task']}\n")
|
23
|
-
|
24
|
+
|
24
25
|
# 添加代理信息
|
25
26
|
if "agent" in context:
|
26
27
|
result.append(f"## 代理\n{context['agent']}\n")
|
27
|
-
|
28
|
+
|
28
29
|
# 添加操作信息
|
29
30
|
if "action" in context:
|
30
31
|
result.append(f"## 请求的操作\n{context['action']}\n")
|
31
|
-
|
32
|
+
|
32
33
|
# 添加原因
|
33
34
|
if "reason" in context:
|
34
35
|
result.append(f"## 原因\n{context['reason']}\n")
|
35
|
-
|
36
|
+
|
36
37
|
# 添加其他键值对
|
37
|
-
other_keys = [
|
38
|
+
other_keys = [
|
39
|
+
k
|
40
|
+
for k in context.keys()
|
41
|
+
if k not in ["title", "description", "task", "agent", "action", "reason"]
|
42
|
+
]
|
38
43
|
if other_keys:
|
39
44
|
result.append("## 附加信息\n")
|
40
45
|
for key in other_keys:
|
@@ -42,18 +47,18 @@ class ContextFormatter:
|
|
42
47
|
if isinstance(value, (dict, list)):
|
43
48
|
value = json.dumps(value, ensure_ascii=False, indent=2)
|
44
49
|
result.append(f"### {key}\n```\n{value}\n```\n")
|
45
|
-
|
50
|
+
|
46
51
|
return "\n".join(result)
|
47
|
-
|
52
|
+
|
48
53
|
@staticmethod
|
49
54
|
def format_for_api(context: Dict[str, Any]) -> Dict[str, Any]:
|
50
55
|
"""将上下文格式化为API友好的格式"""
|
51
56
|
# 复制上下文以避免修改原始数据
|
52
57
|
formatted = context.copy()
|
53
|
-
|
58
|
+
|
54
59
|
# 确保所有值都是可序列化的
|
55
60
|
for key, value in formatted.items():
|
56
61
|
if not isinstance(value, (str, int, float, bool, list, dict, type(None))):
|
57
62
|
formatted[key] = str(value)
|
58
|
-
|
59
|
-
return formatted
|
63
|
+
|
64
|
+
return formatted
|