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.
@@ -1,28 +1,32 @@
1
1
  import asyncio
2
2
  import logging
3
- from typing import Dict, Any, Optional
3
+ from typing import Dict, Any, Optional, Tuple
4
4
 
5
5
  import aiohttp
6
6
  from pydantic import SecretStr
7
- from concurrent.futures import ThreadPoolExecutor
8
- from gohumanloop.core.interface import (
9
- HumanLoopResult, HumanLoopStatus, HumanLoopType
10
- )
7
+ from concurrent.futures import ThreadPoolExecutor, Future
8
+ from gohumanloop.core.interface import HumanLoopResult, HumanLoopStatus, HumanLoopType
11
9
  from gohumanloop.providers.base import BaseProvider
12
10
  from gohumanloop.models.api_model import (
13
- APIResponse, HumanLoopRequestData, HumanLoopStatusParams, HumanLoopStatusResponse,
14
- HumanLoopCancelData, HumanLoopCancelConversationData, HumanLoopContinueData
11
+ APIResponse,
12
+ HumanLoopRequestData,
13
+ HumanLoopStatusParams,
14
+ HumanLoopStatusResponse,
15
+ HumanLoopCancelData,
16
+ HumanLoopCancelConversationData,
17
+ HumanLoopContinueData,
15
18
  )
16
19
 
17
20
  logger = logging.getLogger(__name__)
18
21
 
22
+
19
23
  class APIProvider(BaseProvider):
20
24
  """API-based human-in-the-loop provider that supports integration with third-party service platforms
21
-
25
+
22
26
  This provider communicates with a central service platform via HTTP requests, where the service platform
23
27
  handles specific third-party service integrations (such as WeChat, Feishu, DingTalk, etc.).
24
28
  """
25
-
29
+
26
30
  def __init__(
27
31
  self,
28
32
  name: str,
@@ -32,10 +36,10 @@ class APIProvider(BaseProvider):
32
36
  request_timeout: int = 30,
33
37
  poll_interval: int = 5,
34
38
  max_retries: int = 3,
35
- config: Optional[Dict[str, Any]] = None
39
+ config: Optional[Dict[str, Any]] = None,
36
40
  ):
37
41
  """Initialize API provider
38
-
42
+
39
43
  Args:
40
44
  name: Provider name
41
45
  api_base_url: Base URL for API service
@@ -47,20 +51,19 @@ class APIProvider(BaseProvider):
47
51
  config: Additional configuration parameters
48
52
  """
49
53
  super().__init__(name, config)
50
- self.api_base_url = api_base_url.rstrip('/')
54
+ self.api_base_url = api_base_url.rstrip("/")
51
55
  self.api_key = api_key
52
56
  self.default_platform = default_platform
53
57
  self.request_timeout = request_timeout
54
58
  self.poll_interval = poll_interval
55
59
  self.max_retries = max_retries
56
-
60
+
57
61
  # Store the currently running polling tasks.
58
- self._poll_tasks = {}
59
- # Create thread pool for background service execution
62
+ self._poll_tasks: Dict[Tuple[str, str], Future] = {}
63
+ # Create thread pool for background service execution
60
64
  self._executor = ThreadPoolExecutor(max_workers=10)
61
65
 
62
-
63
- def __del__(self):
66
+ def __del__(self) -> None:
64
67
  """析构函数,确保线程池被正确关闭"""
65
68
  self._executor.shutdown(wait=False)
66
69
 
@@ -68,7 +71,7 @@ class APIProvider(BaseProvider):
68
71
  for task_key, future in list(self._poll_tasks.items()):
69
72
  future.cancel()
70
73
  self._poll_tasks.clear()
71
-
74
+
72
75
  def __str__(self) -> str:
73
76
  """Returns a string description of this instance"""
74
77
  base_str = super().__str__()
@@ -76,49 +79,51 @@ class APIProvider(BaseProvider):
76
79
  if self.default_platform:
