langfun 0.1.1.dev20240805__tar.gz → 0.1.1.dev20240807__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/PKG-INFO +1 -1
  2. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/__init__.py +1 -0
  3. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/eval/base.py +5 -43
  4. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/logging.py +8 -35
  5. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/logging_test.py +8 -2
  6. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/message.py +78 -2
  7. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/message_test.py +49 -0
  8. langfun-0.1.1.dev20240807/langfun/core/repr_utils.py +173 -0
  9. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/repr_utils_test.py +14 -0
  10. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun.egg-info/PKG-INFO +1 -1
  11. langfun-0.1.1.dev20240805/langfun/core/repr_utils.py +0 -83
  12. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/LICENSE +0 -0
  13. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/README.md +0 -0
  14. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/__init__.py +0 -0
  15. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/coding/__init__.py +0 -0
  16. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/coding/python/__init__.py +0 -0
  17. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/coding/python/correction.py +0 -0
  18. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/coding/python/correction_test.py +0 -0
  19. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/coding/python/errors.py +0 -0
  20. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/coding/python/errors_test.py +0 -0
  21. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/coding/python/execution.py +0 -0
  22. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/coding/python/execution_test.py +0 -0
  23. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/coding/python/generation.py +0 -0
  24. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/coding/python/generation_test.py +0 -0
  25. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/coding/python/parsing.py +0 -0
  26. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/coding/python/parsing_test.py +0 -0
  27. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/coding/python/permissions.py +0 -0
  28. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/coding/python/permissions_test.py +0 -0
  29. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/component.py +0 -0
  30. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/component_test.py +0 -0
  31. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/concurrent.py +0 -0
  32. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/concurrent_test.py +0 -0
  33. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/console.py +0 -0
  34. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/console_test.py +0 -0
  35. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/eval/__init__.py +0 -0
  36. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/eval/base_test.py +0 -0
  37. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/eval/matching.py +0 -0
  38. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/eval/matching_test.py +0 -0
  39. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/eval/patching.py +0 -0
  40. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/eval/patching_test.py +0 -0
  41. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/eval/scoring.py +0 -0
  42. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/eval/scoring_test.py +0 -0
  43. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/langfunc.py +0 -0
  44. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/langfunc_test.py +0 -0
  45. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/language_model.py +0 -0
  46. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/language_model_test.py +0 -0
  47. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/llms/__init__.py +0 -0
  48. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/llms/anthropic.py +0 -0
  49. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/llms/anthropic_test.py +0 -0
  50. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/llms/cache/__init__.py +0 -0
  51. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/llms/cache/base.py +0 -0
  52. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/llms/cache/in_memory.py +0 -0
  53. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/llms/cache/in_memory_test.py +0 -0
  54. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/llms/fake.py +0 -0
  55. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/llms/fake_test.py +0 -0
  56. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/llms/google_genai.py +0 -0
  57. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/llms/google_genai_test.py +0 -0
  58. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/llms/groq.py +0 -0
  59. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/llms/groq_test.py +0 -0
  60. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/llms/llama_cpp.py +0 -0
  61. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/llms/llama_cpp_test.py +0 -0
  62. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/llms/openai.py +0 -0
  63. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/llms/openai_test.py +0 -0
  64. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/llms/rest.py +0 -0
  65. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/llms/rest_test.py +0 -0
  66. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/llms/vertexai.py +0 -0
  67. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/llms/vertexai_test.py +0 -0
  68. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/memories/__init__.py +0 -0
  69. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/memories/conversation_history.py +0 -0
  70. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/memories/conversation_history_test.py +0 -0
  71. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/memory.py +0 -0
  72. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/modalities/__init__.py +0 -0
  73. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/modalities/audio.py +0 -0
  74. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/modalities/audio_test.py +0 -0
  75. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/modalities/image.py +0 -0
  76. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/modalities/image_test.py +0 -0
  77. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/modalities/mime.py +0 -0
  78. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/modalities/mime_test.py +0 -0
  79. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/modalities/ms_office.py +0 -0
  80. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/modalities/ms_office_test.py +0 -0
  81. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/modalities/pdf.py +0 -0
  82. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/modalities/pdf_test.py +0 -0
  83. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/modalities/video.py +0 -0
  84. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/modalities/video_test.py +0 -0
  85. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/modality.py +0 -0
  86. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/modality_test.py +0 -0
  87. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/natural_language.py +0 -0
  88. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/natural_language_test.py +0 -0
  89. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/sampling.py +0 -0
  90. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/sampling_test.py +0 -0
  91. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/structured/__init__.py +0 -0
  92. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/structured/completion.py +0 -0
  93. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/structured/completion_test.py +0 -0
  94. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/structured/description.py +0 -0
  95. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/structured/description_test.py +0 -0
  96. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/structured/function_generation.py +0 -0
  97. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/structured/function_generation_test.py +0 -0
  98. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/structured/mapping.py +0 -0
  99. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/structured/mapping_test.py +0 -0
  100. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/structured/parsing.py +0 -0
  101. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/structured/parsing_test.py +0 -0
  102. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/structured/prompting.py +0 -0
  103. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/structured/prompting_test.py +0 -0
  104. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/structured/schema.py +0 -0
  105. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/structured/schema_generation.py +0 -0
  106. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/structured/schema_generation_test.py +0 -0
  107. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/structured/schema_test.py +0 -0
  108. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/structured/scoring.py +0 -0
  109. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/structured/scoring_test.py +0 -0
  110. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/subscription.py +0 -0
  111. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/subscription_test.py +0 -0
  112. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/template.py +0 -0
  113. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/template_test.py +0 -0
  114. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/templates/__init__.py +0 -0
  115. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/templates/completion.py +0 -0
  116. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/templates/completion_test.py +0 -0
  117. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/templates/conversation.py +0 -0
  118. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/templates/conversation_test.py +0 -0
  119. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/templates/demonstration.py +0 -0
  120. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/templates/demonstration_test.py +0 -0
  121. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/templates/selfplay.py +0 -0
  122. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/templates/selfplay_test.py +0 -0
  123. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/text_formatting.py +0 -0
  124. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun/core/text_formatting_test.py +0 -0
  125. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun.egg-info/SOURCES.txt +0 -0
  126. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun.egg-info/dependency_links.txt +0 -0
  127. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun.egg-info/requires.txt +0 -0
  128. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/langfun.egg-info/top_level.txt +0 -0
  129. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/setup.cfg +0 -0
  130. {langfun-0.1.1.dev20240805 → langfun-0.1.1.dev20240807}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langfun
