langfun 0.1.2.dev202510240805__py3-none-any.whl → 0.1.2.dev202510250803__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.

Files changed (41) hide show
  1. langfun/core/concurrent_test.py +1 -0
  2. langfun/core/data/conversion/anthropic_test.py +8 -6
  3. langfun/core/data/conversion/gemini_test.py +12 -9
  4. langfun/core/data/conversion/openai.py +134 -30
  5. langfun/core/data/conversion/openai_test.py +161 -17
  6. langfun/core/eval/v2/progress_tracking_test.py +3 -0
  7. langfun/core/langfunc_test.py +4 -2
  8. langfun/core/language_model.py +6 -6
  9. langfun/core/language_model_test.py +9 -3
  10. langfun/core/llms/__init__.py +2 -1
  11. langfun/core/llms/cache/base.py +3 -1
  12. langfun/core/llms/cache/in_memory_test.py +14 -4
  13. langfun/core/llms/deepseek.py +1 -1
  14. langfun/core/llms/groq.py +1 -1
  15. langfun/core/llms/llama_cpp.py +1 -1
  16. langfun/core/llms/openai.py +7 -2
  17. langfun/core/llms/openai_compatible.py +134 -27
  18. langfun/core/llms/openai_compatible_test.py +207 -20
  19. langfun/core/llms/openai_test.py +0 -2
  20. langfun/core/llms/vertexai.py +2 -2
  21. langfun/core/message.py +78 -44
  22. langfun/core/message_test.py +56 -81
  23. langfun/core/modalities/__init__.py +8 -0
  24. langfun/core/modalities/mime.py +9 -0
  25. langfun/core/modality.py +104 -27
  26. langfun/core/modality_test.py +42 -12
  27. langfun/core/sampling_test.py +20 -4
  28. langfun/core/structured/completion.py +2 -7
  29. langfun/core/structured/completion_test.py +23 -43
  30. langfun/core/structured/mapping.py +4 -13
  31. langfun/core/structured/querying.py +13 -11
  32. langfun/core/structured/querying_test.py +65 -29
  33. langfun/core/template.py +39 -13
  34. langfun/core/template_test.py +83 -17
  35. langfun/env/event_handlers/metric_writer_test.py +3 -3
  36. langfun/env/load_balancers_test.py +2 -2
  37. {langfun-0.1.2.dev202510240805.dist-info → langfun-0.1.2.dev202510250803.dist-info}/METADATA +1 -1
  38. {langfun-0.1.2.dev202510240805.dist-info → langfun-0.1.2.dev202510250803.dist-info}/RECORD +41 -41
  39. {langfun-0.1.2.dev202510240805.dist-info → langfun-0.1.2.dev202510250803.dist-info}/WHEEL +0 -0
  40. {langfun-0.1.2.dev202510240805.dist-info → langfun-0.1.2.dev202510250803.dist-info}/licenses/LICENSE +0 -0
  41. {langfun-0.1.2.dev202510240805.dist-info → langfun-0.1.2.dev202510250803.dist-info}/top_level.txt +0 -0
langfun/core/message.py CHANGED
@@ -20,7 +20,7 @@ import contextlib
20
20
  import functools
21
21
  import inspect
22
22
  import io
23
- from typing import Annotated, Any, ClassVar, Optional, Type, Union
23
+ from typing import Annotated, Any, Callable, ClassVar, Optional, Type, Union
24
24
 
25
25
  from langfun.core import modality
26
26
  from langfun.core import natural_language
