langfun 0.1.2.dev202510230805__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 (44) 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/base_test.py +4 -4
  7. langfun/core/eval/v2/progress_tracking_test.py +3 -0
  8. langfun/core/langfunc_test.py +6 -4
  9. langfun/core/language_model.py +15 -6
  10. langfun/core/language_model_test.py +9 -3
  11. langfun/core/llms/__init__.py +7 -1
  12. langfun/core/llms/anthropic.py +130 -0
  13. langfun/core/llms/cache/base.py +3 -1
  14. langfun/core/llms/cache/in_memory_test.py +14 -4
  15. langfun/core/llms/deepseek.py +1 -1
  16. langfun/core/llms/gemini.py +2 -5
  17. langfun/core/llms/groq.py +1 -1
  18. langfun/core/llms/llama_cpp.py +1 -1
  19. langfun/core/llms/openai.py +7 -2
  20. langfun/core/llms/openai_compatible.py +136 -27
  21. langfun/core/llms/openai_compatible_test.py +207 -20
  22. langfun/core/llms/openai_test.py +0 -2
  23. langfun/core/llms/vertexai.py +12 -2
  24. langfun/core/message.py +78 -44
  25. langfun/core/message_test.py +56 -81
  26. langfun/core/modalities/__init__.py +8 -0
  27. langfun/core/modalities/mime.py +9 -0
  28. langfun/core/modality.py +104 -27
  29. langfun/core/modality_test.py +42 -12
  30. langfun/core/sampling_test.py +20 -4
  31. langfun/core/structured/completion.py +2 -7
  32. langfun/core/structured/completion_test.py +23 -43
  33. langfun/core/structured/mapping.py +4 -13
  34. langfun/core/structured/querying.py +13 -11
  35. langfun/core/structured/querying_test.py +65 -29
  36. langfun/core/template.py +39 -13
  37. langfun/core/template_test.py +83 -17
  38. langfun/env/event_handlers/metric_writer_test.py +3 -3
  39. langfun/env/load_balancers_test.py +2 -2
  40. {langfun-0.1.2.dev202510230805.dist-info → langfun-0.1.2.dev202510250803.dist-info}/METADATA +1 -1
  41. {langfun-0.1.2.dev202510230805.dist-info → langfun-0.1.2.dev202510250803.dist-info}/RECORD +44 -44
  42. {langfun-0.1.2.dev202510230805.dist-info → langfun-0.1.2.dev202510250803.dist-info}/WHEEL +0 -0
  43. {langfun-0.1.2.dev202510230805.dist-info → langfun-0.1.2.dev202510250803.dist-info}/licenses/LICENSE +0 -0
  44. {langfun-0.1.2.dev202510230805.dist-info → langfun-0.1.2.dev202510250803.dist-info}/top_level.txt +0 -0
@@ -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
@@ -15,6 +15,7 @@
15
15
 
16
16
  import base64
17
17
  import functools
18
+ import hashlib
18
19
  from typing import Annotated, Any, Iterable, Type, Union
19
20
  import langfun.core as lf
20
21
  # Placeholder for Google-internal internet access import.
@@ -87,6 +88,14 @@ class Mime(lf.Modality):
87
88
  """Returns True if the MIME type is a binary type."""
88
89
  return not self.is_text
89
90
 
91
+ @property
92
+ def hash(self) -> str:
93
+ """Returns the hash of the MIME content."""
94
+ # Hash the URI to avoid downloading the content.
95
+ if self.uri is not None:
96
+ return hashlib.md5(self.uri.encode()).hexdigest()[:8]
97
+ return super().hash
98
+
90
99
  def to_text(self) -> str:
91
100
  """Returns the text content of the MIME type."""
92
101
  if not self.is_text:
langfun/core/modality.py CHANGED
@@ -14,23 +14,15 @@
14
14
  """Interface for modality (e.g. Image, Video, etc.)."""
15
15
 
16
16
  import abc