3
- Version: 0.1.1.dev20240805
3
+ Version: 0.1.1.dev20240807
4
4
  Summary: Langfun: Language as Functions.
5
5
  Home-page: https://github.com/google/langfun
6
6
  Author: Langfun Authors
@@ -123,6 +123,7 @@ from langfun.core import console
123
123
 
124
124
  # Helpers for implementing _repr_xxx_ methods.
125
125
  from langfun.core import repr_utils
126
+ Html = repr_utils.Html
126
127
 
127
128
  # Utility for event logging.
128
129
  from langfun.core import logging
@@ -18,7 +18,6 @@ import collections
18
18
  import dataclasses
19
19
  import functools
20
20
  import hashlib
21
- import html
22
21
  import inspect
23
22
  import io
24
23
  import os
@@ -531,53 +530,16 @@ class Evaluable(lf.Component):
531
530
  def _render_message(self, message: lf.Message, s: io.StringIO) -> None:
532
531
  for m in message.trace():
533
532
  if 'lm-input' in m.tags:
534
- text_color = 'green'
533
+ color = 'green'
535
534
  elif 'lm-response' in m.tags:
536
- text_color = 'blue'
535
+ color = 'blue'
537
536
  else:
538
- text_color = 'black'
537
+ continue
539
538
 
