langfun 0.1.2.dev202509300805__py3-none-any.whl → 0.1.2.dev202510010805__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.

Potentially problematic release.


This version of langfun might be problematic. Click here for more details.

@@ -752,6 +752,11 @@ class Gemini(rest.REST):
752
752
  prompt.as_format('gemini', chunk_preprocessor=modality_conversion)
753
753
  )
754
754
  request['contents'] = contents
755
+ # Users could use `metadata_gemini_tools` to pass Gemini tools. For example,
756
+ # for enabling Search Grounding, users could pass:
757
+ # metadata_gemini_tools=[{'google_search': {}}]
758
+ if tools := prompt.metadata.get('gemini_tools'):
759
+ request['tools'] = tools
755
760
  return request
756
761
 
757
762
  def _generation_config(
langfun/core/llms/rest.py CHANGED
@@ -98,7 +98,9 @@ class REST(lf.LanguageModel):
98
98
  raise lf.TemporaryLMError(str(e)) from e
99
99
  except (
100
100
  requests.exceptions.ConnectionError,
101
+ requests.exceptions.ChunkedEncodingError,
101
102
  ConnectionError,
103
+ ConnectionResetError,
102
104
  ) as e:
103
105
  error_message = str(e)
104
106
  if 'REJECTED_CLIENT_THROTTLED' in error_message:
@@ -107,6 +109,8 @@ class REST(lf.LanguageModel):
107
109
  raise lf.TemporaryLMError(error_message) from e
108
110
  if 'UNREACHABLE_ERROR' in error_message:
109
111
  raise lf.TemporaryLMError(error_message) from e
112
+ if 'Connection reset by peer' in error_message:
113
+ raise lf.TemporaryLMError(error_message) from e
110
114
  raise lf.LMError(error_message) from e
111
115
 
112
116
  def _error(self, status_code: int, content: str) -> lf.LMError:
@@ -359,6 +359,7 @@ class BaseEnvironment(interface.Environment):
359
359
  return
360
360
 
361
361
  self._set_status(self.Status.SHUTTING_DOWN)
362
+ self.on_shutting_down()
362
363
 
363
364
  shutting_down_time = time.time()
364
365
  try:
@@ -419,7 +420,6 @@ class BaseEnvironment(interface.Environment):
419
420
  )
420
421
  # Append is atomic and does not require locking.
421
422
  self._sandbox_pool.append(sandbox)
422
- self._offline_start_time = None
423
423
  return sandbox
424
424
  except (
425
425
  interface.EnvironmentError, interface.SandboxStateError
@@ -432,18 +432,28 @@ class BaseEnvironment(interface.Environment):
432
432
  set_acquired: bool = False,
433
433
  ) -> base_sandbox.BaseSandbox:
434
434
  """Brings up a new sandbox."""
435
- sandbox = self._create_sandbox(
436
- sandbox_id=sandbox_id,
437
- reusable=self.enable_pooling,
438
- proactive_session_setup=self.proactive_session_setup,
439
- keepalive_interval=self.sandbox_keepalive_interval,
440
- )
441
- for handler in self.event_handlers:
442
- sandbox.add_event_handler(handler)
443
- sandbox.start()
444
- if set_acquired:
445
- sandbox.set_acquired()
446
- return sandbox
435
+ env_error = None
436
+ try:
437
+ sandbox = self._create_sandbox(
438
+ sandbox_id=sandbox_id,
439
+ reusable=self.enable_pooling,
440
+ proactive_session_setup=self.proactive_session_setup,
441
+ keepalive_interval=self.sandbox_keepalive_interval,
442
+ )
443
+ for handler in self.event_handlers:
444
+ sandbox.add_event_handler(handler)
445
+ sandbox.start()
446
+ if set_acquired:
447
+ sandbox.set_acquired()
448
+ return sandbox
449
+ except (interface.EnvironmentError, interface.SandboxStateError) as e:
450
+ env_error = e
451
+ raise e
452
+ finally:
453
+ if env_error is None:
454
+ self._offline_start_time = None
455
+ elif self._offline_start_time is None:
456
+ self._offline_start_time = time.time()
447
457
 