17
+ import contextlib
17
18
  import functools
18
19
  import hashlib
19
- from typing import Any, ContextManager
20
+ import re
21
+ from typing import Any, ContextManager, Iterator
20
22
  from langfun.core import component
21
23
  import pyglove as pg
22
24
 
23
25
 
24
- _TLS_MODALITY_AS_REF = '__format_modality_as_ref__'
25
-
26
-
27
- def format_modality_as_ref(enabled: bool = True) -> ContextManager[None]:
28
- """A context manager that formats modality objects as references."""
29
- return pg.object_utils.thread_local_value_scope(
30
- _TLS_MODALITY_AS_REF, enabled, False
31
- )
32
-
33
-
34
26
  class Modality(component.Component, pg.views.HtmlTreeView.Extension):
35
27
  """Base class for multimodal object."""
36
28
 
@@ -39,15 +31,18 @@ class Modality(component.Component, pg.views.HtmlTreeView.Extension):
39
31
 
40
32
  def _on_bound(self):
41
33
  super()._on_bound()
42
- # Invalidate cached hash if modality member is changed.
34
+ # Invalidate cached hash and id if modality member is changed.
43
35
  self.__dict__.pop('hash', None)
36
+ self.__dict__.pop('id', None)
44
37
 
45
38
  def format(self, *args, **kwargs) -> str:
46
- if self.referred_name is None or not pg.object_utils.thread_local_get(
47
- _TLS_MODALITY_AS_REF, False
48
- ):
39
+ if not pg.object_utils.thread_local_get(_TLS_MODALITY_AS_REF, False):
49
40
  return super().format(*args, **kwargs)
50
- return Modality.text_marker(self.referred_name)
41
+
42
+ capture_scope = get_modality_capture_context()
43
+ if capture_scope is not None:
44
+ capture_scope.capture(self)
45
+ return Modality.text_marker(self.id)
51
46
 
52
47
  def __str_kwargs__(self) -> dict[str, Any]:
53
48
  # For modality objects, we don't want to use markdown format when they
@@ -70,14 +65,11 @@ class Modality(component.Component, pg.views.HtmlTreeView.Extension):
70
65
  """Returns a marker in the text for this object."""
71
66
  return Modality.REF_START + var_name + Modality.REF_END
72
67
 
73
- @property
74
- def referred_name(self) -> str | None:
68
+ @functools.cached_property
69
+ def id(self) -> str | None:
75
70
  """Returns the referred name of this object in its template."""
76
- if not self.sym_path:
77
- return None
78
- # Strip the metadata prefix under message.
79
- path = str(self.sym_path)
80
- return path[9:] if path.startswith('metadata.') else path
71
+ modality_type = _camel_to_snake(self.__class__.__name__)
72
+ return f'{modality_type}:{self.hash}'
81
73
 
82
74
  @classmethod
83
75
  def from_value(cls, value: pg.Symbolic) -> dict[str, 'Modality']:
@@ -86,7 +78,7 @@ class Modality(component.Component, pg.views.HtmlTreeView.Extension):
86
78
  def _visit(k, v, p):
87
79
  del k, p
88
80
  if isinstance(v, Modality):
89
- modalities[v.referred_name] = v
81
+ modalities[v.id] = v
90
82
  return pg.TraverseAction.CONTINUE
91
83
  return pg.TraverseAction.ENTER
92
84
 
@@ -102,7 +94,7 @@ class ModalityRef(pg.Object, pg.typing.CustomTyping):
102
94
  structure.
103
95
  """
104
96
 
105
- name: str
97
+ id: str
106
98
 
107
99
  def custom_apply(
108
100
  self, path: pg.KeyPath, value_spec: pg.ValueSpec, *args, **kwargs
@@ -122,12 +114,97 @@ class ModalityRef(pg.Object, pg.typing.CustomTyping):
122
114
  """
123
115
 
124
116
  def _placehold(k, v, p):
125
- del p
117
+ del k, p
126
118
  if isinstance(v, Modality):