540
539
  s.write(
541
- f'<div style="color: {text_color}; white-space: pre-wrap;'
542
- 'padding: 10px; border: 1px solid; margin-top: 10px">'
540
+ f'<div style="color: {color}; border: 1px solid; margin-top: 10px">'
543
541
  )
544
- s.write(html.escape(m.get('formatted_text', m.text)))
545
-
546
- # Write output.
547
- if m.result is not None:
548
- s.write(
549
- '<div style="color: magenta; white-space: pre-wrap;'
550
- 'padding: 10px; border: 1px solid; margin: 10px">'
551
- )
552
- s.write(html.escape(pg.format(m.result)))
553
- s.write('</div>')
554
-
555
- # Write modality information.
556
- if 'lm-input' in m.tags or 'lm-response' in m.tags:
557
- modalities = m.referred_modalities()
558
- if modalities:
559
- s.write(f'<div style="color: {text_color}; white-space: pre-wrap;'
560
- 'padding: 10px; border: 1px solid; margin-top: 10px"><table>')
561
- for name, modality in modalities.items():
562
- s.write(f'<tr><td>{name}</td><td>')
563
- if hasattr(modality, '_repr_html_'):
564
- s.write(modality._repr_html_()) # pylint: disable=protected-access
565
- else:
566
- s.write(html.escape(pg.format(modality, max_bytes_len=32)))
567
- s.write('</td></tr>')
568
- s.write('</table></div>')
569
-
570
- # Write usage information.
571
- if m.metadata.get('usage', None):
572
- s.write(
573
- '<div style="background-color: #EEEEEE; color: black; '
574
- 'white-space: pre-wrap; padding: 10px; border: 0px solid; '
575
- 'margin: 10px">'
576
- f'prompt: {m.usage.prompt_tokens} tokens, '
577
- f'response: {m.usage.completion_tokens} tokens, '
578
- f'total: {m.usage.total_tokens} tokens'
579
- '</div>'
580
- )
542
+ s.write(m._repr_html_()) # pylint: disable=protected-access
581
543
  s.write('</div>')
582
544
 
583
545
  @classmethod
@@ -19,6 +19,7 @@ import typing
19
19
  from typing import Any, Literal, ContextManager
20
20
 
21
21
  from langfun.core import console
22
+ from langfun.core import repr_utils
22
23
  import pyglove as pg
23
24
 
24
25
 
@@ -55,31 +56,19 @@ class LogEntry(pg.Object):
55
56
  s.write(f'<div style="padding-left: {padding_left}px;">')
56
57
  s.write(self._message_display)
57
58
  if self.metadata:
58
- s.write('<div style="padding-left: 20px; margin-top: 10px">')
59
- s.write('<table style="border-top: 1px solid #EEEEEE;">')
60
- for k, v in self.metadata.items():
61
- if hasattr(v, '_repr_html_'):
62
- cs = v._repr_html_() # pylint: disable=protected-access
63
- else:
64
- cs = f'<span style="white-space: pre-wrap">{str(v)}</span>'
65
- key_span = self._round_text(k, color='#F1C40F', margin_bottom='0px')
66
- s.write(
67
- '<tr>'
68
- '<td style="padding: 5px; vertical-align: top; '
69
- f'border-bottom: 1px solid #EEEEEE">{key_span}</td>'
70
- '<td style="padding: 5px; vertical-align: top; '
71
- f'border-bottom: 1px solid #EEEEEE">{cs}</td></tr>'
72
- )
73
- s.write('</table></div>')
59
+ s.write(repr_utils.html_repr(self.metadata))
60
+ s.write('</div>')
74
61
  return s.getvalue()
75
62
 
76
63
  @property
77
- def _message_text_color(self) -> str:
64
+ def _message_text_bgcolor(self) -> str:
78
65
  match self.level:
79
66
  case 'debug':
80
67
  return '#EEEEEE'
81
68
  case 'info':
82
69
  return '#A3E4D7'
70
+ case 'warning':
71
+ return '#F8C471'
83
72
  case 'error':