448
458
  def _bring_up_sandbox_with_retry(
449
459
  self,
@@ -486,8 +496,6 @@ class BaseEnvironment(interface.Environment):
486
496
  shutdown_env_upon_outage: bool = True
487
497
  ):
488
498
  """Raises error if the grace period has passed or wait for retry."""
489
- if self._offline_start_time is None:
490
- self._offline_start_time = time.time()
491
499
  if self.offline_duration > self.outage_grace_period:
492
500
  if shutdown_env_upon_outage:
493
501
  self.shutdown()
@@ -505,39 +513,47 @@ class BaseEnvironment(interface.Environment):
505
513
  """Housekeeping loop for the environment."""
506
514
  while self._status not in (self.Status.SHUTTING_DOWN, self.Status.OFFLINE):
507
515
  housekeep_start_time = time.time()
516
+
517
+ is_online = True
508
518
  dead_pool_indices = [
509
519
  i for i, s in enumerate(self._sandbox_pool)
510
520
  if s.status == interface.Sandbox.Status.OFFLINE
511
521
  ]
512
- if dead_pool_indices and not self._replace_dead_sandboxes(
513
- dead_pool_indices
514
- ):
522
+ replaced_indices = []
523
+
524
+ if dead_pool_indices:
525
+ replaced_indices = self._replace_dead_sandboxes(dead_pool_indices)
526
+ if not replaced_indices:
527
+ is_online = self.offline_duration < self.outage_grace_period
528
+
529
+ self._housekeep_counter += 1
530
+ duration = time.time() - housekeep_start_time
531
+ kwargs = dict(
532
+ dead_pool_indices=dead_pool_indices,
533
+ replaced_indices=replaced_indices,
534
+ offline_duration=self.offline_duration,
535
+ )
536
+ if is_online:
537
+ self.on_housekeep(duration, **kwargs)
538
+ time.sleep(self.housekeep_interval)
539
+ else:
515
540
  self.shutdown()
516
- self._housekeep_counter += 1
517
541
  self.on_housekeep(
518
- time.time() - housekeep_start_time,
542
+ duration,
519
543
  interface.EnvironmentOutageError(
520
- environment=self,
521
- offline_duration=self.offline_duration
522
- )
544
+ environment=self, offline_duration=self.offline_duration
545
+ ),
546
+ **kwargs
523
547
  )
524
- break
525
- self._housekeep_counter += 1
526
- self.on_housekeep(time.time() - housekeep_start_time)
527
- time.sleep(self.housekeep_interval)
528
548
 
529
- def _replace_dead_sandboxes(self, dead_pool_indices: list[int]) -> bool:
549
+ def _replace_dead_sandboxes(self, dead_pool_indices: list[int]) -> list[int]:
530
550
  """Replaces a dead sandbox with a new one.
531
551
 
532
552
  Args:
533
553
  dead_pool_indices: The indices of the dead sandboxes to replace.
534
554
 
535
555
  Returns:
536
- Whether all of the dead sandboxes are replaced successfully.
537
-
538
- Raises:
539
- interface.EnvironmentOutageError: If the XBox sandboxes cannot be created
540
- within the wait time specified by `xbox_outage_grace_period`.
556
+ Successfully replaced indices.
541
557
  """
542
558
  pg.logging.warning(
543
559
  '[%s]: %s maintenance: '
@@ -548,21 +564,31 @@ class BaseEnvironment(interface.Environment):
548
564
  )
549
565
  def _replace(i: int):
550
566
  generation = int(self._sandbox_pool[i].id.sandbox_id.split(':')[1])
551
- self._sandbox_pool[i] = self._bring_up_sandbox_with_retry(
552
- f'{i}:{generation + 1}', shutdown_env_upon_outage=False
553
- )
567
+ self._sandbox_pool[i] = self._bring_up_sandbox(f'{i}:{generation + 1}')
554
568
 
555
569
  # TODO(daiyip): Consider to loose the condition to allow some dead
556
570
  # sandboxes to be replaced successfully.
