langfun 0.1.2.dev202509120804__py3-none-any.whl → 0.1.2.dev202512150805__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 (162) hide show
  1. langfun/__init__.py +1 -1
  2. langfun/core/__init__.py +7 -1
  3. langfun/core/agentic/__init__.py +8 -1
  4. langfun/core/agentic/action.py +740 -112
  5. langfun/core/agentic/action_eval.py +9 -2
  6. langfun/core/agentic/action_test.py +189 -24
  7. langfun/core/async_support.py +104 -5
  8. langfun/core/async_support_test.py +23 -0
  9. langfun/core/coding/python/correction.py +19 -9
  10. langfun/core/coding/python/execution.py +14 -12
  11. langfun/core/coding/python/generation.py +21 -16
  12. langfun/core/coding/python/sandboxing.py +23 -3
  13. langfun/core/component.py +42 -3
  14. langfun/core/concurrent.py +70 -6
  15. langfun/core/concurrent_test.py +9 -2
  16. langfun/core/console.py +1 -1
  17. langfun/core/data/conversion/anthropic.py +12 -3
  18. langfun/core/data/conversion/anthropic_test.py +8 -6
  19. langfun/core/data/conversion/gemini.py +11 -2
  20. langfun/core/data/conversion/gemini_test.py +48 -9
  21. langfun/core/data/conversion/openai.py +145 -31
  22. langfun/core/data/conversion/openai_test.py +161 -17
  23. langfun/core/eval/base.py +48 -44
  24. langfun/core/eval/base_test.py +5 -5
  25. langfun/core/eval/matching.py +5 -2
  26. langfun/core/eval/patching.py +3 -3
  27. langfun/core/eval/scoring.py +4 -3
  28. langfun/core/eval/v2/__init__.py +3 -0
  29. langfun/core/eval/v2/checkpointing.py +148 -46
  30. langfun/core/eval/v2/checkpointing_test.py +9 -2
  31. langfun/core/eval/v2/config_saver.py +37 -0
  32. langfun/core/eval/v2/config_saver_test.py +36 -0
  33. langfun/core/eval/v2/eval_test_helper.py +104 -3
  34. langfun/core/eval/v2/evaluation.py +102 -19
  35. langfun/core/eval/v2/evaluation_test.py +9 -3
  36. langfun/core/eval/v2/example.py +50 -40
  37. langfun/core/eval/v2/example_test.py +16 -8
  38. langfun/core/eval/v2/experiment.py +95 -20
  39. langfun/core/eval/v2/experiment_test.py +19 -0
  40. langfun/core/eval/v2/metric_values.py +31 -3
  41. langfun/core/eval/v2/metric_values_test.py +32 -0
  42. langfun/core/eval/v2/metrics.py +157 -44
  43. langfun/core/eval/v2/metrics_test.py +39 -18
  44. langfun/core/eval/v2/progress.py +31 -1
  45. langfun/core/eval/v2/progress_test.py +27 -0
  46. langfun/core/eval/v2/progress_tracking.py +13 -5
  47. langfun/core/eval/v2/progress_tracking_test.py +9 -1
  48. langfun/core/eval/v2/reporting.py +88 -71
  49. langfun/core/eval/v2/reporting_test.py +24 -6
  50. langfun/core/eval/v2/runners/__init__.py +30 -0
  51. langfun/core/eval/v2/{runners.py → runners/base.py} +73 -180
  52. langfun/core/eval/v2/runners/beam.py +354 -0
  53. langfun/core/eval/v2/runners/beam_test.py +153 -0
  54. langfun/core/eval/v2/runners/ckpt_monitor.py +350 -0
  55. langfun/core/eval/v2/runners/ckpt_monitor_test.py +213 -0
  56. langfun/core/eval/v2/runners/debug.py +40 -0
  57. langfun/core/eval/v2/runners/debug_test.py +76 -0
  58. langfun/core/eval/v2/runners/parallel.py +243 -0
  59. langfun/core/eval/v2/runners/parallel_test.py +182 -0
  60. langfun/core/eval/v2/runners/sequential.py +47 -0
  61. langfun/core/eval/v2/runners/sequential_test.py +169 -0
  62. langfun/core/langfunc.py +45 -130
  63. langfun/core/langfunc_test.py +7 -5
  64. langfun/core/language_model.py +189 -36
  65. langfun/core/language_model_test.py +54 -3
  66. langfun/core/llms/__init__.py +14 -1
  67. langfun/core/llms/anthropic.py +157 -2
  68. langfun/core/llms/azure_openai.py +29 -17
  69. langfun/core/llms/cache/base.py +25 -3
  70. langfun/core/llms/cache/in_memory.py +48 -7
  71. langfun/core/llms/cache/in_memory_test.py +14 -4
  72. langfun/core/llms/compositional.py +25 -1
  73. langfun/core/llms/deepseek.py +30 -2
  74. langfun/core/llms/fake.py +32 -1
  75. langfun/core/llms/gemini.py +90 -12
  76. langfun/core/llms/gemini_test.py +110 -0
  77. langfun/core/llms/google_genai.py +52 -1
  78. langfun/core/llms/groq.py +28 -3
  79. langfun/core/llms/llama_cpp.py +23 -4
  80. langfun/core/llms/openai.py +120 -3
  81. langfun/core/llms/openai_compatible.py +148 -27
  82. langfun/core/llms/openai_compatible_test.py +207 -20
  83. langfun/core/llms/openai_test.py +0 -2
  84. langfun/core/llms/rest.py +16 -1
  85. langfun/core/llms/vertexai.py +78 -8
  86. langfun/core/logging.py +1 -1
  87. langfun/core/mcp/__init__.py +10 -0
  88. langfun/core/mcp/client.py +177 -0
  89. langfun/core/mcp/client_test.py +71 -0
  90. langfun/core/mcp/session.py +241 -0
  91. langfun/core/mcp/session_test.py +54 -0
  92. langfun/core/mcp/testing/simple_mcp_client.py +33 -0
  93. langfun/core/mcp/testing/simple_mcp_server.py +33 -0
  94. langfun/core/mcp/tool.py +254 -0
  95. langfun/core/mcp/tool_test.py +197 -0
  96. langfun/core/memory.py +1 -0
  97. langfun/core/message.py +160 -55
  98. langfun/core/message_test.py +65 -81
  99. langfun/core/modalities/__init__.py +8 -0
  100. langfun/core/modalities/audio.py +21 -1
  101. langfun/core/modalities/image.py +73 -3
  102. langfun/core/modalities/image_test.py +116 -0
  103. langfun/core/modalities/mime.py +78 -4
  104. langfun/core/modalities/mime_test.py +59 -0
  105. langfun/core/modalities/pdf.py +19 -1
  106. langfun/core/modalities/video.py +21 -1
  107. langfun/core/modality.py +167 -29
  108. langfun/core/modality_test.py +42 -12
  109. langfun/core/natural_language.py +1 -1
  110. langfun/core/sampling.py +4 -4
  111. langfun/core/sampling_test.py +20 -4
  112. langfun/core/structured/__init__.py +2 -24
  113. langfun/core/structured/completion.py +34 -44
  114. langfun/core/structured/completion_test.py +23 -43
  115. langfun/core/structured/description.py +54 -50
  116. langfun/core/structured/function_generation.py +29 -12
  117. langfun/core/structured/mapping.py +81 -37
  118. langfun/core/structured/parsing.py +95 -79
  119. langfun/core/structured/parsing_test.py +0 -3
  120. langfun/core/structured/querying.py +230 -154
  121. langfun/core/structured/querying_test.py +69 -33
  122. langfun/core/structured/schema/__init__.py +49 -0
  123. langfun/core/structured/schema/base.py +664 -0
  124. langfun/core/structured/schema/base_test.py +531 -0
  125. langfun/core/structured/schema/json.py +174 -0
  126. langfun/core/structured/schema/json_test.py +121 -0
  127. langfun/core/structured/schema/python.py +316 -0
  128. langfun/core/structured/schema/python_test.py +410 -0
  129. langfun/core/structured/schema_generation.py +33 -14
  130. langfun/core/structured/scoring.py +47 -36
  131. langfun/core/structured/tokenization.py +26 -11
  132. langfun/core/subscription.py +2 -2
  133. langfun/core/template.py +175 -50
  134. langfun/core/template_test.py +123 -17
  135. langfun/env/__init__.py +43 -0
  136. langfun/env/base_environment.py +827 -0
  137. langfun/env/base_environment_test.py +473 -0
  138. langfun/env/base_feature.py +304 -0
  139. langfun/env/base_feature_test.py +228 -0
  140. langfun/env/base_sandbox.py +842 -0
  141. langfun/env/base_sandbox_test.py +1235 -0
  142. langfun/env/event_handlers/__init__.py +14 -0
  143. langfun/env/event_handlers/chain.py +233 -0
  144. langfun/env/event_handlers/chain_test.py +253 -0
  145. langfun/env/event_handlers/event_logger.py +472 -0
  146. langfun/env/event_handlers/event_logger_test.py +304 -0
  147. langfun/env/event_handlers/metric_writer.py +726 -0
  148. langfun/env/event_handlers/metric_writer_test.py +214 -0
  149. langfun/env/interface.py +1640 -0
  150. langfun/env/interface_test.py +153 -0
  151. langfun/env/load_balancers.py +59 -0
  152. langfun/env/load_balancers_test.py +141 -0
  153. langfun/env/test_utils.py +507 -0
  154. {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512150805.dist-info}/METADATA +7 -3
  155. langfun-0.1.2.dev202512150805.dist-info/RECORD +217 -0
  156. langfun/core/eval/v2/runners_test.py +0 -343
  157. langfun/core/structured/schema.py +0 -987
  158. langfun/core/structured/schema_test.py +0 -982
  159. langfun-0.1.2.dev202509120804.dist-info/RECORD +0 -172
  160. {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512150805.dist-info}/WHEEL +0 -0
  161. {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512150805.dist-info}/licenses/LICENSE +0 -0
  162. {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512150805.dist-info}/top_level.txt +0 -0