84
73
  return '#F5C6CB'
85
74
  case 'fatal':
@@ -99,25 +88,9 @@ class LogEntry(pg.Object):
99
88
 
100
89
  @property
101
90
  def _message_display(self) -> str:
102
- return self._round_text(
91
+ return repr_utils.html_round_text(
103
92
  self._time_display + '&nbsp;' + self.message,
104
- color=self._message_text_color,
105
- )
106
-
107
- def _round_text(
108
- self,
109
- text: str,
110
- *,
111
- color: str = '#EEEEEE',
112
- display: str = 'inline-block',
113
- margin_top: str = '5px',
114
- margin_bottom: str = '5px',
115
- whitespace: str = 'pre-wrap') -> str:
116
- return (
117
- f'<span style="background:{color}; display:{display};'
118
- f'border-radius:10px; padding:5px; '
119
- f'margin-top: {margin_top}; margin-bottom: {margin_bottom}; '
120
- f'white-space: {whitespace}">{text}</span>'
93
+ background_color=self._message_text_bgcolor,
121
94
  )
122
95
 
123
96
 
@@ -43,8 +43,14 @@ class LoggingTest(unittest.TestCase):
43
43
  self.assertEqual(logging.fatal('hi').level, 'fatal')
44
44
 
45
45
  def test_repr_html(self):
46
- entry = logging.log('info', 'hi', indent=1, x=1, y=2)
47
- self.assertIn('<div', entry._repr_html_())
46
+ def assert_color(entry, color):
47
+ self.assertIn(f'background-color: {color}', entry._repr_html_())
48
+
49
+ assert_color(logging.debug('hi', indent=0), '#EEEEEE')
50
+ assert_color(logging.info('hi', indent=1), '#A3E4D7')
51
+ assert_color(logging.warning('hi', x=1, y=2), '#F8C471')
52
+ assert_color(logging.error('hi', indent=2, x=1, y=2), '#F5C6CB')
53
+ assert_color(logging.fatal('hi', indent=2, x=1, y=2), '#F19CBB')
48
54
 
49
55
 
50
56
  if __name__ == '__main__':
@@ -14,11 +14,13 @@
14
14
  """Messages that are exchanged between users and agents."""
15
15
 
16
16
  import contextlib
17
+ import html
17
18
  import io
18
19
  from typing import Annotated, Any, Optional, Union
19
20
 
20
21
  from langfun.core import modality
21
22
  from langfun.core import natural_language
23
+ from langfun.core import repr_utils
22
24
  import pyglove as pg
23
25
 
24
26
 
@@ -304,15 +306,16 @@ class Message(natural_language.NaturalLanguageFormattable, pg.Object):
304
306
  m.referred_name: m for m in chunks if isinstance(m, modality.Modality)
305
307
  }
306
308
 
307
- def chunk(self) -> list[str | modality.Modality]:
309
+ def chunk(self, text: str | None = None) -> list[str | modality.Modality]:
308
310
  """Chunk a message into a list of str or modality objects."""
309
311
  chunks = []
310
312
 
311
313
  def add_text_chunk(text_piece: str) -> None:
312
314
  if text_piece:
313
315
  chunks.append(text_piece)
316
+ if text is None:
317
+ text = self.text
314
318
 
315
- text = self.text
316
319
  chunk_start = 0
317
320
  ref_end = 0
318
321
  while chunk_start < len(text):
@@ -490,6 +493,79 @@ class Message(natural_language.NaturalLanguageFormattable, pg.Object):
490
493
  v = self.metadata[key]
491
494
  return v.value if isinstance(v, pg.Ref) else v
492
495
 
