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.
- gohumanloop/__init__.py +6 -8
- gohumanloop/adapters/__init__.py +4 -4
- gohumanloop/adapters/langgraph_adapter.py +348 -207
- gohumanloop/cli/main.py +4 -1
- gohumanloop/core/interface.py +181 -215
- gohumanloop/core/manager.py +332 -265
- 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.5.dist-info → gohumanloop-0.0.6.dist-info}/WHEEL +1 -1
- gohumanloop-0.0.5.dist-info/METADATA +0 -35
- gohumanloop-0.0.5.dist-info/RECORD +0 -30
- {gohumanloop-0.0.5.dist-info → gohumanloop-0.0.6.dist-info}/entry_points.txt +0 -0
- {gohumanloop-0.0.5.dist-info → gohumanloop-0.0.6.dist-info}/licenses/LICENSE +0 -0
- {gohumanloop-0.0.5.dist-info → gohumanloop-0.0.6.dist-info}/top_level.txt +0 -0
gohumanloop/providers/base.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
from abc import ABC
|
2
|
-
import
|
3
|
-
from typing import Dict, Any, Optional, List
|
2
|
+
from typing import Dict, Any, Optional, List, Tuple
|
4
3
|
import asyncio
|
5
4
|
import json
|
6
5
|
import uuid
|
@@ -10,25 +9,32 @@ from gohumanloop.utils.threadsafedict import ThreadSafeDict
|
|
10
9
|
from gohumanloop.utils import run_async_safely
|
11
10
|
|
12
11
|
from gohumanloop.core.interface import (
|
13
|
-
HumanLoopProvider,
|
12
|
+
HumanLoopProvider,
|
13
|
+
HumanLoopResult,
|
14
|
+
HumanLoopStatus,
|
15
|
+
HumanLoopType,
|
14
16
|
)
|
15
17
|
|
18
|
+
|
16
19
|
class BaseProvider(HumanLoopProvider, ABC):
|
17
20
|
"""Base implementation of human-in-the-loop provider"""
|
18
21
|
|
19
|
-
|
20
|
-
def __init__(self, name: str, config: Optional[Dict[str, Any]] = None):
|
22
|
+
def __init__(self, name: str, config: Optional[Dict[str, Any]] = None):
|
21
23
|
self.config = config or {}
|
22
24
|
# Custom name, will use UUID if not provided
|
23
25
|
self.name = name
|
24
26
|
# Store request information using (conversation_id, request_id) as key
|
25
|
-
self._requests
|
27
|
+
self._requests: ThreadSafeDict[
|
28
|
+
Tuple[str, str], Dict[str, Any]
|
29
|
+
] = (
|
30
|
+
ThreadSafeDict()
|
31
|
+
) # Using thread-safe dictionary to store request information
|
26
32
|
# Store conversation information, including request list and latest request ID
|
27
|
-
self._conversations = {}
|
33
|
+
self._conversations: Dict[str, Dict[str, Any]] = {}
|
28
34
|
# For quick lookup of requests in conversations
|
29
|
-
self._conversation_requests = defaultdict(list)
|
35
|
+
self._conversation_requests: defaultdict[str, List[str]] = defaultdict(list)
|
30
36
|
# Store timeout tasks
|
31
|
-
self._timeout_tasks = {}
|
37
|
+
self._timeout_tasks: Dict[Tuple[str, str], asyncio.Task] = {}
|
32
38
|
|
33
39
|
self.prompt_template = self.config.get("prompt_template", "{context}")
|
34
40
|
|
@@ -36,13 +42,18 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
36
42
|
"""Returns a string description of this instance"""
|
37
43
|
total_conversations = len(self._conversations)
|
38
44
|
total_requests = len(self._requests)
|
39
|
-
active_requests = sum(
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
45
|
+
active_requests = sum(
|
46
|
+
1
|
47
|
+
for req in self._requests.values()
|
48
|
+
if req["status"] in [HumanLoopStatus.PENDING, HumanLoopStatus.INPROGRESS]
|
49
|
+
)
|
50
|
+
|
51
|
+
return (
|
52
|
+
f"conversations={total_conversations}, "
|
53
|
+
f"total_requests={total_requests}, "
|
54
|
+
f"active_requests={active_requests})"
|
55
|
+
)
|
56
|
+
|
46
57
|
def __repr__(self) -> str:
|
47
58
|
"""Returns a detailed string representation of this instance"""
|
48
59
|
return self.__str__()
|
@@ -56,13 +67,13 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
56
67
|
conversation_id: str,
|
57
68
|
request_id: str,
|
58
69
|
error: Optional[str] = None,
|
59
|
-
):
|
70
|
+
) -> None:
|
60
71
|
"""Update request status"""
|
61
72
|
request_key = (conversation_id, request_id)
|
62
73
|
if request_key in self._requests:
|
63
74
|
self._requests[request_key]["status"] = HumanLoopStatus.ERROR
|
64
75
|
self._requests[request_key]["error"] = error
|
65
|
-
|
76
|
+
|
66
77
|
def _store_request(
|
67
78
|
self,
|
68
79
|
conversation_id: str,
|
@@ -84,7 +95,7 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
84
95
|
"status": HumanLoopStatus.PENDING,
|
85
96
|
"timeout": timeout,
|
86
97
|
}
|
87
|
-
|
98
|
+
|
88
99
|
# Update conversation information
|
89
100
|
if conversation_id not in self._conversations:
|
90
101
|
self._conversations[conversation_id] = {
|
@@ -92,24 +103,29 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
92
103
|
"latest_request_id": None,
|
93
104
|
"created_at": datetime.now().isoformat(),
|
94
105
|
}
|
95
|
-
|
106
|
+
|
96
107
|
# Add request to conversation request list
|
97
108
|
self._conversation_requests[conversation_id].append(request_id)
|
98
109
|
# Update latest request ID
|
99
110
|
self._conversations[conversation_id]["latest_request_id"] = request_id
|
100
|
-
|
101
|
-
def _get_request(
|
111
|
+
|
112
|
+
def _get_request(
|
113
|
+
self, conversation_id: str, request_id: str
|
114
|
+
) -> Optional[Dict[str, Any]]:
|
102
115
|
"""Get request information"""
|
103
|
-
|
104
|
-
|
116
|
+
ret: Optional[Dict[str, Any]] = self._requests.get(
|
117
|
+
(conversation_id, request_id)
|
118
|
+
)
|
119
|
+
return ret
|
120
|
+
|
105
121
|
def _get_conversation(self, conversation_id: str) -> Optional[Dict[str, Any]]:
|
106
122
|
"""Get conversation information"""
|
107
123
|
return self._conversations.get(conversation_id)
|
108
|
-
|
124
|
+
|
109
125
|
def _get_conversation_requests(self, conversation_id: str) -> List[str]:
|
110
126
|
"""Get all request IDs in the conversation"""
|
111
127
|
return self._conversation_requests.get(conversation_id, [])
|
112
|
-
|
128
|
+
|
113
129
|
async def async_request_humanloop(
|
114
130
|
self,
|
115
131
|
task_id: str,
|
@@ -117,10 +133,10 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
117
133
|
loop_type: HumanLoopType,
|
118
134
|
context: Dict[str, Any],
|
119
135
|
metadata: Optional[Dict[str, Any]] = None,
|
120
|
-
timeout: Optional[int] = None
|
136
|
+
timeout: Optional[int] = None,
|
121
137
|
) -> HumanLoopResult:
|
122
138
|
"""Request human-in-the-loop interaction
|
123
|
-
|
139
|
+
|
124
140
|
Args:
|
125
141
|
task_id: Task identifier
|
126
142
|
conversation_id: Conversation ID for multi-turn dialogues
|
@@ -128,13 +144,13 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
128
144
|
context: Context information provided to human
|
129
145
|
metadata: Additional metadata
|
130
146
|
timeout: Request timeout in seconds
|
131
|
-
|
147
|
+
|
132
148
|
Returns:
|
133
149
|
HumanLoopResult: Result object containing request ID and initial status
|
134
150
|
"""
|
135
151
|
# Subclasses must implement this method
|
136
152
|
raise NotImplementedError("Subclasses must implement request_humanloop")
|
137
|
-
|
153
|
+
|
138
154
|
def request_humanloop(
|
139
155
|
self,
|
140
156
|
task_id: str,
|
@@ -142,10 +158,10 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
142
158
|
loop_type: HumanLoopType,
|
143
159
|
context: Dict[str, Any],
|
144
160
|
metadata: Optional[Dict[str, Any]] = None,
|
145
|
-
timeout: Optional[int] = None
|
161
|
+
timeout: Optional[int] = None,
|
146
162
|
) -> HumanLoopResult:
|
147
163
|
"""Request human-in-the-loop interaction (synchronous version)
|
148
|
-
|
164
|
+
|
149
165
|
Args:
|
150
166
|
task_id: Task identifier
|
151
167
|
conversation_id: Conversation ID for multi-turn dialogues
|
@@ -153,34 +169,32 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
153
169
|
context: Context information provided to human
|
154
170
|
metadata: Additional metadata
|
155
171
|
timeout: Request timeout in seconds
|
156
|
-
|
172
|
+
|
157
173
|
Returns:
|
158
174
|
HumanLoopResult: Result object containing request ID and initial status
|
159
175
|
"""
|
160
176
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
)
|
177
|
+
result: HumanLoopResult = run_async_safely(
|
178
|
+
self.async_request_humanloop(
|
179
|
+
task_id=task_id,
|
180
|
+
conversation_id=conversation_id,
|
181
|
+
loop_type=loop_type,
|
182
|
+
context=context,
|
183
|
+
metadata=metadata,
|
184
|
+
timeout=timeout,
|
170
185
|
)
|
171
|
-
|
186
|
+
)
|
187
|
+
return result
|
172
188
|
|
173
189
|
async def async_check_request_status(
|
174
|
-
self,
|
175
|
-
conversation_id: str,
|
176
|
-
request_id: str
|
190
|
+
self, conversation_id: str, request_id: str
|
177
191
|
) -> HumanLoopResult:
|
178
192
|
"""Check request status
|
179
|
-
|
193
|
+
|
180
194
|
Args:
|
181
195
|
conversation_id: Conversation identifier for multi-turn dialogues
|
182
196
|
request_id: Request identifier for specific interaction request
|
183
|
-
|
197
|
+
|
184
198
|
Returns:
|
185
199
|
HumanLoopResult: Result object containing current request status, including status, response data, etc.
|
186
200
|
"""
|
@@ -191,43 +205,40 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
191
205
|
request_id=request_id,
|
192
206
|
loop_type=HumanLoopType.CONVERSATION,
|
193
207
|
status=HumanLoopStatus.ERROR,
|
194
|
-
error=f"Request '{request_id}' not found in conversation '{conversation_id}'"
|
208
|
+
error=f"Request '{request_id}' not found in conversation '{conversation_id}'",
|
195
209
|
)
|
196
|
-
|
210
|
+
|
197
211
|
# Subclasses need to implement specific status check logic
|
198
212
|
raise NotImplementedError("Subclasses must implement check_request_status")
|
199
213
|
|
200
|
-
|
201
214
|
def check_request_status(
|
202
|
-
self,
|
203
|
-
conversation_id: str,
|
204
|
-
request_id: str
|
215
|
+
self, conversation_id: str, request_id: str
|
205
216
|
) -> HumanLoopResult:
|
206
217
|
"""Check conversation status (synchronous version)
|
207
|
-
|
218
|
+
|
208
219
|
Args:
|
209
220
|
conversation_id: Conversation identifier
|
210
|
-
|
221
|
+
|
211
222
|
Returns:
|
212
223
|
HumanLoopResult: Result containing the status of the latest request in the conversation
|
213
224
|
"""
|
214
225
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
request_id=request_id
|
219
|
-
)
|
226
|
+
result: HumanLoopResult = run_async_safely(
|
227
|
+
self.async_check_request_status(
|
228
|
+
conversation_id=conversation_id, request_id=request_id
|
220
229
|
)
|
230
|
+
)
|
231
|
+
|
232
|
+
return result
|
221
233
|
|
222
234
|
async def async_check_conversation_status(
|
223
|
-
self,
|
224
|
-
conversation_id: str
|
235
|
+
self, conversation_id: str
|
225
236
|
) -> HumanLoopResult:
|
226
237
|
"""Check conversation status
|
227
|
-
|
238
|
+
|
228
239
|
Args:
|
229
240
|
conversation_id: Conversation identifier
|
230
|
-
|
241
|
+
|
231
242
|
Returns:
|
232
243
|
HumanLoopResult: Result containing the status of the latest request in the conversation
|
233
244
|
"""
|
@@ -238,9 +249,9 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
238
249
|
request_id="",
|
239
250
|
loop_type=HumanLoopType.CONVERSATION,
|
240
251
|
status=HumanLoopStatus.ERROR,
|
241
|
-
error=f"Conversation '{conversation_id}' not found"
|
252
|
+
error=f"Conversation '{conversation_id}' not found",
|
242
253
|
)
|
243
|
-
|
254
|
+
|
244
255
|
latest_request_id = conversation_info.get("latest_request_id")
|
245
256
|
if not latest_request_id:
|
246
257
|
return HumanLoopResult(
|
@@ -248,42 +259,34 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
248
259
|
request_id="",
|
249
260
|
loop_type=HumanLoopType.CONVERSATION,
|
250
261
|
status=HumanLoopStatus.ERROR,
|
251
|
-
error=f"No requests found in conversation '{conversation_id}'"
|
262
|
+
error=f"No requests found in conversation '{conversation_id}'",
|
252
263
|
)
|
253
|
-
|
264
|
+
|
254
265
|
return await self.async_check_request_status(conversation_id, latest_request_id)
|
255
|
-
|
256
|
-
|
257
|
-
def check_conversation_status(
|
258
|
-
self,
|
259
|
-
conversation_id: str
|
260
|
-
) -> HumanLoopResult:
|
266
|
+
|
267
|
+
def check_conversation_status(self, conversation_id: str) -> HumanLoopResult:
|
261
268
|
"""Check conversation status (synchronous version)
|
262
|
-
|
269
|
+
|
263
270
|
Args:
|
264
271
|
conversation_id: Conversation identifier
|
265
|
-
|
272
|
+
|
266
273
|
Returns:
|
267
274
|
HumanLoopResult: Result containing the status of the latest request in the conversation
|
268
275
|
"""
|
269
|
-
|
270
|
-
return run_async_safely(
|
271
|
-
self.async_check_conversation_status(
|
272
|
-
conversation_id=conversation_id
|
273
|
-
)
|
274
|
-
)
|
275
276
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
277
|
+
result: HumanLoopResult = run_async_safely(
|
278
|
+
self.async_check_conversation_status(conversation_id=conversation_id)
|
279
|
+
)
|
280
|
+
|
281
|
+
return result
|
282
|
+
|
283
|
+
async def async_cancel_request(self, conversation_id: str, request_id: str) -> bool:
|
281
284
|
"""Cancel human-in-the-loop request
|
282
|
-
|
285
|
+
|
283
286
|
Args:
|
284
287
|
conversation_id: Conversation identifier for multi-turn dialogues
|
285
288
|
request_id: Request identifier for specific interaction request
|
286
|
-
|
289
|
+
|
287
290
|
Returns:
|
288
291
|
bool: Whether cancellation was successful, True indicates success, False indicates failure
|
289
292
|
"""
|
@@ -299,44 +302,38 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
299
302
|
self._requests[request_key]["status"] = HumanLoopStatus.CANCELLED
|
300
303
|
return True
|
301
304
|
return False
|
302
|
-
|
303
|
-
def cancel_request(
|
304
|
-
self,
|
305
|
-
conversation_id: str,
|
306
|
-
request_id: str
|
307
|
-
) -> bool:
|
305
|
+
|
306
|
+
def cancel_request(self, conversation_id: str, request_id: str) -> bool:
|
308
307
|
"""Cancel human-in-the-loop request (synchronous version)
|
309
|
-
|
308
|
+
|
310
309
|
Args:
|
311
310
|
conversation_id: Conversation identifier for multi-turn dialogues
|
312
311
|
request_id: Request identifier for specific interaction request
|
313
|
-
|
312
|
+
|
314
313
|
Returns:
|
315
314
|
bool: Whether cancellation was successful, True indicates success, False indicates failure
|
316
315
|
"""
|
317
316
|
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
request_id=request_id
|
322
|
-
)
|
317
|
+
result: bool = run_async_safely(
|
318
|
+
self.async_cancel_request(
|
319
|
+
conversation_id=conversation_id, request_id=request_id
|
323
320
|
)
|
321
|
+
)
|
324
322
|
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
) -> bool:
|
323
|
+
return result
|
324
|
+
|
325
|
+
async def async_cancel_conversation(self, conversation_id: str) -> bool:
|
329
326
|
"""Cancel the entire conversation
|
330
|
-
|
327
|
+
|
331
328
|
Args:
|
332
329
|
conversation_id: Conversation identifier
|
333
|
-
|
330
|
+
|
334
331
|
Returns:
|
335
332
|
bool: Whether the cancellation was successful
|
336
333
|
"""
|
337
334
|
if conversation_id not in self._conversations:
|
338
335
|
return False
|
339
|
-
|
336
|
+
|
340
337
|
# Cancel all requests in the conversation
|
341
338
|
success = True
|
342
339
|
for request_id in self._get_conversation_requests(conversation_id):
|
@@ -344,37 +341,36 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
344
341
|
if request_key in self._requests:
|
345
342
|
# Update request status to cancelled
|
346
343
|
# Only requests in intermediate states (PENDING/IN_PROGRESS) can be cancelled
|
347
|
-
if self._requests[request_key]["status"] in [
|
344
|
+
if self._requests[request_key]["status"] in [
|
345
|
+
HumanLoopStatus.PENDING,
|
346
|
+
HumanLoopStatus.INPROGRESS,
|
347
|
+
]:
|
348
348
|
self._requests[request_key]["status"] = HumanLoopStatus.CANCELLED
|
349
|
-
|
349
|
+
|
350
350
|
# Cancel the timeout task for this request
|
351
351
|
if request_key in self._timeout_tasks:
|
352
352
|
self._timeout_tasks[request_key].cancel()
|
353
353
|
del self._timeout_tasks[request_key]
|
354
354
|
else:
|
355
355
|
success = False
|
356
|
-
|
356
|
+
|
357
357
|
return success
|
358
|
-
|
359
358
|
|
360
|
-
def cancel_conversation(
|
361
|
-
self,
|
362
|
-
conversation_id: str
|
363
|
-
) -> bool:
|
359
|
+
def cancel_conversation(self, conversation_id: str) -> bool:
|
364
360
|
"""Cancel the entire conversation (synchronous version)
|
365
|
-
|
361
|
+
|
366
362
|
Args:
|
367
363
|
conversation_id: Conversation identifier
|
368
|
-
|
364
|
+
|
369
365
|
Returns:
|
370
366
|
bool: Whether the cancellation was successful
|
371
367
|
"""
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
368
|
+
|
369
|
+
result: bool = run_async_safely(
|
370
|
+
self.async_cancel_conversation(conversation_id=conversation_id)
|
371
|
+
)
|
372
|
+
|
373
|
+
return result
|
378
374
|
|
379
375
|
async def async_continue_humanloop(
|
380
376
|
self,
|
@@ -384,13 +380,13 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
384
380
|
timeout: Optional[int] = None,
|
385
381
|
) -> HumanLoopResult:
|
386
382
|
"""Continue human-in-the-loop interaction
|
387
|
-
|
383
|
+
|
388
384
|
Args:
|
389
385
|
conversation_id: Conversation ID for multi-turn dialogues
|
390
386
|
context: Context information provided to human
|
391
387
|
metadata: Additional metadata
|
392
388
|
timeout: Request timeout in seconds
|
393
|
-
|
389
|
+
|
394
390
|
Returns:
|
395
391
|
HumanLoopResult: Result object containing request ID and status
|
396
392
|
"""
|
@@ -402,12 +398,11 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
402
398
|
request_id="",
|
403
399
|
loop_type=HumanLoopType.CONVERSATION,
|
404
400
|
status=HumanLoopStatus.ERROR,
|
405
|
-
error=f"Conversation '{conversation_id}' not found"
|
401
|
+
error=f"Conversation '{conversation_id}' not found",
|
406
402
|
)
|
407
|
-
|
403
|
+
|
408
404
|
# Subclasses need to implement specific continuation logic
|
409
405
|
raise NotImplementedError("Subclasses must implement continue_humanloop")
|
410
|
-
|
411
406
|
|
412
407
|
def continue_humanloop(
|
413
408
|
self,
|
@@ -417,32 +412,36 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
417
412
|
timeout: Optional[int] = None,
|
418
413
|
) -> HumanLoopResult:
|
419
414
|
"""Continue human-in-the-loop interaction (synchronous version)
|
420
|
-
|
415
|
+
|
421
416
|
Args:
|
422
417
|
conversation_id: Conversation ID for multi-turn dialogues
|
423
418
|
context: Context information provided to human
|
424
419
|
metadata: Additional metadata
|
425
420
|
timeout: Request timeout in seconds
|
426
|
-
|
421
|
+
|
427
422
|
Returns:
|
428
423
|
HumanLoopResult: Result object containing request ID and status
|
429
424
|
"""
|
430
425
|
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
)
|
426
|
+
result: HumanLoopResult = run_async_safely(
|
427
|
+
self.async_continue_humanloop(
|
428
|
+
conversation_id=conversation_id,
|
429
|
+
context=context,
|
430
|
+
metadata=metadata,
|
431
|
+
timeout=timeout,
|
438
432
|
)
|
433
|
+
)
|
439
434
|
|
440
|
-
|
435
|
+
return result
|
436
|
+
|
437
|
+
async def async_get_conversation_history(
|
438
|
+
self, conversation_id: str
|
439
|
+
) -> List[Dict[str, Any]]:
|
441
440
|
"""Get complete history for the specified conversation
|
442
|
-
|
441
|
+
|
443
442
|
Args:
|
444
443
|
conversation_id: Conversation identifier
|
445
|
-
|
444
|
+
|
446
445
|
Returns:
|
447
446
|
List[Dict[str, Any]]: List of conversation history records, each containing request ID,
|
448
447
|
status, context, response and other information
|
@@ -452,54 +451,58 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
452
451
|
request_key = (conversation_id, request_id)
|
453
452
|
if request_key in self._requests:
|
454
453
|
request_info = self._requests[request_key]
|
455
|
-
conversation_history.append(
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
454
|
+
conversation_history.append(
|
455
|
+
{
|
456
|
+
"request_id": request_id,
|
457
|
+
"status": request_info.get("status").value
|
458
|
+
if request_info.get("status")
|
459
|
+
else None,
|
460
|
+
"context": request_info.get("context"),
|
461
|
+
"response": request_info.get("response"),
|
462
|
+
"responded_by": request_info.get("responded_by"),
|
463
|
+
"responded_at": request_info.get("responded_at"),
|
464
|
+
}
|
465
|
+
)
|
463
466
|
return conversation_history
|
464
467
|
|
465
468
|
def get_conversation_history(self, conversation_id: str) -> List[Dict[str, Any]]:
|
466
469
|
"""Get complete history for the specified conversation (synchronous version)
|
467
|
-
|
470
|
+
|
468
471
|
Args:
|
469
472
|
conversation_id: Conversation identifier
|
470
|
-
|
473
|
+
|
471
474
|
Returns:
|
472
475
|
List[Dict[str, Any]]: List of conversation history records, each containing request ID,
|
473
476
|
status, context, response and other information
|
474
477
|
"""
|
475
478
|
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
+
result: List[Dict[str, Any]] = run_async_safely(
|
480
|
+
self.async_get_conversation_history(conversation_id=conversation_id)
|
481
|
+
)
|
482
|
+
|
483
|
+
return result
|
479
484
|
|
480
485
|
async def _async_create_timeout_task(
|
481
|
-
self,
|
482
|
-
|
483
|
-
request_id: str,
|
484
|
-
timeout: int
|
485
|
-
):
|
486
|
+
self, conversation_id: str, request_id: str, timeout: int
|
487
|
+
) -> None:
|
486
488
|
"""Create timeout task
|
487
|
-
|
489
|
+
|
488
490
|
Args:
|
489
491
|
conversation_id: Conversation ID
|
490
492
|
request_id: Request ID
|
491
493
|
timeout: Timeout duration in seconds
|
492
494
|
"""
|
493
|
-
|
495
|
+
|
496
|
+
async def timeout_task() -> None:
|
494
497
|
await asyncio.sleep(timeout)
|
495
|
-
|
498
|
+
|
496
499
|
# Check current status
|
497
500
|
request_info = self._get_request(conversation_id, request_id)
|
498
501
|
if not request_info:
|
499
502
|
return
|
500
|
-
|
503
|
+
|
501
504
|
current_status = request_info.get("status", HumanLoopStatus.PENDING)
|
502
|
-
|
505
|
+
|
503
506
|
# Only trigger timeout when status is PENDING
|
504
507
|
# INPROGRESS status means conversation is ongoing, should not be considered as timeout
|
505
508
|
if current_status == HumanLoopStatus.PENDING:
|
@@ -514,11 +517,10 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
514
517
|
self._timeout_tasks[(conversation_id, request_id)].cancel()
|
515
518
|
new_task = asyncio.create_task(timeout_task())
|
516
519
|
self._timeout_tasks[(conversation_id, request_id)] = new_task
|
517
|
-
|
520
|
+
|
518
521
|
task = asyncio.create_task(timeout_task())
|
519
522
|
self._timeout_tasks[(conversation_id, request_id)] = task
|
520
523
|
|
521
|
-
|
522
524
|
def build_prompt(
|
523
525
|
self,
|
524
526
|
task_id: str,
|
@@ -528,20 +530,23 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
528
530
|
created_at: str,
|
529
531
|
context: Dict[str, Any],
|
530
532
|
metadata: Optional[Dict[str, Any]] = None,
|
531
|
-
color: Optional[bool] = None
|
533
|
+
color: Optional[bool] = None,
|
532
534
|
) -> str:
|
533
535
|
"""
|
534
536
|
Dynamically generate prompt based on content, only showing sections with content,
|
535
537
|
and adapt to different terminal color display.
|
536
538
|
color: None=auto detect, True=force color, False=no color
|
537
539
|
"""
|
540
|
+
|
538
541
|
# Auto detect if terminal supports ANSI colors
|
539
|
-
def _supports_color():
|
542
|
+
def _supports_color() -> bool:
|
540
543
|
try:
|
541
544
|
import sys
|
545
|
+
|
542
546
|
if not hasattr(sys.stdout, "isatty") or not sys.stdout.isatty():
|
543
547
|
return False
|
544
548
|
import os
|
549
|
+
|
545
550
|
if os.name == "nt":
|
546
551
|
# Windows 10+ supports ANSI, older versions don't
|
547
552
|
return "ANSICON" in os.environ or "WT_SESSION" in os.environ
|
@@ -554,7 +559,7 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
554
559
|
|
555
560
|
# Define colors
|
556
561
|
if color:
|
557
|
-
COLOR_TITLE = "\033[94m"
|
562
|
+
COLOR_TITLE = "\033[94m" # bright blue
|
558
563
|
COLOR_RESET = "\033[0m"
|
559
564
|
else:
|
560
565
|
COLOR_TITLE = ""
|
@@ -574,7 +579,9 @@ class BaseProvider(HumanLoopProvider, ABC):
|
|
574
579
|
|
575
580
|
if context.get("additional"):
|
576
581
|
lines.append(f"\n{COLOR_TITLE}=== Additional Context ==={COLOR_RESET}")
|
577
|
-
lines.append(
|
582
|
+
lines.append(
|
583
|
+
json.dumps(context["additional"], indent=2, ensure_ascii=False)
|
584
|
+
)
|
578
585
|
|
579
586
|
if metadata:
|
580
587
|
lines.append(f"\n{COLOR_TITLE}=== Metadata ==={COLOR_RESET}")
|