@@ -29,34 +29,64 @@ class ModalityTest(unittest.TestCase):
29
29
 
30
30
  def test_basic(self):
31
31
  v = CustomModality('a')
32
- self.assertIsNone(v.referred_name)
32
+ self.assertEqual(v.id, 'custom_modality:0cc175b9')
33
33
  self.assertEqual(str(v), "CustomModality(\n content = 'a'\n)")
34
34
  self.assertEqual(v.hash, '0cc175b9')
35
35
 
36
36
  _ = pg.Dict(metadata=pg.Dict(x=pg.Dict(metadata=pg.Dict(y=v))))
37
- self.assertEqual(v.referred_name, 'x.metadata.y')
37
+ self.assertEqual(v.id, 'custom_modality:0cc175b9')
38
38
  self.assertEqual(str(v), "CustomModality(\n content = 'a'\n)")
39
39
  with modality.format_modality_as_ref():
40
- self.assertEqual(str(v), '<<[[x.metadata.y]]>>')
40
+ self.assertEqual(str(v), '<<[[custom_modality:0cc175b9]]>>')
41
+
42
+ def test_capture_rendered_modalities(self):
43
+ x = CustomModality('a')
44
+ y = CustomModality('b')
45
+ z = CustomModality('b')
46
+
47
+ with modality.capture_rendered_modalities() as rendered_modalities:
48
+ with modality.format_modality_as_ref():
49
+ self.assertEqual(
50
+ f'Hello {x} {y} {z}',
51
+ (
52
+ 'Hello <<[[custom_modality:0cc175b9]]>> '
53
+ '<<[[custom_modality:92eb5ffe]]>> '
54
+ '<<[[custom_modality:92eb5ffe]]>>'
55
+ )
56
+ )
57
+ self.assertEqual(len(rendered_modalities), 2)
58
+ self.assertIs(rendered_modalities['custom_modality:0cc175b9'].value, x)
59
+ # y and z share the same content will be treated as the same object.
60
+ self.assertIs(rendered_modalities['custom_modality:92eb5ffe'].value, z)
41
61
 