496
+ def _repr_html_(self):
497
+ return self.to_html().content
498
+
499
+ def to_html(
500
+ self,
501
+ include_message_type: bool = True
502
+ ) -> repr_utils.Html:
503
+ """Returns the HTML representation of the message."""
504
+ s = io.StringIO()
505
+ s.write('<div style="padding:0px 10px 0px 10px;">')
506
+ # Title bar.
507
+ if include_message_type:
508
+ s.write(
509
+ repr_utils.html_round_text(
510
+ self.__class__.__name__,
511
+ text_color='white',
512
+ background_color=self._text_color(),
513
+ )
514
+ )
515
+ s.write('<hr>')
516
+
517
+ # Body.
518
+ s.write(
519
+ f'<span style="color: {self._text_color()}; white-space: pre-wrap;">'
520
+ )
521
+
522
+ # NOTE(daiyip): LLM may reformat the text from the input, therefore
523
+ # we proritize the formatted text if it's available.
524
+ maybe_reformatted = self.get('formatted_text')
525
+ referred_chunks = {}
526
+ for chunk in self.chunk(maybe_reformatted):
527
+ if isinstance(chunk, str):
528
+ s.write(html.escape(chunk))
529
+ else:
530
+ assert isinstance(chunk, modality.Modality), chunk
531
+ s.write('&nbsp;')
532
+ s.write(repr_utils.html_round_text(
533
+ chunk.referred_name,
534
+ text_color='black',
535
+ background_color='#f7dc6f'
536
+ ))
537
+ s.write('&nbsp;')
538
+ referred_chunks[chunk.referred_name] = chunk
539
+ s.write('</span>')
540
+
541
+ def item_color(k, v):
542
+ if isinstance(v, modality.Modality):
543
+ return ('black', '#f7dc6f', None, None) # Light yellow
544
+ elif k == 'result':
545
+ return ('white', 'purple', 'purple', None) # Blue.
546
+ elif k in ('usage',):
547
+ return ('white', '#e74c3c', None, None) # Red.
548
+ else:
549
+ return ('white', '#17202a', None, None) # Dark gray
550
+
551
+ # TODO(daiyip): Revisit the logic in deciding what metadata keys to
552
+ # expose to the user.
553
+ if referred_chunks:
554
+ s.write(repr_utils.html_repr(referred_chunks, item_color))
555
+
556
+ if 'lm-response' in self.tags:
557
+ s.write(repr_utils.html_repr(self.metadata, item_color))
558
+ s.write('</div>')
559
+ return repr_utils.Html(s.getvalue())
560
+
561
+ def _text_color(self) -> str:
562
+ match self.__class__.__name__:
563
+ case 'UserMessage':
564
+ return 'green'
565
+ case 'AIMessage':
566
+ return 'blue'
567
+ case _:
568
+ return 'black'
493
569
 
494
570
  #
495
571
  # Messages of different roles.
@@ -26,6 +26,9 @@ class CustomModality(modality.Modality):
26
26
  def to_bytes(self):
27
27
  return self.content.encode()
28
28
 
29
+ def _repr_html_(self):
30
+ return f'<div>CustomModality: {self.content}</div>'
31
+
29
32
 
30
33
  class MessageTest(unittest.TestCase):
31
34
 
@@ -315,6 +318,52 @@ class MessageTest(unittest.TestCase):
315
318
  )
316
319
  )
317
320
 