77
80
  api_info += f" Default Platform: {self.default_platform}\n"
78
81
  return f"{api_info}{base_str}"
79
-
82
+
80
83
  async def _async_make_api_request(
81
- self,
82
- endpoint: str,
83
- method: str = "POST",
84
+ self,
85
+ endpoint: str,
86
+ method: str = "POST",
84
87
  data: Optional[Dict[str, Any]] = None,
85
88
  params: Optional[Dict[str, Any]] = None,
86
- headers: Optional[Dict[str, Any]] = None
89
+ headers: Optional[Dict[str, Any]] = None,
87
90
  ) -> Optional[Dict[str, Any]]:
88
91
  """Make API request
89
-
92
+
90
93
  Args:
91
94
  endpoint: API endpoint path
92
95
  method: HTTP method (GET, POST, etc.)
93
96
  data: Request body data
94
- params: URL query parameters
97
+ params: URL query parameters
95
98
  headers: Request headers
96
-
99
+
97
100
  Returns:
98
101
  Dict[str, Any]: API response data
99
-
102
+
100
103
  Raises:
101
104
  Exception: If API request fails
102
105
  """
103
106
  url = f"{self.api_base_url}/{endpoint.lstrip('/')}"
104
-
107
+
105
108
  # Prepare request headers
106
109
  request_headers = {
107
110
  "Content-Type": "application/json",
108
111
  }
109
112
  # Add authentication information
110
113
  if self.api_key:
111
- request_headers["Authorization"] = f"Bearer {self.api_key.get_secret_value()}"
112
-
114
+ request_headers[
115
+ "Authorization"
116
+ ] = f"Bearer {self.api_key.get_secret_value()}"
117
+
113
118
  # Merge custom headers
114
119
  if headers:
115
120
  request_headers.update(headers)
116
-
121
+
117
122
  # Prepare request data
118
123
  json_data = None
119
124
  if data:
120
125
  json_data = data
121
-
126
+
122
127
  # Send request
123
128
  for attempt in range(self.max_retries):
124
129
  try:
@@ -129,24 +134,30 @@ class APIProvider(BaseProvider):
129
134
  json=json_data,
130
135
  params=params,
131
136
  headers=request_headers,
132
- timeout=self.request_timeout
137
+ timeout=self.request_timeout,
133
138
  ) as response:
134
- response_data = await response.json()
139
+ response_data: Dict[str, Any] = await response.json()
135
140
  # Check response status
136
141
  if response.status >= 400:
137
- error_msg = response_data.get("error", f"API request failed: {response.status}")
142
+ error_msg = response_data.get(
143
+ "error", f"API request failed: {response.status}"
144
+ )
138
145
  logger.error(f"API request failed: {error_msg}")
139
-
146
+
140
147
  # Retry if not the last attempt
141
148
  if attempt < self.max_retries - 1:
142
- await asyncio.sleep(1 * (attempt + 1)) # Exponential backoff
149
+ await asyncio.sleep(
150
+ 1 * (attempt + 1)
151
+ ) # Exponential backoff
143
152
  continue
144
-
153
+
145
154
  raise Exception(error_msg)
146
-
155
+
147
156
  return response_data
148
157
  except asyncio.TimeoutError:
149
- logger.warning(f"API request timeout (attempt {attempt+1}/{self.max_retries})")
158
+ logger.warning(
159
+ f"API request timeout (attempt {attempt+1}/{self.max_retries})"
160
+ )
150
161
  if attempt < self.max_retries - 1:
151
162
  await asyncio.sleep(1 * (attempt + 1))
152
163
  continue
@@ -157,7 +168,9 @@ class APIProvider(BaseProvider):
157
168
  await asyncio.sleep(1 * (attempt + 1))
158
169
  continue
159
170
  raise