42
62
 
43
63
  class ModalityRefTest(unittest.TestCase):
44
64
 
45
- def test_placehold(self):
65
+ def test_placehold_and_restore(self):
46
66
  class A(pg.Object):
47
67
  x: Any
48
68
  y: Any
49
69
 
50
- a = A(x=dict(z=CustomModality('a')), y=CustomModality('b'))
70
+ image_a = CustomModality('a')
71
+ image_b = CustomModality('b')
72
+ a = A(x=dict(z=image_a), y=image_b)
73
+ a_placehold = modality.ModalityRef.placehold(a)
51
74
  self.assertEqual(
52
- modality.ModalityRef.placehold(a),
53
- A(x=dict(z=modality.ModalityRef('x.z')), y=modality.ModalityRef('y')),
75
+ a_placehold,
76
+ A(x=dict(z=modality.ModalityRef(image_a.id)),
77
+ y=modality.ModalityRef(image_b.id)),
78
+ )
79
+ a_restore = modality.ModalityRef.restore(
80
+ a_placehold.clone(),
81
+ {image_a.id: image_a, image_b.id: image_b},
54
82
  )
83
+ self.assertTrue(pg.eq(a_restore, a))
55
84
  self.assertEqual(
56
85
  modality.ModalityRef.placehold(a.x),
57
- # The prefix 'x' of referred name is preserved.
58
- dict(z=modality.ModalityRef('x.z')),
86
+ dict(z=modality.ModalityRef(image_a.id)),
59
87
  )