321
+ def test_html(self):
322
+ m = message.UserMessage(
323
+ 'hi, this is a <<[[img1]]>> and <<[[x.img2]]>>',
324
+ img1=CustomModality('foo'),
325
+ x=dict(img2=CustomModality('bar')),
326
+ )
327
+ self.assertEqual(
328
+ m._repr_html_(),
329
+ (
330
+ '<div style="padding:0px 10px 0px 10px;"><span style="color: white;'
331
+ 'background-color: green;display:inline-block; border-radius:10px; '
332
+ 'padding:5px; margin-top: 5px; margin-bottom: 5px; white-space: '
333
+ 'pre-wrap">UserMessage</span><hr><span style="color: green; '
334
+ 'white-space: pre-wrap;">hi, this is a&nbsp;<span style="color: '
335
+ 'black;background-color: #f7dc6f;display:inline-block; '
336
+ 'border-radius:10px; padding:5px; margin-top: 5px; margin-bottom: '
337
+ '5px; white-space: pre-wrap">img1</span>&nbsp;and&nbsp;<span style'
338
+ '="color: black;background-color: #f7dc6f;display:inline-block; '
339
+ 'border-radius:10px; padding:5px; margin-top: 5px; margin-bottom: '
340
+ '5px; white-space: pre-wrap">x.img2</span>&nbsp;</span><div style='
341
+ '"padding-left: 20px; margin-top: 10px"><table style="border-top: '
342
+ '1px solid #EEEEEE;"><tr><td style="padding: 5px; vertical-align: '
343
+ 'top; border-bottom: 1px solid #EEEEEE"><span style="color: black;'
344
+ 'background-color: #f7dc6f;display:inline-block; border-radius:'
345
+ '10px; padding:5px; margin-top: 5px; margin-bottom: 0px; '
346
+ 'white-space: pre-wrap">img1</span></td><td style="padding: 15px '
347
+ '5px 5px 5px; vertical-align: top; border-bottom: 1px solid '
348
+ '#EEEEEE;"><div>CustomModality: foo</div></td></tr><tr><td style='
349
+ '"padding: 5px; vertical-align: top; border-bottom: 1px solid '
350
+ '#EEEEEE"><span style="color: black;background-color: #f7dc6f;'
351
+ 'display:inline-block; border-radius:10px; padding:5px; margin-top:'
352
+ ' 5px; margin-bottom: 0px; white-space: pre-wrap">x.img2</span>'
353
+ '</td><td style="padding: 15px 5px 5px 5px; vertical-align: top; '
354
+ 'border-bottom: 1px solid #EEEEEE;"><div>CustomModality: bar</div>'
355
+ '</td></tr></table></div></div>'
356
+ )
357
+ )
358
+ self.assertIn(
359
+ 'background-color: blue',
360
+ message.AIMessage('hi').to_html().content,
361
+ )
362
+ self.assertIn(
363
+ 'background-color: black',
364
+ message.SystemMessage('hi').to_html().content,
365
+ )
366
+
318
367
 
319
368
  if __name__ == '__main__':
320
369
  unittest.main()
