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 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.
@@ -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()
@@ -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(example: Any):
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, dryrun: bool = False
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
 
@@ -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, dryrun: bool = False
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)
@@ -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, dryrun: bool = False
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, Sequence
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
- max_str_len_for_summary: int = pg.View.PresetArgValue(80), # pytype: disable=annotation-type-mismatch
61
+ max_summary_len_for_str: int = 80,
61
62
  **kwargs
62
63
  ) -> str:
63
- if len(self.message) > max_str_len_for_summary:
64
- message = self.message[:max_str_len_for_summary] + '...'
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
- css_class=['log-time']
73
+ css_classes=['log-time']
73
74
  ),
74
75
  pg.Html.element(
75
76
  'span',
76
77
  [pg.Html.escape(message)],
77
- css_class=['log-summary'],
78
+ css_classes=['log-summary'],
78
79
  ),
79
80
  )
80
81
  return view.summary(
81
82
  self,
82
83
  title=title or s,
83
- max_str_len_for_summary=max_str_len_for_summary,
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
- collapse_log_metadata_level: int | None = pg.View.PresetArgValue(0),
93
- max_str_len_for_summary: int = pg.View.PresetArgValue(80),
94
- collapse_level: int | None = pg.View.PresetArgValue(1),
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) < max_str_len_for_summary:
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
- css_class=['log-text'],
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=child_path,
121
+ root_path=root_path + 'metadata',
118
122
  parent=self,
119
- collapse_level=(
120
- view.max_collapse_level(
121
- collapse_level,
122
- collapse_log_metadata_level,
123
- child_path
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
- css_class=['log-metadata'],
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
- css_class=['complex_value'],
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
- def _html_style(self) -> list[str]:
141
- return super()._html_style() + [
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,
@@ -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="summary_title"><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>
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 &gt; 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
- max_str_len_for_summary=10,
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="summary_title"><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</div><div class="summary_title">Dict(...)</div></summary><div class="complex_value dict"><table><tr><td><span class="object_key str">x</span><span class="tooltip key-path">metadata.x</span></td><td><div><span class="simple_value int">1</span></div></td></tr><tr><td><span class="object_key str">y</span><span class="tooltip key-path">metadata.y</span></td><td><div><span class="simple_value int">2</span></div></td></tr></table></div></details></div></div></details>
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 &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>
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 &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>
86
102
  """
87
103
  )
88
104