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,6 +1,5 @@
1
1
  from abc import ABC
2
- import re
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, HumanLoopResult, HumanLoopStatus, HumanLoopType
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 = ThreadSafeDict() # Using thread-safe dictionary to store request information
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(1 for req in self._requests.values()
40
- if req["status"] in [HumanLoopStatus.PENDING, HumanLoopStatus.INPROGRESS])
41
-
42
- return (f"conversations={total_conversations}, "
43
- f"total_requests={total_requests}, "
44
- f"active_requests={active_requests})")
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(self, conversation_id: str, request_id: str) -> Optional[Dict[str, Any]]:
111
+
112
+ def _get_request(
113
+ self, conversation_id: str, request_id: str
114
+ ) -> Optional[Dict[str, Any]]:
102
115
  """Get request information"""
103
- return self._requests.get((conversation_id, request_id))
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
- return run_async_safely(
162
- self.async_request_humanloop(
163
- task_id=task_id,
164
- conversation_id=conversation_id,
165
- loop_type=loop_type,
166
- context=context,
167
- metadata=metadata,
168
- timeout=timeout
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
- return run_async_safely(
216
- self.async_check_request_status(
217
- conversation_id=conversation_id,
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
- async def async_cancel_request(
277
- self,
278
- conversation_id: str,
279
- request_id: str
280
- ) -> bool:
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
- return run_async_safely(
319
- self.async_cancel_request(
320
- conversation_id=conversation_id,
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
- async def async_cancel_conversation(
326
- self,
327
- conversation_id: str
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 [HumanLoopStatus.PENDING, HumanLoopStatus.INPROGRESS]:
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
- return run_async_safely(
374
- self.async_cancel_conversation(
375
- conversation_id=conversation_id
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
- return run_async_safely(
432
- self.async_continue_humanloop(
433
- conversation_id=conversation_id,
434
- context=context,
435
- metadata=metadata,
436
- timeout=timeout
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
- def async_get_conversation_history(self, conversation_id: str) -> List[Dict[str, Any]]:
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
- "request_id": request_id,
457
- "status": request_info.get("status").value if request_info.get("status") else None,
458
- "context": request_info.get("context"),
459
- "response": request_info.get("response"),
460
- "responded_by": request_info.get("responded_by"),
461
- "responded_at": request_info.get("responded_at")
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
- return run_async_safely(
477
- self.async_get_conversation_history(conversation_id=conversation_id)
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
- conversation_id: str,
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
- async def timeout_task():
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" # bright blue
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(json.dumps(context["additional"], indent=2, ensure_ascii=False))
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}")