@@ -0,0 +1,173 @@
1
+ # Copyright 2024 The Langfun Authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """Helpers for implementing _repr_xxx_ methods."""
15
+
16
+ import collections
17
+ import contextlib
18
+ import io
19
+ from typing import Any, Callable, Iterator
20
+
21
+ from langfun.core import component
22
+ import pyglove as pg
23
+
24
+
25
+ class Html(pg.Object):
26
+ """A HTML adapter for rendering."""
27
+ content: str
28
+
29
+ def _repr_html_(self) -> str:
30
+ return self.content
31
+
32
+
33
+ @contextlib.contextmanager
34
+ def share_parts() -> Iterator[dict[str, int]]:
35
+ """Context manager for defining the context (scope) of shared content.
36
+
37
+ Under the context manager, call to `lf.write_shared` with the same content
38
+ will be written only once. This is useful for writing shared content such as
39
+ shared style and script sections in the HTML.
40
+
41
+ Example:
42
+ ```
43
+ class Foo(pg.Object):
44
+ def _repr_html_(self) -> str:
45
+ s = io.StringIO()
46
+ lf.repr_utils.write_shared_part(s, '<style>..</style>')
47
+ lf.repr_utils.write_shared_part(s, '<script>..</script>')
48
+ return s.getvalue()
49
+
50
+ with lf.repr_utils.share_parts() as share_parts:
51
+ # The <style> and <script> section will be written only once.
52
+ lf.console.display(Foo())
53
+ lf.console.display(Foo())
54
+
55
+ # Assert that the shared content is attempted to be written twice.
56
+ assert share_parts['<style>..</style>'] == 2
57
+ ```
58
+
59
+ Yields:
60
+ A dictionary mapping the shared content to the number of times it is
61
+ attempted to be written.
62
+ """
63
+ context = component.context_value(
64
+ '__shared_parts__', collections.defaultdict(int)
65
+ )
66
+ with component.context(__shared_parts__=context):
67
+ try:
68
+ yield context
69
+ finally:
70
+ pass
71
+
72
+
73
+ def write_maybe_shared(s: io.StringIO, content: str) -> bool:
74
+ """Writes a maybe shared part to an string stream.
75
+
76
+ Args:
77
+ s: The string stream to write to.
78
+ content: A maybe shared content to write.
79
+
80
+ Returns:
81
+ True if the content is written to the string. False if the content is
82
+ already written under the same share context.
83
+ """
84
+ context = component.context_value('__shared_parts__', None)
85
+ if context is None:
86
+ s.write(content)
87
+ return True
88
+ written = content in context
89
+ if not written:
90
+ s.write(content)
91
+ context[content] += 1
92
+ return not written
93
+
94
+
95
+ def html_repr(
96
+ value: Any,
97
+ item_color: Callable[
98
+ [str, str],
99
+ tuple[
100
+ str | None, # Label text color
101
+ str | None, # Label background color
102
+ str | None, # Value text color
103
+ str | None, # Value background color
104
+ ]
105
+ ] | None = None # pylint: disable=bad-whitespace
106
+ ) -> str:
107
+ """Writes a list of key-value pairs to an string stream.
108
+
109
+ Args:
110
+ value: A value to be rendered in HTML.
111
+ item_color: A function that takes the key and value and returns a tuple
112
+ of four strings, the text color and background color of the label and
113
+ value respectively. If None, a default color scheme will be used.
114
+
115
+ Returns:
116
+ The HTML representation of the value.
117
+ """
118
+ s = io.StringIO()
119
+ s.write('<div style="padding-left: 20px; margin-top: 10px">')
120
+ s.write('<table style="border-top: 1px solid #EEEEEE;">')
121
+ item_color = item_color or (lambda k, v: (None, '#F1C40F', None, None))
122
+
123
+ for k, v in pg.object_utils.flatten(value).items():
124
+ if isinstance(v, pg.Ref):
125
+ v = v.value
126
+ if hasattr(v, '_repr_html_'):
127
+ cs = v._repr_html_() # pylint: disable=protected-access
128
+ else:
129
+ cs = f'<span style="white-space: pre-wrap">{str(v)}</span>'
130
+
131
+ key_color, key_bg_color, value_color, value_bg_color = item_color(k, v)
132
+ key_span = html_round_text(
133
+ k,
134
+ text_color=key_color,
135
+ background_color=key_bg_color,
136
+ margin_bottom='0px'
137
+ )
138
+ value_color_style = f'color: {value_color};' if value_color else ''
139
+ value_bg_color_style = (
140
+ f'background-color: {value_bg_color};' if value_bg_color else ''
141
+ )
142
+ s.write(
143
+ '<tr>'
144
+ '<td style="padding: 5px; vertical-align: top; '
145
+ f'border-bottom: 1px solid #EEEEEE">{key_span}</td>'
146
+ '<td style="padding: 15px 5px 5px 5px; vertical-align: top; '
147
+ 'border-bottom: 1px solid #EEEEEE;'
148
+ f'{value_color_style}{value_bg_color_style}">{cs}</td></tr>'
149
+ )
150
+ s.write('</table></div>')
151
+ return s.getvalue()
152
+
153
+
154
+ def html_round_text(
155
+ text: str,
156
+ *,
157
+ text_color: str = 'black',
158
+ background_color: str = '#EEEEEE',
159
+ display: str = 'inline-block',
160
+ margin_top: str = '5px',
161
+ margin_bottom: str = '5px',
162
+ whitespace: str = 'pre-wrap') -> str:
163
+ """Renders a HTML span with rounded corners."""
164
+ color_style = f'color: {text_color};' if text_color else ''
165
+ bg_color_style = (
166
+ f'background-color: {background_color};' if background_color else ''
167
+ )
168
+ return (
169
+ f'<span style="{color_style}{bg_color_style}'
170
+ f'display:{display}; border-radius:10px; padding:5px; '
171
+ f'margin-top: {margin_top}; margin-bottom: {margin_bottom}; '
172
+ f'white-space: {whitespace}">{text}</span>'
173
+ )
@@ -17,6 +17,7 @@ import io
17
17
  import unittest