557
- return not any([
558
- error for _, _, error in lf.concurrent_map(
559
- _replace, dead_pool_indices,
560
- max_workers=min(
561
- self.pool_operation_max_parallelism,
562
- len(dead_pool_indices)
563
- ),
564
- )
565
- ])
571
+ replaced_indices = []
572
+ for index, _, error in lf.concurrent_map(
573
+ _replace, dead_pool_indices,
574
+ max_workers=min(
575
+ self.pool_operation_max_parallelism,
576
+ len(dead_pool_indices)
577
+ ),
578
+ ):
579
+ if error is None:
580
+ replaced_indices.append(index)
581
+
582
+ pg.logging.warning(
583
+ '[%s]: %s maintenance: '
584
+ '%d/%d dead sandbox(es) have been replaced with new ones. (slots=%s)',
585
+ self.id,
586
+ self.__class__.__name__,
587
+ len(replaced_indices),
588
+ len(dead_pool_indices),
589
+ replaced_indices
590
+ )
591
+ return replaced_indices
566
592
 
567
593
  #
568
594
  # Event handlers subclasses can override.
@@ -584,12 +610,20 @@ class BaseEnvironment(interface.Environment):
584
610
  def on_housekeep(
585
611
  self,
586
612
  duration: float,
587
- error: BaseException | None = None
613
+ error: BaseException | None = None,
614
+ **kwargs
588
615
  ) -> None:
589
616
  """Called when the environment finishes a round of housekeeping."""
590
617
  housekeep_counter = self.housekeep_counter
591
618
  for handler in self.event_handlers:
592
- handler.on_environment_housekeep(self, housekeep_counter, duration, error)
619
+ handler.on_environment_housekeep(
620
+ self, housekeep_counter, duration, error, **kwargs
621
+ )
622
+
623
+ def on_shutting_down(self) -> None:
624
+ """Called when the environment is shutting down."""
625
+ for handler in self.event_handlers:
626
+ handler.on_environment_shutting_down(self, self.offline_duration)
593
627
 