88
+ with self.assertRaisesRegex(ValueError, 'Modality .* not found'):
89
+ modality.ModalityRef.restore(a_placehold, {image_a.id: image_a})
60
90
 
61
91
  def test_from_value(self):
62
92
  class A(pg.Object):
@@ -68,8 +98,8 @@ class ModalityRefTest(unittest.TestCase):
68
98
  pg.eq(
69
99
  modality.Modality.from_value(a),
70
100
  {
71
- 'x.z': CustomModality('a'),
72
- 'y': CustomModality('b'),
101
+ 'custom_modality:0cc175b9': CustomModality('a'),
102
+ 'custom_modality:92eb5ffe': CustomModality('b'),
73
103
  },
74
104
  )
75
105
  )
@@ -77,7 +107,7 @@ class ModalityRefTest(unittest.TestCase):
77
107
  pg.eq(
78
108
  modality.Modality.from_value(a.x.z),
79
109
  {
80
- 'x.z': CustomModality('a'),
110
+ 'custom_modality:0cc175b9': CustomModality('a'),
81
111
  },
82
112
  )
83
113
  )
@@ -11,7 +11,7 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
- """Natural language utilities."""
14
+ """Natural language formatting."""
15
15
 
16
16
  import abc
17
17
  import pyglove as pg
langfun/core/sampling.py CHANGED
@@ -38,10 +38,10 @@ def sweep(
38
38
  Union[message_lib.Message, BaseException, None], # LM output.
39
39
  ],
40
40
  ]:
41
- """Sweeps the input/output of this LangFunc concurrently.
41
+ """Sweeps the input/output of a LangFunc search space concurrently.
42
42
 
43
43
  Args:
44
- lfun: An LangFunc object that contains `pg.oneof` as the search space
44
+ lfun: An LangFunc object that contains `pg.oneof` as the search space
45
45
  for sampling.
46
46
  num_examples: Number of examples to sample.
47
47
  max_workers: Max number of concurrent workers to do sampling.
@@ -84,10 +84,10 @@ def random_sample(
84
84
  Union[message_lib.Message, BaseException, None], # LM output.
85
85
  ],
86
86
  ]:
87
- """Random samples the input/output of this LangFunc concurrently.
87
+ """Random samples the input/output of a LangFunc search space concurrently.
88
88
 
89
89
  Args:
90
- lfun: An LangFunc object that contains `pg.oneof` as the search space
90
+ lfun: An LangFunc object that contains `pg.oneof` as the search space
91
91
  for sampling.
92
92
  num_examples: Number of examples to sample.
93
93
  max_workers: Max number of concurrent workers to do sampling.
@@ -39,8 +39,13 @@ class SamplingTest(unittest.TestCase):
39
39
  l = LangFunc('Compute {{x}} and {{y}}', x=pg.oneof([1, 2]))
40
40
  with component.context(lm=ExcitedEchoer()):
41
41
  samples = list(sampling.sweep(l, y=pg.oneof([3, 4])))