160
-
171
+
172
+ return None
173
+
161
174
  async def async_request_humanloop(
162
175
  self,
163
176
  task_id: str,
@@ -165,10 +178,10 @@ class APIProvider(BaseProvider):
165
178
  loop_type: HumanLoopType,
166
179
  context: Dict[str, Any],
167
180
  metadata: Optional[Dict[str, Any]] = None,
168
- timeout: Optional[int] = None
181
+ timeout: Optional[int] = None,
169
182
  ) -> HumanLoopResult:
170
183
  """Request human-in-the-loop interaction
171
-
184
+
172
185
  Args:
173
186
  task_id: Task identifier
174
187
  conversation_id: Conversation ID for multi-turn dialogue
@@ -176,12 +189,12 @@ class APIProvider(BaseProvider):
176
189
  context: Context information provided to humans
177
190
  metadata: Additional metadata
178
191
  timeout: Request timeout in seconds
179
-
192
+
180
193
  Returns:
181
194
  HumanLoopResult: Result object containing request ID and initial status
182
195
  """
183
196
  metadata = metadata or {}
184
-
197
+
185
198
  # Generate request ID
186
199
  request_id = self._generate_request_id()
187
200
  platform = metadata.get("platform", self.default_platform)
@@ -193,20 +206,24 @@ class APIProvider(BaseProvider):
193
206
  loop_type=loop_type,
194
207
  context=context,
195
208
  metadata={**metadata, "platform": platform},
196
- timeout=timeout
209
+ timeout=timeout,
197
210
  )
198
-
211
+
199
212
  # Determine which platform to use
200
213
  if not platform:
201
- self._update_request_status_error(conversation_id, request_id, "Platform not specified. Please set 'platform' in metadata or set default_platform during initialization")
214
+ self._update_request_status_error(
215
+ conversation_id,
216
+ request_id,
217
+ "Platform not specified. Please set 'platform' in metadata or set default_platform during initialization",
218
+ )
202
219
  return HumanLoopResult(
203
220
  conversation_id=conversation_id,
204
221
  request_id=request_id,
205
222
  loop_type=loop_type,
206
223
  status=HumanLoopStatus.ERROR,
207
- error="Platform not specified. Please set 'platform' in metadata or set default_platform during initialization"
224
+ error="Platform not specified. Please set 'platform' in metadata or set default_platform during initialization",
208
225
  )
209
-
226
+
210
227
  # Prepare API request data
211
228
  request_data = HumanLoopRequestData(
212
229
  task_id=task_id,
@@ -215,62 +232,72 @@ class APIProvider(BaseProvider):
215
232
  loop_type=loop_type.value,
216
233
  context=context,
217
234
  platform=platform,
218
- metadata=metadata
235
+ metadata=metadata,
219
236
  ).model_dump()
220
-
237
+
221
238
  try:
222
239
  # Send API request
223
240
  response = await self._async_make_api_request(
224
- endpoint="v1/humanloop/request",
225
- method="POST",
226
- data=request_data
241
+ endpoint="v1/humanloop/request", method="POST", data=request_data
227
242
  )
228
-
243
+
229
244
  # Check API response
230
- api_response = APIResponse(**response)
245
+ response_data = response or {}
246
+ api_response = APIResponse(**response_data)
231
247
  if not api_response.success:
232
- error_msg = api_response.error or "API request failed without error message"
248
+ error_msg = (
249
+ api_response.error or "API request failed without error message"
250
+ )
233
251
  # Update request status to error
234
- self._update_request_status_error(conversation_id, request_id, error_msg)
235
-
252
+ self._update_request_status_error(
253
+ conversation_id, request_id, error_msg
254
+ )
255
+
236
256
  return HumanLoopResult(
237
257
  conversation_id=conversation_id,
238
258
  request_id=request_id,
239
259
  loop_type=loop_type,
240
260
  status=HumanLoopStatus.ERROR,
241
- error=error_msg
261
+ error=error_msg,
242
262
  )
243
-
263
+
244
264
  # Create polling task
