langfun 0.1.2.dev202410170804__py3-none-any.whl → 0.1.2.dev202410190803__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.
- langfun/core/component.py +48 -0
- langfun/core/component_test.py +48 -0
- langfun/core/concurrent.py +6 -0
- langfun/core/eval/base.py +11 -7
- langfun/core/eval/matching.py +9 -8
- langfun/core/eval/scoring.py +3 -1
- langfun/core/logging.py +39 -28
- langfun/core/logging_test.py +21 -5
- langfun/core/message.py +104 -64
- langfun/core/message_test.py +101 -11
- langfun/core/modalities/mime.py +32 -20
- langfun/core/modalities/mime_test.py +8 -4
- langfun/core/structured/mapping.py +46 -29
- langfun/core/structured/mapping_test.py +11 -5
- langfun/core/structured/schema.py +1 -1
- langfun/core/template.py +36 -49
- langfun/core/template_test.py +39 -1
- {langfun-0.1.2.dev202410170804.dist-info → langfun-0.1.2.dev202410190803.dist-info}/METADATA +1 -1
- {langfun-0.1.2.dev202410170804.dist-info → langfun-0.1.2.dev202410190803.dist-info}/RECORD +22 -22
- {langfun-0.1.2.dev202410170804.dist-info → langfun-0.1.2.dev202410190803.dist-info}/LICENSE +0 -0
- {langfun-0.1.2.dev202410170804.dist-info → langfun-0.1.2.dev202410190803.dist-info}/WHEEL +0 -0
- {langfun-0.1.2.dev202410170804.dist-info → langfun-0.1.2.dev202410190803.dist-info}/top_level.txt +0 -0
langfun/core/component.py
CHANGED
@@ -286,6 +286,54 @@ class ContextualAttribute(pg.symbolic.ValueFromParentChain):
|
|
286
286
|
else:
|
287
287
|
return pg.MISSING_VALUE
|
288
288
|
|
289
|
+
def _html_tree_view_content(
|
290
|
+
self,
|
291
|
+
*,
|
292
|
+
view: pg.views.HtmlTreeView,
|
293
|
+
parent: Any,
|
294
|
+
root_path: pg.KeyPath,
|
295
|
+
**kwargs,
|
296
|
+
) -> pg.Html:
|
297
|
+
inferred_value = pg.MISSING_VALUE
|
298
|
+
if isinstance(parent, pg.Symbolic) and root_path:
|
299
|
+
inferred_value = parent.sym_inferred(root_path.key, pg.MISSING_VALUE)
|
300
|
+
|
301
|
+
if inferred_value is not pg.MISSING_VALUE:
|
302
|
+
kwargs.pop('name', None)
|
303
|
+
return view.render(
|
304
|
+
inferred_value, parent=self, root_path=root_path + '<inferred>',
|
305
|
+
**view.get_passthrough_kwargs(**kwargs)
|
306
|
+
)
|
307
|
+
return pg.Html.element(
|
308
|
+
'div',
|
309
|
+
[
|
310
|
+
'(not available)',
|
311
|
+
],
|
312
|
+
css_classes=['unavailable-contextual'],
|
313
|
+
)
|
314
|
+
|
315
|
+
def _html_tree_view_config(self) -> dict[str, Any]:
|
316
|
+
return pg.views.HtmlTreeView.get_kwargs(
|
317
|
+
super()._html_tree_view_config(),
|
318
|
+
dict(
|
319
|
+
collapse_level=1,
|
320
|
+
)
|
321
|
+
)
|
322
|
+
|
323
|
+
@classmethod
|
324
|
+
def _html_tree_view_css_styles(cls) -> list[str]:
|
325
|
+
return super()._html_tree_view_css_styles() + [
|
326
|
+
"""
|
327
|
+
.contextual-attribute {
|
328
|
+
color: purple;
|
329
|
+
}
|
330
|
+
.unavailable-contextual {
|
331
|
+
color: gray;
|
332
|
+
font-style: italic;
|
333
|
+
}
|
334
|
+
"""
|
335
|
+
]
|
336
|
+
|
289
337
|
|
290
338
|
# NOTE(daiyip): Returning Any instead of `lf.ContextualAttribute` to avoid
|
291
339
|
# pytype check error as `contextual()` can be assigned to any type.
|
langfun/core/component_test.py
CHANGED
@@ -13,6 +13,8 @@
|
|
13
13
|
# limitations under the License.
|
14
14
|
"""Contextual component and app test."""
|
15
15
|
|
16
|
+
import inspect
|
17
|
+
from typing import Any
|
16
18
|
import unittest
|
17
19
|
import weakref
|
18
20
|
|
@@ -297,6 +299,52 @@ class ContextualAttributeTest(unittest.TestCase):
|
|
297
299
|
self.assertEqual(c.z, 3)
|
298
300
|
self.assertEqual(b.z, 3)
|
299
301
|
|
302
|
+
def test_to_html(self):
|
303
|
+
class A(lf.Component):
|
304
|
+
x: int = 1
|
305
|
+
y: int = lf.contextual()
|
306
|
+
|
307
|
+
def assert_content(html, expected):
|
308
|
+
expected = inspect.cleandoc(expected).strip()
|
309
|
+
actual = html.content.strip()
|
310
|
+
if actual != expected:
|
311
|
+
print(actual)
|
312
|
+
self.assertEqual(actual.strip(), expected)
|
313
|
+
|
314
|
+
self.assertIn(
|
315
|
+
inspect.cleandoc(
|
316
|
+
"""
|
317
|
+
.contextual-attribute {
|
318
|
+
color: purple;
|
319
|
+
}
|
320
|
+
.unavailable-contextual {
|
321
|
+
color: gray;
|
322
|
+
font-style: italic;
|
323
|
+
}
|
324
|
+
"""
|
325
|
+
),
|
326
|
+
A().to_html().style_section,
|
327
|
+
)
|
328
|
+
|
329
|
+
assert_content(
|
330
|
+
A().to_html(enable_summary_tooltip=False),
|
331
|
+
"""
|
332
|
+
<details open class="pyglove a"><summary><div class="summary-title">A(...)</div></summary><div class="complex-value a"><details open class="pyglove int"><summary><div class="summary-name">x<span class="tooltip">x</span></div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details><details open class="pyglove contextual-attribute"><summary><div class="summary-name">y<span class="tooltip">y</span></div><div class="summary-title">ContextualAttribute(...)</div></summary><div class="unavailable-contextual">(not available)</div></details></div></details>
|
333
|
+
"""
|
334
|
+
)
|
335
|
+
|
336
|
+
class B(lf.Component):
|
337
|
+
z: Any
|
338
|
+
y: int = 2
|
339
|
+
|
340
|
+
b = B(A())
|
341
|
+
assert_content(
|
342
|
+
b.z.to_html(enable_summary_tooltip=False),
|
343
|
+
"""
|
344
|
+
<details open class="pyglove a"><summary><div class="summary-title">A(...)</div></summary><div class="complex-value a"><details open class="pyglove int"><summary><div class="summary-name">x<span class="tooltip">x</span></div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details><details open class="pyglove contextual-attribute"><summary><div class="summary-name">y<span class="tooltip">y</span></div><div class="summary-title">ContextualAttribute(...)</div></summary><span class="simple-value int">2</span></details></div></details>
|
345
|
+
"""
|
346
|
+
)
|
347
|
+
|
300
348
|
|
301
349
|
if __name__ == '__main__':
|
302
350
|
unittest.main()
|
langfun/core/concurrent.py
CHANGED
@@ -921,5 +921,11 @@ def _progress_control(
|
|
921
921
|
raise ValueError(f'Unsupported progress bar type: {progress_bar}')
|
922
922
|
|
923
923
|
|
924
|
+
def get_executor(
|
925
|
+
resource_id: str,
|
926
|
+
max_workers: int | None = None) -> concurrent.futures.ThreadPoolExecutor:
|
927
|
+
"""Gets a thread pool executor associated with a resource id."""
|
928
|
+
return _executor_pool.get(resource_id, max_workers)
|
929
|
+
|
924
930
|
# The global executor pool based on resource IDs.
|
925
931
|
_executor_pool = ExecutorPool()
|
langfun/core/eval/base.py
CHANGED
@@ -1087,7 +1087,7 @@ class Evaluation(Evaluable):
|
|
1087
1087
|
)
|
1088
1088
|
error = e
|
1089
1089
|
|
1090
|
-
copy.audit(example, output_message, error, dryrun=True)
|
1090
|
+
copy.audit(1, example, output_message, error, dryrun=True)
|
1091
1091
|
result = copy.finalize()
|
1092
1092
|
|
1093
1093
|
if verbose:
|
@@ -1124,19 +1124,20 @@ class Evaluation(Evaluable):
|
|
1124
1124
|
with lf.use_settings(debug=debug, cache=self.cache):
|
1125
1125
|
self._reset()
|
1126
1126
|
|
1127
|
-
def _process(
|
1127
|
+
def _process(idx_and_example: Any):
|
1128
1128
|
# NOTE(daiyip): set the `input` symbol of the globals to None, so LLM
|
1129
1129
|
# generated code with calls to `input` will raise an error, thus not
|
1130
1130
|
# blocking the evaluation.
|
1131
|
+
_, example = idx_and_example
|
1131
1132
|
with lf_coding.context(input=None):
|
1132
1133
|
output_message = self.process(example, **(self.additional_args or {}))
|
1133
1134
|
self.process_output(example, output_message)
|
1134
1135
|
return output_message
|
1135
1136
|
|
1136
1137
|
try:
|
1137
|
-
for example, message, error in lf.concurrent_map(
|
1138
|
+
for (idx, example), message, error in lf.concurrent_map(
|
1138
1139
|
_process,
|
1139
|
-
examples,
|
1140
|
+
enumerate(examples),
|
1140
1141
|
max_workers=self.max_workers,
|
1141
1142
|
show_progress=progress_bar or False,
|
1142
1143
|
status_fn=self._status,
|
@@ -1148,7 +1149,7 @@ class Evaluation(Evaluable):
|
|
1148
1149
|
if isinstance(error, lf_structured.MappingError)
|
1149
1150
|
else None
|
1150
1151
|
)
|
1151
|
-
self.audit(example, message, error)
|
1152
|
+
self.audit(idx + 1, example, message, error)
|
1152
1153
|
finally:
|
1153
1154
|
# Save cache upon completion or interruption.
|
1154
1155
|
if self.dir and self.cache:
|
@@ -1437,6 +1438,7 @@ class Evaluation(Evaluable):
|
|
1437
1438
|
|
1438
1439
|
def audit(
|
1439
1440
|
self,
|
1441
|
+
example_idx: int,
|
1440
1442
|
example: Any,
|
1441
1443
|
message: lf.Message | None,
|
1442
1444
|
error: Exception | None = None,
|
@@ -1445,6 +1447,7 @@ class Evaluation(Evaluable):
|
|
1445
1447
|
"""Audits the example against the output. Subclasses should override.
|
1446
1448
|
|
1447
1449
|
Args:
|
1450
|
+
example_idx: 1-based index of the example in its dataset.
|
1448
1451
|
example: The input object.
|
1449
1452
|
message: The entire message returned by the LM, which could be used to
|
1450
1453
|
trace the LM input, response and parsed structure. If error is raised
|
@@ -1465,7 +1468,7 @@ class Evaluation(Evaluable):
|
|
1465
1468
|
else:
|
1466
1469
|
assert message is not None
|
1467
1470
|
output = message.text if self.schema is None else message.result
|
1468
|
-
self.audit_processed(example, output, message, dryrun=dryrun)
|
1471
|
+
self.audit_processed(example_idx, example, output, message, dryrun=dryrun)
|
1469
1472
|
|
1470
1473
|
# Audit usage.
|
1471
1474
|
if message is not None:
|
@@ -1482,7 +1485,8 @@ class Evaluation(Evaluable):
|
|
1482
1485
|
self._num_usages += 1
|
1483
1486
|
|
1484
1487
|
def audit_processed(
|
1485
|
-
self, example: Any, output: Any, message: lf.Message,
|
1488
|
+
self, example_idx: int, example: Any, output: Any, message: lf.Message,
|
1489
|
+
dryrun: bool = False
|
1486
1490
|
) -> None:
|
1487
1491
|
"""Audits a successfully processed example. Subclass should override."""
|
1488
1492
|
|
langfun/core/eval/matching.py
CHANGED
@@ -41,8 +41,8 @@ class Matching(base.Evaluation):
|
|
41
41
|
"""Returns the answer from the structure output."""
|
42
42
|
|
43
43
|
@property
|
44
|
-
def matches(self) -> list[tuple[Any, Any, lf.Message]]:
|
45
|
-
"""Returns the matches examples, outputs and the output messages."""
|
44
|
+
def matches(self) -> list[tuple[int, Any, Any, lf.Message]]:
|
45
|
+
"""Returns the matches IDs, examples, outputs and the output messages."""
|
46
46
|
return self._matches
|
47
47
|
|
48
48
|
@property
|
@@ -57,7 +57,7 @@ class Matching(base.Evaluation):
|
|
57
57
|
return self.num_matches / self.num_completed
|
58
58
|
|
59
59
|
@property
|
60
|
-
def mismatches(self) -> list[tuple[Any, Any, lf.Message]]:
|
60
|
+
def mismatches(self) -> list[tuple[int, Any, Any, lf.Message]]:
|
61
61
|
"""Returns the mismatches examples, outputs and output messages."""
|
62
62
|
return self._mismatches
|
63
63
|
|
@@ -87,7 +87,8 @@ class Matching(base.Evaluation):
|
|
87
87
|
self._mismatches = []
|
88
88
|
|
89
89
|
def audit_processed(
|
90
|
-
self, example: Any, output: Any, message: lf.Message,
|
90
|
+
self, example_idx: int, example: Any, output: Any, message: lf.Message,
|
91
|
+
dryrun: bool = False
|
91
92
|
) -> None:
|
92
93
|
groundtruth = self.groundtruth(example)
|
93
94
|
answer = self.answer(output, example)
|
@@ -107,9 +108,9 @@ class Matching(base.Evaluation):
|
|
107
108
|
)
|
108
109
|
|
109
110
|
if self.match(answer, groundtruth):
|
110
|
-
self._matches.append((example, output, message))
|
111
|
+
self._matches.append((example_idx, example, output, message))
|
111
112
|
else:
|
112
|
-
self._mismatches.append((example, output, message))
|
113
|
+
self._mismatches.append((example_idx, example, output, message))
|
113
114
|
|
114
115
|
def match(self, answer: Any, groundtruth: Any) -> bool:
|
115
116
|
"""Matches answer against the groundtruth. Subclasses can override."""
|
@@ -247,7 +248,7 @@ class Matching(base.Evaluation):
|
|
247
248
|
# Fall back to the default format.
|
248
249
|
return None
|
249
250
|
|
250
|
-
for i, (example, output, message) in enumerate(self.matches):
|
251
|
+
for i, (_, example, output, message) in enumerate(self.matches):
|
251
252
|
bgcolor = 'white' if i % 2 == 0 else '#DDDDDD'
|
252
253
|
s.write(f'<tr style="background-color: {bgcolor}"><td>{i + 1}</td>')
|
253
254
|
input_str = lf.repr_utils.escape_quoted(
|
@@ -282,7 +283,7 @@ class Matching(base.Evaluation):
|
|
282
283
|
'</tr>'
|
283
284
|
)
|
284
285
|
|
285
|
-
for i, (example, output, message) in enumerate(self.mismatches):
|
286
|
+
for i, (_, example, output, message) in enumerate(self.mismatches):
|
286
287
|
bgcolor = 'white' if i % 2 == 0 else '#DDDDDD'
|
287
288
|
s.write(f'<tr style="background-color: {bgcolor}"><td>{i + 1}</td>')
|
288
289
|
input_str = pg.format(example, verbose=False, max_bytes_len=32)
|
langfun/core/eval/scoring.py
CHANGED
@@ -62,8 +62,10 @@ class Scoring(base.Evaluation):
|
|
62
62
|
self._scored = []
|
63
63
|
|
64
64
|
def audit_processed(
|
65
|
-
self, example: Any, output: Any, message: lf.Message,
|
65
|
+
self, example_idx: int, example: Any, output: Any, message: lf.Message,
|
66
|
+
dryrun: bool = False
|
66
67
|
) -> None:
|
68
|
+
del example_idx
|
67
69
|
score = self.score(example, output)
|
68
70
|
|
69
71
|
if dryrun:
|
langfun/core/logging.py
CHANGED
@@ -15,8 +15,9 @@
|
|
15
15
|
|
16
16
|
import contextlib
|
17
17
|
import datetime
|
18
|
+
import functools
|
18
19
|
import typing
|
19
|
-
from typing import Any, Iterator, Literal
|
20
|
+
from typing import Any, Iterator, Literal
|
20
21
|
|
21
22
|
from langfun.core import component
|
22
23
|
from langfun.core import console
|
@@ -57,11 +58,11 @@ class LogEntry(pg.Object):
|
|
57
58
|
self,
|
58
59
|
view: pg.views.HtmlTreeView,
|
59
60
|
title: str | pg.Html | None = None,
|
60
|
-
|
61
|
+
max_summary_len_for_str: int = 80,
|
61
62
|
**kwargs
|
62
63
|
) -> str:
|
63
|
-
if len(self.message) >
|
64
|
-
message = self.message[:
|
64
|
+
if len(self.message) > max_summary_len_for_str:
|
65
|
+
message = self.message[:max_summary_len_for_str] + '...'
|
65
66
|
else:
|
66
67
|
message = self.message
|
67
68
|
|
@@ -69,18 +70,18 @@ class LogEntry(pg.Object):
|
|
69
70
|
pg.Html.element(
|
70
71
|
'span',
|
71
72
|
[self.time.strftime('%H:%M:%S')],
|
72
|
-
|
73
|
+
css_classes=['log-time']
|
73
74
|
),
|
74
75
|
pg.Html.element(
|
75
76
|
'span',
|
76
77
|
[pg.Html.escape(message)],
|
77
|
-
|
78
|
+
css_classes=['log-summary'],
|
78
79
|
),
|
79
80
|
)
|
80
81
|
return view.summary(
|
81
82
|
self,
|
82
83
|
title=title or s,
|
83
|
-
|
84
|
+
max_summary_len_for_str=max_summary_len_for_str,
|
84
85
|
**kwargs,
|
85
86
|
)
|
86
87
|
|
@@ -89,43 +90,45 @@ class LogEntry(pg.Object):
|
|
89
90
|
self,
|
90
91
|
view: pg.views.HtmlTreeView,
|
91
92
|
root_path: pg.KeyPath,
|
92
|
-
|
93
|
-
|
94
|
-
|
93
|
+
max_summary_len_for_str: int = 80,
|
94
|
+
collapse_level: int | None = 1,
|
95
|
+
extra_flags: dict[str, Any] | None = None,
|
95
96
|
**kwargs
|
96
97
|
) -> pg.Html:
|
97
98
|
# pytype: enable=annotation-type-mismatch
|
99
|
+
extra_flags = extra_flags if extra_flags is not None else {}
|
100
|
+
collapse_log_metadata_level: int | None = extra_flags.get(
|
101
|
+
'collapse_log_metadata_level', None
|
102
|
+
)
|
98
103
|
def render_message_text():
|
99
|
-
if len(self.message) <
|
104
|
+
if len(self.message) < max_summary_len_for_str:
|
100
105
|
return None
|
101
106
|
return pg.Html.element(
|
102
107
|
'span',
|
103
108
|
[pg.Html.escape(self.message)],
|
104
|
-
|
109
|
+
css_classes=['log-text'],
|
105
110
|
)
|
106
111
|
|
107
112
|
def render_metadata():
|
108
113
|
if not self.metadata:
|
109
114
|
return None
|
110
|
-
child_path = root_path + 'metadata'
|
111
115
|
return pg.Html.element(
|
112
116
|
'div',
|
113
117
|
[
|
114
118
|
view.render(
|
115
119
|
self.metadata,
|
116
120
|
name='metadata',
|
117
|
-
root_path=
|
121
|
+
root_path=root_path + 'metadata',
|
118
122
|
parent=self,
|
119
|
-
collapse_level=(
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
)
|
123
|
+
collapse_level=view.get_collapse_level(
|
124
|
+
(collapse_level, -1), collapse_log_metadata_level,
|
125
|
+
),
|
126
|
+
max_summary_len_for_str=max_summary_len_for_str,
|
127
|
+
extra_flags=extra_flags,
|
128
|
+
**view.get_passthrough_kwargs(**kwargs),
|
126
129
|
)
|
127
130
|
],
|
128
|
-
|
131
|
+
css_classes=['log-metadata'],
|
129
132
|
)
|
130
133
|
|
131
134
|
return pg.Html.element(
|
@@ -134,12 +137,23 @@ class LogEntry(pg.Object):
|
|
134
137
|
render_message_text(),
|
135
138
|
render_metadata(),
|
136
139
|
],
|
137
|
-
|
140
|
+
css_classes=['complex_value'],
|
141
|
+
)
|
142
|
+
|
143
|
+
def _html_tree_view_config(self) -> dict[str, Any]:
|
144
|
+
return pg.views.HtmlTreeView.get_kwargs(
|
145
|
+
super()._html_tree_view_config(),
|
146
|
+
dict(
|
147
|
+
css_classes=[f'log-{self.level}'],
|
148
|
+
)
|
138
149
|
)
|
139
150
|
|
140
|
-
|
141
|
-
|
151
|
+
@classmethod
|
152
|
+
@functools.cache
|
153
|
+
def _html_tree_view_css_styles(cls) -> list[str]:
|
154
|
+
return super()._html_tree_view_css_styles() + [
|
142
155
|
"""
|
156
|
+
/* Langfun LogEntry styles. */
|
143
157
|
.log-time {
|
144
158
|
color: #222;
|
145
159
|
font-size: 12px;
|
@@ -203,9 +217,6 @@ class LogEntry(pg.Object):
|
|
203
217
|
"""
|
204
218
|
]
|
205
219
|
|
206
|
-
def _html_element_class(self) -> Sequence[str] | None:
|
207
|
-
return super()._html_element_class() + [f'log-{self.level}']
|
208
|
-
|
209
220
|
|
210
221
|
def log(level: LogLevel,
|
211
222
|
message: str,
|
langfun/core/logging_test.py
CHANGED
@@ -69,20 +69,36 @@ class LoggingTest(unittest.TestCase):
|
|
69
69
|
time=time, metadata={}
|
70
70
|
).to_html(enable_summary_tooltip=False),
|
71
71
|
"""
|
72
|
-
<details open class="pyglove log-entry log-info"><summary><div class="
|
72
|
+
<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 > 3</span></div></summary><div class="complex_value"></div></details>
|
73
73
|
"""
|
74
74
|
)
|
75
75
|
self.assert_html_content(
|
76
76
|
logging.LogEntry(
|
77
77
|
level='error', message='This is a longer message: 5 + 2 > 3',
|
78
|
-
time=time, metadata=dict(x=1, y=2)
|
78
|
+
time=time, metadata=dict(x=dict(z=1), y=2)
|
79
79
|
).to_html(
|
80
|
-
|
80
|
+
extra_flags=dict(
|
81
|
+
collapse_log_metadata_level=1,
|
82
|
+
),
|
83
|
+
max_summary_len_for_str=10,
|
81
84
|
enable_summary_tooltip=False,
|
82
|
-
collapse_log_metadata_level=1
|
83
85
|
),
|
84
86
|
"""
|
85
|
-
<details open class="pyglove log-entry log-error"><summary><div class="
|
87
|
+
<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 > 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>
|
88
|
+
"""
|
89
|
+
)
|
90
|
+
self.assert_html_content(
|
91
|
+
logging.LogEntry(
|
92
|
+
level='error', message='This is a longer message: 5 + 2 > 3',
|
93
|
+
time=time, metadata=dict(x=dict(z=1), y=2)
|
94
|
+
).to_html(
|
95
|
+
extra_flags=dict(
|
96
|
+
max_summary_len_for_str=10,
|
97
|
+
),
|
98
|
+
enable_summary_tooltip=False,
|
99
|
+
),
|
100
|
+
"""
|
101
|
+
<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 > 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>
|
86
102
|
"""
|
87
103
|
)
|
88
104
|
|