@@ -86,6 +86,11 @@ class Message(
86
86
 
87
87
  sender: Annotated[str, 'The sender of the message.']
88
88
 
89
+ referred_modalities: Annotated[
90
+ dict[str, pg.Ref[modality.Modality]],
91
+ 'The modality objects referred in the message.'
92
+ ] = pg.Dict()
93
+
89
94
  metadata: Annotated[
90
95
  dict[str, Any],
91
96
  (
@@ -111,6 +116,11 @@ class Message(
111
116
  *,
112
117
  # Default sender is specified in subclasses.
113
118
  sender: str | pg.object_utils.MissingValue = pg.MISSING_VALUE,
119
+ referred_modalities: (
120
+ list[modality.Modality]
121
+ | dict[str, modality.Modality]
122
+ | None
123
+ ) = None,
114
124
  metadata: dict[str, Any] | None = None,
115
125
  tags: list[str] | None = None,
116
126
  source: Optional['Message'] = None,
@@ -125,6 +135,7 @@ class Message(
125
135
  Args:
126
136
  text: The text in the message.
127
137
  sender: The sender name of the message.
138
+ referred_modalities: The modality objects referred in the message.
128
139
  metadata: Structured meta-data associated with this message.
129
140
  tags: Tags for the message.
130
141
  source: The source message of the current message.
@@ -138,9 +149,13 @@ class Message(
138
149
  """
139
150
  metadata = metadata or {}
140
151
  metadata.update(kwargs)
152
+ if isinstance(referred_modalities, list):
153
+ referred_modalities = {m.id: pg.Ref(m) for m in referred_modalities}
154
+
141
155
  super().__init__(
142
156
  text=text,
143
157
  metadata=metadata,
158
+ referred_modalities=referred_modalities or {},
144
159
  tags=tags or [],
145
160
  sender=sender,
146
161
  allow_partial=allow_partial,
@@ -186,7 +201,7 @@ class Message(
186
201
  A message created from the value.
187
202
  """
188
203
  if isinstance(value, modality.Modality):
189
- return cls('<<[[object]]>>', object=value)
204
+ return cls(f'<<[[{value.id}]]>>', referred_modalities=[value])
190
205
  if isinstance(value, Message):
191
206
  return value
192
207
  if isinstance(value, str):
@@ -280,8 +295,7 @@ class Message(
280
295
  if key_path == Message.PATH_TEXT:
281
296
  return self.text
282
297
  else:
283
- v = self.metadata.sym_get(key_path, default, use_inferred=True)
284
- return v.value if isinstance(v, pg.Ref) else v
298
+ return self.metadata.sym_get(key_path, default, use_inferred=True)
285
299
 
286
300
  #
287
301
  # API for accessing the structured result and error.
@@ -361,43 +375,53 @@ class Message(
361
375
  # API for supporting modalities.
362
376
  #
363
377
 
378
+ def modalities(
379
+ self,
380
+ filter: ( # pylint: disable=redefined-builtin
381
+ Type[modality.Modality]
382
+ | Callable[[modality.Modality], bool]
383
+ | None
384
+ ) = None # pylint: disable=bad-whitespace
385
+ ) -> list[modality.Modality]:
386
+ """Returns the modality objects referred in the message."""
387
+ if inspect.isclass(filter) and issubclass(filter, modality.Modality):
388
+ filter_fn = lambda v: isinstance(v, filter) # pytype: disable=wrong-arg-types
389
+ elif filter is None:
390
+ filter_fn = lambda v: True
391
+ else:
392
+ filter_fn = filter
393
+ return [v for v in self.referred_modalities.values() if filter_fn(v)]
394
+
364
395
  @property
365
- def text_with_modality_hash(self) -> str:
366
- """Returns text with modality object placeheld by their 8-byte MD5 hash."""
367
- parts = [self.text]
368
- for name, modality_obj in self.referred_modalities().items():
369
- parts.append(
370
- f'<{name}>{modality_obj.hash}</{name}>'
371
- )
372
- return ''.join(parts)
396
+ def images(self) -> list[modality.Modality]:
397
+ """Returns the image objects referred in the message."""
398
+ assert False, 'Overridden in core/modalities/__init__.py'
399
+
400
+ @property
401
+ def videos(self) -> list[modality.Modality]:
402
+ """Returns the video objects referred in the message."""
403
+ assert False, 'Overridden in core/modalities/__init__.py'
404
+
405
+ @property
406
+ def audios(self) -> list[modality.Modality]:
407
+ """Returns the audio objects referred in the message."""
408
+ assert False, 'Overridden in core/modalities/__init__.py'
373
409
 
374
410
  def get_modality(
375
- self, var_name: str, default: Any = None, from_message_chain: bool = True
411
+ self,
412
+ var_name: str,
413
+ default: Any = None
376
414
  ) -> modality.Modality | None:
377
415
  """Gets the modality object referred in the message.
378
416
 
379
417
  Args:
380
418
  var_name: The referred variable name for the modality object.
381
419
  default: default value.
382
- from_message_chain: If True, the look up will be performed from the
383
- message chain. Otherwise it will be performed in current message.
384
420
 
385
421
  Returns:
386
422
  A modality object if found, otherwise None.
387
423
  """
388
- obj = self.get(var_name, None)
389
- if isinstance(obj, modality.Modality):
390
- return obj
391
- elif obj is None and self.source is not None:
392
- return self.source.get_modality(var_name, default, from_message_chain)
393
- return default
394
-
395
- def referred_modalities(self) -> dict[str, modality.Modality]:
396
- """Returns modality objects attached on this message."""
397
- chunks = self.chunk()
398
- return {
399
- m.referred_name: m for m in chunks if isinstance(m, modality.Modality)
400
- }
424
+ return self.referred_modalities.get(var_name, default)
401
425
 
402
426
  def chunk(self, text: str | None = None) -> list[str | modality.Modality]:
403
427
  """Chunk a message into a list of str or modality objects."""
@@ -425,10 +449,15 @@ class Message(
425
449
 
426
450
  var_name = text[var_start:ref_end].strip()
427
451
  var_value = self.get_modality(var_name)
428
- if var_value is not None:
429
- add_text_chunk(text[chunk_start:ref_start].strip(' '))
430
- chunks.append(var_value)
431
- chunk_start = ref_end + len(modality.Modality.REF_END)
452
+ if var_value is None:
453
+ raise ValueError(
454
+ f'Unknown modality reference: {var_name!r}. '
455
+ 'Please make sure the modality object is present in '
456
+ f'`referred_modalities` when creating {self.__class__.__name__}.'
457
+ )
458
+ add_text_chunk(text[chunk_start:ref_start].strip(' '))
459
+ chunks.append(var_value)
460
+ chunk_start = ref_end + len(modality.Modality.REF_END)
432
461
  return chunks
433
462
 
434
463
  @classmethod
@@ -437,8 +466,8 @@ class Message(
437
466
  ) -> 'Message':
438
467
  """Assembly a message from a list of string or modality objects."""
439
468
  fused_text = io.StringIO()
440
- ref_index = 0
441
469
  metadata = dict()
470
+ referred_modalities = dict()
442
471
  last_char = None
443
472
  for i, chunk in enumerate(chunks):
444
473
  if i > 0 and last_char not in ('\t', ' ', '\n', None):
@@ -451,14 +480,16 @@ class Message(
451
480
  last_char = None
452
481
  else:
453
482
  assert isinstance(chunk, modality.Modality), chunk
454
- var_name = f'obj{ref_index}'
455
- fused_text.write(modality.Modality.text_marker(var_name))
483
+ fused_text.write(modality.Modality.text_marker(chunk.id))
456
484
  last_char = modality.Modality.REF_END[-1]
457
485
  # Make a reference if the chunk is already owned by another object
458
486
  # to avoid copy.
459
- metadata[var_name] = pg.maybe_ref(chunk)
460
- ref_index += 1
461
- return cls(fused_text.getvalue().strip(), metadata=metadata)
487
+ referred_modalities[chunk.id] = pg.Ref(chunk)
488
+ return cls(
489
+ fused_text.getvalue().strip(),
490
+ referred_modalities=referred_modalities,
491
+ metadata=metadata,
492
+ )
462
493
 
463
494
  #
464
495
  # Tagging
@@ -551,6 +582,11 @@ class Message(
551
582
  #
552
583
 
553
584
  def natural_language_format(self) -> str:
585
+ """Returns the natural language format representation."""
586
+ # Propagate the modality references to parent context if any.
587
+ if capture_context := modality.get_modality_capture_context():
588
+ for v in self.referred_modalities.values():
589
+ capture_context.capture(v)
554
590
  return self.text
555
591
 
556
592
  def __eq__(self, other: Any) -> bool:
@@ -568,8 +604,7 @@ class Message(
568
604
  def __getattr__(self, key: str) -> Any:
569
605
  if key not in self.metadata:
570
606
  raise AttributeError(key)
571
- v = self.metadata[key]
572
- return v.value if isinstance(v, pg.Ref) else v
607
+ return self.metadata[key]
573
608
 
574
609
  def _html_tree_view_content(
575
610
  self,
@@ -646,15 +681,14 @@ class Message(
646
681
  s.write(s.escape(chunk))
647
682
  else:
648
683
  assert isinstance(chunk, modality.Modality), chunk
649
- child_path = pg.KeyPath(['metadata', chunk.referred_name], root_path)
650
684
  s.write(
651
685
  pg.Html.element(
652
686
  'div',
653
687
  [
654
688
  view.render(
655
689
  chunk,
656
- name=chunk.referred_name,
657
- root_path=child_path,
690
+ name=chunk.id,
691
+ root_path=chunk.sym_path,
658
692
  collapse_level=(
659
693
  0 if collapse_modalities_in_text else 1
660
694
  ),
@@ -667,7 +701,7 @@ class Message(
667
701
  css_classes=['modality-in-text'],
668
702
  )
669
703
  )
670
- referred_chunks[chunk.referred_name] = chunk
704
+ referred_chunks[chunk.id] = chunk
671
705
  s.write('</div>')
672
706
  return s
673
707
 
@@ -60,10 +60,19 @@ class MessageTest(unittest.TestCase):
60
60
  self.assertTrue(
61
61
  pg.eq(message.UserMessage.from_value('hi'), message.UserMessage('hi'))
62
62
  )
63
+ foo = CustomModality('foo')
63
64
  self.assertTrue(
64
65
  pg.eq(
65
- message.UserMessage.from_value(CustomModality('foo')),
66
- message.UserMessage('<<[[object]]>>', object=CustomModality('foo')),
66
+ message.UserMessage.from_value(foo),
67
+ message.UserMessage(f'<<[[{foo.id}]]>>', referred_modalities=[foo]),
68
+ )
69
+ )
70
+ self.assertTrue(
71
+ pg.eq(
72
+ message.UserMessage.from_value(foo),
73
+ message.UserMessage(
74
+ f'<<[[{foo.id}]]>>', referred_modalities={foo.id: foo}
75
+ ),
67
76
  )
68
77
  )
69
78
  m = message.UserMessage('hi')
@@ -224,76 +233,39 @@ class MessageTest(unittest.TestCase):
224
233
  self.assertEqual(str(m), m.text)
225
234
 
226
235
  def test_get_modality(self):
236
+ foo = CustomModality('foo')
237
+ bar = CustomModality('bar')
227
238
  m1 = message.UserMessage(
228
- 'hi, this is a {{img1}} and {{x.img2}}',
229
- img1=CustomModality('foo'),
230
- x=dict(img2=pg.Ref(CustomModality('bar'))),
239
+ 'hi',
240
+ referred_modalities={
241
+ foo.id: foo,
242
+ bar.id: pg.Ref(bar),
243
+ },
231
244
  )
232
- self.assertIs(m1.get_modality('img1'), m1.img1)
233
- self.assertIs(m1.get_modality('x.img2'), m1.x.img2)
245
+ self.assertIs(m1.get_modality(foo.id), foo)
246
+ self.assertIs(m1.get_modality(bar.id), bar)
234
247
  self.assertIsNone(m1.get_modality('video'))
248
+ self.assertEqual(len(m1.modalities()), 2)
249
+ self.assertEqual(len(m1.modalities(CustomModality)), 2)
235
250
 
236
- m2 = message.SystemMessage('class Question:\n image={{img1}}', source=m1)
237
- self.assertIs(m2.get_modality('img1'), m1.img1)
238
- # We could get the modality object even it's not directly used by current
239
- # message.
240
- self.assertIs(m2.get_modality('x.img2'), m1.x.img2)
241
- self.assertIsNone(m2.get_modality('video'))
242
-
243
- m3 = message.AIMessage(
244
- 'This is the {{output_image}} based on {{x.img2}}',
245
- output_image=CustomModality('bar'),
246
- source=m2,
247
- )
248
- self.assertIs(m3.get_modality('x.img2'), m1.x.img2)
249
- self.assertIs(m3.get_modality('output_image'), m3.output_image)
250
- self.assertIsNone(m3.get_modality('video'))
251
-
252
- def test_referred_modalities(self):
253
- m1 = message.UserMessage(
254
- 'hi, this is a <<[[img1]]>> and <<[[x.img2]]>>',
255
- img1=CustomModality('foo'),
256
- x=dict(img2=CustomModality('bar')),
257
- )
258
- m2 = message.SystemMessage('class Question:\n image={{img1}}', source=m1)
259
- m3 = message.AIMessage(
260
- (
261
- 'This is the <<[[output_image]]>> based on <<[[x.img2]]>>, '
262
- '{{unknown_var}}'
263
- ),
264
- output_image=CustomModality('bar'),
265
- source=m2,
266
- )
267
- self.assertEqual(
268
- m3.referred_modalities(),
269
- {
270
- 'output_image': m3.output_image,
271
- 'x.img2': m1.x.img2,
272
- },
273
- )
251
+ class MyModality(modality.Modality):
252
+ pass
274
253
 
275
- def test_text_with_modality_hash(self):
276
- m = message.UserMessage(
277
- 'hi, this is a <<[[img1]]>> and <<[[x.img2]]>>',
278
- img1=CustomModality('foo'),
279
- x=dict(img2=CustomModality('bar')),
280
- )
281
- self.assertEqual(
282
- m.text_with_modality_hash,
283
- (
284
- 'hi, this is a <<[[img1]]>> and <<[[x.img2]]>>'
285
- '<img1>acbd18db</img1><x.img2>37b51d19</x.img2>'
286
- )
287
- )
254
+ self.assertEqual(len(m1.modalities(MyModality)), 0)
255
+ self.assertEqual(len(m1.modalities(lambda x: x.content == 'foo')), 1)
288
256
 
289
257
  def test_chunking(self):
258
+ foo = CustomModality('foo')
259
+ bar = CustomModality('bar')
290
260
  m = message.UserMessage(
291
- inspect.cleandoc("""
292
- Hi, this is <<[[a]]>> and this is {{b}}.
293
- <<[[x.c]]>> {{something else
261
+ inspect.cleandoc(f"""
262
+ Hi, this is <<[[{foo.id}]]>> and this is {{b}}.
263
+ <<[[{bar.id}]]>> something else
294
264
  """),
295
- a=CustomModality('foo'),
296
- x=dict(c=CustomModality('bar')),
265
+ referred_modalities={
266
+ foo.id: pg.Ref(foo),
267
+ bar.id: pg.Ref(bar),
268
+ },
297
269
  )
298
270
  chunks = m.chunk()
299
271
  self.assertTrue(
@@ -301,10 +273,10 @@ class MessageTest(unittest.TestCase):
301
273
  chunks,
302
274
  [
303
275
  'Hi, this is',
304
- CustomModality('foo'),
305
- 'and this is {{b}}.\n',
306
- CustomModality('bar'),
307
- '{{something else',
276
+ foo,
277
+ 'and this is {b}.\n',
278
+ bar,
279
+ 'something else',
308
280
  ],
309
281
  )
310
282
  )
@@ -312,15 +284,17 @@ class MessageTest(unittest.TestCase):
312
284
  pg.eq(
313
285
  message.AIMessage.from_chunks(chunks),
314
286
  message.AIMessage(
315
- inspect.cleandoc("""
316
- Hi, this is <<[[obj0]]>> and this is {{b}}.
317
- <<[[obj1]]>> {{something else
318
- """),
319
- obj0=pg.Ref(m.a),
320
- obj1=pg.Ref(m.x.c),
287
+ (
288
+ f'Hi, this is <<[[{foo.id}]]>> and this '
289
+ f'is {{b}}.\n<<[[{bar.id}]]>> '
290
+ 'something else'
291
+ ),
292
+ referred_modalities=[foo, bar],
321
293
  ),
322
294
  )
323
295
  )
296
+ with self.assertRaisesRegex(ValueError, 'Unknown modality reference'):
297
+ message.UserMessage('<<[[abc]]>>').chunk()
324
298
 
325
299
  def assert_html_content(self, html, expected):
326
300
  expected = inspect.cleandoc(expected).strip()
@@ -404,25 +378,26 @@ class MessageTest(unittest.TestCase):
404
378
  <details open class="pyglove user-message lf-message"><summary><div class="summary-title lf-message">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"></div><div class="message-text">what is a &lt;div&gt;</div><div class="message-metadata"><details open class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></div></div></details>
405
379
  """
406
380
  )
381
+ image = CustomModality('bird')
407
382
  self.assert_html_content(
408
383
  message.UserMessage(
409
- 'what is this <<[[image]]>>',
384
+ f'what is this <<[[{image.id}]]>>',
410
385
  tags=['lm-input'],
411
- image=CustomModality('bird')
386
+ referred_modalities=[image],
412
387
  ).to_html(
413
388
  enable_summary_tooltip=False,
414
389
  extra_flags=dict(include_message_metadata=False)
415
390
  ),
416
391
  """
417
- <details open class="pyglove user-message lf-message"><summary><div class="summary-title lf-message">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>lm-input</span></div><div class="message-text">what is this<div class="modality-in-text"><details class="pyglove custom-modality"><summary><div class="summary-name">image<span class="tooltip">metadata.image</span></div><div class="summary-title">CustomModality(...)</div></summary><div class="complex-value custom-modality"><details open class="pyglove str"><summary><div class="summary-name">content<span class="tooltip">metadata.image.content</span></div><div class="summary-title">str</div></summary><span class="simple-value str">&#x27;bird&#x27;</span></details></div></details></div></div></div></details>
392
+ <details open class="pyglove user-message lf-message"><summary><div class="summary-title lf-message">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>lm-input</span></div><div class="message-text">what is this<div class="modality-in-text"><details class="pyglove custom-modality"><summary><div class="summary-name">custom_modality:abaecf8c<span class="tooltip"></span></div><div class="summary-title">CustomModality(...)</div></summary><div class="complex-value custom-modality"><details open class="pyglove str"><summary><div class="summary-name">content<span class="tooltip">content</span></div><div class="summary-title">str</div></summary><span class="simple-value str">&#x27;bird&#x27;</span></details></div></details></div></div></div></details>
418
393
  """
419
394
  )
420
395
 
421
396
  def test_html_ai_message(self):
422
397
  image = CustomModality('foo')
423
398
  user_message = message.UserMessage(
424
- 'What is in this image? <<[[image]]>> this is a test',
425
- metadata=dict(image=image),
399
+ f'What is in this image? <<[[{image.id}]]>> this is a test',
400
+ referred_modalities=[image],
426
401
  source=message.UserMessage('User input'),
427
402
  tags=['lm-input']
428
403
  )
@@ -438,7 +413,7 @@ class MessageTest(unittest.TestCase):
438
413
  self.assert_html_content(
439
414
  ai_message.to_html(enable_summary_tooltip=False),
440
415
  """
441
- <details open class="pyglove ai-message lf-message"><summary><div class="summary-title lf-message">AIMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>lm-response</span><span>lm-output</span></div><div class="message-text">My name is Gemini</div><div class="message-result"><details open class="pyglove dict"><summary><div class="summary-name">result<span class="tooltip">metadata.result</span></div><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><details open class="pyglove int"><summary><div class="summary-name">x<span class="tooltip">metadata.result.x</span></div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details><details open class="pyglove int"><summary><div class="summary-name">y<span class="tooltip">metadata.result.y</span></div><div class="summary-title">int</div></summary><span class="simple-value int">2</span></details><details class="pyglove dict"><summary><div class="summary-name">z<span class="tooltip">metadata.result.z</span></div><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><details class="pyglove list"><summary><div class="summary-name">a<span class="tooltip">metadata.result.z.a</span></div><div class="summary-title">List(...)</div></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">metadata.result.z.a[0]</span></td><td><span class="simple-value int">12</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">metadata.result.z.a[1]</span></td><td><span class="simple-value int">323</span></td></tr></table></div></details></div></details></div></details></div><div class="message-usage"><details open class="pyglove lm-sampling-usage"><summary><div class="summary-name">llm usage<span class="tooltip">metadata.usage</span></div><div class="summary-title">LMSamplingUsage(...)</div></summary><div class="complex-value lm-sampling-usage"><table><tr><td><span class="object-key str">prompt_tokens</span><span class="tooltip">metadata.usage.prompt_tokens</span></td><td><span class="simple-value int">10</span></td></tr><tr><td><span class="object-key str">completion_tokens</span><span class="tooltip">metadata.usage.completion_tokens</span></td><td><span class="simple-value int">2</span></td></tr><tr><td><span class="object-key str">total_tokens</span><span class="tooltip">metadata.usage.total_tokens</span></td><td><span class="simple-value int">12</span></td></tr><tr><td><span class="object-key str">num_requests</span><span class="tooltip">metadata.usage.num_requests</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key str">estimated_cost</span><span class="tooltip">metadata.usage.estimated_cost</span></td><td><span class="simple-value none-type">None</span></td></tr><tr><td><span class="object-key str">retry_stats</span><span class="tooltip">metadata.usage.retry_stats</span></td><td><details class="pyglove retry-stats"><summary><div class="summary-title">RetryStats(...)</div></summary><div class="complex-value retry-stats"><table><tr><td><span class="object-key str">num_occurences</span><span class="tooltip">metadata.usage.retry_stats.num_occurences</span></td><td><span class="simple-value int">0</span></td></tr><tr><td><span class="object-key str">total_wait_interval</span><span class="tooltip">metadata.usage.retry_stats.total_wait_interval</span></td><td><span class="simple-value float">0.0</span></td></tr><tr><td><span class="object-key str">total_call_interval</span><span class="tooltip">metadata.usage.retry_stats.total_call_interval</span></td><td><span class="simple-value float">0.0</span></td></tr><tr><td><span class="object-key str">errors</span><span class="tooltip">metadata.usage.retry_stats.errors</span></td><td><details class="pyglove dict"><summary><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></td></tr></table></div></details></td></tr><tr><td><span class="object-key str">completion_tokens_details</span><span class="tooltip">metadata.usage.completion_tokens_details</span></td><td><span class="simple-value none-type">None</span></td></tr></table></div></details></div><div class="message-metadata"><details open class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></div><details open class="pyglove user-message lf-message"><summary><div class="summary-name lf-message">source<span class="tooltip lf-message">source</span></div><div class="summary-title lf-message">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>lm-input</span></div><div class="message-text">What is in this image?<div class="modality-in-text"><details class="pyglove custom-modality"><summary><div class="summary-name">image<span class="tooltip">source.metadata.image</span></div><div class="summary-title">CustomModality(...)</div></summary><div class="complex-value custom-modality"><details open class="pyglove str"><summary><div class="summary-name">content<span class="tooltip">source.metadata.image.content</span></div><div class="summary-title">str</div></summary><span class="simple-value str">&#x27;foo&#x27;</span></details></div></details></div>this is a test</div><div class="message-metadata"><details open class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">source.metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><details class="pyglove custom-modality"><summary><div class="summary-name">image<span class="tooltip">source.metadata.image</span></div><div class="summary-title">CustomModality(...)</div></summary><div class="complex-value custom-modality"><details open class="pyglove str"><summary><div class="summary-name">content<span class="tooltip">source.metadata.image.content</span></div><div class="summary-title">str</div></summary><span class="simple-value str">&#x27;foo&#x27;</span></details></div></details></div></details></div></div></details></div></details>
416
+ <details open class="pyglove ai-message lf-message"><summary><div class="summary-title lf-message">AIMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>lm-response</span><span>lm-output</span></div><div class="message-text">My name is Gemini</div><div class="message-result"><details open class="pyglove dict"><summary><div class="summary-name">result<span class="tooltip">metadata.result</span></div><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><details open class="pyglove int"><summary><div class="summary-name">x<span class="tooltip">metadata.result.x</span></div><div class="summary-title">int</div></summary><span class="simple-value int">1</span></details><details open class="pyglove int"><summary><div class="summary-name">y<span class="tooltip">metadata.result.y</span></div><div class="summary-title">int</div></summary><span class="simple-value int">2</span></details><details class="pyglove dict"><summary><div class="summary-name">z<span class="tooltip">metadata.result.z</span></div><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><details class="pyglove list"><summary><div class="summary-name">a<span class="tooltip">metadata.result.z.a</span></div><div class="summary-title">List(...)</div></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">metadata.result.z.a[0]</span></td><td><span class="simple-value int">12</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">metadata.result.z.a[1]</span></td><td><span class="simple-value int">323</span></td></tr></table></div></details></div></details></div></details></div><div class="message-usage"><details open class="pyglove lm-sampling-usage"><summary><div class="summary-name">llm usage<span class="tooltip">metadata.usage</span></div><div class="summary-title">LMSamplingUsage(...)</div></summary><div class="complex-value lm-sampling-usage"><table><tr><td><span class="object-key str">prompt_tokens</span><span class="tooltip">metadata.usage.prompt_tokens</span></td><td><span class="simple-value int">10</span></td></tr><tr><td><span class="object-key str">completion_tokens</span><span class="tooltip">metadata.usage.completion_tokens</span></td><td><span class="simple-value int">2</span></td></tr><tr><td><span class="object-key str">total_tokens</span><span class="tooltip">metadata.usage.total_tokens</span></td><td><span class="simple-value int">12</span></td></tr><tr><td><span class="object-key str">num_requests</span><span class="tooltip">metadata.usage.num_requests</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key str">estimated_cost</span><span class="tooltip">metadata.usage.estimated_cost</span></td><td><span class="simple-value none-type">None</span></td></tr><tr><td><span class="object-key str">retry_stats</span><span class="tooltip">metadata.usage.retry_stats</span></td><td><details class="pyglove retry-stats"><summary><div class="summary-title">RetryStats(...)</div></summary><div class="complex-value retry-stats"><table><tr><td><span class="object-key str">num_occurences</span><span class="tooltip">metadata.usage.retry_stats.num_occurences</span></td><td><span class="simple-value int">0</span></td></tr><tr><td><span class="object-key str">total_wait_interval</span><span class="tooltip">metadata.usage.retry_stats.total_wait_interval</span></td><td><span class="simple-value float">0.0</span></td></tr><tr><td><span class="object-key str">total_call_interval</span><span class="tooltip">metadata.usage.retry_stats.total_call_interval</span></td><td><span class="simple-value float">0.0</span></td></tr><tr><td><span class="object-key str">errors</span><span class="tooltip">metadata.usage.retry_stats.errors</span></td><td><details class="pyglove dict"><summary><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></td></tr></table></div></details></td></tr><tr><td><span class="object-key str">completion_tokens_details</span><span class="tooltip">metadata.usage.completion_tokens_details</span></td><td><span class="simple-value none-type">None</span></td></tr></table></div></details></div><div class="message-metadata"><details open class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></div><details open class="pyglove user-message lf-message"><summary><div class="summary-name lf-message">source<span class="tooltip lf-message">source</span></div><div class="summary-title lf-message">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>lm-input</span></div><div class="message-text">What is in this image?<div class="modality-in-text"><details class="pyglove custom-modality"><summary><div class="summary-name">custom_modality:acbd18db<span class="tooltip"></span></div><div class="summary-title">CustomModality(...)</div></summary><div class="complex-value custom-modality"><details open class="pyglove str"><summary><div class="summary-name">content<span class="tooltip">content</span></div><div class="summary-title">str</div></summary><span class="simple-value str">&#x27;foo&#x27;</span></details></div></details></div>this is a test</div><div class="message-metadata"><details open class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">source.metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></div></div></details></div></details>
442
417
  """
443
418
  )
444
419
  self.assert_html_content(
@@ -455,7 +430,7 @@ class MessageTest(unittest.TestCase):
455
430
  ),
456
431
  ),
457
432
  """
458
- <details open class="pyglove ai-message lf-message"><summary><div class="summary-title lf-message">AIMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>lm-response</span><span>lm-output</span></div><div class="message-text">My name is Gemini</div><div class="message-result"><details class="pyglove dict"><summary><div class="summary-name">result<span class="tooltip">metadata.result</span></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">metadata.result.x</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key str">y</span><span class="tooltip">metadata.result.y</span></td><td><span class="simple-value int">2</span></td></tr><tr><td><span class="object-key str">z</span><span class="tooltip">metadata.result.z</span></td><td><details class="pyglove dict"><summary><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str">a</span><span class="tooltip">metadata.result.z.a</span></td><td><details class="pyglove list"><summary><div class="summary-title">List(...)</div></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">metadata.result.z.a[0]</span></td><td><span class="simple-value int">12</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">metadata.result.z.a[1]</span></td><td><span class="simple-value int">323</span></td></tr></table></div></details></td></tr></table></div></details></td></tr></table></div></details></div><div class="message-usage"><details class="pyglove lm-sampling-usage"><summary><div class="summary-name">llm usage<span class="tooltip">metadata.usage</span></div><div class="summary-title">LMSamplingUsage(...)</div></summary><div class="complex-value lm-sampling-usage"><table><tr><td><span class="object-key str">prompt_tokens</span><span class="tooltip">metadata.usage.prompt_tokens</span></td><td><span class="simple-value int">10</span></td></tr><tr><td><span class="object-key str">completion_tokens</span><span class="tooltip">metadata.usage.completion_tokens</span></td><td><span class="simple-value int">2</span></td></tr><tr><td><span class="object-key str">total_tokens</span><span class="tooltip">metadata.usage.total_tokens</span></td><td><span class="simple-value int">12</span></td></tr><tr><td><span class="object-key str">num_requests</span><span class="tooltip">metadata.usage.num_requests</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key str">estimated_cost</span><span class="tooltip">metadata.usage.estimated_cost</span></td><td><span class="simple-value none-type">None</span></td></tr><tr><td><span class="object-key str">retry_stats</span><span class="tooltip">metadata.usage.retry_stats</span></td><td><details class="pyglove retry-stats"><summary><div class="summary-title">RetryStats(...)</div></summary><div class="complex-value retry-stats"><table><tr><td><span class="object-key str">num_occurences</span><span class="tooltip">metadata.usage.retry_stats.num_occurences</span></td><td><span class="simple-value int">0</span></td></tr><tr><td><span class="object-key str">total_wait_interval</span><span class="tooltip">metadata.usage.retry_stats.total_wait_interval</span></td><td><span class="simple-value float">0.0</span></td></tr><tr><td><span class="object-key str">total_call_interval</span><span class="tooltip">metadata.usage.retry_stats.total_call_interval</span></td><td><span class="simple-value float">0.0</span></td></tr><tr><td><span class="object-key str">errors</span><span class="tooltip">metadata.usage.retry_stats.errors</span></td><td><details class="pyglove dict"><summary><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></td></tr></table></div></details></td></tr><tr><td><span class="object-key str">completion_tokens_details</span><span class="tooltip">metadata.usage.completion_tokens_details</span></td><td><span class="simple-value none-type">None</span></td></tr></table></div></details></div><div class="message-metadata"><details class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></div><details class="pyglove user-message lf-message"><summary><div class="summary-name lf-message">source<span class="tooltip lf-message">source</span></div><div class="summary-title lf-message">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>lm-input</span></div><div class="message-text">What is in this image?<div class="modality-in-text"><details open class="pyglove custom-modality"><summary><div class="summary-name">image<span class="tooltip">source.metadata.image</span></div><div class="summary-title">CustomModality(...)</div></summary><div class="complex-value custom-modality"><table><tr><td><span class="object-key str">content</span><span class="tooltip">source.metadata.image.content</span></td><td><span class="simple-value str">&#x27;foo&#x27;</span></td></tr></table></div></details></div>this is a test</div><div class="message-metadata"><details class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">source.metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str">image</span><span class="tooltip">source.metadata.image</span></td><td><details class="pyglove custom-modality"><summary><div class="summary-title">CustomModality(...)</div></summary><div class="complex-value custom-modality"><table><tr><td><span class="object-key str">content</span><span class="tooltip">source.metadata.image.content</span></td><td><span class="simple-value str">&#x27;foo&#x27;</span></td></tr></table></div></details></td></tr></table></div></details></div><details class="pyglove user-message lf-message"><summary><div class="summary-name lf-message">source<span class="tooltip lf-message">source.source</span></div><div class="summary-title lf-message">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"></div><div class="message-text">User input</div><div class="message-metadata"><details class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">source.source.metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></div></div></details></div></details></div></details>
433
+ <details open class="pyglove ai-message lf-message"><summary><div class="summary-title lf-message">AIMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>lm-response</span><span>lm-output</span></div><div class="message-text">My name is Gemini</div><div class="message-result"><details class="pyglove dict"><summary><div class="summary-name">result<span class="tooltip">metadata.result</span></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">metadata.result.x</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key str">y</span><span class="tooltip">metadata.result.y</span></td><td><span class="simple-value int">2</span></td></tr><tr><td><span class="object-key str">z</span><span class="tooltip">metadata.result.z</span></td><td><details class="pyglove dict"><summary><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str">a</span><span class="tooltip">metadata.result.z.a</span></td><td><details class="pyglove list"><summary><div class="summary-title">List(...)</div></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">metadata.result.z.a[0]</span></td><td><span class="simple-value int">12</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">metadata.result.z.a[1]</span></td><td><span class="simple-value int">323</span></td></tr></table></div></details></td></tr></table></div></details></td></tr></table></div></details></div><div class="message-usage"><details class="pyglove lm-sampling-usage"><summary><div class="summary-name">llm usage<span class="tooltip">metadata.usage</span></div><div class="summary-title">LMSamplingUsage(...)</div></summary><div class="complex-value lm-sampling-usage"><table><tr><td><span class="object-key str">prompt_tokens</span><span class="tooltip">metadata.usage.prompt_tokens</span></td><td><span class="simple-value int">10</span></td></tr><tr><td><span class="object-key str">completion_tokens</span><span class="tooltip">metadata.usage.completion_tokens</span></td><td><span class="simple-value int">2</span></td></tr><tr><td><span class="object-key str">total_tokens</span><span class="tooltip">metadata.usage.total_tokens</span></td><td><span class="simple-value int">12</span></td></tr><tr><td><span class="object-key str">num_requests</span><span class="tooltip">metadata.usage.num_requests</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key str">estimated_cost</span><span class="tooltip">metadata.usage.estimated_cost</span></td><td><span class="simple-value none-type">None</span></td></tr><tr><td><span class="object-key str">retry_stats</span><span class="tooltip">metadata.usage.retry_stats</span></td><td><details class="pyglove retry-stats"><summary><div class="summary-title">RetryStats(...)</div></summary><div class="complex-value retry-stats"><table><tr><td><span class="object-key str">num_occurences</span><span class="tooltip">metadata.usage.retry_stats.num_occurences</span></td><td><span class="simple-value int">0</span></td></tr><tr><td><span class="object-key str">total_wait_interval</span><span class="tooltip">metadata.usage.retry_stats.total_wait_interval</span></td><td><span class="simple-value float">0.0</span></td></tr><tr><td><span class="object-key str">total_call_interval</span><span class="tooltip">metadata.usage.retry_stats.total_call_interval</span></td><td><span class="simple-value float">0.0</span></td></tr><tr><td><span class="object-key str">errors</span><span class="tooltip">metadata.usage.retry_stats.errors</span></td><td><details class="pyglove dict"><summary><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></td></tr></table></div></details></td></tr><tr><td><span class="object-key str">completion_tokens_details</span><span class="tooltip">metadata.usage.completion_tokens_details</span></td><td><span class="simple-value none-type">None</span></td></tr></table></div></details></div><div class="message-metadata"><details class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></div><details class="pyglove user-message lf-message"><summary><div class="summary-name lf-message">source<span class="tooltip lf-message">source</span></div><div class="summary-title lf-message">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>lm-input</span></div><div class="message-text">What is in this image?<div class="modality-in-text"><details open class="pyglove custom-modality"><summary><div class="summary-name">custom_modality:acbd18db<span class="tooltip"></span></div><div class="summary-title">CustomModality(...)</div></summary><div class="complex-value custom-modality"><table><tr><td><span class="object-key str">content</span><span class="tooltip">content</span></td><td><span class="simple-value str">&#x27;foo&#x27;</span></td></tr></table></div></details></div>this is a test</div><div class="message-metadata"><details class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">source.metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></div><details class="pyglove user-message lf-message"><summary><div class="summary-name lf-message">source<span class="tooltip lf-message">source.source</span></div><div class="summary-title lf-message">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"></div><div class="message-text">User input</div><div class="message-metadata"><details class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">source.source.metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></div></div></details></div></details></div></details>
459
434
  """
460
435
  )
461
436
  self.assert_html_content(
@@ -472,7 +447,7 @@ class MessageTest(unittest.TestCase):
472
447
  ),
473
448
  ),
474
449
  """
475
- <details open class="pyglove ai-message lf-message"><summary><div class="summary-title lf-message">AIMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>lm-response</span><span>lm-output</span></div><div class="message-text">My name is Gemini</div><div class="message-result"><details open class="pyglove dict"><summary><div class="summary-name">result<span class="tooltip">metadata.result</span></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">metadata.result.x</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key str">y</span><span class="tooltip">metadata.result.y</span></td><td><span class="simple-value int">2</span></td></tr><tr><td><span class="object-key str">z</span><span class="tooltip">metadata.result.z</span></td><td><details class="pyglove dict"><summary><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str">a</span><span class="tooltip">metadata.result.z.a</span></td><td><details class="pyglove list"><summary><div class="summary-title">List(...)</div></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">metadata.result.z.a[0]</span></td><td><span class="simple-value int">12</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">metadata.result.z.a[1]</span></td><td><span class="simple-value int">323</span></td></tr></table></div></details></td></tr></table></div></details></td></tr></table></div></details></div><div class="message-usage"><details open class="pyglove lm-sampling-usage"><summary><div class="summary-name">llm usage<span class="tooltip">metadata.usage</span></div><div class="summary-title">LMSamplingUsage(...)</div></summary><div class="complex-value lm-sampling-usage"><table><tr><td><span class="object-key str">prompt_tokens</span><span class="tooltip">metadata.usage.prompt_tokens</span></td><td><span class="simple-value int">10</span></td></tr><tr><td><span class="object-key str">completion_tokens</span><span class="tooltip">metadata.usage.completion_tokens</span></td><td><span class="simple-value int">2</span></td></tr><tr><td><span class="object-key str">total_tokens</span><span class="tooltip">metadata.usage.total_tokens</span></td><td><span class="simple-value int">12</span></td></tr><tr><td><span class="object-key str">num_requests</span><span class="tooltip">metadata.usage.num_requests</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key str">estimated_cost</span><span class="tooltip">metadata.usage.estimated_cost</span></td><td><span class="simple-value none-type">None</span></td></tr><tr><td><span class="object-key str">retry_stats</span><span class="tooltip">metadata.usage.retry_stats</span></td><td><details class="pyglove retry-stats"><summary><div class="summary-title">RetryStats(...)</div></summary><div class="complex-value retry-stats"><table><tr><td><span class="object-key str">num_occurences</span><span class="tooltip">metadata.usage.retry_stats.num_occurences</span></td><td><span class="simple-value int">0</span></td></tr><tr><td><span class="object-key str">total_wait_interval</span><span class="tooltip">metadata.usage.retry_stats.total_wait_interval</span></td><td><span class="simple-value float">0.0</span></td></tr><tr><td><span class="object-key str">total_call_interval</span><span class="tooltip">metadata.usage.retry_stats.total_call_interval</span></td><td><span class="simple-value float">0.0</span></td></tr><tr><td><span class="object-key str">errors</span><span class="tooltip">metadata.usage.retry_stats.errors</span></td><td><details class="pyglove dict"><summary><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></td></tr></table></div></details></td></tr><tr><td><span class="object-key str">completion_tokens_details</span><span class="tooltip">metadata.usage.completion_tokens_details</span></td><td><span class="simple-value none-type">None</span></td></tr></table></div></details></div><div class="message-metadata"><details open class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></div><details open class="pyglove user-message lf-message"><summary><div class="summary-name lf-message">source<span class="tooltip lf-message">source</span></div><div class="summary-title lf-message">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>lm-input</span></div><div class="message-text">What is in this image?<div class="modality-in-text"><details class="pyglove custom-modality"><summary><div class="summary-name">image<span class="tooltip">source.metadata.image</span></div><div class="summary-title">CustomModality(...)</div></summary><div class="complex-value custom-modality"><table><tr><td><span class="object-key str">content</span><span class="tooltip">source.metadata.image.content</span></td><td><span class="simple-value str">&#x27;foo&#x27;</span></td></tr></table></div></details></div>this is a test</div><div class="message-metadata"><details open class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">source.metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str">image</span><span class="tooltip">source.metadata.image</span></td><td><details class="pyglove custom-modality"><summary><div class="summary-title">CustomModality(...)</div></summary><div class="complex-value custom-modality"><table><tr><td><span class="object-key str">content</span><span class="tooltip">source.metadata.image.content</span></td><td><span class="simple-value str">&#x27;foo&#x27;</span></td></tr></table></div></details></td></tr></table></div></details></div><details open class="pyglove user-message lf-message"><summary><div class="summary-name lf-message">source<span class="tooltip lf-message">source.source</span></div><div class="summary-title lf-message">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"></div><div class="message-text">User input</div><div class="message-metadata"><details open class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">source.source.metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></div></div></details></div></details></div></details>
450
+ <details open class="pyglove ai-message lf-message"><summary><div class="summary-title lf-message">AIMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>lm-response</span><span>lm-output</span></div><div class="message-text">My name is Gemini</div><div class="message-result"><details open class="pyglove dict"><summary><div class="summary-name">result<span class="tooltip">metadata.result</span></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">metadata.result.x</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key str">y</span><span class="tooltip">metadata.result.y</span></td><td><span class="simple-value int">2</span></td></tr><tr><td><span class="object-key str">z</span><span class="tooltip">metadata.result.z</span></td><td><details class="pyglove dict"><summary><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><table><tr><td><span class="object-key str">a</span><span class="tooltip">metadata.result.z.a</span></td><td><details class="pyglove list"><summary><div class="summary-title">List(...)</div></summary><div class="complex-value list"><table><tr><td><span class="object-key int">0</span><span class="tooltip">metadata.result.z.a[0]</span></td><td><span class="simple-value int">12</span></td></tr><tr><td><span class="object-key int">1</span><span class="tooltip">metadata.result.z.a[1]</span></td><td><span class="simple-value int">323</span></td></tr></table></div></details></td></tr></table></div></details></td></tr></table></div></details></div><div class="message-usage"><details open class="pyglove lm-sampling-usage"><summary><div class="summary-name">llm usage<span class="tooltip">metadata.usage</span></div><div class="summary-title">LMSamplingUsage(...)</div></summary><div class="complex-value lm-sampling-usage"><table><tr><td><span class="object-key str">prompt_tokens</span><span class="tooltip">metadata.usage.prompt_tokens</span></td><td><span class="simple-value int">10</span></td></tr><tr><td><span class="object-key str">completion_tokens</span><span class="tooltip">metadata.usage.completion_tokens</span></td><td><span class="simple-value int">2</span></td></tr><tr><td><span class="object-key str">total_tokens</span><span class="tooltip">metadata.usage.total_tokens</span></td><td><span class="simple-value int">12</span></td></tr><tr><td><span class="object-key str">num_requests</span><span class="tooltip">metadata.usage.num_requests</span></td><td><span class="simple-value int">1</span></td></tr><tr><td><span class="object-key str">estimated_cost</span><span class="tooltip">metadata.usage.estimated_cost</span></td><td><span class="simple-value none-type">None</span></td></tr><tr><td><span class="object-key str">retry_stats</span><span class="tooltip">metadata.usage.retry_stats</span></td><td><details class="pyglove retry-stats"><summary><div class="summary-title">RetryStats(...)</div></summary><div class="complex-value retry-stats"><table><tr><td><span class="object-key str">num_occurences</span><span class="tooltip">metadata.usage.retry_stats.num_occurences</span></td><td><span class="simple-value int">0</span></td></tr><tr><td><span class="object-key str">total_wait_interval</span><span class="tooltip">metadata.usage.retry_stats.total_wait_interval</span></td><td><span class="simple-value float">0.0</span></td></tr><tr><td><span class="object-key str">total_call_interval</span><span class="tooltip">metadata.usage.retry_stats.total_call_interval</span></td><td><span class="simple-value float">0.0</span></td></tr><tr><td><span class="object-key str">errors</span><span class="tooltip">metadata.usage.retry_stats.errors</span></td><td><details class="pyglove dict"><summary><div class="summary-title">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></td></tr></table></div></details></td></tr><tr><td><span class="object-key str">completion_tokens_details</span><span class="tooltip">metadata.usage.completion_tokens_details</span></td><td><span class="simple-value none-type">None</span></td></tr></table></div></details></div><div class="message-metadata"><details open class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></div><details open class="pyglove user-message lf-message"><summary><div class="summary-name lf-message">source<span class="tooltip lf-message">source</span></div><div class="summary-title lf-message">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"><span>lm-input</span></div><div class="message-text">What is in this image?<div class="modality-in-text"><details class="pyglove custom-modality"><summary><div class="summary-name">custom_modality:acbd18db<span class="tooltip"></span></div><div class="summary-title">CustomModality(...)</div></summary><div class="complex-value custom-modality"><table><tr><td><span class="object-key str">content</span><span class="tooltip">content</span></td><td><span class="simple-value str">&#x27;foo&#x27;</span></td></tr></table></div></details></div>this is a test</div><div class="message-metadata"><details open class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">source.metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></div><details open class="pyglove user-message lf-message"><summary><div class="summary-name lf-message">source<span class="tooltip lf-message">source.source</span></div><div class="summary-title lf-message">UserMessage(...)</div></summary><div class="complex_value"><div class="message-tags"></div><div class="message-text">User input</div><div class="message-metadata"><details open class="pyglove dict message-metadata"><summary><div class="summary-name message-metadata">metadata<span class="tooltip message-metadata">source.source.metadata</span></div><div class="summary-title message-metadata">Dict(...)</div></summary><div class="complex-value dict"><span class="empty-container"></span></div></details></div></div></details></div></details></div></details>
476
451
  """
477
452
  )
478
453
 
@@ -24,6 +24,14 @@ from langfun.core.modalities.image import Image
24
24
  from langfun.core.modalities.pdf import PDF
25
25
  from langfun.core.modalities.video import Video
26
26
 
27
+ from langfun.core import message as _message_lib
28
+
29
+ # Override the `images`, `videos` and `audios` properties of `Message` to
30
+ # return the modalities of the corresponding types.
31
+ _message_lib.Message.images = property(lambda self: self.modalities(Image))
32
+ _message_lib.Message.videos = property(lambda self: self.modalities(Video))
33
+ _message_lib.Message.audios = property(lambda self: self.modalities(Audio))
34
+
27
35
  # pylint: enable=g-import-not-at-top
28
36
  # pylint: enable=g-bad-import-order
29
37
  # pylint: enable=g-importing-member