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.
- langfun/__init__.py +1 -1
- langfun/core/__init__.py +7 -1
- langfun/core/agentic/__init__.py +8 -1
- langfun/core/agentic/action.py +740 -112
- langfun/core/agentic/action_eval.py +9 -2
- langfun/core/agentic/action_test.py +189 -24
- langfun/core/async_support.py +104 -5
- langfun/core/async_support_test.py +23 -0
- langfun/core/coding/python/correction.py +19 -9
- langfun/core/coding/python/execution.py +14 -12
- langfun/core/coding/python/generation.py +21 -16
- langfun/core/coding/python/sandboxing.py +23 -3
- langfun/core/component.py +42 -3
- langfun/core/concurrent.py +70 -6
- langfun/core/concurrent_test.py +9 -2
- langfun/core/console.py +1 -1
- langfun/core/data/conversion/anthropic.py +12 -3
- langfun/core/data/conversion/anthropic_test.py +8 -6
- langfun/core/data/conversion/gemini.py +11 -2
- langfun/core/data/conversion/gemini_test.py +48 -9
- langfun/core/data/conversion/openai.py +145 -31
- langfun/core/data/conversion/openai_test.py +161 -17
- langfun/core/eval/base.py +48 -44
- langfun/core/eval/base_test.py +5 -5
- langfun/core/eval/matching.py +5 -2
- langfun/core/eval/patching.py +3 -3
- langfun/core/eval/scoring.py +4 -3
- langfun/core/eval/v2/__init__.py +3 -0
- langfun/core/eval/v2/checkpointing.py +148 -46
- langfun/core/eval/v2/checkpointing_test.py +9 -2
- langfun/core/eval/v2/config_saver.py +37 -0
- langfun/core/eval/v2/config_saver_test.py +36 -0
- langfun/core/eval/v2/eval_test_helper.py +104 -3
- langfun/core/eval/v2/evaluation.py +102 -19
- langfun/core/eval/v2/evaluation_test.py +9 -3
- langfun/core/eval/v2/example.py +50 -40
- langfun/core/eval/v2/example_test.py +16 -8
- langfun/core/eval/v2/experiment.py +95 -20
- langfun/core/eval/v2/experiment_test.py +19 -0
- langfun/core/eval/v2/metric_values.py +31 -3
- langfun/core/eval/v2/metric_values_test.py +32 -0
- langfun/core/eval/v2/metrics.py +157 -44
- langfun/core/eval/v2/metrics_test.py +39 -18
- langfun/core/eval/v2/progress.py +31 -1
- langfun/core/eval/v2/progress_test.py +27 -0
- langfun/core/eval/v2/progress_tracking.py +13 -5
- langfun/core/eval/v2/progress_tracking_test.py +9 -1
- langfun/core/eval/v2/reporting.py +88 -71
- langfun/core/eval/v2/reporting_test.py +24 -6
- langfun/core/eval/v2/runners/__init__.py +30 -0
- langfun/core/eval/v2/{runners.py → runners/base.py} +73 -180
- langfun/core/eval/v2/runners/beam.py +354 -0
- langfun/core/eval/v2/runners/beam_test.py +153 -0
- langfun/core/eval/v2/runners/ckpt_monitor.py +350 -0
- langfun/core/eval/v2/runners/ckpt_monitor_test.py +213 -0
- langfun/core/eval/v2/runners/debug.py +40 -0
- langfun/core/eval/v2/runners/debug_test.py +76 -0
- langfun/core/eval/v2/runners/parallel.py +243 -0
- langfun/core/eval/v2/runners/parallel_test.py +182 -0
- langfun/core/eval/v2/runners/sequential.py +47 -0
- langfun/core/eval/v2/runners/sequential_test.py +169 -0
- langfun/core/langfunc.py +45 -130
- langfun/core/langfunc_test.py +7 -5
- langfun/core/language_model.py +189 -36
- langfun/core/language_model_test.py +54 -3
- langfun/core/llms/__init__.py +14 -1
- langfun/core/llms/anthropic.py +157 -2
- langfun/core/llms/azure_openai.py +29 -17
- langfun/core/llms/cache/base.py +25 -3
- langfun/core/llms/cache/in_memory.py +48 -7
- langfun/core/llms/cache/in_memory_test.py +14 -4
- langfun/core/llms/compositional.py +25 -1
- langfun/core/llms/deepseek.py +30 -2
- langfun/core/llms/fake.py +32 -1
- langfun/core/llms/gemini.py +90 -12
- langfun/core/llms/gemini_test.py +110 -0
- langfun/core/llms/google_genai.py +52 -1
- langfun/core/llms/groq.py +28 -3
- langfun/core/llms/llama_cpp.py +23 -4
- langfun/core/llms/openai.py +120 -3
- langfun/core/llms/openai_compatible.py +148 -27
- langfun/core/llms/openai_compatible_test.py +207 -20
- langfun/core/llms/openai_test.py +0 -2
- langfun/core/llms/rest.py +16 -1
- langfun/core/llms/vertexai.py +78 -8
- langfun/core/logging.py +1 -1
- langfun/core/mcp/__init__.py +10 -0
- langfun/core/mcp/client.py +177 -0
- langfun/core/mcp/client_test.py +71 -0
- langfun/core/mcp/session.py +241 -0
- langfun/core/mcp/session_test.py +54 -0
- langfun/core/mcp/testing/simple_mcp_client.py +33 -0
- langfun/core/mcp/testing/simple_mcp_server.py +33 -0
- langfun/core/mcp/tool.py +254 -0
- langfun/core/mcp/tool_test.py +197 -0
- langfun/core/memory.py +1 -0
- langfun/core/message.py +160 -55
- langfun/core/message_test.py +65 -81
- langfun/core/modalities/__init__.py +8 -0
- langfun/core/modalities/audio.py +21 -1
- langfun/core/modalities/image.py +73 -3
- langfun/core/modalities/image_test.py +116 -0
- langfun/core/modalities/mime.py +78 -4
- langfun/core/modalities/mime_test.py +59 -0
- langfun/core/modalities/pdf.py +19 -1
- langfun/core/modalities/video.py +21 -1
- langfun/core/modality.py +167 -29
- langfun/core/modality_test.py +42 -12
- langfun/core/natural_language.py +1 -1
- langfun/core/sampling.py +4 -4
- langfun/core/sampling_test.py +20 -4
- langfun/core/structured/__init__.py +2 -24
- langfun/core/structured/completion.py +34 -44
- langfun/core/structured/completion_test.py +23 -43
- langfun/core/structured/description.py +54 -50
- langfun/core/structured/function_generation.py +29 -12
- langfun/core/structured/mapping.py +81 -37
- langfun/core/structured/parsing.py +95 -79
- langfun/core/structured/parsing_test.py +0 -3
- langfun/core/structured/querying.py +230 -154
- langfun/core/structured/querying_test.py +69 -33
- langfun/core/structured/schema/__init__.py +49 -0
- langfun/core/structured/schema/base.py +664 -0
- langfun/core/structured/schema/base_test.py +531 -0
- langfun/core/structured/schema/json.py +174 -0
- langfun/core/structured/schema/json_test.py +121 -0
- langfun/core/structured/schema/python.py +316 -0
- langfun/core/structured/schema/python_test.py +410 -0
- langfun/core/structured/schema_generation.py +33 -14
- langfun/core/structured/scoring.py +47 -36
- langfun/core/structured/tokenization.py +26 -11
- langfun/core/subscription.py +2 -2
- langfun/core/template.py +175 -50
- langfun/core/template_test.py +123 -17
- langfun/env/__init__.py +43 -0
- langfun/env/base_environment.py +827 -0
- langfun/env/base_environment_test.py +473 -0
- langfun/env/base_feature.py +304 -0
- langfun/env/base_feature_test.py +228 -0
- langfun/env/base_sandbox.py +842 -0
- langfun/env/base_sandbox_test.py +1235 -0
- langfun/env/event_handlers/__init__.py +14 -0
- langfun/env/event_handlers/chain.py +233 -0
- langfun/env/event_handlers/chain_test.py +253 -0
- langfun/env/event_handlers/event_logger.py +472 -0
- langfun/env/event_handlers/event_logger_test.py +304 -0
- langfun/env/event_handlers/metric_writer.py +726 -0
- langfun/env/event_handlers/metric_writer_test.py +214 -0
- langfun/env/interface.py +1640 -0
- langfun/env/interface_test.py +153 -0
- langfun/env/load_balancers.py +59 -0
- langfun/env/load_balancers_test.py +141 -0
- langfun/env/test_utils.py +507 -0
- {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512150805.dist-info}/METADATA +7 -3
- langfun-0.1.2.dev202512150805.dist-info/RECORD +217 -0
- langfun/core/eval/v2/runners_test.py +0 -343
- langfun/core/structured/schema.py +0 -987
- langfun/core/structured/schema_test.py +0 -982
- langfun-0.1.2.dev202509120804.dist-info/RECORD +0 -172
- {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512150805.dist-info}/WHEEL +0 -0
- {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512150805.dist-info}/licenses/LICENSE +0 -0
- {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512150805.dist-info}/top_level.txt +0 -0
langfun/core/template.py
CHANGED
|
@@ -49,20 +49,94 @@ class Template(
|
|
|
49
49
|
pg.typing.CustomTyping,
|
|
50
50
|
pg.views.HtmlTreeView.Extension
|
|
51
51
|
):
|
|
52
|
-
"""Langfun string template.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
r"""Langfun string template.
|
|
53
|
+
|
|
54
|
+
`lf.Template` provides a flexible way to define and render prompts using
|
|
55
|
+
the Jinja2 templating engine. It supports variable substitution, composition
|
|
56
|
+
of templates, multi-modal content, and easy reuse through subclassing.
|
|
57
|
+
|
|
58
|
+
**Key Features:**
|
|
59
|
+
|
|
60
|
+
* **Jinja2 Syntax**: Leverages the full power of Jinja2 for
|
|
61
|
+
expressions, loops, and conditional logic within templates.
|
|
62
|
+
(See [Jinja2 documentation](
|
|
63
|
+
https://jinja.palletsprojects.com/en/3.1.x/templates/))
|
|
64
|
+
* **Composition**: Templates can include other `lf.Template`, `lf.Message`
|
|
65
|
+
and message-convertible objects, allowing complex prompts to be built from
|
|
66
|
+
reusable components.
|
|
67
|
+
* **Multi-Modal Support**: Seamlessly integrates multi-modal objects
|
|
68
|
+
(like `lf.Image`, `lf.Audio`) into templates.
|
|
69
|
+
* **Subclassing**: Define reusable prompt structures by subclassing
|
|
70
|
+
`lf.Template` and setting defaults.
|
|
71
|
+
* **Context Awareness**: Variables can be resolved from `render()`
|
|
72
|
+
arguments, template attributes, or the surrounding `lf.context`.
|
|
73
|
+
|
|
74
|
+
**1. Basic Usage:**
|
|
75
|
+
Variables are indicated by `{{variable_name}}`.
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
t = lf.Template("Hello, {{name}}!", name="World")
|
|
79
|
+
print(t.render())
|
|
80
|
+
# Output: Hello, World!
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**2. Providing Variables at Render Time:**
|
|
84
|
+
Variables can be provided when calling `render()`:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
t = lf.Template("Hello, {{name}}!")
|
|
88
|
+
print(t.render(name="Alice"))
|
|
89
|
+
# Output: Hello, Alice!
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**3. Composition:**
|
|
93
|
+
Embed templates within other templates for modularity:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
t = lf.Template(
|
|
97
|
+
"{{greeting}}, {{question}}",
|
|
98
|
+
greeting=lf.Template("Hello {{user}}", user="Bob"),
|
|
99
|
+
question=lf.UserMessage("How are you?")
|
|
100
|
+
)
|
|
101
|
+
print(t.render())
|
|
102
|
+
# Output: Hello Bob, How are you?
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**4. Multi-Modal Content:**
|
|
106
|
+
Reference modality objects in the template string:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
image = lf.Image.from_path(...)
|
|
110
|
+
t = lf.Template("Describe this image: <<[[{{image.id}}]]>>", image=image)
|
|
111
|
+
# When rendered, 't' can be passed to a multi-modal model.
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**5. Subclassing for Reusability:**
|
|
115
|
+
Define a reusable template via subclassing. The docstring can serve as the
|
|
116
|
+
default template string if it doesn't contain "THIS IS NOT A TEMPLATE".
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
class Greeting(lf.Template):
|
|
120
|
+
'''Greeting prompt.
|
|
121
|
+
|
|
122
|
+
Hello, {{user}}!
|
|
123
|
+
'''
|
|
124
|
+
user: str = 'guest'
|
|
125
|
+
|
|
126
|
+
print(Greeting().render())
|
|
127
|
+
# Output: Hello, guest!
|
|
128
|
+
print(Greeting(user='Admin').render())
|
|
129
|
+
# Output: Hello, Admin!
|
|
130
|
+
```
|
|
57
131
|
"""
|
|
58
132
|
|
|
59
133
|
template_str: Annotated[
|
|
60
134
|
str,
|
|
61
135
|
(
|
|
62
|
-
'A template string in jinja2 syntax. During `render`,
|
|
63
|
-
'will be resolved from 1)
|
|
64
|
-
'
|
|
65
|
-
'
|
|
136
|
+
'A template string in jinja2 syntax. During `render`, variables '
|
|
137
|
+
'will be resolved from: 1) keyword arguments passed to `render`; '
|
|
138
|
+
'2) attributes of this object; and 3) attributes of its containing '
|
|
139
|
+
'objects.'
|
|
66
140
|
),
|
|
67
141
|
]
|
|
68
142
|
|
|
@@ -70,16 +144,16 @@ class Template(
|
|
|
70
144
|
bool,
|
|
71
145
|
(
|
|
72
146
|
'If True, `inspect.cleandoc` will be applied on `template_str` to '
|
|
73
|
-
'
|
|
74
|
-
'
|
|
147
|
+
'remove leading/trailing spaces and fix indentation. Otherwise, '
|
|
148
|
+
'the `template_str` will be used as is.'
|
|
75
149
|
),
|
|
76
150
|
] = True
|
|
77
151
|
|
|
78
152
|
__kwargs__: Annotated[
|
|
79
153
|
Any,
|
|
80
154
|
(
|
|
81
|
-
'Wildcard keyword arguments for `__init__` that can be referred
|
|
82
|
-
'the template string. This allows
|
|
155
|
+
'Wildcard keyword arguments for `__init__` that can be referred to '
|
|
156
|
+
'in the template string. This allows modularizing prompts with '
|
|
83
157
|
'fully or partially bound variables.'
|
|
84
158
|
),
|
|
85
159
|
]
|
|
@@ -171,6 +245,7 @@ class Template(
|
|
|
171
245
|
|
|
172
246
|
# Last render output.
|
|
173
247
|
self._cached_render_output = None
|
|
248
|
+
self._referred_modalities = None
|
|
174
249
|
|
|
175
250
|
@property
|
|
176
251
|
def render_output(self) -> message_lib.Message | None:
|
|
@@ -195,12 +270,14 @@ class Template(
|
|
|
195
270
|
"""Returns referred variables.
|
|
196
271
|
|
|
197
272
|
Args:
|
|
198
|
-
specified: If True, include only variables
|
|
199
|
-
include only variables that are not specified.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
273
|
+
specified: If True, include only variables specified with a value.
|
|
274
|
+
If False, include only variables that are not specified.
|
|
275
|
+
If None, include both specified and unspecified variables.
|
|
276
|
+
closure: If True, include variables from referred `lf.Template` objects
|
|
277
|
+
recursively. Otherwise, only include variables used in this template.
|
|
278
|
+
leaf: If True, include only variables that are not `lf.Template` objects.
|
|
279
|
+
If False, include only variables that are `lf.Template` objects.
|
|
280
|
+
If None, include both.
|
|
204
281
|
|
|
205
282
|
Returns:
|
|
206
283
|
A list of variable names that match the criteria.
|
|
@@ -229,17 +306,17 @@ class Template(
|
|
|
229
306
|
|
|
230
307
|
@property
|
|
231
308
|
def missing_vars(self) -> Set[str]:
|
|
232
|
-
"""Returns
|
|
309
|
+
"""Returns missing variable names from this and referred templates."""
|
|
233
310
|
return self.vars(closure=True, specified=False)
|
|
234
311
|
|
|
235
312
|
@classmethod
|
|
236
313
|
def raw_str(cls, text: str) -> str:
|
|
237
|
-
"""Returns a template string that
|
|
314
|
+
"""Returns a template string that preserves the text as original."""
|
|
238
315
|
return '{% raw %}' + text + '{% endraw %}'
|
|
239
316
|
|
|
240
317
|
@classmethod
|
|
241
318
|
def from_raw_str(cls, text: str) -> 'Template':
|
|
242
|
-
"""Returns a template that
|
|
319
|
+
"""Returns a template that preserves the text as original."""
|
|
243
320
|
return cls(cls.raw_str(text), clean=False)
|
|
244
321
|
|
|
245
322
|
def render(
|
|
@@ -253,18 +330,20 @@ class Template(
|
|
|
253
330
|
"""Renders the template with variables from the context.
|
|
254
331
|
|
|
255
332
|
Args:
|
|
256
|
-
allow_partial:
|
|
257
|
-
variables
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
333
|
+
allow_partial: If True, allows partial rendering, which leaves unresolved
|
|
334
|
+
variables in place in the output text. Otherwise, raises error when
|
|
335
|
+
there are unresolved variables.
|
|
336
|
+
implicit: If True, reuse the rendering output if a parent `lf.Template`
|
|
337
|
+
is rendering current `lf.Template` multiple times. This is important
|
|
338
|
+
for making sure all references to the same `lf.Template` within a single
|
|
261
339
|
top-level rendering would return the same result. If False, every call
|
|
262
340
|
to `render` will trigger the actual rendering process.
|
|
263
341
|
message_cls: The message class used for creating the return value.
|
|
264
|
-
**kwargs: Values for template variables
|
|
342
|
+
**kwargs: Values for template variables, which override values from
|
|
343
|
+
member attributes or context.
|
|
265
344
|
|
|
266
345
|
Returns:
|
|
267
|
-
|
|
346
|
+
A Message object containing the rendered result.
|
|
268
347
|
"""
|
|
269
348
|
try:
|
|
270
349
|
pg.object_utils.thread_local_push(_TLS_RENDER_STACK, self)
|
|
@@ -309,6 +388,17 @@ class Template(
|
|
|
309
388
|
f'The value for template variable {var_name!r} is not '
|
|
310
389
|
f'provided. Template: {self.template_str!r}'
|
|
311
390
|
)
|
|
391
|
+
# Short-circuit common types that do not need conversions.
|
|
392
|
+
elif not isinstance(
|
|
393
|
+
var_value, (
|
|
394
|
+
bool, int, float, str, list, dict,
|
|
395
|
+
Template, message_lib.Message, modality.Modality
|
|
396
|
+
)
|
|
397
|
+
) and message_lib.Message.is_convertible(type(var_value)):
|
|
398
|
+
# Automatically convert the value to a message if it is
|
|
399
|
+
# convertible. This allows users to drop message convertible
|
|
400
|
+
# objects into template for nested rendering.
|
|
401
|
+
var_value = message_lib.Message.from_value(var_value)
|
|
312
402
|
inputs[var_name] = var_value
|
|
313
403
|
|
|
314
404
|
# Enable Python format for builtin types during template rendering,
|
|
@@ -322,24 +412,46 @@ class Template(
|
|
|
322
412
|
compact=True,
|
|
323
413
|
python_format=True,
|
|
324
414
|
):
|
|
325
|
-
|
|
326
|
-
#
|
|
415
|
+
|
|
416
|
+
# Capture the modality objects whose references are being
|
|
417
|
+
# rendered
|
|
327
418
|
# in the template.
|
|
328
|
-
with modality.
|
|
329
|
-
|
|
419
|
+
with modality.capture_rendered_modalities() as modality_refs:
|
|
420
|
+
|
|
421
|
+
# Natural language formattable objects will be returned in
|
|
422
|
+
# natural language when they are directly returned as rendering
|
|
423
|
+
# elements in the template.
|
|
424
|
+
with modality.format_modality_as_ref():
|
|
425
|
+
rendered_text = self._template.render(**inputs)
|
|
330
426
|
|
|
331
|
-
|
|
332
|
-
|
|
427
|
+
# Carry the modality references passed from the constructor.
|
|
428
|
+
# This is to support modality objects that is already rendered
|
|
429
|
+
# in the template string.
|
|
430
|
+
if self._referred_modalities:
|
|
431
|
+
modality_refs.update(self._referred_modalities)
|
|
333
432
|
|
|
334
433
|
if self.clean:
|
|
335
434
|
rendered_text = rendered_text.strip()
|
|
336
435
|
|
|
337
|
-
metadata.
|
|
338
|
-
|
|
339
|
-
|
|
436
|
+
# Fill message metadata.
|
|
437
|
+
metadata = {
|
|
438
|
+
'__template_input__': {
|
|
439
|
+
k: pg.Ref(v) for k, v in inputs.items()
|
|
440
|
+
if not inspect.ismethod(v)
|
|
441
|
+
},
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
# Carry additional metadata.
|
|
445
|
+
# TODO(daiyip): Consider to put additional metadata into a separate
|
|
446
|
+
# key under `metadata`.
|
|
447
|
+
metadata.update(self.additional_metadata())
|
|
340
448
|
|
|
341
449
|
# Fill the variables for rendering the template as metadata.
|
|
342
|
-
message = message_cls(
|
|
450
|
+
message = message_cls(
|
|
451
|
+
text=rendered_text,
|
|
452
|
+
referred_modalities=modality_refs,
|
|
453
|
+
metadata=metadata
|
|
454
|
+
)
|
|
343
455
|
|
|
344
456
|
# Tag input as rendered message.
|
|
345
457
|
message.tag(message_lib.Message.TAG_RENDERED)
|
|
@@ -366,7 +478,14 @@ class Template(
|
|
|
366
478
|
assert top is self, (top, self)
|
|
367
479
|
|
|
368
480
|
def additional_metadata(self) -> dict[str, Any]:
|
|
369
|
-
"""Returns additional
|
|
481
|
+
"""Returns additional metadata to be carried in the rendered message.
|
|
482
|
+
|
|
483
|
+
Subclasses can override this method to inject additional metadata based on
|
|
484
|
+
their own logic.
|
|
485
|
+
|
|
486
|
+
Returns:
|
|
487
|
+
A dict of metadata to be added to the output message.
|
|
488
|
+
"""
|
|
370
489
|
metadata = {}
|
|
371
490
|
# Carry metadata from `lf.context`.
|
|
372
491
|
for k, v in component.all_contextual_values().items():
|
|
@@ -374,7 +493,7 @@ class Template(
|
|
|
374
493
|
metadata[k.removeprefix(_ADDITIONAL_METADATA_PREFIX)] = v
|
|
375
494
|
|
|
376
495
|
# Carry metadata from fields.
|
|
377
|
-
for k, v in self.sym_init_args.
|
|
496
|
+
for k, v in self.sym_init_args.sym_items():
|
|
378
497
|
if k.startswith(_ADDITIONAL_METADATA_PREFIX):
|
|
379
498
|
metadata[k.removeprefix(_ADDITIONAL_METADATA_PREFIX)] = v
|
|
380
499
|
return metadata
|
|
@@ -391,7 +510,7 @@ class Template(
|
|
|
391
510
|
child_transform: Callable[[pg.KeyPath, pg.typing.Field, Any], Any]
|
|
392
511
|
| None = None,
|
|
393
512
|
) -> Tuple[bool, Any]:
|
|
394
|
-
"""Makes
|
|
513
|
+
"""Makes template applicable to `pg.typing.Str()`."""
|
|
395
514
|
del allow_partial
|
|
396
515
|
del child_transform
|
|
397
516
|
|
|
@@ -425,10 +544,11 @@ class Template(
|
|
|
425
544
|
|
|
426
545
|
@property
|
|
427
546
|
def DEFAULT(self) -> 'Template':
|
|
428
|
-
"""
|
|
547
|
+
"""Refers to the default value used for this template.
|
|
429
548
|
|
|
430
|
-
This
|
|
431
|
-
value of current template
|
|
549
|
+
This property is intended to be used in template string for referring to
|
|
550
|
+
the default value of current template, useful for wrapping the default
|
|
551
|
+
template. For example:
|
|
432
552
|
|
|
433
553
|
Scenario 1: Use instance-level template_str to override the class default.
|
|
434
554
|
|
|
@@ -512,19 +632,24 @@ class Template(
|
|
|
512
632
|
value: Union[str, message_lib.Message, 'Template'],
|
|
513
633
|
**kwargs
|
|
514
634
|
) -> 'Template':
|
|
515
|
-
"""
|
|
635
|
+
"""Creates a template object from a value."""
|
|
516
636
|
if isinstance(value, cls):
|
|
517
637
|
return value.clone(override=kwargs) if kwargs else value # pylint: disable=no-value-for-parameter
|
|
518
638
|
if isinstance(value, str):
|
|
519
639
|
return cls(template_str=value, **kwargs)
|
|
520
|
-
if isinstance(value, message_lib.Message):
|
|
521
|
-
kwargs.update(value.metadata)
|
|
522
|
-
return cls(template_str=value.text, **kwargs)
|
|
523
640
|
if isinstance(value, Template):
|
|
524
|
-
lfun = cls(template_str=value.template_str, **kwargs)
|
|
641
|
+
lfun = cls(template_str=value.template_str, **kwargs) # pylint: disable=attribute-error
|
|
525
642
|
# So lfun could acccess all attributes from value.
|
|
526
643
|
lfun.sym_setparent(value)
|
|
527
644
|
return lfun
|
|
645
|
+
if message_lib.Message.is_convertible(type(value)):
|
|
646
|
+
value = message_lib.Message.from_value(value)
|
|
647
|
+
if isinstance(value, message_lib.Message):
|
|
648
|
+
for k, v in value.metadata.sym_items(): # pylint: disable=attribute-error
|
|
649
|
+
kwargs[_ADDITIONAL_METADATA_PREFIX + k] = v
|
|
650
|
+
t = cls(template_str=value.text, **kwargs)
|
|
651
|
+
t._referred_modalities = value.referred_modalities
|
|
652
|
+
return t
|
|
528
653
|
return cls(template_str='{{input}}', input=value, **kwargs)
|
|
529
654
|
|
|
530
655
|
def _html_tree_view_content(
|
langfun/core/template_test.py
CHANGED
|
@@ -176,6 +176,69 @@ class DefinitionTest(unittest.TestCase):
|
|
|
176
176
|
Template('{{x=1')
|
|
177
177
|
|
|
178
178
|
|
|
179
|
+
class FromValueTest(unittest.TestCase):
|
|
180
|
+
|
|
181
|
+
def test_from_str(self):
|
|
182
|
+
t = Template.from_value('Hello {{x}}', x=1)
|
|
183
|
+
self.assertTrue(pg.eq(t, Template('Hello {{x}}', x=1)))
|
|
184
|
+
self.assertEqual(t.render(), 'Hello 1')
|
|
185
|
+
|
|
186
|
+
def test_from_message(self):
|
|
187
|
+
class CustomModality(modality.Modality):
|
|
188
|
+
content: str
|
|
189
|
+
|
|
190
|
+
def to_bytes(self):
|
|
191
|
+
return self.content.encode()
|
|
192
|
+
|
|
193
|
+
message = Template('{{image}}', image=CustomModality('foo')).render()
|
|
194
|
+
t = Template.from_value(message)
|
|
195
|
+
m = t.render()
|
|
196
|
+
self.assertTrue(
|
|
197
|
+
pg.eq(m.modalities(), [CustomModality('foo')])
|
|
198
|
+
)
|
|
199
|
+
self.assertEqual(message.metadata, m.metadata)
|
|
200
|
+
|
|
201
|
+
def test_from_message_convertible(self):
|
|
202
|
+
|
|
203
|
+
class MyFormat(pg.Object):
|
|
204
|
+
text: str
|
|
205
|
+
|
|
206
|
+
class MyConverter(message_lib.MessageConverter): # pylint: disable=unused-variable
|
|
207
|
+
FORMAT_ID = 'int'
|
|
208
|
+
OUTPUT_TYPE = MyFormat
|
|
209
|
+
|
|
210
|
+
def to_value(self, m: message_lib.Message) -> MyFormat:
|
|
211
|
+
return MyFormat(text=m.text)
|
|
212
|
+
|
|
213
|
+
def from_value(self, value: MyFormat) -> message_lib.Message:
|
|
214
|
+
return message_lib.UserMessage(value.text)
|
|
215
|
+
|
|
216
|
+
t = Template.from_value(MyFormat(text='1'))
|
|
217
|
+
self.assertEqual(t.render(), '1')
|
|
218
|
+
|
|
219
|
+
def test_from_same_template(self):
|
|
220
|
+
t = Template('Hello {{x}}', x=1)
|
|
221
|
+
t2 = Template.from_value(t)
|
|
222
|
+
self.assertTrue(pg.eq(t, t2))
|
|
223
|
+
self.assertEqual(t2.x, 1)
|
|
224
|
+
|
|
225
|
+
def test_from_different_template(self):
|
|
226
|
+
t = Template('Hello {{x}}', x=1)
|
|
227
|
+
|
|
228
|
+
class MyTemplate(Template):
|
|
229
|
+
pass
|
|
230
|
+
|
|
231
|
+
t2 = MyTemplate.from_value(t, x=2)
|
|
232
|
+
self.assertIsInstance(t2, MyTemplate)
|
|
233
|
+
self.assertEqual(t2.template_str, t.template_str)
|
|
234
|
+
self.assertEqual(t2.x, 2)
|
|
235
|
+
|
|
236
|
+
def test_from_python_object(self):
|
|
237
|
+
t = Template.from_value(pg.Dict(x=1, y=2))
|
|
238
|
+
self.assertEqual(t.template_str, '{{input}}')
|
|
239
|
+
self.assertEqual(t.input, pg.Dict(x=1, y=2))
|
|
240
|
+
|
|
241
|
+
|
|
179
242
|
class VarsTest(unittest.TestCase):
|
|
180
243
|
|
|
181
244
|
def assert_missing_vars(self, t: Template, missing_vars: set[str]):
|
|
@@ -251,6 +314,10 @@ class VarsTest(unittest.TestCase):
|
|
|
251
314
|
|
|
252
315
|
class RenderTest(unittest.TestCase):
|
|
253
316
|
|
|
317
|
+
def setUp(self):
|
|
318
|
+
super().setUp()
|
|
319
|
+
self.maxDiff = None
|
|
320
|
+
|
|
254
321
|
def test_clean(self):
|
|
255
322
|
l = Template('\n Hello\n ')
|
|
256
323
|
self.assertEqual(l.render(), 'Hello')
|
|
@@ -279,19 +346,20 @@ class RenderTest(unittest.TestCase):
|
|
|
279
346
|
)
|
|
280
347
|
v = l.render()
|
|
281
348
|
self.assertEqual(v, 'How are you 1')
|
|
282
|
-
self.assertIs(v.x, l.x)
|
|
283
|
-
self.assertEqual(v.x.render_output, 'are you 1')
|
|
284
|
-
self.assertIs(v.x.y, l.x.y)
|
|
285
|
-
self.
|
|
286
|
-
self.assertEqual(v.x.y.
|
|
349
|
+
self.assertIs(v.__template_input__.x, l.x)
|
|
350
|
+
self.assertEqual(v.__template_input__.x.render_output, 'are you 1')
|
|
351
|
+
self.assertIs(v.__template_input__.x.y, l.x.y)
|
|
352
|
+
self.assertIs(v.__template_input__.x.y.n, l.x.y.n)
|
|
353
|
+
self.assertEqual(v.__template_input__.x.y.render_output, 'you 1')
|
|
354
|
+
self.assertEqual(v.__template_input__.x.y.n, 1)
|
|
287
355
|
|
|
288
356
|
def test_render_with_call_args(self):
|
|
289
357
|
l = Template('Hello {{x}} and {{y}}', x=1, y=Template('{{z}}'), z=3)
|
|
290
358
|
v = l.render(x=2)
|
|
291
359
|
self.assertEqual(v, 'Hello 2 and 3')
|
|
292
|
-
self.assertEqual(v.x, 2)
|
|
293
|
-
self.assertEqual(v.y, '3')
|
|
294
|
-
self.assertEqual(v.y.z, 3)
|
|
360
|
+
self.assertEqual(v.__template_input__.x, 2)
|
|
361
|
+
self.assertEqual(v.__template_input__.y, '3')
|
|
362
|
+
self.assertEqual(v.__template_input__.y.z, 3)
|
|
295
363
|
|
|
296
364
|
def test_render_cache(self):
|
|
297
365
|
class DynamicContent(Template):
|
|
@@ -321,11 +389,17 @@ class RenderTest(unittest.TestCase):
|
|
|
321
389
|
def to_bytes(self):
|
|
322
390
|
return self.content.encode()
|
|
323
391
|
|
|
392
|
+
foo = CustomModality('foo')
|
|
393
|
+
message = Template('This is {{ x }} and {{ a }}', x=1, a=foo).render()
|
|
324
394
|
self.assertEqual(
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
395
|
+
message,
|
|
396
|
+
'This is 1 and <<[[custom_modality:acbd18db]]>>',
|
|
397
|
+
)
|
|
398
|
+
self.assertIn(
|
|
399
|
+
'custom_modality:acbd18db', message.referred_modalities
|
|
400
|
+
)
|
|
401
|
+
self.assertIs(
|
|
402
|
+
message.referred_modalities['custom_modality:acbd18db'], foo
|
|
329
403
|
)
|
|
330
404
|
|
|
331
405
|
def test_render_with_default(self):
|
|
@@ -511,12 +585,44 @@ class RenderTest(unittest.TestCase):
|
|
|
511
585
|
self.assert_partial(Template('Hello {{len(x)}}'), 'Hello {{len(x)}}')
|
|
512
586
|
|
|
513
587
|
def test_additional_metadata(self):
|
|
514
|
-
t = Template('hi', metadata_weights=1.0, y=2)
|
|
515
|
-
self.assertEqual(
|
|
588
|
+
t = Template('hi {{y}}', metadata_weights=1.0, y=2, z=1)
|
|
589
|
+
self.assertEqual(
|
|
590
|
+
t.render(),
|
|
591
|
+
message_lib.UserMessage(
|
|
592
|
+
'hi 2', weights=1.0, __template_input__={'y': 2}
|
|
593
|
+
)
|
|
594
|
+
)
|
|
516
595
|
|
|
517
|
-
t = Template('hi')
|
|
518
|
-
with component.context(metadata_weights=1.0, y=
|
|
519
|
-
self.assertEqual(
|
|
596
|
+
t = Template('hi {{y}}')
|
|
597
|
+
with component.context(metadata_weights=1.0, y=3):
|
|
598
|
+
self.assertEqual(
|
|
599
|
+
t.render(z=2),
|
|
600
|
+
message_lib.UserMessage(
|
|
601
|
+
'hi 3', weights=1.0, __template_input__={'y': 3}
|
|
602
|
+
)
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
def test_render_with_message_convertible_type(self):
|
|
606
|
+
class MyFormat(pg.Object):
|
|
607
|
+
text: str
|
|
608
|
+
|
|
609
|
+
class MyConverter(message_lib.MessageConverter): # pylint: disable=unused-variable
|
|
610
|
+
FORMAT_ID = 'another-format'
|
|
611
|
+
OUTPUT_TYPE = MyFormat
|
|
612
|
+
|
|
613
|
+
def to_value(self, m: message_lib.Message) -> MyFormat:
|
|
614
|
+
return MyFormat(text=m.text)
|
|
615
|
+
|
|
616
|
+
def from_value(self, value: MyFormat) -> message_lib.Message:
|
|
617
|
+
return message_lib.UserMessage(value.text)
|
|
618
|
+
|
|
619
|
+
t = Template('Hello {{x}}', x=MyFormat(text='world'))
|
|
620
|
+
rendered_message = t.render()
|
|
621
|
+
self.assertEqual(rendered_message, 'Hello world')
|
|
622
|
+
self.assertIsInstance(
|
|
623
|
+
rendered_message.__template_input__['x'], message_lib.UserMessage
|
|
624
|
+
)
|
|
625
|
+
self.assertEqual(rendered_message.__template_input__['x'].text, 'world')
|
|
520
626
|
|
|
521
627
|
|
|
522
628
|
class TemplateRenderEventTest(unittest.TestCase):
|
langfun/env/__init__.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Copyright 2025 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
|
+
"""Environment for LLM agents."""
|
|
15
|
+
|
|
16
|
+
# pylint: disable=g-importing-member, g-bad-import-order, g-import-not-at-top
|
|
17
|
+
from langfun.env.interface import EnvironmentError # pylint: disable=redefined-builtin
|
|
18
|
+
from langfun.env.interface import EnvironmentOutageError
|
|
19
|
+
from langfun.env.interface import EnvironmentOverloadError
|
|
20
|
+
from langfun.env.interface import SandboxError
|
|
21
|
+
from langfun.env.interface import SandboxStateError
|
|
22
|
+
|
|
23
|
+
from langfun.env.interface import Environment
|
|
24
|
+
from langfun.env.interface import Sandbox
|
|
25
|
+
from langfun.env.interface import Feature
|
|
26
|
+
|
|
27
|
+
from langfun.env.interface import EventHandler
|
|
28
|
+
|
|
29
|
+
# Decorators for sandbox/feature methods.
|
|
30
|
+
from langfun.env.interface import treat_as_sandbox_state_error
|
|
31
|
+
from langfun.env.interface import log_activity
|
|
32
|
+
|
|
33
|
+
from langfun.env.base_environment import BaseEnvironment
|
|
34
|
+
from langfun.env.base_sandbox import BaseSandbox
|
|
35
|
+
from langfun.env.base_feature import BaseFeature
|
|
36
|
+
|
|
37
|
+
from langfun.env import load_balancers
|
|
38
|
+
from langfun.env.load_balancers import LoadBalancer
|
|
39
|
+
|
|
40
|
+
# Import all event handlers.
|
|
41
|
+
from langfun.env.event_handlers import *
|
|
42
|
+
|
|
43
|
+
# Google-internal imports.
|