langfun 0.0.2.dev20240330__py3-none-any.whl → 0.1.2.dev202501140804__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 (145) hide show
  1. langfun/__init__.py +22 -2
  2. langfun/core/__init__.py +17 -5
  3. langfun/core/agentic/__init__.py +30 -0
  4. langfun/core/agentic/action.py +854 -0
  5. langfun/core/agentic/action_eval.py +150 -0
  6. langfun/core/agentic/action_eval_test.py +109 -0
  7. langfun/core/agentic/action_test.py +136 -0
  8. langfun/core/coding/python/__init__.py +5 -11
  9. langfun/core/coding/python/correction.py +37 -28
  10. langfun/core/coding/python/correction_test.py +29 -3
  11. langfun/core/coding/python/execution.py +40 -216
  12. langfun/core/coding/python/execution_test.py +29 -89
  13. langfun/core/coding/python/generation.py +21 -11
  14. langfun/core/coding/python/generation_test.py +2 -2
  15. langfun/core/coding/python/parsing.py +108 -193
  16. langfun/core/coding/python/parsing_test.py +2 -105
  17. langfun/core/component.py +69 -2
  18. langfun/core/component_test.py +54 -0
  19. langfun/core/concurrent.py +414 -117
  20. langfun/core/concurrent_test.py +111 -24
  21. langfun/core/console.py +18 -5
  22. langfun/core/console_test.py +17 -0
  23. langfun/core/eval/__init__.py +17 -0
  24. langfun/core/eval/base.py +767 -140
  25. langfun/core/eval/base_test.py +238 -53
  26. langfun/core/eval/matching.py +80 -76
  27. langfun/core/eval/matching_test.py +19 -9
  28. langfun/core/eval/patching.py +130 -0
  29. langfun/core/eval/patching_test.py +170 -0
  30. langfun/core/eval/scoring.py +37 -28
  31. langfun/core/eval/scoring_test.py +21 -3
  32. langfun/core/eval/v2/__init__.py +42 -0
  33. langfun/core/eval/v2/checkpointing.py +380 -0
  34. langfun/core/eval/v2/checkpointing_test.py +228 -0
  35. langfun/core/eval/v2/eval_test_helper.py +136 -0
  36. langfun/core/eval/v2/evaluation.py +725 -0
  37. langfun/core/eval/v2/evaluation_test.py +180 -0
  38. langfun/core/eval/v2/example.py +305 -0
  39. langfun/core/eval/v2/example_test.py +128 -0
  40. langfun/core/eval/v2/experiment.py +1048 -0
  41. langfun/core/eval/v2/experiment_test.py +433 -0
  42. langfun/core/eval/v2/metric_values.py +156 -0
  43. langfun/core/eval/v2/metric_values_test.py +80 -0
  44. langfun/core/eval/v2/metrics.py +357 -0
  45. langfun/core/eval/v2/metrics_test.py +203 -0
  46. langfun/core/eval/v2/progress.py +348 -0
  47. langfun/core/eval/v2/progress_test.py +82 -0
  48. langfun/core/eval/v2/progress_tracking.py +210 -0
  49. langfun/core/eval/v2/progress_tracking_test.py +66 -0
  50. langfun/core/eval/v2/reporting.py +270 -0
  51. langfun/core/eval/v2/reporting_test.py +158 -0
  52. langfun/core/eval/v2/runners.py +488 -0
  53. langfun/core/eval/v2/runners_test.py +334 -0
  54. langfun/core/langfunc.py +3 -21
  55. langfun/core/langfunc_test.py +26 -8
  56. langfun/core/language_model.py +686 -48
  57. langfun/core/language_model_test.py +681 -44
  58. langfun/core/llms/__init__.py +100 -12
  59. langfun/core/llms/anthropic.py +488 -0
  60. langfun/core/llms/anthropic_test.py +235 -0
  61. langfun/core/llms/cache/base.py +21 -2
  62. langfun/core/llms/cache/in_memory.py +13 -0
  63. langfun/core/llms/cache/in_memory_test.py +88 -28
  64. langfun/core/llms/compositional.py +101 -0
  65. langfun/core/llms/compositional_test.py +73 -0
  66. langfun/core/llms/deepseek.py +117 -0
  67. langfun/core/llms/deepseek_test.py +61 -0
  68. langfun/core/llms/fake.py +39 -26
  69. langfun/core/llms/fake_test.py +136 -11
  70. langfun/core/llms/gemini.py +507 -0
  71. langfun/core/llms/gemini_test.py +195 -0
  72. langfun/core/llms/google_genai.py +62 -218
  73. langfun/core/llms/google_genai_test.py +9 -197
  74. langfun/core/llms/groq.py +276 -0
  75. langfun/core/llms/groq_test.py +64 -0
  76. langfun/core/llms/llama_cpp.py +15 -40
  77. langfun/core/llms/llama_cpp_test.py +4 -30
  78. langfun/core/llms/openai.py +436 -226
  79. langfun/core/llms/openai_compatible.py +179 -0
  80. langfun/core/llms/openai_compatible_test.py +495 -0
  81. langfun/core/llms/openai_test.py +35 -174
  82. langfun/core/llms/rest.py +113 -0
  83. langfun/core/llms/rest_test.py +111 -0
  84. langfun/core/llms/vertexai.py +192 -0
  85. langfun/core/llms/vertexai_test.py +52 -0
  86. langfun/core/logging.py +284 -0
  87. langfun/core/logging_test.py +125 -0
  88. langfun/core/message.py +319 -9
  89. langfun/core/message_test.py +190 -13
  90. langfun/core/modalities/__init__.py +6 -2
  91. langfun/core/modalities/audio.py +30 -0
  92. langfun/core/modalities/audio_test.py +63 -0
  93. langfun/core/modalities/image.py +39 -20
  94. langfun/core/modalities/image_test.py +52 -9
  95. langfun/core/modalities/mime.py +206 -29
  96. langfun/core/modalities/mime_test.py +90 -9
  97. langfun/core/modalities/ms_office.py +117 -0
  98. langfun/core/modalities/ms_office_test.py +389 -0
  99. langfun/core/modalities/pdf.py +22 -0
  100. langfun/core/modalities/pdf_test.py +57 -0
  101. langfun/core/modalities/video.py +9 -23
  102. langfun/core/modalities/video_test.py +3 -3
  103. langfun/core/modality.py +26 -3
  104. langfun/core/modality_test.py +2 -2
  105. langfun/core/sampling.py +11 -11
  106. langfun/core/structured/__init__.py +15 -16
  107. langfun/core/structured/completion.py +32 -5
  108. langfun/core/structured/completion_test.py +9 -8
  109. langfun/core/structured/description.py +2 -2
  110. langfun/core/structured/description_test.py +3 -3
  111. langfun/core/structured/function_generation.py +278 -0
  112. langfun/core/structured/function_generation_test.py +399 -0
  113. langfun/core/structured/mapping.py +150 -46
  114. langfun/core/structured/mapping_test.py +105 -0
  115. langfun/core/structured/parsing.py +33 -21
  116. langfun/core/structured/parsing_test.py +71 -22
  117. langfun/core/structured/querying.py +746 -0
  118. langfun/core/structured/{prompting_test.py → querying_test.py} +545 -60
  119. langfun/core/structured/schema.py +208 -99
  120. langfun/core/structured/schema_generation.py +1 -1
  121. langfun/core/structured/schema_generation_test.py +2 -2
  122. langfun/core/structured/schema_test.py +133 -34
  123. langfun/core/structured/scoring.py +125 -19
  124. langfun/core/structured/scoring_test.py +30 -0
  125. langfun/core/structured/tokenization.py +64 -0
  126. langfun/core/structured/tokenization_test.py +48 -0
  127. langfun/core/template.py +240 -11
  128. langfun/core/template_test.py +146 -1
  129. langfun/core/templates/conversation.py +9 -0
  130. langfun/core/templates/conversation_test.py +4 -3
  131. langfun/core/templates/selfplay_test.py +14 -2
  132. langfun-0.1.2.dev202501140804.dist-info/METADATA +225 -0
  133. langfun-0.1.2.dev202501140804.dist-info/RECORD +153 -0
  134. {langfun-0.0.2.dev20240330.dist-info → langfun-0.1.2.dev202501140804.dist-info}/WHEEL +1 -1
  135. langfun/core/coding/python/errors.py +0 -108
  136. langfun/core/coding/python/errors_test.py +0 -99
  137. langfun/core/coding/python/permissions.py +0 -90
  138. langfun/core/coding/python/permissions_test.py +0 -86
  139. langfun/core/structured/prompting.py +0 -217
  140. langfun/core/text_formatting.py +0 -162
  141. langfun/core/text_formatting_test.py +0 -47
  142. langfun-0.0.2.dev20240330.dist-info/METADATA +0 -99
  143. langfun-0.0.2.dev20240330.dist-info/RECORD +0 -102
  144. {langfun-0.0.2.dev20240330.dist-info → langfun-0.1.2.dev202501140804.dist-info}/LICENSE +0 -0
  145. {langfun-0.0.2.dev20240330.dist-info → langfun-0.1.2.dev202501140804.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,52 @@
1
+ # Copyright 2024 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
+ """Tests for VertexAI models."""
15
+
16
+ import os
17
+ import unittest
18
+ from unittest import mock
19
+
20
+ from langfun.core.llms import vertexai
21
+
22
+
23
+ class VertexAITest(unittest.TestCase):
24
+ """Tests for Vertex model with REST API."""
25
+
26
+ @mock.patch.object(vertexai.VertexAI, 'credentials', new=True)
27
+ def test_project_and_location_check(self):
28
+ with self.assertRaisesRegex(ValueError, 'Please specify `project`'):
29
+ _ = vertexai.VertexAIGeminiPro1()._api_initialized
30
+
31
+ with self.assertRaisesRegex(ValueError, 'Please specify `location`'):
32
+ _ = vertexai.VertexAIGeminiPro1(project='abc')._api_initialized
33
+
34
+ self.assertTrue(
35
+ vertexai.VertexAIGeminiPro1(
36
+ project='abc', location='us-central1'
37
+ )._api_initialized
38
+ )
39
+
40
+ os.environ['VERTEXAI_PROJECT'] = 'abc'
41
+ os.environ['VERTEXAI_LOCATION'] = 'us-central1'
42
+ model = vertexai.VertexAIGeminiPro1()
43
+ self.assertTrue(model.model_id.startswith('VertexAI('))
44
+ self.assertIn('us-central1', model.api_endpoint)
45
+ self.assertTrue(model._api_initialized)
46
+ self.assertIsNotNone(model._session)
47
+ del os.environ['VERTEXAI_PROJECT']
48
+ del os.environ['VERTEXAI_LOCATION']
49
+
50
+
51
+ if __name__ == '__main__':
52
+ unittest.main()
@@ -0,0 +1,284 @@
1
+ # Copyright 2024 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
+ """Langfun event logging."""
15
+
16
+ import contextlib
17
+ import datetime
18
+ import functools
19
+ import typing
20
+ from typing import Any, Iterator, Literal
21
+
22
+ from langfun.core import component
23
+ from langfun.core import console
24
+ import pyglove as pg
25
+
26
+
27
+ LogLevel = Literal['debug', 'info', 'error', 'warning', 'fatal']
28
+ _LOG_LEVELS = list(typing.get_args(LogLevel))
29
+
30
+
31
+ @contextlib.contextmanager
32
+ def use_log_level(log_level: LogLevel = 'info') -> Iterator[None]:
33
+ """Contextmanager to enable logging at a given level."""
34
+ with component.context(__event_log_level__=log_level):
35
+ try:
36
+ yield
37
+ finally:
38
+ pass
39
+
40
+
41
+ def get_log_level() -> LogLevel:
42
+ """Gets the current minimum log level."""
43
+ return component.context_value('__event_log_level__', 'info')
44
+
45
+
46
+ class LogEntry(pg.Object, pg.views.HtmlTreeView.Extension):
47
+ """Event log entry."""
48
+ time: datetime.datetime
49
+ level: LogLevel
50
+ message: str
51
+ metadata: dict[str, Any] = pg.Dict()
52
+ indent: int = 0
53
+
54
+ def should_output(self, min_log_level: LogLevel) -> bool:
55
+ return _LOG_LEVELS.index(self.level) >= _LOG_LEVELS.index(min_log_level)
56
+
57
+ def format(self,
58
+ compact: bool = False,
59
+ verbose: bool = True,
60
+ root_indent: int = 0,
61
+ *,
62
+ text_format: bool = True,
63
+ **kwargs):
64
+ if text_format:
65
+ s = f"""{self.time.strftime('%H:%M:%S')} {self.level.upper()} - {self.message}"""
66
+ if self.metadata:
67
+ s += f' (metadata: {self.metadata!r})'
68
+ return s
69
+ return super().format(
70
+ compact=compact,
71
+ verbose=verbose,
72
+ root_indent=root_indent,
73
+ **kwargs
74
+ )
75
+
76
+ def _html_tree_view_summary(
77
+ self,
78
+ view: pg.views.HtmlTreeView,
79
+ title: str | pg.Html | None = None,
80
+ max_summary_len_for_str: int = 80,
81
+ **kwargs
82
+ ) -> pg.Html | None:
83
+ if len(self.message) > max_summary_len_for_str:
84
+ message = self.message[:max_summary_len_for_str] + '...'
85
+ else:
86
+ message = self.message
87
+
88
+ s = pg.Html(
89
+ pg.Html.element(
90
+ 'span',
91
+ [self.time.strftime('%H:%M:%S')],
92
+ css_classes=['log-time']
93
+ ),
94
+ pg.Html.element(
95
+ 'span',
96
+ [pg.Html.escape(message)],
97
+ css_classes=['log-summary'],
98
+ ),
99
+ )
100
+ return view.summary(
101
+ self,
102
+ title=title or s,
103
+ max_summary_len_for_str=max_summary_len_for_str,
104
+ **kwargs,
105
+ )
106
+
107
+ # pytype: disable=annotation-type-mismatch
108
+ def _html_tree_view_content(
109
+ self,
110
+ view: pg.views.HtmlTreeView,
111
+ root_path: pg.KeyPath | None = None,
112
+ max_summary_len_for_str: int = 80,
113
+ collapse_level: int | None = 1,
114
+ extra_flags: dict[str, Any] | None = None,
115
+ **kwargs
116
+ ) -> pg.Html:
117
+ # pytype: enable=annotation-type-mismatch
118
+ extra_flags = extra_flags if extra_flags is not None else {}
119
+ collapse_log_metadata_level: int | None = extra_flags.get(
120
+ 'collapse_log_metadata_level', None
121
+ )
122
+ def render_message_text():
123
+ if len(self.message) < max_summary_len_for_str:
124
+ return None
125
+ return pg.Html.element(
126
+ 'span',
127
+ [pg.Html.escape(self.message)],
128
+ css_classes=['log-text'],
129
+ )
130
+
131
+ def render_metadata():
132
+ if not self.metadata:
133
+ return None
134
+ return pg.Html.element(
135
+ 'div',
136
+ [
137
+ view.render(
138
+ self.metadata,
139
+ name='metadata',
140
+ root_path=pg.KeyPath('metadata', root_path),
141
+ parent=self,
142
+ collapse_level=view.get_collapse_level(
143
+ (collapse_level, -1), collapse_log_metadata_level,
144
+ ),
145
+ max_summary_len_for_str=max_summary_len_for_str,
146
+ extra_flags=extra_flags,
147
+ **view.get_passthrough_kwargs(**kwargs),
148
+ )
149
+ ],
150
+ css_classes=['log-metadata'],
151
+ )
152
+
153
+ return pg.Html.element(
154
+ 'div',
155
+ [
156
+ render_message_text(),
157
+ render_metadata(),
158
+ ],
159
+ css_classes=['complex_value'],
160
+ )
161
+
162
+ def _html_tree_view_config(self) -> dict[str, Any]:
163
+ return pg.views.HtmlTreeView.get_kwargs(
164
+ super()._html_tree_view_config(),
165
+ dict(
166
+ css_classes=[f'log-{self.level}'],
167
+ )
168
+ )
169
+
170
+ @classmethod
171
+ @functools.cache
172
+ def _html_tree_view_css_styles(cls) -> list[str]:
173
+ return super()._html_tree_view_css_styles() + [
174
+ """
175
+ /* Langfun LogEntry styles. */
176
+ .log-time {
177
+ color: #222;
178
+ font-size: 12px;
179
+ padding-right: 10px;
180
+ }
181
+ .log-summary {
182
+ font-weight: normal;
183
+ font-style: italic;
184
+ padding: 4px;
185
+ }
186
+ .log-debug > summary > .summary_title::before {
187
+ content: '🛠️ '
188
+ }
189
+ .log-info > summary > .summary_title::before {
190
+ content: '💡 '
191
+ }
192
+ .log-warning > summary > .summary_title::before {
193
+ content: '❗ '
194
+ }
195
+ .log-error > summary > .summary_title::before {
196
+ content: '❌ '
197
+ }
198
+ .log-fatal > summary > .summary_title::before {
199
+ content: '💀 '
200
+ }
201
+ .log-text {
202
+ display: block;
203
+ color: black;
204
+ font-style: italic;
205
+ padding: 20px;
206
+ border-radius: 5px;
207
+ background: rgba(255, 255, 255, 0.5);
208
+ white-space: pre-wrap;
209
+ }
210
+ details.log-entry {
211
+ margin: 0px 0px 10px;
212
+ border: 0px;
213
+ }
214
+ div.log-metadata {
215
+ margin: 10px 0px 0px 0px;
216
+ }
217
+ .log-metadata > details {
218
+ background-color: rgba(255, 255, 255, 0.5);
219
+ border: 1px solid transparent;
220
+ }
221
+ .log-debug {
222
+ background-color: #EEEEEE
223
+ }
224
+ .log-warning {
225
+ background-color: #F8C471
226
+ }
227
+ .log-info {
228
+ background-color: #A3E4D7
229
+ }
230
+ .log-error {
231
+ background-color: #F5C6CB
232
+ }
233
+ .log-fatal {
234
+ background-color: #F19CBB
235
+ }
236
+ """
237
+ ]
238
+
239
+
240
+ def log(level: LogLevel,
241
+ message: str,
242
+ *,
243
+ indent: int = 0,
244
+ **kwargs) -> LogEntry:
245
+ """Logs a message."""
246
+ entry = LogEntry(
247
+ indent=indent,
248
+ level=level,
249
+ time=datetime.datetime.now(),
250
+ message=message,
251
+ metadata=kwargs,
252
+ )
253
+ if entry.should_output(get_log_level()):
254
+ if console.under_notebook():
255
+ console.display(entry)
256
+ else:
257
+ # TODO(daiyip): Improve the console output formatting.
258
+ console.write(entry)
259
+ return entry
260
+
261
+
262
+ def debug(message: str, *, indent: int = 0, **kwargs) -> LogEntry:
263
+ """Logs a debug message to the session."""
264
+ return log('debug', message, indent=indent, **kwargs)
265
+
266
+
267
+ def info(message: str, *, indent: int = 0, **kwargs) -> LogEntry:
268
+ """Logs an info message to the session."""
269
+ return log('info', message, indent=indent, **kwargs)
270
+
271
+
272
+ def warning(message: str, *, indent: int = 0, **kwargs) -> LogEntry:
273
+ """Logs an info message to the session."""
274
+ return log('warning', message, indent=indent, **kwargs)
275
+
276
+
277
+ def error(message: str, *, indent: int = 0, **kwargs) -> LogEntry:
278
+ """Logs an error message to the session."""
279
+ return log('error', message, indent=indent, **kwargs)
280
+
281
+
282
+ def fatal(message: str, *, indent: int = 0, **kwargs) -> LogEntry:
283
+ """Logs a fatal message to the session."""
284
+ return log('fatal', message, indent=indent, **kwargs)
@@ -0,0 +1,125 @@
1
+ # Copyright 2024 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
+ """Tests for langfun.core.logging."""
15
+
16
+ import datetime
17
+ import inspect
18
+ import unittest
19
+
20
+ from langfun.core import logging
21
+
22
+
23
+ class LoggingTest(unittest.TestCase):
24
+
25
+ def test_use_log_level(self):
26
+ self.assertEqual(logging.get_log_level(), 'info')
27
+ with logging.use_log_level('debug'):
28
+ self.assertEqual(logging.get_log_level(), 'debug')
29
+ with logging.use_log_level(None):
30
+ self.assertIsNone(logging.get_log_level(), None)
31
+ self.assertEqual(logging.get_log_level(), 'debug')
32
+ self.assertEqual(logging.get_log_level(), 'info')
33
+
34
+ def test_log(self):
35
+ entry = logging.log('info', 'hi', indent=1, x=1, y=2)
36
+ self.assertEqual(entry.level, 'info')
37
+ self.assertEqual(entry.message, 'hi')
38
+ self.assertEqual(entry.indent, 1)
39
+ self.assertEqual(entry.metadata, {'x': 1, 'y': 2})
40
+
41
+ self.assertEqual(logging.debug('hi').level, 'debug')
42
+ self.assertEqual(logging.info('hi').level, 'info')
43
+ self.assertEqual(logging.warning('hi').level, 'warning')
44
+ self.assertEqual(logging.error('hi').level, 'error')
45
+ self.assertEqual(logging.fatal('hi').level, 'fatal')
46
+
47
+ def test_repr_html(self):
48
+ def assert_color(entry, color):
49
+ self.assertIn(f'background-color: {color}', entry._repr_html_())
50
+
51
+ assert_color(logging.debug('hi', indent=0), '#EEEEEE')
52
+ assert_color(logging.info('hi', indent=1), '#A3E4D7')
53
+ assert_color(logging.warning('hi', x=1, y=2), '#F8C471')
54
+ assert_color(logging.error('hi', indent=2, x=1, y=2), '#F5C6CB')
55
+ assert_color(logging.fatal('hi', indent=2, x=1, y=2), '#F19CBB')
56
+
57
+ def assert_html_content(self, html, expected):
58
+ expected = inspect.cleandoc(expected).strip()
59
+ actual = html.content.strip()
60
+ if actual != expected:
61
+ print(actual)
62
+ self.assertEqual(actual, expected)
63
+
64
+ def test_format(self):
65
+ time = datetime.datetime(2024, 10, 10, 12, 30, 45)
66
+ self.assertEqual(
67
+ str(
68
+ logging.LogEntry(
69
+ level='info', message='hello\nworld',
70
+ time=time, metadata=dict(x=1),
71
+ )
72
+ ),
73
+ '12:30:45 INFO - hello\nworld (metadata: {x=1})',
74
+ )
75
+ self.assertIn(
76
+ 'LogEntry(',
77
+ logging.LogEntry(
78
+ level='info', message='hello\nworld',
79
+ time=time, metadata=dict(x=1),
80
+ ).format(text_format=False),
81
+ )
82
+
83
+ def test_html(self):
84
+ time = datetime.datetime(2024, 10, 10, 12, 30, 45)
85
+ self.assert_html_content(
86
+ logging.LogEntry(
87
+ level='info', message='5 + 2 > 3',
88
+ time=time, metadata={}
89
+ ).to_html(enable_summary_tooltip=False),
90
+ """
91
+ <details open class="pyglove log-entry log-info"><summary><div class="summary-title log-info"><span class="log-time">12:30:45</span><span class="log-summary">5 + 2 &gt; 3</span></div></summary><div class="complex_value"></div></details>
92
+ """
93
+ )
94
+ self.assert_html_content(
95
+ logging.LogEntry(
96
+ level='error', message='This is a longer message: 5 + 2 > 3',
97
+ time=time, metadata=dict(x=dict(z=1), y=2)
98
+ ).to_html(
99
+ extra_flags=dict(
100
+ collapse_log_metadata_level=1,
101
+ ),
102
+ max_summary_len_for_str=10,
103
+ enable_summary_tooltip=False,
104
+ ),
105
+ """
106
+ <details open class="pyglove log-entry log-error"><summary><div class="summary-title log-error"><span class="log-time">12:30:45</span><span class="log-summary">This is a ...</span></div></summary><div class="complex_value"><span class="log-text">This is a longer message: 5 + 2 &gt; 3</span><div class="log-metadata"><details open class="pyglove dict"><summary><div class="summary-name">metadata<span class="tooltip">metadata</span></div><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><details class="pyglove dict"><summary><div class="summary-name">x<span class="tooltip">metadata.x</span></div><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><details open class="pyglove int"><summary><div class="summary-name">z<span class="tooltip">metadata.x.z</span></div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details></div></details><details open class="pyglove int"><summary><div class="summary-name">y<span class="tooltip">metadata.y</span></div><div class="summary-title">int</div></summary><span class="simple-value int">2</span></details></div></details></div></div></details>
107
+ """
108
+ )
109
+ self.assert_html_content(
110
+ logging.LogEntry(
111
+ level='error', message='This is a longer message: 5 + 2 > 3',
112
+ time=time, metadata=dict(x=dict(z=1), y=2)
113
+ ).to_html(
114
+ extra_flags=dict(
115
+ max_summary_len_for_str=10,
116
+ ),
117
+ enable_summary_tooltip=False,
118
+ ),
119
+ """
120
+ <details open class="pyglove log-entry log-error"><summary><div class="summary-title log-error"><span class="log-time">12:30:45</span><span class="log-summary">This is a longer message: 5 + 2 &gt; 3</span></div></summary><div class="complex_value"><div class="log-metadata"><details open class="pyglove dict"><summary><div class="summary-name">metadata<span class="tooltip">metadata</span></div><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><details open class="pyglove dict"><summary><div class="summary-name">x<span class="tooltip">metadata.x</span></div><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><details open class="pyglove int"><summary><div class="summary-name">z<span class="tooltip">metadata.x.z</span></div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details></div></details><details open class="pyglove int"><summary><div class="summary-name">y<span class="tooltip">metadata.y</span></div><div class="summary-title">int</div></summary><span class="simple-value int">2</span></details></div></details></div></div></details>
121
+ """
122
+ )
123
+
124
+ if __name__ == '__main__':
125
+ unittest.main()