245
265
  self._poll_tasks[(conversation_id, request_id)] = self._executor.submit(
246
- self._run_async_poll_request_status, conversation_id, request_id, platform
266
+ self._run_async_poll_request_status,
267
+ conversation_id,
268
+ request_id,
269
+ platform,
247
270
  )
248
-
271
+
249
272
  # Create timeout task if timeout is set
250
273
  if timeout:
251
- await self._async_create_timeout_task(conversation_id, request_id, timeout)
252
-
274
+ await self._async_create_timeout_task(
275
+ conversation_id, request_id, timeout
276
+ )
277
+
253
278
  return HumanLoopResult(
254
279
  conversation_id=conversation_id,
255
280
  request_id=request_id,
256
281
  loop_type=loop_type,
257
- status=HumanLoopStatus.PENDING
282
+ status=HumanLoopStatus.PENDING,
258
283
  )
259
-
284
+
260
285
  except Exception as e:
261
286
  logger.error(f"Failed to request human-in-the-loop: {str(e)}")
262
287
  # Update request status to error
263
- self._update_request_status_error(conversation_id, request_id, str(e))
264
-
288
+ self._update_request_status_error(conversation_id, request_id, str(e))
289
+
265
290
  return HumanLoopResult(
266
291
  conversation_id=conversation_id,
267
292
  request_id=request_id,
268
293
  loop_type=loop_type,
269
294
  status=HumanLoopStatus.ERROR,
270
- error=str(e)
295
+ error=str(e),
271
296
  )
272
297
 
273
- def _run_async_poll_request_status(self, conversation_id: str, request_id: str, platform: str):
298
+ def _run_async_poll_request_status(
299
+ self, conversation_id: str, request_id: str, platform: str
300
+ ) -> None:
274
301
  """Run asynchronous API interaction in a separate thread"""
275
302
  # Create new event loop
276
303
  loop = asyncio.new_event_loop()
@@ -278,7 +305,9 @@ class APIProvider(BaseProvider):
278
305
 
279
306
  try:
280
307
  # Run interaction processing in the new event loop
281
- loop.run_until_complete(self._async_poll_request_status(conversation_id, request_id, platform))
308
+ loop.run_until_complete(
309
+ self._async_poll_request_status(conversation_id, request_id, platform)
310
+ )
282
311
  finally:
283
312
  loop.close()
284
313
  # Remove from task dictionary
@@ -286,16 +315,14 @@ class APIProvider(BaseProvider):
286
315
  del self._poll_tasks[(conversation_id, request_id)]
287
316
 
288
317
  async def async_check_request_status(
289
- self,
290
- conversation_id: str,
291
- request_id: str
318
+ self, conversation_id: str, request_id: str
292
319
  ) -> HumanLoopResult:
293
320
  """Check request status
294
-
321
+
295
322
  Args:
296
323
  conversation_id: Conversation identifier
297
324
  request_id: Request identifier
298
-
325
+
299
326
  Returns:
300
327
  HumanLoopResult: Result object containing current status
301
328
  """
@@ -306,9 +333,9 @@ class APIProvider(BaseProvider):
306
333
  request_id=request_id,
307
334
  loop_type=HumanLoopType.CONVERSATION,
308
335
  status=HumanLoopStatus.ERROR,
309
- error=f"Request '{request_id}' not found in conversation '{conversation_id}'"
336
+ error=f"Request '{request_id}' not found in conversation '{conversation_id}'",
310
337
  )
311
-
338
+
312
339
  result = HumanLoopResult(
313
340
  conversation_id=conversation_id,
314
341
  request_id=request_id,
@@ -318,23 +345,18 @@ class APIProvider(BaseProvider):
318
345
  feedback=request_info.get("feedback", {}),
319
346
  responded_by=request_info.get("responded_by", None),
320
347
  responded_at=request_info.get("responded_at", None),
321
- error=request_info.get("error", None)
348
+ error=request_info.get("error", None),
322
349
  )