127
- return ModalityRef(name=value.sym_path + k)
119
+ return ModalityRef(id=v.id)
128
120
  return v
129
121
  return value.clone().rebind(_placehold, raise_on_no_change=False)
130
122
 
123
+ @classmethod
124
+ def restore(cls, value: pg.Symbolic, modalities: dict[str, Modality]) -> Any:
125
+ """Returns a copy of value by replacing refs with modality objects."""
126
+ def _restore(k, v, p):
127
+ del k, p
128
+ if isinstance(v, ModalityRef):
129
+ modality_object = modalities.get(v.id)
130
+ if modality_object is None:
131
+ raise ValueError(
132
+ f'Modality {v.id} not found in modalities {modalities.keys()}'
133
+ )
134
+ return modality_object
135
+ return v
136
+ return value.rebind(_restore, raise_on_no_change=False)
137
+
131
138
 
132
139
  class ModalityError(RuntimeError): # pylint: disable=g-bad-exception-name
133
140
  """Exception raised when modality is not supported."""
141
+
142
+
143
+ #
144
+ # Context managers to deal with modality objects.
145
+ #
146
+
147
+
148
+ _TLS_MODALITY_CAPTURE_SCOPE = '__modality_capture_scope__'
149
+ _TLS_MODALITY_AS_REF = '__format_modality_as_ref__'
150
+
151
+
152
+ def format_modality_as_ref(enabled: bool = True) -> ContextManager[None]:
153
+ """A context manager that formats modality objects as references."""
154
+ return pg.object_utils.thread_local_value_scope(
155
+ _TLS_MODALITY_AS_REF, enabled, False
156
+ )
157
+
158
+
159
+ class _ModalityCaptureContext:
160
+ """A context to capture modality objects when being rendered."""
161
+
162
+ def __init__(self):
163
+ self._references: dict[str, pg.Ref[Modality]] = {}
164
+
165
+ def capture(self, modality: Modality) -> None:
166
+ """Captures the modality object."""
167
+ self._references[modality.id] = pg.Ref(modality)
168
+
169
+ @property
170
+ def references(self) -> dict[str, pg.Ref[Modality]]:
171
+ """Returns the modality references captured in this context."""
172
+ return self._references
173
+
174
+
175
+ @contextlib.contextmanager
176
+ def capture_rendered_modalities() -> Iterator[dict[str, pg.Ref[Modality]]]:
177
+ """Capture modality objects whose references is being rendered.
178
+
179
+ Example:
180
+ ```
181
+ image = lf.Image.from_url(...)
182
+ with lf.modality.capture_rendered_modalities() as rendered_modalities:
183
+ with lf.modality.format_modality_as_ref():
184
+ print(f'Hello {image}')
185
+ self.assertEqual(rendered_modalities, {'image:<hash>': pg.Ref(image)})
186
+ ```
187
+ """
188
+ context = get_modality_capture_context()
189
+ top_level = context is None
190
+ if top_level:
191
+ context = _ModalityCaptureContext()
192
+ pg.object_utils.thread_local_set(_TLS_MODALITY_CAPTURE_SCOPE, context)
193
+
194
+ try:
195
+ yield context.references # pylint: disable=attribute-error
196
+ finally:
197
+ if top_level:
198
+ pg.object_utils.thread_local_del(_TLS_MODALITY_CAPTURE_SCOPE)
199
+
200
+
201
+ def get_modality_capture_context() -> _ModalityCaptureContext | None:
202
+ """Returns the current modality capture context."""
203
+ return pg.object_utils.thread_local_get(_TLS_MODALITY_CAPTURE_SCOPE, None)
204
+
205
+
206
+ def _camel_to_snake(name: str) -> str:
207
+ """Converts a camelCase name to snake_case."""
208
+ return re.sub(
209
+ pattern=r'([A-Z]+)', repl=r'_\1', string=name
210
+ ).lower().lstrip('_')