594
628
  def on_shutdown(
595
629
  self,
@@ -186,11 +186,12 @@ class BaseFeature(interface.Feature):
186
186
  def on_housekeep(
187
187
  self,
188
188
  duration: float,
189
- error: BaseException | None = None
189
+ error: BaseException | None = None,
190
+ **kwargs
190
191
  ) -> None:
191
192
  """Called when the feature has done housekeeping."""
192
193
  self.sandbox.on_feature_housekeep(
193
- self, self._housekeep_counter, duration, error
194
+ self, self._housekeep_counter, duration, error, **kwargs
194
195
  )
195
196
 
196
197
  def on_setup_session(
@@ -706,7 +706,6 @@ class BaseSandbox(interface.Sandbox):
706
706
  while self._status not in (self.Status.SHUTTING_DOWN, self.Status.OFFLINE):
707
707
  housekeep_start = time.time()
708
708
  if self.keepalive_interval is not None:
709
-
710
709
  if time.time() - last_ping > self.keepalive_interval:
711
710
  try:
712
711
  self.ping()
@@ -796,13 +795,14 @@ class BaseSandbox(interface.Sandbox):
796
795
  def on_housekeep(
797
796
  self,
798
797
  duration: float,
799
- error: BaseException | None = None
798
+ error: BaseException | None = None,
799
+ **kwargs
800
800
  ) -> None:
801
801
  """Called when the sandbox finishes a round of housekeeping."""
802
802
  counter = self._housekeep_counter
803
803
  for handler in self._event_handlers:
804
804
  handler.on_sandbox_housekeep(
805
- self.environment, self, counter, duration, error
805
+ self.environment, self, counter, duration, error, **kwargs
806
806
  )
807
807
 
808
808
  def on_feature_setup(
langfun/env/base_test.py CHANGED
@@ -280,7 +280,11 @@ class EnvironmentTests(unittest.TestCase):
280
280
  with self.assertRaises(interface.SandboxStateError):
281
281
  sb.shell('bad command', raise_error=interface.SandboxStateError)
282
282
  self.assertEqual(sb.status, interface.Sandbox.Status.OFFLINE)
283
- env.wait_for_housekeeping()
283
+ sb_offline_time = time.time()
284
+ while time.time() - sb_offline_time < 10:
285
+ if not env.is_online:
286
+ break
287
+ time.sleep(0.5)
284
288
  self.assertFalse(env.is_online)
285
289
 
286
290
 
@@ -1177,6 +1181,7 @@ class SandboxActivityTests(unittest.TestCase):
1177
1181
  sb.shell('test_feature')
1178
1182
  sb.remove_event_handler(event_handler)
1179
1183
  events = list(event_handler.logs)
1184
+ sb.wait_until_not(interface.Sandbox.Status.SETTING_UP)
1180
1185
  self.assertGreater(len(events), 0)
1181
1186
  with env.sandbox('session2') as sb:
1182
1187
  sb.shell('test_feature')
@@ -76,7 +76,16 @@ class _FeatureEventHandler:
76
76
  duration: float,
77
77
  error: BaseException | None
78
78
  ) -> None:
79
- """Called when a sandbox feature is setup."""
79
+ """Called when a sandbox feature is setup.
80
+
81
+ Args:
82
+ environment: The environment.
83
+ sandbox: The sandbox.
84
+ feature: The feature.
85
+ duration: The feature setup duration in seconds.
86
+ error: The error happened during the feature setup. If None,
87
+ the feature setup performed normally.
88
+ """
80
89
 
81
90
  def on_feature_teardown(
82
91
  self,
@@ -86,7 +95,16 @@ class _FeatureEventHandler:
86
95
  duration: float,
87
96
  error: BaseException | None
88
97
  ) -> None:
89
- """Called when a sandbox feature is teardown."""
98
+ """Called when a sandbox feature is teardown.
99
+
100
+ Args:
101
+ environment: The environment.
102
+ sandbox: The sandbox.
103
+ feature: The feature.
104
+ duration: The feature teardown duration in seconds.
105
+ error: The error happened during the feature teardown. If None,
106
+ the feature teardown performed normally.
107
+ """
90
108
 
91
109
  def on_feature_teardown_session(
92
110
  self,
@@ -97,7 +115,17 @@ class _FeatureEventHandler:
97
115
  duration: float,
98
116
  error: BaseException | None
99
117
  ) -> None:
100
- """Called when a feature is teardown with a session."""
118
+ """Called when a feature is teardown with a session.
119
+
120
+ Args:
121
+ environment: The environment.
122
+ sandbox: The sandbox.
123
+ feature: The feature.
124
+ session_id: The session ID.
125
+ duration: The feature teardown session duration in seconds.
126
+ error: The error happened during the feature teardown session. If
127
+ None, the feature teardown session performed normally.
128
+ """
101
129
 
102
130
  def on_feature_setup_session(
103
131
  self,
@@ -106,9 +134,19 @@ class _FeatureEventHandler:
106
134
  feature: Feature,
107
135
  session_id: str | None,
108
136
  duration: float,
109
- error: BaseException | None
137
+ error: BaseException | None,
110
138
  ) -> None:
111
- """Called when a feature is setup with a session."""
139
+ """Called when a feature is setup with a session.
140
+
141
+ Args:
142
+ environment: The environment.
143
+ sandbox: The sandbox.
144
+ feature: The feature.
145
+ session_id: The session ID.
146
+ duration: The feature setup session duration in seconds.
147
+ error: The error happened during the feature setup session. If
148
+ None, the feature setup session performed normally.
149
+ """
112
150
 
113
151
  def on_feature_housekeep(
114
152
  self,
@@ -117,9 +155,21 @@ class _FeatureEventHandler:
117
155
  feature: Feature,
118
156
  counter: int,
119
157
  duration: float,
120
- error: BaseException | None
158
+ error: BaseException | None,
159
+ **kwargs,
121
160
  ) -> None:
122
- """Called when a sandbox feature is housekeeping."""
161
+ """Called when a sandbox feature is housekeeping.
162
+
163
+ Args:
164
+ environment: The environment.
165
+ sandbox: The sandbox.
166
+ feature: The feature.
167
+ counter: Zero-based counter of the housekeeping round.
168
+ duration: The feature housekeeping duration in seconds.
169
+ error: The error happened during the feature housekeeping. If None, the
170
+ feature housekeeping normally.
171
+ **kwargs: Feature-specific properties computed during housekeeping.
172
+ """
123
173
 
124
174
 
125
175
  class _SandboxEventHandler(_FeatureEventHandler, _SessionEventHandler):
@@ -210,7 +260,8 @@ class _SandboxEventHandler(_FeatureEventHandler, _SessionEventHandler):
210
260
  sandbox: Sandbox,
211
261
  counter: int,
212
262
  duration: float,
213
- error: BaseException | None
263
+ error: BaseException | None,
264
+ **kwargs
214
265
  ) -> None:
215
266
  """Called when a sandbox finishes a round of housekeeping.
216
267
 
@@ -221,6 +272,7 @@ class _SandboxEventHandler(_FeatureEventHandler, _SessionEventHandler):
221
272
  duration: The sandbox housekeeping duration in seconds.
222
273
  error: The error that caused the sandbox to housekeeping. If None, the
223
274
  sandbox housekeeping normally.
275
+ **kwargs: Sandbox-specific properties computed during housekeeping.
224
276
  """
225
277
 
226
278
 
@@ -254,7 +306,8 @@ class EventHandler(_SandboxEventHandler):
254
306
  environment: Environment,
255
307
  counter: int,
256
308
  duration: float,
257
- error: BaseException | None
309
+ error: BaseException | None,
310
+ **kwargs
258
311
  ) -> None:
259
312
  """Called when the environment finishes a round of housekeeping.
260
313
 
@@ -264,6 +317,19 @@ class EventHandler(_SandboxEventHandler):
264
317
  duration: The environment start duration in seconds.
265
318
  error: The error that failed the housekeeping. If None, the
266
319
  housekeeping succeeded.
320
+ **kwargs: Environment-specific properties computed during housekeeping.
321
+ """
322
+
323
+ def on_environment_shutting_down(
324
+ self,
325
+ environment: Environment,
326
+ offline_duration: float,
327
+ ) -> None:
328
+ """Called when the environment is shutting down.
329
+
330
+ Args:
331
+ environment: The environment.
332
+ offline_duration: The environment offline duration in seconds.
267
333
  """
268
334
 
269
335
  def on_environment_shutdown(
@@ -105,7 +105,9 @@ class EventLogger(pg.Object, base.EventHandler):
105
105
  error: BaseException | None,
106
106
  ) -> str:
107
107
  if error is not None:
108
- message = f'{message} with error: {error}'
108
+ message = (
109
+ f'{message} with error: {pg.utils.ErrorInfo.from_exception(error)}'
110
+ )
109
111
  return message
110
112
 
111
113
  def _keep(
@@ -133,6 +135,20 @@ class EventLogger(pg.Object, base.EventHandler):
133
135
  styles=['bold'],
134
136
  )
135
137
 
138
+ def on_environment_shutting_down(
139
+ self,
140
+ environment: base.Environment,
141
+ offline_duration: float,
142
+ ) -> None:
143
+ """Called when the environment is shutting down."""
144
+ self._print(
145
+ f'[{environment.id}] environment shutting down '
146
+ f'(offline_duration={offline_duration:.2f} seconds)',
147
+ error=None,
148
+ color='green',
149
+ styles=['bold'],
150
+ )
151
+
136
152
  def on_environment_start(
137
153
  self,
138
154
  environment: base.Environment,
@@ -153,13 +169,15 @@ class EventLogger(pg.Object, base.EventHandler):
153
169
  environment: base.Environment,
154
170
  counter: int,
155
171
  duration: float,
156
- error: BaseException | None
172
+ error: BaseException | None,
173
+ **kwargs
157
174
  ) -> None:
158
175
  """Called when the environment is housekeeping."""
159
176
  if self.housekeep_status:
160
177
  self._print(
161
- f'[{environment.id}] environment housekeeping complete'
162
- f'(counter={counter}, duration={duration:.2f} seconds)',
178
+ f'[{environment.id}] environment housekeeping complete '
179
+ f'(counter={counter}, duration={duration:.2f} seconds, '
180
+ f'housekeep_info={kwargs})',
163
181
  error=error,
164
182
  color='green',
165
183
  )
@@ -246,13 +264,15 @@ class EventLogger(pg.Object, base.EventHandler):
246
264
  sandbox: base.Sandbox,
247
265
  counter: int,
248
266
  duration: float,
249
- error: BaseException | None
267
+ error: BaseException | None,
268
+ **kwargs
250
269
  ) -> None:
251
270
  """Called when a sandbox feature is housekeeping."""
252
271
  if self.sandbox_status and self.housekeep_status:
253
272
  self._print(
254
273
  f'[{sandbox.id}] sandbox housekeeping complete '
255
- f'(counter={counter}, duration={duration:.2f} seconds)',
274
+ f'(counter={counter}, duration={duration:.2f} seconds, '
275
+ f'housekeep_info={kwargs})',
256
276
  error=error,
257
277
  color='white',
258
278
  )
@@ -334,13 +354,15 @@ class EventLogger(pg.Object, base.EventHandler):
334
354
  feature: base.Feature,
335
355
  counter: int,
336
356
  duration: float,
337
- error: BaseException | None
357
+ error: BaseException | None,
358
+ **kwargs
338
359
  ) -> None:
339
360
  """Called when a sandbox feature is housekeeping."""
340
361
  if self.feature_status and self.housekeep_status:
341
362
  self._print(
342
363
  f'[{sandbox.id}/<idle>/{feature.name}] feature housekeeping complete '
343
- f'(counter={counter}, (duration={duration:.2f} seconds)',
364
+ f'(counter={counter}, (duration={duration:.2f} seconds, '
365
+ f'housekeep_info={kwargs})',
344
366
  error=error,
345
367
  color='white',
346
368
  )
@@ -125,6 +125,7 @@ class EventLoggerTest(unittest.TestCase):
125
125
  unexpected_substrings=[
126
126
  'environment starting',
127
127
  'environment started',
128
+ 'environment shutting down',
128
129
  'environment shutdown',
129
130
  'environment housekeeping',
130
131
  'sandbox started',
@@ -143,6 +144,7 @@ class EventLoggerTest(unittest.TestCase):
143
144
  expected_substrings=[
144
145
  'environment starting',
145
146
  'environment started',
147
+ 'environment shutting down',
146
148
  'environment shutdown',
147
149
  'environment housekeeping',
148
150
  ],
@@ -166,6 +168,7 @@ class EventLoggerTest(unittest.TestCase):
166
168
  expected_substrings=[
167
169
  'environment starting',
168
170
  'environment started',
171
+ 'environment shutting down',
169
172
  'environment shutdown',
170
173
  'environment housekeeping',
171
174
  'feature setup complete',
@@ -191,6 +194,7 @@ class EventLoggerTest(unittest.TestCase):
191
194
  expected_substrings=[
192
195
  'environment starting',
193
196
  'environment started',
197
+ 'environment shutting down',
194
198
  'environment shutdown',
195
199
  'environment housekeeping',
196
200
  'environment stats',
@@ -217,6 +221,7 @@ class EventLoggerTest(unittest.TestCase):
217
221
  expected_substrings=[
218
222
  'environment starting',
219
223
  'environment started',
224
+ 'environment shutting down',
220
225
  'environment shutdown',
221
226
  'environment stats',
222
227
  'environment housekeeping',
@@ -243,6 +248,7 @@ class EventLoggerTest(unittest.TestCase):
243
248
  expected_substrings=[
244
249
  'environment starting',
245
250
  'environment started',
251
+ 'environment shutting down',
246
252
  'environment shutdown',
247
253
  'environment stats',
248
254
  'sandbox started',
@@ -269,6 +275,7 @@ class EventLoggerTest(unittest.TestCase):
269
275
  expected_substrings=[
270
276
  'environment starting',
271
277
  'environment started',
278
+ 'environment shutting down',
272
279
  'environment shutdown',
273
280
  'environment housekeeping',
274
281
  ],