323
-
350
+
324
351
  return result
325
352
 
326
-
327
- async def async_cancel_request(
328
- self,
329
- conversation_id: str,
330
- request_id: str
331
- ) -> bool:
353
+ async def async_cancel_request(self, conversation_id: str, request_id: str) -> bool:
332
354
  """Cancel human-in-the-loop request
333
-
355
+
334
356
  Args:
335
357
  conversation_id: Conversation identifier for multi-turn dialogue
336
358
  request_id: Request identifier for specific interaction request
337
-
359
+
338
360
  Returns:
339
361
  bool: Whether cancellation was successful, True for success, False for failure
340
362
  """
@@ -342,59 +364,57 @@ class APIProvider(BaseProvider):
342
364
  result = await super().async_cancel_request(conversation_id, request_id)
343
365
  if not result:
344
366
  return False
345
-
367
+
346
368
  # Get request information
347
369
  request_info = self._get_request(conversation_id, request_id)
348
370
  if not request_info:
349
371
  return False
350
-
372
+
351
373
  # Get platform information
352
374
  platform = request_info.get("metadata", {}).get("platform")
353
375
  if not platform:
354
- logger.error(f"Cancel request failed: Platform information not found")
376
+ logger.error("Cancel request failed: Platform information not found")
355
377
  return False
356
-
378
+
357
379
  try:
358
380
  # Send API request to cancel request
359
381
  cancel_data = HumanLoopCancelData(
360
382
  conversation_id=conversation_id,
361
383
  request_id=request_id,
362
- platform=platform
384
+ platform=platform,
363
385
  ).model_dump()
364
-
386
+
365
387
  response = await self._async_make_api_request(
366
- endpoint="v1/humanloop/cancel",
367
- method="POST",
368
- data=cancel_data
388
+ endpoint="v1/humanloop/cancel", method="POST", data=cancel_data
369
389
  )
370
-
390
+
371
391
  # Check API response
372
- api_response = APIResponse(**response)
392
+ response_data = response or {}
393
+ api_response = APIResponse(**response_data)
373
394
  if not api_response.success:
374
- error_msg = api_response.error or "Cancel request failed without error message"
395
+ error_msg = (
396
+ api_response.error or "Cancel request failed without error message"
397
+ )
375
398
  logger.error(f"Cancel request failed: {error_msg}")
376
399
  return False
377
-
400
+
378
401
  # Cancel polling task
379
402
  if (conversation_id, request_id) in self._poll_tasks:
380
403
  self._poll_tasks[(conversation_id, request_id)].cancel()
381
404
  del self._poll_tasks[(conversation_id, request_id)]
382
-
405
+
383
406
  return True
384
-
407
+
385
408
  except Exception as e:
386
409
  logger.error(f"Cancel request failed: {str(e)}")
387
410
  return False
388
-
389
- async def async_cancel_conversation(
390
- self,
391
- conversation_id: str
392
- ) -> bool:
411
+
412
+ async def async_cancel_conversation(self, conversation_id: str) -> bool:
393
413
  """Cancel entire conversation
394
-
414
+
395
415
  Args:
396
416
  conversation_id: Conversation identifier
397
-
417
+
398
418
  Returns:
399
419
  bool: Whether cancellation was successful
400
420
  """
@@ -402,55 +422,57 @@ class APIProvider(BaseProvider):
402
422
  result = await super().async_cancel_conversation(conversation_id)
403
423
  if not result:
404
424
  return False
405
-
425
+
406
426
  # Get all requests in the conversation
407
427
  request_ids = self._get_conversation_requests(conversation_id)
408
428
  if not request_ids:
409
429
  return True # No requests to cancel
410
-
430
+
411
431
  # Get platform info from first request (assuming all requests use same platform)
412
432
  first_request = self._get_request(conversation_id, request_ids[0])
413
433
  if not first_request:
414
434
  return False
