langfun 0.1.2.dev202509120804__py3-none-any.whl → 0.1.2.dev202512040805__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.
Files changed (162) hide show
  1. langfun/__init__.py +1 -1
  2. langfun/core/__init__.py +7 -1
  3. langfun/core/agentic/__init__.py +8 -1
  4. langfun/core/agentic/action.py +740 -112
  5. langfun/core/agentic/action_eval.py +9 -2
  6. langfun/core/agentic/action_test.py +189 -24
  7. langfun/core/async_support.py +104 -5
  8. langfun/core/async_support_test.py +23 -0
  9. langfun/core/coding/python/correction.py +19 -9
  10. langfun/core/coding/python/execution.py +14 -12
  11. langfun/core/coding/python/generation.py +21 -16
  12. langfun/core/coding/python/sandboxing.py +23 -3
  13. langfun/core/component.py +42 -3
  14. langfun/core/concurrent.py +70 -6
  15. langfun/core/concurrent_test.py +9 -2
  16. langfun/core/console.py +1 -1
  17. langfun/core/data/conversion/anthropic.py +12 -3
  18. langfun/core/data/conversion/anthropic_test.py +8 -6
  19. langfun/core/data/conversion/gemini.py +11 -2
  20. langfun/core/data/conversion/gemini_test.py +48 -9
  21. langfun/core/data/conversion/openai.py +145 -31
  22. langfun/core/data/conversion/openai_test.py +161 -17
  23. langfun/core/eval/base.py +48 -44
  24. langfun/core/eval/base_test.py +5 -5
  25. langfun/core/eval/matching.py +5 -2
  26. langfun/core/eval/patching.py +3 -3
  27. langfun/core/eval/scoring.py +4 -3
  28. langfun/core/eval/v2/__init__.py +2 -0
  29. langfun/core/eval/v2/checkpointing.py +76 -7
  30. langfun/core/eval/v2/checkpointing_test.py +9 -2
  31. langfun/core/eval/v2/config_saver.py +37 -0
  32. langfun/core/eval/v2/config_saver_test.py +36 -0
  33. langfun/core/eval/v2/eval_test_helper.py +104 -3
  34. langfun/core/eval/v2/evaluation.py +92 -17
  35. langfun/core/eval/v2/evaluation_test.py +9 -3
  36. langfun/core/eval/v2/example.py +50 -40
  37. langfun/core/eval/v2/example_test.py +16 -8
  38. langfun/core/eval/v2/experiment.py +84 -15
  39. langfun/core/eval/v2/experiment_test.py +19 -0
  40. langfun/core/eval/v2/metric_values.py +31 -3
  41. langfun/core/eval/v2/metric_values_test.py +32 -0
  42. langfun/core/eval/v2/metrics.py +157 -44
  43. langfun/core/eval/v2/metrics_test.py +39 -18
  44. langfun/core/eval/v2/progress.py +31 -1
  45. langfun/core/eval/v2/progress_test.py +27 -0
  46. langfun/core/eval/v2/progress_tracking.py +13 -5
  47. langfun/core/eval/v2/progress_tracking_test.py +9 -1
  48. langfun/core/eval/v2/reporting.py +90 -71
  49. langfun/core/eval/v2/reporting_test.py +24 -6
  50. langfun/core/eval/v2/runners/__init__.py +30 -0
  51. langfun/core/eval/v2/{runners.py → runners/base.py} +72 -180
  52. langfun/core/eval/v2/runners/beam.py +354 -0
  53. langfun/core/eval/v2/runners/beam_test.py +153 -0
  54. langfun/core/eval/v2/runners/ckpt_monitor.py +294 -0
  55. langfun/core/eval/v2/runners/ckpt_monitor_test.py +162 -0
  56. langfun/core/eval/v2/runners/debug.py +40 -0
  57. langfun/core/eval/v2/runners/debug_test.py +76 -0
  58. langfun/core/eval/v2/runners/parallel.py +243 -0
  59. langfun/core/eval/v2/runners/parallel_test.py +182 -0
  60. langfun/core/eval/v2/runners/sequential.py +47 -0
  61. langfun/core/eval/v2/runners/sequential_test.py +169 -0
  62. langfun/core/langfunc.py +45 -130
  63. langfun/core/langfunc_test.py +7 -5
  64. langfun/core/language_model.py +189 -36
  65. langfun/core/language_model_test.py +54 -3
  66. langfun/core/llms/__init__.py +12 -1
  67. langfun/core/llms/anthropic.py +157 -2
  68. langfun/core/llms/azure_openai.py +29 -17
  69. langfun/core/llms/cache/base.py +25 -3
  70. langfun/core/llms/cache/in_memory.py +48 -7
  71. langfun/core/llms/cache/in_memory_test.py +14 -4
  72. langfun/core/llms/compositional.py +25 -1
  73. langfun/core/llms/deepseek.py +30 -2
  74. langfun/core/llms/fake.py +32 -1
  75. langfun/core/llms/gemini.py +64 -12
  76. langfun/core/llms/gemini_test.py +110 -0
  77. langfun/core/llms/google_genai.py +34 -1
  78. langfun/core/llms/groq.py +28 -3
  79. langfun/core/llms/llama_cpp.py +23 -4
  80. langfun/core/llms/openai.py +120 -3
  81. langfun/core/llms/openai_compatible.py +148 -27
  82. langfun/core/llms/openai_compatible_test.py +207 -20
  83. langfun/core/llms/openai_test.py +0 -2
  84. langfun/core/llms/rest.py +16 -1
  85. langfun/core/llms/vertexai.py +58 -8
  86. langfun/core/logging.py +1 -1
  87. langfun/core/mcp/__init__.py +10 -0
  88. langfun/core/mcp/client.py +177 -0
  89. langfun/core/mcp/client_test.py +71 -0
  90. langfun/core/mcp/session.py +241 -0
  91. langfun/core/mcp/session_test.py +54 -0
  92. langfun/core/mcp/testing/simple_mcp_client.py +33 -0
  93. langfun/core/mcp/testing/simple_mcp_server.py +33 -0
  94. langfun/core/mcp/tool.py +254 -0
  95. langfun/core/mcp/tool_test.py +197 -0
  96. langfun/core/memory.py +1 -0
  97. langfun/core/message.py +160 -55
  98. langfun/core/message_test.py +65 -81
  99. langfun/core/modalities/__init__.py +8 -0
  100. langfun/core/modalities/audio.py +21 -1
  101. langfun/core/modalities/image.py +73 -3
  102. langfun/core/modalities/image_test.py +116 -0
  103. langfun/core/modalities/mime.py +64 -3
  104. langfun/core/modalities/mime_test.py +11 -0
  105. langfun/core/modalities/pdf.py +19 -1
  106. langfun/core/modalities/video.py +21 -1
  107. langfun/core/modality.py +167 -29
  108. langfun/core/modality_test.py +42 -12
  109. langfun/core/natural_language.py +1 -1
  110. langfun/core/sampling.py +4 -4
  111. langfun/core/sampling_test.py +20 -4
  112. langfun/core/structured/__init__.py +2 -24
  113. langfun/core/structured/completion.py +34 -44
  114. langfun/core/structured/completion_test.py +23 -43
  115. langfun/core/structured/description.py +54 -50
  116. langfun/core/structured/function_generation.py +29 -12
  117. langfun/core/structured/mapping.py +81 -37
  118. langfun/core/structured/parsing.py +95 -79
  119. langfun/core/structured/parsing_test.py +0 -3
  120. langfun/core/structured/querying.py +230 -154
  121. langfun/core/structured/querying_test.py +69 -33
  122. langfun/core/structured/schema/__init__.py +49 -0
  123. langfun/core/structured/schema/base.py +664 -0
  124. langfun/core/structured/schema/base_test.py +531 -0
  125. langfun/core/structured/schema/json.py +174 -0
  126. langfun/core/structured/schema/json_test.py +121 -0
  127. langfun/core/structured/schema/python.py +316 -0
  128. langfun/core/structured/schema/python_test.py +410 -0
  129. langfun/core/structured/schema_generation.py +33 -14
  130. langfun/core/structured/scoring.py +47 -36
  131. langfun/core/structured/tokenization.py +26 -11
  132. langfun/core/subscription.py +2 -2
  133. langfun/core/template.py +175 -50
  134. langfun/core/template_test.py +123 -17
  135. langfun/env/__init__.py +43 -0
  136. langfun/env/base_environment.py +827 -0
  137. langfun/env/base_environment_test.py +473 -0
  138. langfun/env/base_feature.py +304 -0
  139. langfun/env/base_feature_test.py +228 -0
  140. langfun/env/base_sandbox.py +842 -0
  141. langfun/env/base_sandbox_test.py +1235 -0
  142. langfun/env/event_handlers/__init__.py +14 -0
  143. langfun/env/event_handlers/chain.py +233 -0
  144. langfun/env/event_handlers/chain_test.py +253 -0
  145. langfun/env/event_handlers/event_logger.py +472 -0
  146. langfun/env/event_handlers/event_logger_test.py +304 -0
  147. langfun/env/event_handlers/metric_writer.py +726 -0
  148. langfun/env/event_handlers/metric_writer_test.py +214 -0
  149. langfun/env/interface.py +1640 -0
  150. langfun/env/interface_test.py +153 -0
  151. langfun/env/load_balancers.py +59 -0
  152. langfun/env/load_balancers_test.py +141 -0
  153. langfun/env/test_utils.py +507 -0
  154. {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512040805.dist-info}/METADATA +7 -3
  155. langfun-0.1.2.dev202512040805.dist-info/RECORD +217 -0
  156. langfun/core/eval/v2/runners_test.py +0 -343
  157. langfun/core/structured/schema.py +0 -987
  158. langfun/core/structured/schema_test.py +0 -982
  159. langfun-0.1.2.dev202509120804.dist-info/RECORD +0 -172
  160. {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512040805.dist-info}/WHEEL +0 -0
  161. {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512040805.dist-info}/licenses/LICENSE +0 -0
  162. {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512040805.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,472 @@
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 import interface
20
+ import pyglove as pg
21
+
22
+
23
+ class EventLogger(pg.Object, interface.EventHandler):
24
+ """Event handler for logging debugger."""
25
+
26
+ colored: Annotated[
27
+ bool,
28
+ (
29
+ 'If True, log events with colors.'
30
+ )
31
+ ] = False
32
+
33
+ regex: Annotated[
34
+ str | list[str] | None,
35
+ (
36
+ 'One or a list of regular expressions to filter event messages. '
37
+ 'If None, no filtering will be applied.'
38
+ )
39
+ ] = None
40
+
41
+ error_only: Annotated[
42
+ bool,
43
+ (
44
+ 'If True, log events with errors only.'
45
+ )
46
+ ] = False
47
+
48
+ sandbox_status: Annotated[
49
+ bool,
50
+ (
51
+ 'If True, log events for sandbox status changes.'
52
+ )
53
+ ] = True
54
+
55
+ feature_status: Annotated[
56
+ bool,
57
+ (
58
+ 'If True, log events for feature setup/teardown updates.'
59
+ )
60
+ ] = True
61
+
62
+ session_status: Annotated[
63
+ bool,
64
+ (
65
+ 'If True, log events for session start/end status update.'
66
+ )
67
+ ] = True
68
+
69
+ housekeep_status: Annotated[
70
+ bool,
71
+ (
72
+ 'If True, log housekeeping events.'
73
+ )
74
+ ] = True
75
+
76
+ stats_report_interval: Annotated[
77
+ float | None,
78
+ (
79
+ 'The minimum interval in seconds for reporting the environment '
80
+ 'stats. If None, stats will not be reported.'
81
+ )
82
+ ] = 300.0
83
+
84
+ def _on_bound(self) -> None:
85
+ super()._on_bound()
86
+
87
+ regex_exps = self.regex
88
+ if isinstance(regex_exps, str):
89
+ regex_exps = [regex_exps]
90
+ elif regex_exps is None:
91
+ regex_exps = []
92
+ self._regex_exps = [re.compile(x) for x in regex_exps]
93
+ self._last_stats_report_time = None
94
+
95
+ def _maybe_colored(
96
+ self, message: str, color: str, styles: list[str] | None = None
97
+ ) -> str:
98
+ if self.colored:
99
+ return pg.colored(message, color, styles=styles)
100
+ return message
101
+
102
+ def _format_message(
103
+ self,
104
+ message: str,
105
+ error: BaseException | None,
106
+ ) -> str:
107
+ if error is not None:
108
+ message = (
109
+ f'{message} with error: {pg.utils.ErrorInfo.from_exception(error)}'
110
+ )
111
+ return message
112
+
113
+ def _keep(
114
+ self,
115
+ message: str,
116
+ error: BaseException | None,
117
+ ) -> bool:
118
+ if error is None and self.error_only:
119
+ return False
120
+ if self._regex_exps and all(
121
+ not exp.match(message) for exp in self._regex_exps
122
+ ):
123
+ return False
124
+ return True
125
+
126
+ def on_environment_starting(
127
+ self,
128
+ environment: interface.Environment,
129
+ ) -> None:
130
+ """Called when the environment is starting."""
131
+ self._print(
132
+ f'[{environment.id}] environment starting',
133
+ error=None,
134
+ color='green',
135
+ styles=['bold'],
136
+ )
137
+
138
+ def on_environment_shutting_down(
139
+ self,
140
+ environment: interface.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
+
152
+ def on_environment_start(
153
+ self,
154
+ environment: interface.Environment,
155
+ duration: float,
156
+ error: BaseException | None
157
+ ) -> None:
158
+ """Called when the environment is started."""
159
+ self._print(
160
+ f'[{environment.id}] environment started '
161
+ f'(duration={duration:.2f} seconds)',
162
+ error=error,
163
+ color='green',
164
+ styles=['bold'],
165
+ )
166
+
167
+ def on_environment_housekeep(
168
+ self,
169
+ environment: interface.Environment,
170
+ counter: int,
171
+ duration: float,
172
+ error: BaseException | None,
173
+ **kwargs
174
+ ) -> None:
175
+ """Called when the environment is housekeeping."""
176
+ if self.housekeep_status:
177
+ self._print(
178
+ f'[{environment.id}] environment housekeeping complete '
179
+ f'(counter={counter}, duration={duration:.2f} seconds, '
180
+ f'housekeep_info={kwargs})',
181
+ error=error,
182
+ color='green',
183
+ )
184
+ if (self.stats_report_interval is not None and
185
+ (self._last_stats_report_time is None
186
+ or time.time() - self._last_stats_report_time
187
+ > self.stats_report_interval)):
188
+ self._write_log(
189
+ f'[{environment.id}] environment stats: {environment.stats()}',
190
+ color='magenta',
191
+ error=None,
192
+ )
193
+ self._last_stats_report_time = time.time()
194
+
195
+ def on_environment_shutdown(
196
+ self,
197
+ environment: interface.Environment,
198
+ duration: float,
199
+ lifetime: float,
200
+ error: BaseException | None
201
+ ) -> None:
202
+ """Called when the environment is shutdown."""
203
+ self._print(
204
+ f'[{environment.id}] environment shutdown '
205
+ f'(duration={duration:.2f} seconds), lifetime={lifetime:.2f} seconds)',
206
+ error=error,
207
+ color='green',
208
+ styles=['bold'],
209
+ )
210
+
211
+ def on_sandbox_start(
212
+ self,
213
+ sandbox: interface.Sandbox,
214
+ duration: float,
215
+ error: BaseException | None
216
+ ) -> None:
217
+ if self.sandbox_status:
218
+ self._print(
219
+ f'[{sandbox.id}] sandbox started '
220
+ f'(duration={duration:.2f} seconds)',
221
+ error=error,
222
+ color='white',
223
+ styles=['bold'],
224
+ )
225
+
226
+ def on_sandbox_status_change(
227
+ self,
228
+ sandbox: interface.Sandbox,
229
+ old_status: interface.Sandbox.Status,
230
+ new_status: interface.Sandbox.Status,
231
+ span: float
232
+ ) -> None:
233
+ if self.sandbox_status:
234
+ self._print(
235
+ f'[{sandbox.id}] {old_status.value} '
236
+ f'({span:.2f} seconds) -> {new_status.value}',
237
+ error=None,
238
+ color='white',
239
+ )
240
+
241
+ def on_sandbox_shutdown(
242
+ self,
243
+ sandbox: interface.Sandbox,
244
+ duration: float,
245
+ lifetime: float,
246
+ error: BaseException | None
247
+ ) -> None:
248
+ if self.sandbox_status:
249
+ self._print(
250
+ f'[{sandbox.id}] sandbox shutdown '
251
+ f'(duration={duration:.2f} seconds), '
252
+ f'lifetime={lifetime:.2f} seconds)',
253
+ error=error,
254
+ color='white',
255
+ styles=['bold'],
256
+ )
257
+
258
+ def on_sandbox_session_start(
259
+ self,
260
+ sandbox: interface.Sandbox,
261
+ session_id: str,
262
+ duration: float,
263
+ error: BaseException | None
264
+ ) -> None:
265
+ """Called when a sandbox session starts."""
266
+ if self.session_status:
267
+ self._print(
268
+ f'[{sandbox.id}@{session_id}] sandbox session started '
269
+ f'(duration={duration:.2f} seconds)',
270
+ error=error,
271
+ color='blue',
272
+ )
273
+
274
+ def on_sandbox_session_end(
275
+ self,
276
+ sandbox: interface.Sandbox,
277
+ session_id: str,
278
+ duration: float,
279
+ lifetime: float,
280
+ error: BaseException | None
281
+ ) -> None:
282
+ """Called when a sandbox session ends."""
283
+ if self.session_status:
284
+ self._print(
285
+ f'[{sandbox.id}@{session_id}] sandbox session ended '
286
+ f'(duration={duration:.2f} seconds), '
287
+ f'lifetime={lifetime:.2f} seconds)',
288
+ error=error,
289
+ color='blue',
290
+ )
291
+
292
+ def on_sandbox_activity(
293
+ self,
294
+ name: str,
295
+ sandbox: interface.Sandbox,
296
+ session_id: str | None,
297
+ duration: float,
298
+ error: BaseException | None,
299
+ **kwargs
300
+ ) -> None:
301
+ """Called when a sandbox activity is performed."""
302
+ log_id = f'{sandbox.id}@{session_id or "<idle>"}'
303
+ color = 'yellow' if session_id is None else 'cyan'
304
+ self._print(
305
+ f'[{log_id}] sandbox call {name!r} '
306
+ f'(duration={duration:.2f} seconds, kwargs={kwargs}) ',
307
+ error,
308
+ color=color
309
+ )
310
+
311
+ def on_sandbox_housekeep(
312
+ self,
313
+ sandbox: interface.Sandbox,
314
+ counter: int,
315
+ duration: float,
316
+ error: BaseException | None,
317
+ **kwargs
318
+ ) -> None:
319
+ """Called when a sandbox feature is housekeeping."""
320
+ if self.sandbox_status and self.housekeep_status:
321
+ self._print(
322
+ f'[{sandbox.id}] sandbox housekeeping complete '
323
+ f'(counter={counter}, duration={duration:.2f} seconds, '
324
+ f'housekeep_info={kwargs})',
325
+ error=error,
326
+ color='white',
327
+ )
328
+
329
+ def on_feature_setup(
330
+ self,
331
+ feature: interface.Feature,
332
+ duration: float,
333
+ error: BaseException | None
334
+ ) -> None:
335
+ """Called when a sandbox feature is setup."""
336
+ if self.feature_status:
337
+ self._print(
338
+ f'[{feature.id}] feature setup complete '
339
+ f'(duration={duration:.2f} seconds)',
340
+ error=error,
341
+ color='white',
342
+ )
343
+
344
+ def on_feature_teardown(
345
+ self,
346
+ feature: interface.Feature,
347
+ duration: float,
348
+ error: BaseException | None
349
+ ) -> None:
350
+ """Called when a sandbox feature is teardown."""
351
+ if self.feature_status:
352
+ self._print(
353
+ f'[{feature.id}] feature teardown complete '
354
+ f'(duration={duration:.2f} seconds)',
355
+ error=error,
356
+ color='white',
357
+ )
358
+
359
+ def on_feature_setup_session(
360
+ self,
361
+ feature: interface.Feature,
362
+ session_id: str | None,
363
+ duration: float,
364
+ error: BaseException | None
365
+ ) -> None:
366
+ """Called when a sandbox feature is setup."""
367
+ if self.feature_status:
368
+ self._print(
369
+ f'[{feature.id}@{session_id or "<idle>"}] '
370
+ f'feature setup complete (duration={duration:.2f} seconds)',
371
+ error=error,
372
+ color='yellow',
373
+ )
374
+
375
+ def on_feature_teardown_session(
376
+ self,
377
+ feature: interface.Feature,
378
+ session_id: str,
379
+ duration: float,
380
+ error: BaseException | None
381
+ ) -> None:
382
+ """Called when a sandbox feature is teardown."""
383
+ if self.feature_status:
384
+ self._print(
385
+ f'[{feature.id}@{session_id}] '
386
+ f'feature teardown complete (duration={duration:.2f} seconds)',
387
+ error=error,
388
+ color='yellow',
389
+ )
390
+
391
+ def on_feature_activity(
392
+ self,
393
+ name: str,
394
+ feature: interface.Feature,
395
+ session_id: str | None,
396
+ duration: float,
397
+ error: BaseException | None,
398
+ **kwargs
399
+ ) -> None:
400
+ """Called when a feature activity is performed."""
401
+ log_id = f'{feature.id}@{session_id or "<idle>"}'
402
+ color = 'yellow' if session_id is None else 'cyan'
403
+ self._print(
404
+ f'[{log_id}] feature call {name!r} '
405
+ f'(duration={duration:.2f} seconds, kwargs={kwargs}) ',
406
+ error,
407
+ color=color
408
+ )
409
+
410
+ def on_feature_housekeep(
411
+ self,
412
+ feature: interface.Feature,
413
+ counter: int,
414
+ duration: float,
415
+ error: BaseException | None,
416
+ **kwargs
417
+ ) -> None:
418
+ """Called when a sandbox feature is housekeeping."""
419
+ if self.feature_status and self.housekeep_status:
420
+ self._print(
421
+ f'[{feature.id}] feature housekeeping complete '
422
+ f'(counter={counter}, (duration={duration:.2f} seconds, '
423
+ f'housekeep_info={kwargs})',
424
+ error=error,
425
+ color='white',
426
+ )
427
+
428
+ def _print(
429
+ self,
430
+ message: str,
431
+ error: BaseException | None,
432
+ color: str | None = None,
433
+ styles: list[str] | None = None,
434
+ ):
435
+ message = self._format_message(message, error)
436
+ if not self._keep(message, error):
437
+ return
438
+ self._write_log(message, error, color, styles)
439
+
440
+ def _write_log(
441
+ self,
442
+ message: str,
443
+ error: BaseException | None,
444
+ color: str | None = None,
445
+ styles: list[str] | None = None,
446
+ ):
447
+ message = self._maybe_colored(
448
+ message, color if error is None else 'red', styles=styles
449
+ )
450
+ if error is not None:
451
+ pg.logging.error(message)
452
+ else:
453
+ pg.logging.info(message)
454
+
455
+
456
+ class ConsoleEventLogger(EventLogger):
457
+ """Event handler for console debugger."""
458
+
459
+ colored = True
460
+
461
+ def _write_log(
462
+ self,
463
+ message: str,
464
+ error: BaseException | None,
465
+ color: str | None = None,
466
+ styles: list[str] | None = None
467
+ ):
468
+ print(
469
+ self._maybe_colored(
470
+ message, color if error is None else 'red', styles=styles
471
+ )
472
+ )