42
- samples = sorted(samples, key=lambda x: (x[0].x, x[0].y))
43
-
42
+ samples = sorted(
43
+ samples,
44
+ key=lambda x: (
45
+ x[0].__template_input__.x,
46
+ x[0].__template_input__.y
47
+ )
48
+ )
44
49
  self.assertEqual(
45
50
  samples,
46
51
  [
@@ -57,7 +62,12 @@ class SamplingTest(unittest.TestCase):
57
62
  samples = list(
58
63
  sampling.random_sample(l, y=pg.oneof([2, 4]), num_examples=3, seed=1)
59
64
  )
60
- samples = sorted(samples, key=lambda x: (x[0].x, x[0].y))
65
+ samples = sorted(
66
+ samples, key=lambda x: (
67
+ x[0].__template_input__.x,
68
+ x[0].__template_input__.y
69
+ )
70
+ )
61
71
 
62
72
  self.assertEqual(
63
73
  samples,
@@ -97,7 +107,13 @@ class SamplingTest(unittest.TestCase):
97
107
  silence_on_errors=(AttributeError,),
98
108
  ignore_examples_with_errors=False))
99
109
 
100
- samples = sorted(samples, key=lambda x: (x[0].x, x[0].y))
110
+ samples = sorted(
111
+ samples,
112
+ key=lambda x: (
113
+ x[0].__template_input__.x,
114
+ x[0].__template_input__.y
115
+ )
116
+ )
101
117
  self.assertEqual(
102
118
  [x[0] for x in samples],
103
119
  [
@@ -1,4 +1,4 @@
1
- # Copyright 2023 The Langfun Authors
1
+ # Copyright 2025 The Langfun Authors
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -16,29 +16,7 @@
16
16
  # pylint: disable=g-bad-import-order
17
17
  # pylint: disable=g-importing-member
18
18
 
19
- from langfun.core.structured.schema import include_method_in_prompt
20
-
21
- from langfun.core.structured.schema import Missing
22
- from langfun.core.structured.schema import MISSING
23
- from langfun.core.structured.schema import Unknown
24
- from langfun.core.structured.schema import UNKNOWN
25
-
26
- from langfun.core.structured.schema import Schema
27
- from langfun.core.structured.schema import SchemaProtocol
28
- from langfun.core.structured.schema import schema_spec
29
-
30
- from langfun.core.structured.schema import SchemaError
31
- from langfun.core.structured.schema import JsonError
32
-
33
- from langfun.core.structured.schema import class_dependencies
34
- from langfun.core.structured.schema import class_definition
35
- from langfun.core.structured.schema import class_definitions
36
- from langfun.core.structured.schema import annotation
37
- from langfun.core.structured.schema import structure_from_python
38
-
39
- from langfun.core.structured.schema import schema_repr
40
- from langfun.core.structured.schema import source_form
41
- from langfun.core.structured.schema import value_repr
19
+ from langfun.core.structured.schema import *
42
20
 
43
21
  from langfun.core.structured.schema_generation import generate_class
44
22
  from langfun.core.structured.schema_generation import classgen_example
@@ -116,15 +116,10 @@ class _CompleteStructure(mapping.Mapping):
116
116
  )
117
117
 
118
118
  def postprocess_result(self, result: Any) -> Any:
119
- """Postprocess result."""
119
+ """Postprocesses result."""
120
120
  # Try restore modality objects from the input value to output value.
121
- modalities = self.modalities(self.input)
122
- if modalities:
123
- # Remove the `input` prefix for all entries.
124
- modalities = pg.object_utils.flatten(
125
- pg.object_utils.canonicalize(modalities)['input']
126
- )
127
- result.rebind(modalities)
121
+ if modalities := self.modalities(self.input):
122
+ result = lf.ModalityRef.restore(result, modalities)
128
123
  return result
129
124
 
130
125
  def globals(self):
@@ -156,7 +151,7 @@ class _CompleteStructure(mapping.Mapping):
156
151
  #
157
152
 
158
153
  def has_modality_refs(self, value: Any) -> bool:
159
- """Returns true if the value has modalities."""
154
+ """Returns True if the value has modalities."""
160
155
  return not isinstance(value, lf.Modality) and pg.contains(
161
156
  value, type=lf.Modality
162
157
  )
@@ -186,41 +181,36 @@ def complete(
186
181
  returns_message: bool = False,
187
182
  **kwargs,
188
183
  ) -> Any:
189
- """Complete a symbolic value by filling its missing fields.
190
-
191
- Examples:
192
-
193
- ```
194
- class FlightDuration:
195
- hours: int
196
- minutes: int
197
-
198
- class Flight(pg.Object):
199
- airline: str
200
- flight_number: str
201
- departure_airport_code: str
202
- arrival_airport_code: str
203
- departure_time: str
204
- arrival_time: str
205
- duration: FlightDuration
206
- stops: int
207
- price: float
208
-
209
- prompt = '''
210
- Information about flight UA2631.
211
- '''
212
-
213
- r = lf.query(prompt, Flight)
214
- assert isinstance(r, Flight)
215
- assert r.airline == 'United Airlines'
216
- assert r.departure_airport_code == 'SFO'
217
- assert r.duration.hour = 7
218
- ```
184
+ """Completes a symbolic value by filling its missing fields using an LLM.
185
+
186
+ `lf.complete` is used to fill in missing information in structured
187
+ data. It takes a partially defined `pg.Object` instance where some fields
188
+ are marked as `lf.MISSING`, and uses a language model to infer and
189
+ populate those fields based on the provided values.
190
+
191
+ **Example:**
192
+
193
+ ```python
194
+ import langfun as lf
195
+ import pyglove as pg
196
+
197
+ class Country(pg.Object):
198
+ name: str
199
+ capital: str = lf.MISSING
200
+ population: int = lf.MISSING
201
+
202
+ # Filling missing fields of Country(name='France')
203
+ country = lf.complete(Country(name='France'), lm=lf.llms.Gemini25Flash())
204
+ print(country)
205
+ # Output: Country(name='France', capital='Paris', population=67000000)
206
+ ```
219
207
 
220
208
  Args:
221
- input_value: A symbolic value that may contain missing values.
222
- default: The default value if parsing failed. If not specified, error will
223
- be raised.
209
+ input_value: A symbolic value that may contain missing values marked
210
+ by `lf.MISSING`.
211
+ default: The default value to return if parsing fails. If
212
+ `lf.RAISE_IF_HAS_ERROR` is used (default), an error will be raised
213
+ instead.
224
214
  lm: The language model to use. If not specified, the language model from
225
215
  `lf.context` context manager will be used.
226
216
  examples: An optional list of fewshot examples for helping parsing. If None,
@@ -236,10 +226,10 @@ def complete(
236
226
  returns_message: If True, returns `lf.Message` as the output, instead of
237
227
  returning the structured `message.result`.
238
228
  **kwargs: Keyword arguments passed to the
239
- `lf.structured.NaturalLanguageToStructureed` transform.
229
+ `lf.structured.Mapping` transform.
240
230
 
241
231
  Returns:
242
- The result based on the schema.
232
+ The input object with missing fields completed by LLM.
243
233
  """
244
234
  t = _CompleteStructure(
245
235
  input=schema_lib.mark_missing(input_value),
@@ -407,22 +407,17 @@ class CompleteStructureTest(unittest.TestCase):
407
407
  image: modalities.Image
408
408
  name: str
409
409
 
410
+ image_elephant = modalities.Image.from_bytes(b'image_of_elephant')
411
+ image_rabbit = modalities.Image.from_bytes(b'image_of_rabbit')
410
412
  input_value = schema_lib.mark_missing(
411
- Animal.partial(
412
- modalities.Image.from_bytes(b'image_of_elephant'),
413
- )
413
+ Animal.partial(image_elephant)
414
414
  )
415
415
  l = completion._CompleteStructure(
416
416
  input=input_value,
417
417
  examples=[
418
418
  mapping.MappingExample(
419
- input=Animal.partial(
420
- modalities.Image.from_bytes(b'image_of_rabbit')
421
- ),
422
- output=Animal(
423
- modalities.Image.from_bytes(b'image_of_rabbit'),
424
- 'rabbit',
425
- ),
419
+ input=Animal.partial(image_rabbit),
420
+ output=Animal(image_rabbit, 'rabbit'),
426
421
  )
427
422
  ],
428
423
  )
@@ -430,7 +425,7 @@ class CompleteStructureTest(unittest.TestCase):
430
425
  self.maxDiff = None
431
426
  self.assertEqual(
432
427
  lm_input.text,
433
- inspect.cleandoc("""
428
+ inspect.cleandoc(f"""
434
429
  Please generate the OUTPUT_OBJECT by completing the MISSING fields from the last INPUT_OBJECT.
435
430
 
436
431
  INSTRUCTIONS:
@@ -457,22 +452,22 @@ class CompleteStructureTest(unittest.TestCase):
457
452
  ```python
458
453
  Animal(
459
454
  image=ModalityRef(
460
- name='examples[0].input.image'
455
+ id='{image_rabbit.id}'
461
456
  ),
462
457
  name=MISSING(str)
463
458
  )
464
459
  ```
465
460
 
466
461
  MODALITY_REFERENCES:
467
- {
468
- 'examples[0].input.image': <<[[examples[0].input.image]]>>
469
- }
462
+ {{
463
+ '{image_rabbit.id}': <<[[{image_rabbit.id}]]>>
464
+ }}
470
465
 
471
466
  OUTPUT_OBJECT:
472
467
  ```python
473
468
  Animal(
474
469
  image=ModalityRef(
475
- name='examples[0].output.image'
470
+ id='{image_rabbit.id}'
476
471
  ),
477
472
  name='rabbit'
478
473
  )
@@ -483,16 +478,16 @@ class CompleteStructureTest(unittest.TestCase):
483
478
  ```python
484
479
  Animal(
485
480
  image=ModalityRef(
486
- name='input.image'
481
+ id='{image_elephant.id}'
487
482
  ),
488
483
  name=MISSING(str)
489
484
  )
490
485
  ```
491
486
 
492
487
  MODALITY_REFERENCES:
493
- {
494
- 'input.image': <<[[input.image]]>>
495
- }
488
+ {{
489
+ '{image_elephant.id}': <<[[{image_elephant.id}]]>>
490
+ }}
496
491
 
497
492
  OUTPUT_OBJECT:
498
493
  """),
@@ -500,39 +495,27 @@ class CompleteStructureTest(unittest.TestCase):
500
495
  self.assertTrue(
501
496
  pg.eq(
502
497
  {
503
- 'examples': lm_input.get('examples'),
504
- 'input': lm_input.get('input'),
498
+ 'examples': lm_input.__template_input__.examples,
499
+ 'input': lm_input.__template_input__.mapping_request.input,
505
500
  },
506
501
  {
507
502
  'examples': [
508
503
  mapping.MappingExample(
509
- input=Animal.partial(
510
- image=modalities.Image.from_bytes(
511
- b'image_of_rabbit'
512
- )
513
- ),
514
- output=Animal.partial(
515
- image=modalities.Image.from_bytes(
516
- b'image_of_rabbit'
517
- ),
518
- name='rabbit',
519
- ),
504
+ input=Animal.partial(image_rabbit),
505
+ output=Animal.partial(image_rabbit, 'rabbit'),
520
506
  )
521
507
  ],
522
- 'input': Animal(
523
- image=modalities.Image.from_bytes(b'image_of_elephant'),
524
- name=schema_lib.MISSING,
525
- ),
508
+ 'input': Animal(image_elephant, name=schema_lib.MISSING),
526
509
  },
527
510
  )
528
511
  )
529
512
  lm_output = l(
530
513
  input=input_value,
531
- lm=fake.StaticResponse(inspect.cleandoc("""
514
+ lm=fake.StaticResponse(inspect.cleandoc(f"""
532
515
  ```python
533
516
  Animal(
534
517
  image=ModalityRef(
535
- name='input.image'
518
+ id='{image_elephant.id}'
536
519
  ),
537
520
  name='elephant'
538
521
  )
@@ -542,10 +525,7 @@ class CompleteStructureTest(unittest.TestCase):
542
525
  self.assertTrue(
543
526
  pg.eq(
544
527
  lm_output.result,
545
- Animal(
546
- image=modalities.Image.from_bytes(b'image_of_elephant'),
547
- name='elephant',
548
- ),
528
+ Animal(image=image_elephant, name='elephant'),
549
529
  )
550
530
  )
551
531
 
@@ -23,7 +23,7 @@ import pyglove as pg
23
23
 
24
24
  @pg.use_init_args(['examples'])
25
25
  class _DescribeStructure(mapping.Mapping):
26
- """Describe a structured value in natural language."""
26
+ """Describes a structured value in natural language."""
27
27
 
28
28
  input_title = 'PYTHON_OBJECT'
29
29
  context_title = 'CONTEXT_FOR_DESCRIPTION'
@@ -47,64 +47,68 @@ def describe(
47
47
  cache_seed: int | None = 0,
48
48
  **kwargs,
49
49
  ) -> str:
50
- """Describes a structured value using natural language.
51
-
52
- Examples:
53
-
54
- ```
55
- class FlightDuration(pg.Object):
56
- hours: int
57
- minutes: int
58
-
59
- class Flight(pg.Object):
60
- airline: str
61
- flight_number: str
62
- departure_airport: str
63
- arrival_airport: str
64
- departure_time: str
65
- arrival_time: str
66
- duration: FlightDuration
67
- stops: int
68
- price: float
69
-
70
- text = lf.describe(
71
- Flight(
72
- airline='United Airlines',
73
- flight_number='UA2631',
74
- depature_airport: 'SFO',
75
- arrival_airport: 'JFK',
76
- depature_time: '2023-09-07T05:15:00',
77
- arrival_time: '2023-09-07T12:12:00',
78
- duration: FlightDuration(
79
- hours=7,
80
- minutes=57
81
- ),
82
- stops=1,
83
- price=227,
84
- ))
85
- print(text)
86
-
87
- >> The flight is operated by United Airlines, has the flight number UA2631,
88
- >> departs from San Francisco International Airport (SFO), arrives at John
89
- >> F. Kennedy International Airport (JFK), It departs at
90
- >> 2023-09-07T05:15:00, arrives at 2023-09-07T12:12:00, has a duration of 7
91
- >> hours and 57 minutes, makes 1 stop, and costs $227.
92
- ```
50
+ """Describes a structured value in natural language using an LLM.
51
+
52
+ `lf.describe` takes a Python object, often a `pg.Object` instance,
53
+ and uses a language model to generate a human-readable, natural language
54
+ description of its content. It is the inverse of `lf.parse`.
55
+
56
+ **Example:**
57
+
58
+ ```python
59
+ import langfun as lf
60
+ import pyglove as pg
61
+
62
+ class FlightDuration(pg.Object):
63
+ hours: int
64
+ minutes: int
65
+
66
+ class Flight(pg.Object):
67
+ airline: str
68
+ flight_number: str
69
+ departure_airport: str
70
+ arrival_airport: str
71
+ departure_time: str
72
+ arrival_time: str
73
+ duration: FlightDuration
74
+ stops: int
75
+ price: float
76
+
77
+ flight_info = Flight(
78
+ airline='United Airlines',
79
+ flight_number='UA2631',
80
+ departure_airport='SFO',
81
+ arrival_airport='JFK',
82
+ departure_time='2023-09-07T05:15:00',
83
+ arrival_time='2023-09-07T12:12:00',
84
+ duration=FlightDuration(hours=7, minutes=57),
85
+ stops=1,
86
+ price=227,
87
+ )
88
+
89
+ description = lf.describe(flight_info, lm=lf.llms.Gemini25Flash())
90
+ print(description)
91
+ # Possible output:
92
+ # The flight is operated by United Airlines, with the flight number UA2631,
93
+ # departing from SFO at 2023-09-07T05:15:00 and arriving at JFK at
94
+ # 2023-09-07T12:12:00. The flight duration is 7 hours and 57 minutes,
95
+ # with 1 stop, and costs $227.
96
+ ```
93
97
 
94
98
  Args:
95
99
  value: A structured value to be mapped.
96
100
  context: The context information for describing the structured value.
97
101
  lm: The language model to use. If not specified, the language model from
98
102
  `lf.context` context manager will be used.
99
- examples: An optional list of fewshot examples for helping parsing. If None,
100
- the default one-shot example will be added.
103
+ examples: An optional list of fewshot examples for guiding description.
104
+ If None, default examples will be used.
101
105
  cache_seed: Seed for computing cache key. The cache key is determined by a
102
106
  tuple of (lm, prompt, cache seed). If None, cache will be disabled for
103
107
  the query even cache is configured by the LM.
104
- **kwargs: Keyword arguments passed to the `lf.structured.DescribeStructure`.
108
+ **kwargs: Keyword arguments passed to the `_DescribeStructure`.
105
109
 
106
110
  Returns:
107
- The parsed result based on the schema.
111
+ A natural language description of the input value.
108
112
  """
109
113
  return _DescribeStructure(
110
114
  input=value,
@@ -115,10 +119,10 @@ def describe(
115
119
 
116
120
 
117
121
  def default_describe_examples() -> list[mapping.MappingExample]:
118
- """Default describe examples."""
122
+ """Returns default examples for `lf.describe`."""
119
123
 
120
124
  class Country(pg.Object):
121
- """A example dataclass for structured mapping."""
125
+ """An example dataclass for structured mapping."""
122
126
 
123
127
  name: str
124
128
  continents: list[