415
-
435
+
416
436
  platform = first_request.get("metadata", {}).get("platform")
417
437
  if not platform:
418
- logger.error(f"Cancel conversation failed: Platform information not found")
438
+ logger.error("Cancel conversation failed: Platform information not found")
419
439
  return False
420
-
440
+
421
441
  try:
422
442
  # Send API request to cancel conversation
423
443
  cancel_data = HumanLoopCancelConversationData(
424
- conversation_id=conversation_id,
425
- platform=platform
444
+ conversation_id=conversation_id, platform=platform
426
445
  ).model_dump()
427
-
446
+
428
447
  response = await self._async_make_api_request(
429
448
  endpoint="v1/humanloop/cancel_conversation",
430
449
  method="POST",
431
- data=cancel_data
450
+ data=cancel_data,
432
451
  )
433
-
452
+
434
453
  # Check API response
435
- api_response = APIResponse(**response)
454
+ response_data = response or {}
455
+ api_response = APIResponse(**response_data)
436
456
  if not api_response.success:
437
- error_msg = api_response.error or "Cancel conversation failed without error message"
457
+ error_msg = (
458
+ api_response.error
459
+ or "Cancel conversation failed without error message"
460
+ )
438
461
  logger.error(f"Cancel conversation failed: {error_msg}")
439
462
  return False
440
-
463
+
441
464
  # Cancel all polling tasks
442
465
  for request_id in request_ids:
443
466
  if (conversation_id, request_id) in self._poll_tasks:
444
467
  self._poll_tasks[(conversation_id, request_id)].cancel()
445
468
  del self._poll_tasks[(conversation_id, request_id)]
446
-
469
+
447
470
  return True
448
-
471
+
449
472
  except Exception as e:
450
473
  logger.error(f"Cancel conversation failed: {str(e)}")
451
474
  return False
452
475
 
453
-
454
476
  async def async_continue_humanloop(
455
477
  self,
456
478
  conversation_id: str,
@@ -459,13 +481,13 @@ class APIProvider(BaseProvider):
459
481
  timeout: Optional[int] = None,
460
482
  ) -> HumanLoopResult:
461
483
  """Continue human-in-the-loop interaction
462
-
484
+
463
485
  Args:
464
486
  conversation_id: Conversation ID for multi-turn dialogue
465
487
  context: Context information provided to humans
466
488
  metadata: Additional metadata
467
489
  timeout: Request timeout in seconds
468
-
490
+
469
491
  Returns:
470
492
  HumanLoopResult: Result object containing request ID and status
471
493
  """
@@ -477,14 +499,14 @@ class APIProvider(BaseProvider):
477
499
  request_id="",
478
500
  loop_type=HumanLoopType.CONVERSATION,
479
501
  status=HumanLoopStatus.ERROR,
480
- error=f"Conversation '{conversation_id}' not found"
502
+ error=f"Conversation '{conversation_id}' not found",
481
503
  )
482
-
504
+
483
505
  metadata = metadata or {}
484
-
506
+
485
507
  # Generate request ID
486
508
  request_id = self._generate_request_id()
487
-
509
+
488
510
  # Get task ID
489
511
  task_id = conversation_info.get("task_id", "unknown_task")
490
512
  # Determine which platform to use
@@ -498,19 +520,23 @@ class APIProvider(BaseProvider):
498
520
  loop_type=HumanLoopType.CONVERSATION,
499
521
  context=context,
500
522
  metadata={**metadata, "platform": platform},
501
- timeout=timeout
523
+ timeout=timeout,
502
524
  )
503
525
 
504
526
  if not platform:
