langfun 0.0.2.dev20240330__py3-none-any.whl → 0.1.2.dev202501140804__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.
Files changed (145) hide show
  1. langfun/__init__.py +22 -2
  2. langfun/core/__init__.py +17 -5
  3. langfun/core/agentic/__init__.py +30 -0
  4. langfun/core/agentic/action.py +854 -0
  5. langfun/core/agentic/action_eval.py +150 -0
  6. langfun/core/agentic/action_eval_test.py +109 -0
  7. langfun/core/agentic/action_test.py +136 -0
  8. langfun/core/coding/python/__init__.py +5 -11
  9. langfun/core/coding/python/correction.py +37 -28
  10. langfun/core/coding/python/correction_test.py +29 -3
  11. langfun/core/coding/python/execution.py +40 -216
  12. langfun/core/coding/python/execution_test.py +29 -89
  13. langfun/core/coding/python/generation.py +21 -11
  14. langfun/core/coding/python/generation_test.py +2 -2
  15. langfun/core/coding/python/parsing.py +108 -193
  16. langfun/core/coding/python/parsing_test.py +2 -105
  17. langfun/core/component.py +69 -2
  18. langfun/core/component_test.py +54 -0
  19. langfun/core/concurrent.py +414 -117
  20. langfun/core/concurrent_test.py +111 -24
  21. langfun/core/console.py +18 -5
  22. langfun/core/console_test.py +17 -0
  23. langfun/core/eval/__init__.py +17 -0
  24. langfun/core/eval/base.py +767 -140
  25. langfun/core/eval/base_test.py +238 -53
  26. langfun/core/eval/matching.py +80 -76
  27. langfun/core/eval/matching_test.py +19 -9
  28. langfun/core/eval/patching.py +130 -0
  29. langfun/core/eval/patching_test.py +170 -0
  30. langfun/core/eval/scoring.py +37 -28
  31. langfun/core/eval/scoring_test.py +21 -3
  32. langfun/core/eval/v2/__init__.py +42 -0
  33. langfun/core/eval/v2/checkpointing.py +380 -0
  34. langfun/core/eval/v2/checkpointing_test.py +228 -0
  35. langfun/core/eval/v2/eval_test_helper.py +136 -0
  36. langfun/core/eval/v2/evaluation.py +725 -0
  37. langfun/core/eval/v2/evaluation_test.py +180 -0
  38. langfun/core/eval/v2/example.py +305 -0
  39. langfun/core/eval/v2/example_test.py +128 -0
  40. langfun/core/eval/v2/experiment.py +1048 -0
  41. langfun/core/eval/v2/experiment_test.py +433 -0
  42. langfun/core/eval/v2/metric_values.py +156 -0
  43. langfun/core/eval/v2/metric_values_test.py +80 -0
  44. langfun/core/eval/v2/metrics.py +357 -0
  45. langfun/core/eval/v2/metrics_test.py +203 -0
  46. langfun/core/eval/v2/progress.py +348 -0
  47. langfun/core/eval/v2/progress_test.py +82 -0
  48. langfun/core/eval/v2/progress_tracking.py +210 -0
  49. langfun/core/eval/v2/progress_tracking_test.py +66 -0
  50. langfun/core/eval/v2/reporting.py +270 -0
  51. langfun/core/eval/v2/reporting_test.py +158 -0
  52. langfun/core/eval/v2/runners.py +488 -0
  53. langfun/core/eval/v2/runners_test.py +334 -0
  54. langfun/core/langfunc.py +3 -21
  55. langfun/core/langfunc_test.py +26 -8
  56. langfun/core/language_model.py +686 -48
  57. langfun/core/language_model_test.py +681 -44
  58. langfun/core/llms/__init__.py +100 -12
  59. langfun/core/llms/anthropic.py +488 -0
  60. langfun/core/llms/anthropic_test.py +235 -0
  61. langfun/core/llms/cache/base.py +21 -2
  62. langfun/core/llms/cache/in_memory.py +13 -0
  63. langfun/core/llms/cache/in_memory_test.py +88 -28
  64. langfun/core/llms/compositional.py +101 -0
  65. langfun/core/llms/compositional_test.py +73 -0
  66. langfun/core/llms/deepseek.py +117 -0
  67. langfun/core/llms/deepseek_test.py +61 -0
  68. langfun/core/llms/fake.py +39 -26
  69. langfun/core/llms/fake_test.py +136 -11
  70. langfun/core/llms/gemini.py +507 -0
  71. langfun/core/llms/gemini_test.py +195 -0
  72. langfun/core/llms/google_genai.py +62 -218
  73. langfun/core/llms/google_genai_test.py +9 -197
  74. langfun/core/llms/groq.py +276 -0
  75. langfun/core/llms/groq_test.py +64 -0
  76. langfun/core/llms/llama_cpp.py +15 -40
  77. langfun/core/llms/llama_cpp_test.py +4 -30
  78. langfun/core/llms/openai.py +436 -226
  79. langfun/core/llms/openai_compatible.py +179 -0
  80. langfun/core/llms/openai_compatible_test.py +495 -0
  81. langfun/core/llms/openai_test.py +35 -174
  82. langfun/core/llms/rest.py +113 -0
  83. langfun/core/llms/rest_test.py +111 -0
  84. langfun/core/llms/vertexai.py +192 -0
  85. langfun/core/llms/vertexai_test.py +52 -0
  86. langfun/core/logging.py +284 -0
  87. langfun/core/logging_test.py +125 -0
  88. langfun/core/message.py +319 -9
  89. langfun/core/message_test.py +190 -13
  90. langfun/core/modalities/__init__.py +6 -2
  91. langfun/core/modalities/audio.py +30 -0
  92. langfun/core/modalities/audio_test.py +63 -0
  93. langfun/core/modalities/image.py +39 -20
  94. langfun/core/modalities/image_test.py +52 -9
  95. langfun/core/modalities/mime.py +206 -29
  96. langfun/core/modalities/mime_test.py +90 -9
  97. langfun/core/modalities/ms_office.py +117 -0
  98. langfun/core/modalities/ms_office_test.py +389 -0
  99. langfun/core/modalities/pdf.py +22 -0
  100. langfun/core/modalities/pdf_test.py +57 -0
  101. langfun/core/modalities/video.py +9 -23
  102. langfun/core/modalities/video_test.py +3 -3
  103. langfun/core/modality.py +26 -3
  104. langfun/core/modality_test.py +2 -2
  105. langfun/core/sampling.py +11 -11
  106. langfun/core/structured/__init__.py +15 -16
  107. langfun/core/structured/completion.py +32 -5
  108. langfun/core/structured/completion_test.py +9 -8
  109. langfun/core/structured/description.py +2 -2
  110. langfun/core/structured/description_test.py +3 -3
  111. langfun/core/structured/function_generation.py +278 -0
  112. langfun/core/structured/function_generation_test.py +399 -0
  113. langfun/core/structured/mapping.py +150 -46
  114. langfun/core/structured/mapping_test.py +105 -0
  115. langfun/core/structured/parsing.py +33 -21
  116. langfun/core/structured/parsing_test.py +71 -22
  117. langfun/core/structured/querying.py +746 -0
  118. langfun/core/structured/{prompting_test.py → querying_test.py} +545 -60
  119. langfun/core/structured/schema.py +208 -99
  120. langfun/core/structured/schema_generation.py +1 -1
  121. langfun/core/structured/schema_generation_test.py +2 -2
  122. langfun/core/structured/schema_test.py +133 -34
  123. langfun/core/structured/scoring.py +125 -19
  124. langfun/core/structured/scoring_test.py +30 -0
  125. langfun/core/structured/tokenization.py +64 -0
  126. langfun/core/structured/tokenization_test.py +48 -0
  127. langfun/core/template.py +240 -11
  128. langfun/core/template_test.py +146 -1
  129. langfun/core/templates/conversation.py +9 -0
  130. langfun/core/templates/conversation_test.py +4 -3
  131. langfun/core/templates/selfplay_test.py +14 -2
  132. langfun-0.1.2.dev202501140804.dist-info/METADATA +225 -0
  133. langfun-0.1.2.dev202501140804.dist-info/RECORD +153 -0
  134. {langfun-0.0.2.dev20240330.dist-info → langfun-0.1.2.dev202501140804.dist-info}/WHEEL +1 -1
  135. langfun/core/coding/python/errors.py +0 -108
  136. langfun/core/coding/python/errors_test.py +0 -99
  137. langfun/core/coding/python/permissions.py +0 -90
  138. langfun/core/coding/python/permissions_test.py +0 -86
  139. langfun/core/structured/prompting.py +0 -217
  140. langfun/core/text_formatting.py +0 -162
  141. langfun/core/text_formatting_test.py +0 -47
  142. langfun-0.0.2.dev20240330.dist-info/METADATA +0 -99
  143. langfun-0.0.2.dev20240330.dist-info/RECORD +0 -102
  144. {langfun-0.0.2.dev20240330.dist-info → langfun-0.1.2.dev202501140804.dist-info}/LICENSE +0 -0
  145. {langfun-0.0.2.dev20240330.dist-info → langfun-0.1.2.dev202501140804.dist-info}/top_level.txt +0 -0
