langfun 0.1.2.dev202509250804__py3-none-any.whl → 0.1.2.dev202509260805__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.

@@ -0,0 +1,271 @@
1
+
2
+ # Copyright 2025 The Langfun Authors
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ """Base classes for Langfun environment event handlers."""
16
+
17
+ from langfun.env import interface
18
+
19
+ Environment = interface.Environment
20
+ Sandbox = interface.Sandbox
21
+ Feature = interface.Feature
22
+
23
+
24
+ class _SessionEventHandler:
25
+ """Base class for session event handlers."""
26
+
27
+ def on_session_start(
28
+ self,
29
+ environment: Environment,
30
+ sandbox: Sandbox,
31
+ session_id: str,
32
+ duration: float,
33
+ error: BaseException | None
34
+ ) -> None:
35
+ """Called when a sandbox session starts.
36
+
37
+ Args:
38
+ environment: The environment.
39
+ sandbox: The sandbox.
40
+ session_id: The session ID.
41
+ duration: The time spent on starting the session.
42
+ error: The error that caused the session to start. If None, the session
43
+ started normally.
44
+ """
45
+
46
+ def on_session_end(
47
+ self,
48
+ environment: Environment,
49
+ sandbox: Sandbox,
50
+ session_id: str,
51
+ lifetime: float,
52
+ error: BaseException | None
53
+ ) -> None:
54
+ """Called when a sandbox session ends.
55
+
56
+ Args:
57
+ environment: The environment.
58
+ sandbox: The sandbox.
59
+ session_id: The session ID.
60
+ lifetime: The session lifetime in seconds.
61
+ error: The error that caused the session to end. If None, the session
62
+ ended normally.
63
+ """
64
+
65
+
66
+ class _FeatureEventHandler:
67
+ """Base class for feature event handlers."""
68
+
69
+ def on_feature_setup(
70
+ self,
71
+ environment: Environment,
72
+ sandbox: Sandbox,
73
+ feature: Feature,
74
+ duration: float,
75
+ error: BaseException | None
76
+ ) -> None:
77
+ """Called when a sandbox feature is setup."""
78
+
79
+ def on_feature_teardown(
80
+ self,
81
+ environment: Environment,
82
+ sandbox: Sandbox,
83
+ feature: Feature,
84
+ duration: float,
85
+ error: BaseException | None
86
+ ) -> None:
87
+ """Called when a sandbox feature is teardown."""
88
+
89
+ def on_feature_teardown_session(
90
+ self,
91
+ environment: Environment,
92
+ sandbox: Sandbox,
93
+ feature: Feature,
94
+ session_id: str,
95
+ duration: float,
96
+ error: BaseException | None
97
+ ) -> None:
98
+ """Called when a feature is teardown with a session."""
99
+
100
+ def on_feature_setup_session(
101
+ self,
102
+ environment: Environment,
103
+ sandbox: Sandbox,
104
+ feature: Feature,
105
+ session_id: str | None,
106
+ duration: float,
107
+ error: BaseException | None
108
+ ) -> None:
109
+ """Called when a feature is setup with a session."""
110
+
111
+ def on_feature_housekeep(
112
+ self,
113
+ environment: Environment,
114
+ sandbox: Sandbox,
115
+ feature: Feature,
116
+ counter: int,
117
+ duration: float,
118
+ error: BaseException | None
119
+ ) -> None:
120
+ """Called when a sandbox feature is housekeeping."""
121
+
122
+
123
+ class _SandboxEventHandler(_FeatureEventHandler, _SessionEventHandler):
124
+ """Base class for sandbox event handlers."""
125
+
126
+ def on_sandbox_start(
127
+ self,
128
+ environment: Environment,
129
+ sandbox: Sandbox,
130
+ duration: float,
131
+ error: BaseException | None
132
+ ) -> None:
133
+ """Called when a sandbox is started.
134
+
135
+ Args:
136
+ environment: The environment.
137
+ sandbox: The sandbox.
138
+ duration: The time spent on starting the sandbox.
139
+ error: The error that caused the sandbox to start. If None, the sandbox
140
+ started normally.
141
+ """
142
+
143
+ def on_sandbox_status_change(
144
+ self,
145
+ environment: Environment,
146
+ sandbox: Sandbox,
147
+ old_status: 'Sandbox.Status',
148
+ new_status: 'Sandbox.Status',
149
+ span: float,
150
+ ) -> None:
151
+ """Called when a sandbox status changes.
152
+
153
+ Args:
154
+ environment: The environment.
155
+ sandbox: The sandbox.
156
+ old_status: The old sandbox status.
157
+ new_status: The new sandbox status.
158
+ span: Time spent on the old status in seconds.
159
+ """
160
+
161
+ def on_sandbox_shutdown(
162
+ self,
163
+ environment: Environment,
164
+ sandbox: Sandbox,
165
+ lifetime: float,
166
+ error: BaseException | None
167
+ ) -> None:
168
+ """Called when a sandbox is shutdown.
169
+
170
+ Args:
171
+ environment: The environment.
172
+ sandbox: The sandbox.
173
+ lifetime: The sandbox lifetime in seconds.
174
+ error: The error that caused the sandbox to shutdown. If None, the
175
+ sandbox shutdown normally.
176
+ """
177
+
178
+ def on_sandbox_activity(
179
+ self,
180
+ name: str,
181
+ environment: Environment,
182
+ sandbox: Sandbox,
183
+ feature: Feature | None,
184
+ session_id: str | None,
185
+ duration: float,
186
+ error: BaseException | None,
187
+ **kwargs
188
+ ) -> None:
189
+ """Called when a sandbox activity is performed.
190
+
191
+ Args:
192
+ name: The name of the sandbox activity.
193
+ environment: The environment.
194
+ sandbox: The sandbox.
195
+ feature: The feature that is associated with the sandbox activity.
196
+ session_id: The session ID.
197
+ duration: The sandbox activity duration in seconds.
198
+ error: The error that caused the sandbox activity to perform. If None,
199
+ the sandbox activity performed normally.
200
+ **kwargs: The keyword arguments of the sandbox activity.
201
+ """
202
+
203
+ def on_sandbox_housekeep(
204
+ self,
205
+ environment: Environment,
206
+ sandbox: Sandbox,
207
+ counter: int,
208
+ duration: float,
209
+ error: BaseException | None
210
+ ) -> None:
211
+ """Called when a sandbox finishes a round of housekeeping.
212
+
213
+ Args:
214
+ environment: The environment.
215
+ sandbox: The sandbox.
216
+ counter: Zero-based counter of the housekeeping round.
217
+ duration: The sandbox housekeeping duration in seconds.
218
+ error: The error that caused the sandbox to housekeeping. If None, the
219
+ sandbox housekeeping normally.
220
+ """
221
+
222
+
223
+ class EventHandler(_SandboxEventHandler):
224
+ """Base class for event handlers of an environment."""
225
+
226
+ def on_environment_start(
227
+ self,
228
+ environment: Environment,
229
+ duration: float,
230
+ error: BaseException | None
231
+ ) -> None:
232
+ """Called when the environment is started.
233
+
234
+ Args:
235
+ environment: The environment.
236
+ duration: The environment start duration in seconds.
237
+ error: The error that failed the environment start. If None, the
238
+ environment started normally.
239
+ """
240
+
241
+ def on_environment_housekeep(
242
+ self,
243
+ environment: Environment,
244
+ counter: int,
245
+ duration: float,
246
+ error: BaseException | None
247
+ ) -> None:
248
+ """Called when the environment finishes a round of housekeeping.
249
+
250
+ Args:
251
+ environment: The environment.
252
+ counter: Zero-based counter of the housekeeping round.
253
+ duration: The environment start duration in seconds.
254
+ error: The error that failed the housekeeping. If None, the
255
+ housekeeping succeeded.
256
+ """
257
+
258
+ def on_environment_shutdown(
259
+ self,
260
+ environment: Environment,
261
+ lifetime: float,
262
+ error: BaseException | None
263
+ ) -> None:
264
+ """Called when the environment is shutdown.
265
+
266
+ Args:
267
+ environment: The environment.
268
+ lifetime: The environment lifetime in seconds.
269
+ error: The error that caused the environment to shutdown. If None, the
270
+ environment shutdown normally.
271
+ """
@@ -0,0 +1,415 @@
1
+ # Copyright 2025 The Langfun Authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """Environment event logger."""
15
+
16
+ import re
17
+ import time
18
+ from typing import Annotated
19
+ from langfun.env.event_handlers import base
20
+ import pyglove as pg
21
+
22
+
23
+ class EventLogger(pg.Object, base.EventHandler):
24
+ """Event handler for logging debugger."""
25
+
26
+ regex: Annotated[
27
+ str | list[str] | None,
28
+ (
29
+ 'One or a list of regular expressions to filter event messages. '
30
+ 'If None, no filtering will be applied.'
31
+ )
32
+ ] = None
33
+
34
+ error_only: Annotated[
35
+ bool,
36
+ (
37
+ 'If True, log events with errors only.'
38
+ )
39
+ ] = False
40
+
41
+ sandbox_status: Annotated[
42
+ bool,
43
+ (
44
+ 'If True, log events for sandbox status changes.'
45
+ )
46
+ ] = True
47
+
48
+ feature_status: Annotated[
49
+ bool,
50
+ (
51
+ 'If True, log events for feature setup/teardown updates.'
52
+ )
53
+ ] = True
54
+
55
+ session_status: Annotated[
56
+ bool,
57
+ (
58
+ 'If True, log events for session start/end status update.'
59
+ )
60
+ ] = True
61
+
62
+ housekeep_status: Annotated[
63
+ bool,
64
+ (
65
+ 'If True, log housekeeping events.'
66
+ )
67
+ ] = True
68
+
69
+ stats_report_interval: Annotated[
70
+ float | None,
71
+ (
72
+ 'The minimum interval in seconds for reporting the environment '
73
+ 'stats. If None, stats will not be reported.'
74
+ )
75
+ ] = 300.0
76
+
77
+ def _on_bound(self) -> None:
78
+ super()._on_bound()
79
+
80
+ regex_exps = self.regex
81
+ if isinstance(regex_exps, str):
82
+ regex_exps = [regex_exps]
83
+ elif regex_exps is None:
84
+ regex_exps = []
85
+ self._regex_exps = [re.compile(x) for x in regex_exps]
86
+ self._last_stats_report_time = None
87
+
88
+ def _format_message(
89
+ self,
90
+ message: str,
91
+ error: BaseException | None,
92
+ ) -> str:
93
+ if error is not None:
94
+ message = f'{message} with error: {error}'
95
+ return message
96
+
97
+ def _keep(
98
+ self,
99
+ message: str,
100
+ error: BaseException | None,
101
+ ) -> bool:
102
+ if error is None and self.error_only:
103
+ return False
104
+ if self._regex_exps and all(
105
+ not exp.match(message) for exp in self._regex_exps
106
+ ):
107
+ return False
108
+ return True
109
+
110
+ def on_environment_start(
111
+ self,
112
+ environment: base.Environment,
113
+ duration: float,
114
+ error: BaseException | None
115
+ ) -> None:
116
+ """Called when the environment is started."""
117
+ self._print(
118
+ f'[{environment.id}] environment started '
119
+ f'(duration={duration:.2f} seconds)',
120
+ error=error,
121
+ color='green',
122
+ styles=['bold'],
123
+ )
124
+
125
+ def on_environment_housekeep(
126
+ self,
127
+ environment: base.Environment,
128
+ counter: int,
129
+ duration: float,
130
+ error: BaseException | None
131
+ ) -> None:
132
+ """Called when the environment is housekeeping."""
133
+ if self.housekeep_status:
134
+ self._print(
135
+ f'[{environment.id}] environment housekeeping complete'
136
+ f'(counter={counter}, duration={duration:.2f} seconds)',
137
+ error=error,
138
+ color='green',
139
+ )
140
+ if (self.stats_report_interval is not None and
141
+ (self._last_stats_report_time is None
142
+ or time.time() - self._last_stats_report_time
143
+ > self.stats_report_interval)):
144
+ self._write_log(
145
+ f'[{environment.id}] environment stats: {environment.stats()}',
146
+ color='magenta',
147
+ error=None,
148
+ )
149
+ self._last_stats_report_time = time.time()
150
+
151
+ def on_environment_shutdown(
152
+ self,
153
+ environment: base.Environment,
154
+ lifetime: float,
155
+ error: BaseException | None
156
+ ) -> None:
157
+ """Called when the environment is shutdown."""
158
+ self._print(
159
+ f'[{environment.id}] environment shutdown '
160
+ f'(lifetime={lifetime:.2f} seconds)',
161
+ error=error,
162
+ color='green',
163
+ styles=['bold'],
164
+ )
165
+
166
+ def on_sandbox_start(
167
+ self,
168
+ environment: base.Environment,
169
+ sandbox: base.Sandbox,
170
+ duration: float,
171
+ error: BaseException | None
172
+ ) -> None:
173
+ if self.sandbox_status:
174
+ self._print(
175
+ f'[{sandbox.id}] sandbox started '
176
+ f'(duration={duration:.2f} seconds)',
177
+ error=error,
178
+ color='white',
179
+ styles=['bold'],
180
+ )
181
+
182
+ def on_sandbox_status_change(
183
+ self,
184
+ environment: base.Environment,
185
+ sandbox: base.Sandbox,
186
+ old_status: base.Sandbox.Status,
187
+ new_status: base.Sandbox.Status,
188
+ span: float
189
+ ) -> None:
190
+ if self.sandbox_status:
191
+ self._print(
192
+ f'[{sandbox.id}] {old_status.value} '
193
+ f'({span:.2f} seconds) -> {new_status.value}',
194
+ error=None,
195
+ color='white',
196
+ )
197
+
198
+ def on_sandbox_shutdown(
199
+ self,
200
+ environment: base.Environment,
201
+ sandbox: base.Sandbox,
202
+ lifetime: float,
203
+ error: BaseException | None
204
+ ) -> None:
205
+ if self.sandbox_status:
206
+ self._print(
207
+ f'[{sandbox.id}] sandbox shutdown '
208
+ f'(lifetime={lifetime:.2f} seconds)',
209
+ error=error,
210
+ color='white',
211
+ styles=['bold'],
212
+ )
213
+
214
+ def on_sandbox_housekeep(
215
+ self,
216
+ environment: base.Environment,
217
+ sandbox: base.Sandbox,
218
+ counter: int,
219
+ duration: float,
220
+ error: BaseException | None
221
+ ) -> None:
222
+ """Called when a sandbox feature is housekeeping."""
223
+ if self.sandbox_status and self.housekeep_status:
224
+ self._print(
225
+ f'[{sandbox.id}] sandbox housekeeping complete '
226
+ f'(counter={counter}, duration={duration:.2f} seconds)',
227
+ error=error,
228
+ color='white',
229
+ )
230
+
231
+ def on_feature_setup(
232
+ self,
233
+ environment: base.Environment,
234
+ sandbox: base.Sandbox,
235
+ feature: base.Feature,
236
+ duration: float,
237
+ error: BaseException | None
238
+ ) -> None:
239
+ """Called when a sandbox feature is setup."""
240
+ if self.feature_status:
241
+ self._print(
242
+ f'[{sandbox.id}/<idle>/{feature.name}] feature setup complete '
243
+ f'(duration={duration:.2f} seconds)',
244
+ error=error,
245
+ color='white',
246
+ )
247
+
248
+ def on_feature_teardown(
249
+ self,
250
+ environment: base.Environment,
251
+ sandbox: base.Sandbox,
252
+ feature: base.Feature,
253
+ duration: float,
254
+ error: BaseException | None
255
+ ) -> None:
256
+ """Called when a sandbox feature is teardown."""
257
+ if self.feature_status:
258
+ self._print(
259
+ f'[{sandbox.id}/<idle>/{feature.name}] feature teardown complete '
260
+ f'(duration={duration:.2f} seconds)',
261
+ error=error,
262
+ color='white',
263
+ )
264
+
265
+ def on_feature_setup_session(
266
+ self,
267
+ environment: base.Environment,
268
+ sandbox: base.Sandbox,
269
+ feature: base.Feature,
270
+ session_id: str | None,
271
+ duration: float,
272
+ error: BaseException | None
273
+ ) -> None:
274
+ """Called when a sandbox feature is setup."""
275
+ if self.feature_status:
276
+ self._print(
277
+ f'[{sandbox.id}/{session_id or "<idle>"}/{feature.name}] '
278
+ f'feature setup complete (duration={duration:.2f} seconds)',
279
+ error=error,
280
+ color='yellow',
281
+ )
282
+
283
+ def on_feature_teardown_session(
284
+ self,
285
+ environment: base.Environment,
286
+ sandbox: base.Sandbox,
287
+ feature: base.Feature,
288
+ session_id: str,
289
+ duration: float,
290
+ error: BaseException | None
291
+ ) -> None:
292
+ """Called when a sandbox feature is teardown."""
293
+ if self.feature_status:
294
+ self._print(
295
+ f'[{sandbox.id}/{session_id}>/{feature.name}] '
296
+ f'feature teardown complete (duration={duration:.2f} seconds)',
297
+ error=error,
298
+ color='yellow',
299
+ )
300
+
301
+ def on_feature_housekeep(
302
+ self,
303
+ environment: base.Environment,
304
+ sandbox: base.Sandbox,
305
+ feature: base.Feature,
306
+ counter: int,
307
+ duration: float,
308
+ error: BaseException | None
309
+ ) -> None:
310
+ """Called when a sandbox feature is housekeeping."""
311
+ if self.feature_status and self.housekeep_status:
312
+ self._print(
313
+ f'[{sandbox.id}/<idle>/{feature.name}] feature housekeeping complete '
314
+ f'(counter={counter}, (duration={duration:.2f} seconds)',
315
+ error=error,
316
+ color='white',
317
+ )
318
+
319
+ def on_session_start(
320
+ self,
321
+ environment: base.Environment,
322
+ sandbox: base.Sandbox,
323
+ session_id: str,
324
+ duration: float,
325
+ error: BaseException | None
326
+ ) -> None:
327
+ """Called when a sandbox session starts."""
328
+ if self.session_status:
329
+ self._print(
330
+ f'[{sandbox.id}/{session_id}] session started '
331
+ f'(duration={duration:.2f} seconds)',
332
+ error=error,
333
+ color='blue',
334
+ )
335
+
336
+ def on_session_end(
337
+ self,
338
+ environment: base.Environment,
339
+ sandbox: base.Sandbox,
340
+ session_id: str,
341
+ lifetime: float,
342
+ error: BaseException | None
343
+ ) -> None:
344
+ """Called when a sandbox session ends."""
345
+ if self.session_status:
346
+ self._print(
347
+ f'[{sandbox.id}/{session_id}] session ended '
348
+ f'(lifetime={lifetime:.2f} seconds)',
349
+ error=error,
350
+ color='blue',
351
+ )
352
+
353
+ def on_sandbox_activity(
354
+ self,
355
+ name: str,
356
+ environment: base.Environment,
357
+ sandbox: base.Sandbox,
358
+ feature: base.Feature | None,
359
+ session_id: str | None,
360
+ duration: float,
361
+ error: BaseException | None,
362
+ **kwargs
363
+ ) -> None:
364
+ """Called when a sandbox activity is performed."""
365
+ del environment
366
+ log_id = f'{sandbox.id}/{session_id or "<idle>"}'
367
+ if feature is not None:
368
+ log_id = f'{log_id}/{feature.name}'
369
+
370
+ color = 'yellow' if session_id is None else 'cyan'
371
+ self._print(
372
+ f'[{log_id}] call {name!r} '
373
+ f'(duration={duration:.2f} seconds, kwargs={kwargs}) ',
374
+ error,
375
+ color=color
376
+ )
377
+
378
+ def _print(
379
+ self,
380
+ message: str,
381
+ error: BaseException | None,
382
+ color: str | None = None,
383
+ styles: list[str] | None = None,
384
+ ):
385
+ message = self._format_message(message, error)
386
+ if not self._keep(message, error):
387
+ return
388
+ self._write_log(message, error, color, styles)
389
+
390
+ def _write_log(
391
+ self,
392
+ message: str,
393
+ error: BaseException | None,
394
+ color: str | None = None,
395
+ styles: list[str] | None = None,
396
+ ):
397
+ if error is not None:
398
+ pg.logging.error(pg.colored(message, 'red', styles=styles))
399
+ else:
400
+ pg.logging.info(pg.colored(message, color, styles=styles))
401
+
402
+
403
+ class ConsoleEventLogger(EventLogger):
404
+ """Event handler for console debugger."""
405
+
406
+ def _write_log(
407
+ self,
408
+ message: str,
409
+ error: BaseException | None,
410
+ color: str | None = None,
411
+ styles: list[str] | None = None,
412
+ ):
413
+ print(
414
+ pg.colored(message, color if error is None else 'red', styles=styles)
415
+ )