18
18
 
19
19
  from langfun.core import repr_utils
20
+ import pyglove as pg
20
21
 
21
22
 
22
23
  class SharingContentTest(unittest.TestCase):
@@ -53,6 +54,19 @@ class SharingContentTest(unittest.TestCase):
53
54
  self.assertEqual(ctx1['<style>b</style>'], 2)
54
55
  self.assertEqual(ctx1['<style>a</style>'], 4)
55
56
 
57
+ def test_html(self):
58
+ html = repr_utils.Html('<div>foo</div>')
59
+ self.assertEqual(html.content, '<div>foo</div>')
60
+ self.assertEqual(html._repr_html_(), '<div>foo</div>')
61
+
62
+ def test_html_repr(self):
63
+ class Foo(pg.Object):
64
+ x: int
65
+
66
+ html = repr_utils.html_repr({'foo': pg.Ref(Foo(1))})
67
+ self.assertIn('foo</span>', html)
68
+ self.assertNotIn('Ref', html)
69
+
56
70
 
57
71
  if __name__ == '__main__':
58
72
  unittest.main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: langfun
3
- Version: 0.1.1.dev20240805
3
+ Version: 0.1.1.dev20240807
4
4
  Summary: Langfun: Language as Functions.
5
5
  Home-page: https://github.com/google/langfun
6
6
  Author: Langfun Authors
@@ -1,83 +0,0 @@
1
- # Copyright 2024 The Langfun Authors
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- """Helpers for implementing _repr_xxx_ methods."""
15
-
16
- import collections
17
- import contextlib
18
- import io
19
- from typing import Iterator
20
-
21
- from langfun.core import component
22
-
23
-
24
- @contextlib.contextmanager
25
- def share_parts() -> Iterator[dict[str, int]]:
26
- """Context manager for defining the context (scope) of shared content.
27
-
28
- Under the context manager, call to `lf.write_shared` with the same content
29
- will be written only once. This is useful for writing shared content such as
30
- shared style and script sections in the HTML.
31
-
32
- Example:
33
- ```
34
- class Foo(pg.Object):
35
- def _repr_html_(self) -> str:
36
- s = io.StringIO()
37
- lf.repr_utils.write_shared_part(s, '<style>..</style>')
38
- lf.repr_utils.write_shared_part(s, '<script>..</script>')
39
- return s.getvalue()
40
-
41
- with lf.repr_utils.share_parts() as share_parts:
42
- # The <style> and <script> section will be written only once.
43
- lf.console.display(Foo())
44
- lf.console.display(Foo())
45
-
46
- # Assert that the shared content is attempted to be written twice.
47
- assert share_parts['<style>..</style>'] == 2
48
- ```
49
-
50
- Yields:
51
- A dictionary mapping the shared content to the number of times it is
52
- attempted to be written.
53
- """
54
- context = component.context_value(
55
- '__shared_parts__', collections.defaultdict(int)
56
- )
57
- with component.context(__shared_parts__=context):
58
- try:
59
- yield context
60
- finally:
61
- pass
62
-
63
-
64
- def write_maybe_shared(s: io.StringIO, content: str) -> bool:
65
- """Writes a maybe shared part to an string stream.
66
-
67
- Args:
68
- s: The string stream to write to.
69
- content: A maybe shared content to write.
70
-
71
- Returns:
72
- True if the content is written to the string. False if the content is
73
- already written under the same share context.
74
- """
75
- context = component.context_value('__shared_parts__', None)
76
- if context is None:
77
- s.write(content)
78
- return True
79
- written = content in context
80
- if not written:
81
- s.write(content)
82
- context[content] += 1
83
- return not written