langfun/core/message.py CHANGED
@@ -14,6 +14,7 @@
14
14
  """Messages that are exchanged between users and agents."""
15
15
 
16
16
  import contextlib
17
+ import functools
17
18
  import io
18
19
  from typing import Annotated, Any, Optional, Union
19
20
 
@@ -22,7 +23,11 @@ from langfun.core import natural_language
22
23
  import pyglove as pg
23
24
 
24
25
 
25
- class Message(natural_language.NaturalLanguageFormattable, pg.Object):
26
+ class Message(
27
+ natural_language.NaturalLanguageFormattable,
28
+ pg.Object,
29
+ pg.views.HtmlTreeView.Extension
30
+ ):
26
31
  """Message.
27
32
 
28
33
  ``Message`` is the protocol for users and the system to interact with
@@ -144,7 +149,7 @@ class Message(natural_language.NaturalLanguageFormattable, pg.Object):
144
149
  def from_value(cls, value: Union[str, 'Message']) -> 'Message':
145
150
  """Creates a message from a value or return value itself if a Message."""
146
151
  if isinstance(value, modality.Modality):
147
- return cls('{{object}}', object=value)
152
+ return cls('<<[[object]]>>', object=value)
148
153
  if isinstance(value, Message):
149
154
  return value
150
155
  return cls(value)
@@ -276,6 +281,16 @@ class Message(natural_language.NaturalLanguageFormattable, pg.Object):
276
281
  # API for supporting modalities.
277
282
  #
278
283
 
284
+ @property
285
+ def text_with_modality_hash(self) -> str:
286
+ """Returns text with modality object placeheld by their 8-byte MD5 hash."""
287
+ parts = [self.text]
288
+ for name, modality_obj in self.referred_modalities().items():
289
+ parts.append(
290
+ f'<{name}>{modality_obj.hash}</{name}>'
291
+ )
292
+ return ''.join(parts)
293
+
279
294
  def get_modality(
280
295
  self, var_name: str, default: Any = None, from_message_chain: bool = True
281
296
  ) -> modality.Modality | None:
@@ -304,21 +319,22 @@ class Message(natural_language.NaturalLanguageFormattable, pg.Object):
304
319
  m.referred_name: m for m in chunks if isinstance(m, modality.Modality)
305
320
  }
306
321
 
307
- def chunk(self) -> list[str | modality.Modality]:
322
+ def chunk(self, text: str | None = None) -> list[str | modality.Modality]:
308
323
  """Chunk a message into a list of str or modality objects."""
309
324
  chunks = []
310
325
 
311
326
  def add_text_chunk(text_piece: str) -> None:
312
327
  if text_piece:
313
328
  chunks.append(text_piece)
329
+ if text is None:
330
+ text = self.text
314
331
 
315
- text = self.text
316
332
  chunk_start = 0
317
333
  ref_end = 0
318
334
  while chunk_start < len(text):
319
335
  ref_start = text.find(modality.Modality.REF_START, ref_end)
320
336
  if ref_start == -1:
321
- add_text_chunk(text[chunk_start:].strip())
337
+ add_text_chunk(text[chunk_start:].strip(' '))
322
338
  break
323
339
 
324
340
  var_start = ref_start + len(modality.Modality.REF_START)
@@ -330,29 +346,31 @@ class Message(natural_language.NaturalLanguageFormattable, pg.Object):
330
346
  var_name = text[var_start:ref_end].strip()
331
347
  var_value = self.get_modality(var_name)
332
348
  if var_value is not None:
333
- add_text_chunk(text[chunk_start:ref_start].strip())
349
+ add_text_chunk(text[chunk_start:ref_start].strip(' '))
334
350
  chunks.append(var_value)
335
351
  chunk_start = ref_end + len(modality.Modality.REF_END)
336
352
  return chunks
337
353
 
338
354
  @classmethod
339
355
  def from_chunks(
340
- cls, chunks: list[str | modality.Modality], separator: str = '\n'
356
+ cls, chunks: list[str | modality.Modality], separator: str = ' '
341
357
  ) -> 'Message':
342
358
  """Assembly a message from a list of string or modality objects."""
343
359
  fused_text = io.StringIO()
344
360
  ref_index = 0
345
361
  metadata = dict()
346
-
362
+ last_char = None
347
363
  for i, chunk in enumerate(chunks):
348
- if i > 0:
364
+ if i > 0 and last_char not in ('\t', ' ', '\n'):
349
365
  fused_text.write(separator)
350
366
  if isinstance(chunk, str):
351
367
  fused_text.write(chunk)
368
+ last_char = chunk[-1]
352
369
  else:
353
370
  assert isinstance(chunk, modality.Modality), chunk
354
371
  var_name = f'obj{ref_index}'
355
372
  fused_text.write(modality.Modality.text_marker(var_name))
373
+ last_char = modality.Modality.REF_END[-1]
356
374
  # Make a reference if the chunk is already owned by another object
357
375
  # to avoid copy.
358
376
  metadata[var_name] = pg.maybe_ref(chunk)
@@ -391,6 +409,11 @@ class Message(natural_language.NaturalLanguageFormattable, pg.Object):
391
409
  with pg.notify_on_change(False):
392
410
  self.tags.append(tag)
393
411
 
412
+ def has_tag(self, tag: str | tuple[str, ...]) -> bool:
413
+ if isinstance(tag, str):
414
+ return tag in self.tags
415
+ return any(t in self.tags for t in tag)
416
+
394
417
  #
395
418
  # Message source chain.
396
419
  #
@@ -488,6 +511,293 @@ class Message(natural_language.NaturalLanguageFormattable, pg.Object):
488
511
  v = self.metadata[key]
489
512
  return v.value if isinstance(v, pg.Ref) else v
490
513
 
514
+ def _html_tree_view_content(
515
+ self,
516
+ *,
517
+ view: pg.views.HtmlTreeView,
518
+ root_path: pg.KeyPath | None = None,
519
+ collapse_level: int | None = None,
520
+ extra_flags: dict[str, Any] | None = None,
521
+ **kwargs,
522
+ ) -> pg.Html:
523
+ """Returns the HTML representation of the message.
524
+
525
+ Args:
526
+ view: The HTML tree view.
527
+ root_path: The root path of the message.
528
+ collapse_level: The global collapse level.
529
+ extra_flags: Extra flags to control the rendering.
530
+ - source_tag: tags to filter source messages. If None, the entire
531
+ source chain will be included.
532
+ - include_message_metadata: Whether to include the metadata of the
533
+ message.
534
+ - collapse_modalities_in_text: Whether to collapse the modalities in the
535
+ message text.
536
+ - collapse_llm_usage: Whether to collapse the usage in the message.
537
+ - collapse_message_result_level: The level to collapse the result in the
538
+ message.
539
+ - collapse_message_metadata_level: The level to collapse the metadata in
540
+ the message.
541
+ - collapse_source_message_level: The level to collapse the source in the
542
+ message.
543
+ - collapse_level: The global collapse level.
544
+ **kwargs: Omitted keyword arguments.
545
+
546
+ Returns:
547
+ The HTML representation of the message content.
548
+ """
549
+ extra_flags = extra_flags if extra_flags is not None else {}
550
+
551
+ include_message_metadata: bool = extra_flags.get(
552
+ 'include_message_metadata', True
553
+ )
554
+ source_tag: str | tuple[str, ...] | None = extra_flags.get(
555
+ 'source_tag', ('lm-input', 'lm-output')
556
+ )
557
+ collapse_modalities_in_text: bool = extra_flags.get(
558
+ 'collapse_modalities_in_text', True
559
+ )
560
+ collapse_llm_usage: bool = extra_flags.get(
561
+ 'collapse_llm_usage', False
562
+ )
563
+ collapse_message_result_level: int | None = extra_flags.get(
564
+ 'collapse_message_result_level', 1
565
+ )
566
+ collapse_message_metadata_level: int | None = extra_flags.get(
567
+ 'collapse_message_metadata_level', 1
568
+ )
569
+ collapse_source_message_level: int | None = extra_flags.get(
570
+ 'collapse_source_message_level', 1
571
+ )
572
+ passthrough_kwargs = view.get_passthrough_kwargs(**kwargs)
573
+ def render_tags():
574
+ return pg.Html.element(
575
+ 'div',
576
+ [pg.Html.element('span', [tag]) for tag in self.tags],
577
+ css_classes=['message-tags'],
578
+ )
579
+
580
+ def render_message_text():
581
+ maybe_reformatted = self.get('formatted_text')
582
+ referred_chunks = {}
583
+ s = pg.Html('<div class="message-text">')
584
+ for chunk in self.chunk(maybe_reformatted):
585
+ if isinstance(chunk, str):
586
+ s.write(s.escape(chunk))
587
+ else:
588
+ assert isinstance(chunk, modality.Modality), chunk
589
+ child_path = pg.KeyPath(['metadata', chunk.referred_name], root_path)
590
+ s.write(
591
+ pg.Html.element(
592
+ 'div',
593
+ [
594
+ view.render(
595
+ chunk,
596
+ name=chunk.referred_name,
597
+ root_path=child_path,
598
+ collapse_level=(
599
+ 0 if collapse_modalities_in_text else 1
600
+ ),
601
+ extra_flags=dict(
602
+ display_modality_when_hover=True,
603
+ ),
604
+ **passthrough_kwargs,
605
+ )
606
+ ],
607
+ css_classes=['modality-in-text'],
608
+ )
609
+ )
610
+ referred_chunks[chunk.referred_name] = chunk
611
+ s.write('</div>')
612
+ return s
613
+
614
+ def render_result():
615
+ if 'result' not in self.metadata:
616
+ return None
617
+ child_path = pg.KeyPath(['metadata', 'result'], root_path)
618
+ return pg.Html.element(
619
+ 'div',
620
+ [
621
+ view.render(
622
+ self.result,
623
+ name='result',
624
+ root_path=child_path,
625
+ collapse_level=view.get_collapse_level(
626
+ (collapse_level, -1),
627
+ collapse_message_result_level,
628
+ ),
629
+ extra_flags=extra_flags,
630
+ **passthrough_kwargs,
631
+ )
632
+ ],
633
+ css_classes=['message-result'],
634
+ )
635
+
636
+ def render_usage():
637
+ if 'usage' not in self.metadata:
638
+ return None
639
+ child_path = pg.KeyPath(['metadata', 'usage'], root_path)
640
+ return pg.Html.element(
641
+ 'div',
642
+ [
643
+ view.render(
644
+ self.usage,
645
+ name='llm usage',
646
+ key_style='label',
647
+ root_path=child_path,
648
+ collapse_level=view.get_collapse_level(
649
+ (collapse_level, -1),
650
+ 0 if collapse_llm_usage else 1,
651
+ ),
652
+ extra_flags=extra_flags,
653
+ **view.get_passthrough_kwargs(
654
+ remove=['key_style'], **kwargs
655
+ ),
656
+ )
657
+ ],
658
+ css_classes=['message-usage'],
659
+ )
660
+
661
+ def render_source_message():
662
+ source = self.source
663
+ while (source is not None
664
+ and source_tag is not None
665
+ and not source.has_tag(source_tag)):
666
+ source = source.source
667
+ if source is not None:
668
+ child_path = pg.KeyPath('source', root_path)
669
+ child_extra_flags = extra_flags.copy()
670
+ child_extra_flags['collapse_source_message_level'] = (
671
+ view.get_collapse_level(
672
+ (collapse_source_message_level, -1), 0,
673
+ )
674
+ )
675
+ return view.render(
676
+ self.source,
677
+ name='source',
678
+ root_path=child_path,
679
+ collapse_level=view.get_collapse_level(
680
+ (collapse_level, -1),
681
+ collapse_source_message_level,
682
+ ),
683
+ extra_flags=child_extra_flags,
684
+ **passthrough_kwargs,
685
+ )
686
+ return None
687
+
688
+ def render_metadata():
689
+ if not include_message_metadata:
690
+ return None
691
+ child_path = pg.KeyPath('metadata', root_path)
692
+ return pg.Html.element(
693
+ 'div',
694
+ [
695
+ view.render(
696
+ self.metadata,
697
+ css_classes=['message-metadata'],
698
+ exclude_keys=['usage', 'result'],
699
+ name='metadata',
700
+ root_path=child_path,
701
+ collapse_level=view.get_collapse_level(
702
+ (collapse_level, -1),
703
+ collapse_message_metadata_level,
704
+ ),
705
+ **view.get_passthrough_kwargs(
706
+ remove=['exclude_keys'], **kwargs
707
+ ),
708
+ )
709
+ ],
710
+ css_classes=['message-metadata'],
711
+ )
712
+
713
+ return pg.Html.element(
714
+ 'div',
715
+ [
716
+ render_tags(),
717
+ render_message_text(),
718
+ render_result(),
719
+ render_usage(),
720
+ render_metadata(),
721
+ render_source_message(),
722
+ ],
723
+ css_classes=['complex_value'],
724
+ )
725
+
726
+ @classmethod
727
+ @functools.cache
728
+ def _html_tree_view_config(cls) -> dict[str, Any]:
729
+ return pg.views.HtmlTreeView.get_kwargs(
730
+ super()._html_tree_view_config(),
731
+ dict(
732
+ css_classes=['lf-message'],
733
+ )
734
+ )
735
+
736
+ @classmethod
737
+ @functools.cache
738
+ def _html_tree_view_css_styles(cls) -> list[str]:
739
+ return super()._html_tree_view_css_styles() + [
740
+ """
741
+ /* Langfun Message styles.*/
742
+ [class^="message-"] > details {
743
+ margin: 0px 0px 5px 0px;
744
+ border: 1px solid #EEE;
745
+ }
746
+ .lf-message.summary-title::after {
747
+ content: ' 💬';
748
+ }
749
+ details.pyglove.ai-message {
750
+ border: 1px solid blue;
751
+ color: blue;
752
+ }
753
+ details.pyglove.user-message {
754
+ border: 1px solid green;
755
+ color: green;
756
+ }
757
+ .message-tags {
758
+ margin: 5px 0px 5px 0px;
759
+ font-size: .8em;
760
+ }
761
+ .message-tags > span {
762
+ border-radius: 5px;
763
+ background-color: #CCC;
764
+ padding: 3px;
765
+ margin: 0px 2px 0px 2px;
766
+ color: white;
767
+ }
768
+ .message-text {
769
+ padding: 20px;
770
+ margin: 10px 5px 10px 5px;
771
+ font-style: italic;
772
+ white-space: pre-wrap;
773
+ border: 1px solid #EEE;
774
+ border-radius: 5px;
775
+ background-color: #EEE;
776
+ }
777
+ .modality-in-text {
778
+ display: inline-block;
779
+ }
780
+ .modality-in-text > details.pyglove {
781
+ display: inline-block;
782
+ font-size: 0.8em;
783
+ border: 0;
784
+ background-color: #A6F1A6;
785
+ margin: 0px 5px 0px 5px;
786
+ }
787
+ .message-result {
788
+ color: dodgerblue;
789
+ }
790
+ .message-usage {
791
+ color: orange;
792
+ }
793
+ .message-usage .object-key.str {
794
+ border: 1px solid orange;
795
+ background-color: orange;
796
+ color: white;
797
+ }
798
+ """
799
+ ]
800
+
491
801
 
492
802
  #
493
803
  # Messages of different roles.