505
- self._update_request_status_error(conversation_id, request_id, "Platform not specified. Please set 'platform' in metadata or set default_platform during initialization")
527
+ self._update_request_status_error(
528
+ conversation_id,
529
+ request_id,
530
+ "Platform not specified. Please set 'platform' in metadata or set default_platform during initialization",
531
+ )
506
532
  return HumanLoopResult(
507
533
  conversation_id=conversation_id,
508
534
  request_id=request_id,
509
535
  loop_type=HumanLoopType.CONVERSATION,
510
536
  status=HumanLoopStatus.ERROR,
511
- error="Platform not specified. Please set 'platform' in metadata or set default_platform during initialization"
537
+ error="Platform not specified. Please set 'platform' in metadata or set default_platform during initialization",
512
538
  )
513
-
539
+
514
540
  # Prepare API request data
515
541
  continue_data = HumanLoopContinueData(
516
542
  conversation_id=conversation_id,
@@ -518,68 +544,73 @@ class APIProvider(BaseProvider):
518
544
  task_id=task_id,
519
545
  context=context,
520
546
  platform=platform,
521
- metadata=metadata
547
+ metadata=metadata,
522
548
  ).model_dump()
523
-
549
+
524
550
  try:
525
551
  # Send API request
526
552
  response = await self._async_make_api_request(
527
- endpoint="v1/humanloop/continue",
528
- method="POST",
529
- data=continue_data
553
+ endpoint="v1/humanloop/continue", method="POST", data=continue_data
530
554
  )
531
-
555
+
532
556
  # Check API response
533
- api_response = APIResponse(**response)
557
+ response_data = response or {}
558
+ api_response = APIResponse(**response_data)
534
559
  if not api_response.success:
535
- error_msg = api_response.error or "Continue conversation failed without error message"
536
-
537
- self._update_request_status_error(conversation_id, request_id, error_msg)
560
+ error_msg = (
561
+ api_response.error
562
+ or "Continue conversation failed without error message"
563
+ )
564
+
565
+ self._update_request_status_error(
566
+ conversation_id, request_id, error_msg
567
+ )
538
568
  return HumanLoopResult(
539
569
  conversation_id=conversation_id,
540
570
  request_id=request_id,
541
571
  loop_type=HumanLoopType.CONVERSATION,
542
572
  status=HumanLoopStatus.ERROR,
543
- error=error_msg
573
+ error=error_msg,
544
574
  )
545
-
546
- # Create polling task
575
+
576
+ # Create polling task
547
577
  self._poll_tasks[(conversation_id, request_id)] = self._executor.submit(
548
- self._run_async_poll_request_status, conversation_id, request_id, platform
578
+ self._run_async_poll_request_status,
579
+ conversation_id,
580
+ request_id,
581
+ platform,
549
582
  )
550
-
583
+
551
584
  # Create timeout task if timeout is set
552
585
  if timeout:
553
- await self._async_create_timeout_task(conversation_id, request_id, timeout)
554
-
586
+ await self._async_create_timeout_task(
587
+ conversation_id, request_id, timeout
588
+ )
589
+
555
590
  return HumanLoopResult(
556
591
  conversation_id=conversation_id,
557
592
  request_id=request_id,
558
593
  loop_type=HumanLoopType.CONVERSATION,
559
- status=HumanLoopStatus.PENDING
594
+ status=HumanLoopStatus.PENDING,
560
595
  )
561
-
596
+
562
597
  except Exception as e:
563
598
  logger.error(f"Failed to continue human-in-the-loop: {str(e)}")
564
599
  self._update_request_status_error(conversation_id, request_id, str(e))
565
600
 
566
-
567
601
  return HumanLoopResult(
568
602
  conversation_id=conversation_id,
569
603
  request_id=request_id,
570
604
  loop_type=HumanLoopType.CONVERSATION,
571
605
  status=HumanLoopStatus.ERROR,
572
- error=str(e)
606
+ error=str(e),
573
607
  )
574
-
608
+
575
609
  async def _async_poll_request_status(
576
- self,
577
- conversation_id: str,
578
- request_id: str,
579
- platform: str
610
+ self, conversation_id: str, request_id: str, platform: str
580
611
  ) -> None:
581
612
  """Poll request status
582
-
613
+
583
614
  Args:
584
615
  conversation_id: Conversation identifier
585
616
  request_id: Request identifier
@@ -590,65 +621,78 @@ class APIProvider(BaseProvider):
590
621
  # Get request information
591
622
  request_info = self._get_request(conversation_id, request_id)
592
623
  if not request_info:
593
- logger.warning(f"Polling stopped: Request '{request_id}' not found in conversation '{conversation_id}'")
624
+ logger.warning(
625
+ f"Polling stopped: Request '{request_id}' not found in conversation '{conversation_id}'"
626
+ )
594
627
  return
595
-
628
+
596
629
  # Stop polling if request is in final status
597
630
  status = request_info.get("status")
598
- if status not in [HumanLoopStatus.PENDING, HumanLoopStatus.INPROGRESS]:
631
+ if status not in [HumanLoopStatus.PENDING, HumanLoopStatus.INPROGRESS]:
599
632
  return
600
-
633
+
601
634
  # Send API request to get status
602
635
  params = HumanLoopStatusParams(
603
636
  conversation_id=conversation_id,
604
637
  request_id=request_id,
605
- platform=platform
638
+ platform=platform,
606
639
  ).model_dump()
607
-
640
+
608
641
  response = await self._async_make_api_request(
609
- endpoint="v1/humanloop/status",
610
- method="GET",
611
- params=params
642
+ endpoint="v1/humanloop/status", method="GET", params=params
612
643
  )
613
-
644
+
614
645
  # Parse response
615
- status_response = HumanLoopStatusResponse(**response)
616
-
646
+ response_data = response or {}
647
+ status_response = HumanLoopStatusResponse(**response_data)
648
+
617
649
  # Log error but continue polling if request fails
618
650
  if not status_response.success:
619
651
  logger.warning(f"Failed to get status: {status_response.error}")
620
652
  await asyncio.sleep(self.poll_interval)
621
653
  continue
622
-
654
+
623
655
  # Parse status
624
656
  try:
625
657
  new_status = HumanLoopStatus(status_response.status)
626
658
  except ValueError:
627
- logger.warning(f"Unknown status value: {status_response.status}, using PENDING")
659
+ logger.warning(
660
+ f"Unknown status value: {status_response.status}, using PENDING"
661
+ )
628
662
  new_status = HumanLoopStatus.PENDING
629
-
663
+
630
664
  # Update request information
631
665
  request_key = (conversation_id, request_id)
632
666
  if request_key in self._requests:
633
667
  self._requests[request_key]["status"] = new_status
634
-
668
+
635
669
  # Update response data
636
- for field in ["response", "feedback", "responded_by", "responded_at", "error"]:
670
+ for field in [
671
+ "response",
672
+ "feedback",
673
+ "responded_by",
674
+ "responded_at",
675
+ "error",
676
+ ]:
637
677
  value = getattr(status_response, field, None)
638
678
  if value is not None:
639
679
  self._requests[request_key][field] = value
640
-
641
-
680
+
642
681
  # Stop polling if request is in final status
643
- if new_status not in [HumanLoopStatus.PENDING, HumanLoopStatus.INPROGRESS]:
682
+ if new_status not in [
683
+ HumanLoopStatus.PENDING,
684
+ HumanLoopStatus.INPROGRESS,
685
+ ]:
644
686
  return
645
-
687
+
646
688
  # Wait for next polling interval
647
689
  await asyncio.sleep(self.poll_interval)
648
-
690
+
649
691
  except asyncio.CancelledError:
650
- logger.info(f"Polling task cancelled: conversation '{conversation_id}', request '{request_id}'")
692
+ logger.info(
693
+ f"Polling task cancelled: conversation '{conversation_id}', request '{request_id}'"
694
+ )
651
695
  return
652
696
  except Exception as e:
653
697
  logger.error(f"Polling task error: {str(e)}")
654
- return
698
+ return