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
|
@@ -45,17 +45,17 @@ def evaluate(
|
|
|
45
45
|
global_vars: An optional dict as the globals that could be referenced by the
|
|
46
46
|
code.
|
|
47
47
|
permission: Permission for the Python code to run.
|
|
48
|
-
returns_stdout: If True, the stdout (a
|
|
48
|
+
returns_stdout: If True, the stdout (a string) will be returned.
|
|
49
49
|
outputs_intermediate: Applicable when returns_stdout is False. If True,
|
|
50
|
-
intermediate output will be
|
|
51
|
-
value accessible by key '__result__' and the
|
|
50
|
+
intermediate output will be output as a dict, with the last line's
|
|
51
|
+
value accessible by key '__result__' and the stdout accessible by
|
|
52
52
|
key '__stdout__'. Otherwise the value of the last line will be returned.
|
|
53
53
|
|
|
54
54
|
Returns:
|
|
55
55
|
The value of the last line of the code block. Or a dict of variable
|
|
56
56
|
names of all locals to their evaluated values as the output of the code to
|
|
57
57
|
run. The value for the last line can be accessed by key '__result__'. Or the
|
|
58
|
-
stdout as a
|
|
58
|
+
stdout as a string.
|
|
59
59
|
"""
|
|
60
60
|
return pg.coding.evaluate(
|
|
61
61
|
parsing.clean(code),
|
|
@@ -85,28 +85,30 @@ def run(
|
|
|
85
85
|
|
|
86
86
|
Args:
|
|
87
87
|
code: Python code to run.
|
|
88
|
-
global_vars: An optional dict
|
|
88
|
+
global_vars: An optional dict as the globals that could be referenced by the
|
|
89
|
+
code.
|
|
89
90
|
permission: Permission for the Python code to run.
|
|
90
|
-
returns_stdout: If True, the stdout (a
|
|
91
|
+
returns_stdout: If True, the stdout (a string) will be returned.
|
|
91
92
|
outputs_intermediate: Applicable when returns_stdout is False. If True,
|
|
92
|
-
intermediate output will be
|
|
93
|
-
value accessible by key '__result__' and the
|
|
93
|
+
intermediate output will be output as a dict, with the last line's
|
|
94
|
+
value accessible by key '__result__' and the stdout accessible by
|
|
94
95
|
key '__stdout__'. Otherwise the value of the last line will be returned.
|
|
95
96
|
sandbox: If True, run code in sandbox; If False, run code in current
|
|
96
97
|
process. If None, run in sandbox first, if the output could not be
|
|
97
|
-
serialized and
|
|
98
|
+
serialized and passed to current process, run the code again in current
|
|
98
99
|
process.
|
|
99
|
-
timeout: Execution timeout in seconds. If None, wait the code
|
|
100
|
+
timeout: Execution timeout in seconds. If None, wait for the code to
|
|
101
|
+
complete.
|
|
100
102
|
|
|
101
103
|
Returns:
|
|
102
104
|
The value of the last line of the code block. Or a dict of variable
|
|
103
105
|
names of all locals to their evaluated values as the output of the code to
|
|
104
106
|
run. The value for the last line can be accessed by key '__result__'. Or the
|
|
105
|
-
stdout as a
|
|
107
|
+
stdout as a string.
|
|
106
108
|
|
|
107
109
|
Raises:
|
|
108
110
|
TimeoutError: If the execution time exceeds the timeout.
|
|
109
|
-
Exception:
|
|
111
|
+
Exception: Exceptions that are raised from the code.
|
|
110
112
|
"""
|
|
111
113
|
return pg.coding.run(
|
|
112
114
|
parsing.clean(code),
|
|
@@ -22,9 +22,13 @@ import pyglove as pg
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class PythonCode(pg.Object):
|
|
25
|
-
"""
|
|
25
|
+
"""Represents a piece of Python code that can be executed.
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
When `PythonCode` is instantiated within a `PythonCode.auto_run()` context,
|
|
28
|
+
it automatically executes the code and returns the result of the last
|
|
29
|
+
expression. Otherwise, it acts as a container for the source code, which
|
|
30
|
+
can be executed by calling the instance. The class also supports automatic
|
|
31
|
+
error correction via `lf.coding.run_with_correction` when called.
|
|
28
32
|
"""
|
|
29
33
|
|
|
30
34
|
source: Annotated[
|
|
@@ -56,7 +60,7 @@ class PythonCode(pg.Object):
|
|
|
56
60
|
Otherwise, auto call will be disabled.
|
|
57
61
|
sandbox: If True, run code in sandbox; If False, run code in current
|
|
58
62
|
process. If None, run in sandbox first, if the output could not be
|
|
59
|
-
serialized and
|
|
63
|
+
serialized and passed to current process, run the code again in current
|
|
60
64
|
process. Applicable when `enabled` is set to True.
|
|
61
65
|
timeout: Timeout in seconds. Applicable when both `enabled` and `sandbox`
|
|
62
66
|
are set to True.
|
|
@@ -98,17 +102,17 @@ class PythonCode(pg.Object):
|
|
|
98
102
|
Args:
|
|
99
103
|
sandbox: If True, run code in sandbox; If False, run code in current
|
|
100
104
|
process. If None, run in sandbox first, if the output could not be
|
|
101
|
-
serialized and
|
|
105
|
+
serialized and passed to current process, run the code again in current
|
|
102
106
|
process.
|
|
103
107
|
timeout: Timeout in seconds. If None, there is no timeout. Applicable when
|
|
104
108
|
sandbox is set to True.
|
|
105
109
|
global_vars: Global variables that could be accessed from the source code.
|
|
106
|
-
returns_stdout: If True, the stdout (a
|
|
110
|
+
returns_stdout: If True, the stdout (a string) will be returned.
|
|
107
111
|
outputs_intermediate: Applicable when returns_stdout is False. If True,
|
|
108
|
-
intermediate output will be
|
|
109
|
-
value accessible by key '__result__' and the
|
|
112
|
+
intermediate output will be output as a dict, with the last line's
|
|
113
|
+
value accessible by key '__result__' and the stdout accessible by
|
|
110
114
|
key '__stdout__'. Otherwise the value of the last line will be returned.
|
|
111
|
-
autofix: Number of attempts to
|
|
115
|
+
autofix: Number of attempts to autofix the generated code. If 0, autofix
|
|
112
116
|
is disabled.
|
|
113
117
|
autofix_lm: Language model to be used. If not specified, it will try to
|
|
114
118
|
use the `lm` under `lf.context`.
|
|
@@ -117,8 +121,8 @@ class PythonCode(pg.Object):
|
|
|
117
121
|
The value of the last expression in the source code. Or a dict of local
|
|
118
122
|
variable names defined in the source code to their values if
|
|
119
123
|
`outputs_intermediate` is set to True. The value for the last line can be
|
|
120
|
-
accessed by key '__result__'. Or the stdout as a
|
|
121
|
-
is set to True.
|
|
124
|
+
accessed by key '__result__'. Or the stdout as a string if
|
|
125
|
+
`returns_stdout` is set to True.
|
|
122
126
|
|
|
123
127
|
Raises:
|
|
124
128
|
TimeoutError: If `sandbox` is True and timeout has reached.
|
|
@@ -152,12 +156,12 @@ class PythonCode(pg.Object):
|
|
|
152
156
|
Args:
|
|
153
157
|
sandbox: If True, run code in sandbox; If False, run code in current
|
|
154
158
|
process. If None, run in sandbox first, if the output could not be
|
|
155
|
-
serialized and
|
|
159
|
+
serialized and passed to current process, run the code again in current
|
|
156
160
|
process.
|
|
157
161
|
timeout: Timeout in seconds. If None, there is no timeout. Applicable when
|
|
158
162
|
sandbox is set to True.
|
|
159
163
|
global_vars: Global variables that could be accessed from the source code.
|
|
160
|
-
autofix: Number of attempts to
|
|
164
|
+
autofix: Number of attempts to autofix the generated code. If 0, autofix
|
|
161
165
|
is disabled. Auto-fix is not supported for 'json' protocol.
|
|
162
166
|
autofix_lm: Language model to be used. If not specified, it will try to
|
|
163
167
|
use the `lm` under `lf.context`.
|
|
@@ -182,10 +186,11 @@ class PythonCode(pg.Object):
|
|
|
182
186
|
|
|
183
187
|
|
|
184
188
|
class PythonFunction(pg.Object):
|
|
185
|
-
"""
|
|
189
|
+
"""Represents a Python function defined by source code.
|
|
186
190
|
|
|
187
|
-
|
|
188
|
-
|
|
191
|
+
This class takes Python source code that defines a function and makes it
|
|
192
|
+
callable. The source code is evaluated to create a function object, which
|
|
193
|
+
can then be invoked like a regular Python function.
|
|
189
194
|
"""
|
|
190
195
|
|
|
191
196
|
name: str
|
|
@@ -214,7 +219,7 @@ class PythonFunction(pg.Object):
|
|
|
214
219
|
*args: Positional arguments that will be passed to the implementation.
|
|
215
220
|
sandbox: If True, run code in sandbox; If False, run code in current
|
|
216
221
|
process. If None, run in sandbox first, if the output could not be
|
|
217
|
-
serialized and
|
|
222
|
+
serialized and passed to current process, run the code again in current
|
|
218
223
|
process.
|
|
219
224
|
timeout: Timeout in seconds. If None, there is no timeout. Applicable when
|
|
220
225
|
sandbox is set to True.
|
|
@@ -23,7 +23,14 @@ import pyglove as pg
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class SandboxOutput(pg.Object):
|
|
26
|
-
"""
|
|
26
|
+
"""A structure containing the output from a sandbox execution.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
stdout: The standard output captured during execution.
|
|
30
|
+
stderr: The standard error captured during execution.
|
|
31
|
+
output_files: A dictionary of file names to their byte content for files
|
|
32
|
+
generated during execution.
|
|
33
|
+
"""
|
|
27
34
|
|
|
28
35
|
stdout: Annotated[
|
|
29
36
|
str,
|
|
@@ -42,7 +49,14 @@ class SandboxOutput(pg.Object):
|
|
|
42
49
|
|
|
43
50
|
|
|
44
51
|
class BaseSandbox(pg.Object):
|
|
45
|
-
"""
|
|
52
|
+
"""Base class for Python code sandboxing.
|
|
53
|
+
|
|
54
|
+
A sandbox provides an isolated environment for executing Python code,
|
|
55
|
+
typically with restrictions on file system access, network calls, or other
|
|
56
|
+
potentially harmful operations. This base class defines the interface for
|
|
57
|
+
sandboxes, including methods for running code (`run`), uploading files
|
|
58
|
+
(`upload`), and managing the sandbox lifecycle (`setup`, `cleanup`).
|
|
59
|
+
"""
|
|
46
60
|
|
|
47
61
|
def _on_bound(self):
|
|
48
62
|
super()._on_bound()
|
|
@@ -111,7 +125,13 @@ class BaseSandbox(pg.Object):
|
|
|
111
125
|
|
|
112
126
|
|
|
113
127
|
class MultiProcessingSandbox(BaseSandbox):
|
|
114
|
-
"""
|
|
128
|
+
"""A sandbox implementation using Python's `multiprocessing`.
|
|
129
|
+
|
|
130
|
+
This sandbox executes code in a separate process, providing isolation from
|
|
131
|
+
the main process. It uses a temporary directory for file operations,
|
|
132
|
+
which is cleaned up when the sandbox is closed. It relies on
|
|
133
|
+
`pg.coding.run` with `sandbox=True` for execution.
|
|
134
|
+
"""
|
|
115
135
|
|
|
116
136
|
def _on_bound(self):
|
|
117
137
|
super()._on_bound()
|
langfun/core/component.py
CHANGED
|
@@ -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
|
-
"""
|
|
14
|
+
"""Base component for Langfun."""
|
|
15
15
|
|
|
16
16
|
from typing import ContextManager
|
|
17
17
|
import pyglove as pg
|
|
@@ -22,7 +22,37 @@ RAISE_IF_HAS_ERROR = (pg.MISSING_VALUE,)
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class Component(pg.ContextualObject):
|
|
25
|
-
"""Base class for
|
|
25
|
+
"""Base class for Langfun components.
|
|
26
|
+
|
|
27
|
+
Langfun components are context-aware symbolic objects powered by PyGlove.
|
|
28
|
+
(See [PyGlove basics](https://pyglove.readthedocs.io/en/latest/basics.html)
|
|
29
|
+
for more details).
|
|
30
|
+
|
|
31
|
+
**Context-awareness**
|
|
32
|
+
|
|
33
|
+
Langfun components can have contextual attributes using `lf.contextual`,
|
|
34
|
+
whose values can be provided or overridden via `lf.context` or
|
|
35
|
+
`lf.use_settings`.
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
```python
|
|
39
|
+
import langfun as lf
|
|
40
|
+
|
|
41
|
+
class Bar(lf.Component):
|
|
42
|
+
y = lf.contextual(1)
|
|
43
|
+
|
|
44
|
+
class Foo(lf.Component):
|
|
45
|
+
x = lf.contextual(0)
|
|
46
|
+
bar = Bar()
|
|
47
|
+
|
|
48
|
+
f = Foo()
|
|
49
|
+
assert f.x == 0 and f.bar.y == 1
|
|
50
|
+
|
|
51
|
+
# `lf.context` overrides `lf.contextual` attributes.
|
|
52
|
+
with lf.context(x=10, y=20):
|
|
53
|
+
assert f.x == 10 and f.bar.y == 20
|
|
54
|
+
```
|
|
55
|
+
"""
|
|
26
56
|
|
|
27
57
|
# Allow symbolic assignment, which invalidates the object and recomputes
|
|
28
58
|
# states upon update.
|
|
@@ -78,6 +108,15 @@ def use_settings(
|
|
|
78
108
|
) -> ContextManager[dict[str, pg.utils.ContextualOverride]]:
|
|
79
109
|
"""Shortcut method for overriding component attributes.
|
|
80
110
|
|
|
111
|
+
Example:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
with lf.use_settings(
|
|
115
|
+
lm=lf.llms.Gpt35(),
|
|
116
|
+
temperature=0.0):
|
|
117
|
+
lf.query('who are you?')
|
|
118
|
+
```
|
|
119
|
+
|
|
81
120
|
Args:
|
|
82
121
|
cascade: If True, this override will apply to both current scope and nested
|
|
83
122
|
scope, meaning that this `lf.context` will take precedence over all
|
|
@@ -85,6 +124,6 @@ def use_settings(
|
|
|
85
124
|
**settings: Key/values as override for component attributes.
|
|
86
125
|
|
|
87
126
|
Returns:
|
|
88
|
-
A
|
|
127
|
+
A context manager for overriding settings.
|
|
89
128
|
"""
|
|
90
129
|
return context(cascade=cascade, override_attrs=True, **settings)
|
langfun/core/concurrent.py
CHANGED
|
@@ -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
|
-
"""
|
|
14
|
+
"""Utilities for concurrency in Langfun."""
|
|
15
15
|
|
|
16
16
|
import abc
|
|
17
17
|
import collections
|
|
@@ -97,7 +97,7 @@ class RetryError(RuntimeError):
|
|
|
97
97
|
|
|
98
98
|
|
|
99
99
|
def with_retry(
|
|
100
|
-
func: Callable[
|
|
100
|
+
func: Callable[..., Any],
|
|
101
101
|
retry_on_errors: Union[
|
|
102
102
|
Union[Type[BaseException], Tuple[Type[BaseException], str]],
|
|
103
103
|
Sequence[Union[Type[BaseException], Tuple[Type[BaseException], str]]],
|
|
@@ -108,10 +108,25 @@ def with_retry(
|
|
|
108
108
|
max_retry_interval: int = 300,
|
|
109
109
|
seed: int | None = None,
|
|
110
110
|
) -> Callable[..., Any]:
|
|
111
|
-
"""
|
|
111
|
+
"""Decorator-like function to add retry mechanism to a function.
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
def flaky_function():
|
|
117
|
+
if random.random() < 0.5:
|
|
118
|
+
raise ValueError('error')
|
|
119
|
+
return 1
|
|
120
|
+
|
|
121
|
+
reliable_function = lf.with_retry(
|
|
122
|
+
flaky_function,
|
|
123
|
+
retry_on_errors=ValueError,
|
|
124
|
+
max_attempts=3)
|
|
125
|
+
reliable_function()
|
|
126
|
+
```
|
|
112
127
|
|
|
113
128
|
Args:
|
|
114
|
-
func:
|
|
129
|
+
func: The function to add retry mechanism.
|
|
115
130
|
retry_on_errors: A sequence of exception types or tuples of exception type
|
|
116
131
|
and error messages (described in regular expression) as the desired
|
|
117
132
|
exception types to retry.
|
|
@@ -128,8 +143,7 @@ def with_retry(
|
|
|
128
143
|
determined based on current time.
|
|
129
144
|
|
|
130
145
|
Returns:
|
|
131
|
-
A function with the same signature of
|
|
132
|
-
capability.
|
|
146
|
+
A function with the same signature of `func`, but with retry capability.
|
|
133
147
|
"""
|
|
134
148
|
|
|
135
149
|
def _func(*args, **kwargs):
|
|
@@ -179,6 +193,24 @@ def concurrent_execute(
|
|
|
179
193
|
) -> list[Any]:
|
|
180
194
|
"""Executes a function concurrently under current component context.
|
|
181
195
|
|
|
196
|
+
`lf.concurrent_execute` applies a function to each item in an iterable of
|
|
197
|
+
inputs in parallel and returns a list of results in the same order as the
|
|
198
|
+
inputs. It is a convenient wrapper around `lf.concurrent_map` for synchronous
|
|
199
|
+
bulk processing.
|
|
200
|
+
|
|
201
|
+
**Example:**
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
import langfun as lf
|
|
205
|
+
|
|
206
|
+
def square(x):
|
|
207
|
+
return x ** 2
|
|
208
|
+
|
|
209
|
+
results = lf.concurrent_execute(square, [1, 2, 3, 4], max_workers=2)
|
|
210
|
+
print(results)
|
|
211
|
+
# Output: [1, 4, 9, 16]
|
|
212
|
+
```
|
|
213
|
+
|
|
182
214
|
Args:
|
|
183
215
|
func: A user function.
|
|
184
216
|
parallel_inputs: The inputs for `func` which will be processed in parallel.
|
|
@@ -649,6 +681,38 @@ def concurrent_map(
|
|
|
649
681
|
) -> Iterator[Any]:
|
|
650
682
|
"""Maps inputs to outptus via func concurrently under current context.
|
|
651
683
|
|
|
684
|
+
`lf.concurrent_map` applies a function to each item in an iterable of
|
|
685
|
+
inputs in parallel and yields `(input, output, error)` tuples as they are
|
|
686
|
+
completed. It supports features like ordered/unordered results, progress
|
|
687
|
+
bars, timeouts, and automatic retries for transient errors.
|
|
688
|
+
|
|
689
|
+
**Example:**
|
|
690
|
+
|
|
691
|
+
```python
|
|
692
|
+
import langfun as lf
|
|
693
|
+
import time
|
|
694
|
+
import random
|
|
695
|
+
|
|
696
|
+
def flaky_square(x):
|
|
697
|
+
time.sleep(random.random())
|
|
698
|
+
if random.random() < 0.3:
|
|
699
|
+
raise ValueError("Flaky error")
|
|
700
|
+
return x ** 2
|
|
701
|
+
|
|
702
|
+
# Unordered execution with progress bar and retries
|
|
703
|
+
for input, output, error in lf.concurrent_map(
|
|
704
|
+
flaky_square,
|
|
705
|
+
range(10),
|
|
706
|
+
max_workers=3,
|
|
707
|
+
show_progress=True,
|
|
708
|
+
retry_on_errors=ValueError,
|
|
709
|
+
max_attempts=3):
|
|
710
|
+
if error:
|
|
711
|
+
print(f"Input {input} failed with error: {error}")
|
|
712
|
+
else:
|
|
713
|
+
print(f"Input {input} succeeded with output: {output}")
|
|
714
|
+
```
|
|
715
|
+
|
|
652
716
|
Args:
|
|
653
717
|
func: A user function.
|
|
654
718
|
parallel_inputs: The inputs for `func` which will be processed in parallel.
|
langfun/core/concurrent_test.py
CHANGED
|
@@ -262,6 +262,7 @@ class ProgressControlTest(unittest.TestCase):
|
|
|
262
262
|
with contextlib.redirect_stderr(string_io):
|
|
263
263
|
ctrl.update(1)
|
|
264
264
|
ctrl.refresh()
|
|
265
|
+
sys.stderr.flush()
|
|
265
266
|
self.assertEqual(string_io.getvalue(), '')
|
|
266
267
|
concurrent.progress_bar = 'tqdm'
|
|
267
268
|
|
|
@@ -274,6 +275,7 @@ class ProgressControlTest(unittest.TestCase):
|
|
|
274
275
|
ctrl.set_status('bar')
|
|
275
276
|
ctrl.update(10)
|
|
276
277
|
ctrl.refresh()
|
|
278
|
+
sys.stderr.flush()
|
|
277
279
|
self.assertEqual(
|
|
278
280
|
string_io.getvalue(),
|
|
279
281
|
'\x1b[1m\x1b[31mfoo\x1b[0m: \x1b[34m10% (10/100)\x1b[0m : bar\n'
|
|
@@ -288,6 +290,7 @@ class ProgressControlTest(unittest.TestCase):
|
|
|
288
290
|
self.assertIsInstance(ctrl, concurrent._TqdmProgressControl)
|
|
289
291
|
ctrl.update(10)
|
|
290
292
|
ctrl.refresh()
|
|
293
|
+
sys.stderr.flush()
|
|
291
294
|
self.assertIn('10/100', string_io.getvalue())
|
|
292
295
|
|
|
293
296
|
tqdm = concurrent.tqdm
|
|
@@ -316,6 +319,7 @@ class ProgressBarTest(unittest.TestCase):
|
|
|
316
319
|
for _ in concurrent.concurrent_execute(fun, range(5)):
|
|
317
320
|
concurrent.ProgressBar.refresh()
|
|
318
321
|
concurrent.ProgressBar.uninstall(bar_id)
|
|
322
|
+
sys.stderr.flush()
|
|
319
323
|
output_str = string_io.getvalue()
|
|
320
324
|
self.assertIn('100%', output_str)
|
|
321
325
|
self.assertIn('5/5', output_str)
|
|
@@ -332,7 +336,7 @@ class ProgressBarTest(unittest.TestCase):
|
|
|
332
336
|
concurrent.ProgressBar.update(bar_id, 0, status=1)
|
|
333
337
|
concurrent.ProgressBar.uninstall(bar_id)
|
|
334
338
|
sys.stderr.flush()
|
|
335
|
-
|
|
339
|
+
time.sleep(1)
|
|
336
340
|
self.assertIn('1/4', string_io.getvalue())
|
|
337
341
|
# TODO(daiyip): Re-enable once flakiness is fixed.
|
|
338
342
|
# self.assertIn('2/4', string_io.getvalue())
|
|
@@ -564,7 +568,8 @@ class ConcurrentMapTest(unittest.TestCase):
|
|
|
564
568
|
fun, [1, 2, 3], timeout=1.5, max_workers=1, show_progress=True
|
|
565
569
|
)
|
|
566
570
|
], key=lambda x: x[0])
|
|
567
|
-
|
|
571
|
+
sys.stderr.flush()
|
|
572
|
+
|
|
568
573
|
self.assertEqual( # pylint: disable=g-generic-assert
|
|
569
574
|
output,
|
|
570
575
|
[
|
|
@@ -592,6 +597,7 @@ class ConcurrentMapTest(unittest.TestCase):
|
|
|
592
597
|
show_progress=bar_id, status_fn=lambda p: dict(x=1, y=1)
|
|
593
598
|
)
|
|
594
599
|
], key=lambda x: x[0])
|
|
600
|
+
sys.stderr.flush()
|
|
595
601
|
|
|
596
602
|
self.assertEqual( # pylint: disable=g-generic-assert
|
|
597
603
|
output,
|
|
@@ -602,6 +608,7 @@ class ConcurrentMapTest(unittest.TestCase):
|
|
|
602
608
|
],
|
|
603
609
|
)
|
|
604
610
|
concurrent.ProgressBar.uninstall(bar_id)
|
|
611
|
+
concurrent.ProgressBar.refresh()
|
|
605
612
|
self.assertIn('100%', string_io.getvalue())
|
|
606
613
|
|
|
607
614
|
|
langfun/core/console.py
CHANGED
|
@@ -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
|
-
"""
|
|
14
|
+
"""Utilities for console output and notebook display."""
|
|
15
15
|
|
|
16
16
|
import sys
|
|
17
17
|
from typing import Any
|
|
@@ -21,7 +21,14 @@ from langfun.core import modalities as lf_modalities
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class AnthropicMessageConverter(lf.MessageConverter):
|
|
24
|
-
"""Converter
|
|
24
|
+
"""Converter between Langfun messages and Anthropic API message format.
|
|
25
|
+
|
|
26
|
+
This converter translates `lf.Message` objects into the JSON format required
|
|
27
|
+
by the Anthropic API and vice versa. It handles text and modalities like
|
|
28
|
+
images and PDFs by encoding them in base64 format as expected by Anthropic.
|
|
29
|
+
An optional `chunk_preprocessor` can be provided to modify or filter
|
|
30
|
+
chunks before conversion.
|
|
31
|
+
"""
|
|
25
32
|
|
|
26
33
|
FORMAT_ID = 'anthropic'
|
|
27
34
|
|
|
@@ -30,12 +37,12 @@ class AnthropicMessageConverter(lf.MessageConverter):
|
|
|
30
37
|
(
|
|
31
38
|
'Chunk preprocessor for Langfun chunk to Anthropic chunk conversion. '
|
|
32
39
|
'It will be applied before each Langfun chunk is converted. '
|
|
33
|
-
'If returns None, the chunk will be skipped.'
|
|
40
|
+
'If it returns None, the chunk will be skipped.'
|
|
34
41
|
)
|
|
35
42
|
] = None
|
|
36
43
|
|
|
37
44
|
def to_value(self, message: lf.Message) -> dict[str, Any]:
|
|
38
|
-
"""Converts a Langfun message to
|
|
45
|
+
"""Converts a Langfun message to Anthropic API."""
|
|
39
46
|
content = []
|
|
40
47
|
for chunk in message.chunk():
|
|
41
48
|
if self.chunk_preprocessor:
|
|
@@ -97,6 +104,8 @@ class AnthropicMessageConverter(lf.MessageConverter):
|
|
|
97
104
|
self._safe_read(source, 'media_type')
|
|
98
105
|
).from_bytes(base64.b64decode(self._safe_read(source, 'data')))
|
|
99
106
|
)
|
|
107
|
+
elif t in ('server_tool_use', 'web_search_tool_result'):
|
|
108
|
+
continue
|
|
100
109
|
else:
|
|
101
110
|
raise ValueError(f'Unsupported content part: {part!r}.')
|
|
102
111
|
message = message_cls.from_chunks(chunks)
|
|
@@ -253,14 +253,16 @@ class AnthropicConversionTest(unittest.TestCase):
|
|
|
253
253
|
)
|
|
254
254
|
self.assertEqual(
|
|
255
255
|
m.text,
|
|
256
|
-
'What are the common words from <<[[
|
|
256
|
+
'What are the common words from <<[[image:dc6e1e43]]>> and'
|
|
257
|
+
' <<[[pdf:5daf5f31]]>> ?'
|
|
257
258
|
)
|
|
258
|
-
|
|
259
|
-
self.
|
|
260
|
-
self.assertEqual(
|
|
259
|
+
modalities = m.modalities()
|
|
260
|
+
self.assertIsInstance(modalities[0], lf_modalities.Image)
|
|
261
|
+
self.assertEqual(modalities[0].mime_type, 'image/png')
|
|
262
|
+
self.assertEqual(modalities[0].content, image_content)
|
|
261
263
|
|
|
262
|
-
self.assertIsInstance(
|
|
263
|
-
self.assertEqual(
|
|
264
|
+
self.assertIsInstance(modalities[1], lf_modalities.PDF)
|
|
265
|
+
self.assertEqual(modalities[1].content, pdf_content)
|
|
264
266
|
|
|
265
267
|
|
|
266
268
|
if __name__ == '__main__':
|
|
@@ -21,7 +21,14 @@ from langfun.core import modalities as lf_modalities
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class GeminiMessageConverter(lf.MessageConverter):
|
|
24
|
-
"""Converter
|
|
24
|
+
"""Converter between Langfun messages and Gemini API message format.
|
|
25
|
+
|
|
26
|
+
This converter translates `lf.Message` objects into the JSON format required
|
|
27
|
+
by the public Gemini API (e.g., via Vertex AI or Google AI Studio) and
|
|
28
|
+
vice versa. It handles text and modalities like images, extracting thought
|
|
29
|
+
chunks if present. An optional `chunk_preprocessor` can be provided to
|
|
30
|
+
modify or filter chunks before conversion.
|
|
31
|
+
"""
|
|
25
32
|
|
|
26
33
|
FORMAT_ID = 'gemini'
|
|
27
34
|
|
|
@@ -30,7 +37,7 @@ class GeminiMessageConverter(lf.MessageConverter):
|
|
|
30
37
|
(
|
|
31
38
|
'Chunk preprocessor for Langfun chunk to Gemini chunk conversion. '
|
|
32
39
|
'It will be applied before each Langfun chunk is converted. '
|
|
33
|
-
'If returns None, the chunk will be skipped.'
|
|
40
|
+
'If it returns None, the chunk will be skipped.'
|
|
34
41
|
),
|
|
35
42
|
] = None
|
|
36
43
|
|
|
@@ -131,6 +138,8 @@ class GeminiMessageConverter(lf.MessageConverter):
|
|
|
131
138
|
self._safe_read(data, 'mimeType')
|
|
132
139
|
).from_uri(self._safe_read(data, 'fileUri'))
|
|
133
140
|
)
|
|
141
|
+
elif 'functionCall' in part or 'functionResponse' in part:
|
|
142
|
+
pass
|
|
134
143
|
else:
|
|
135
144
|
raise ValueError(f'Unsupported content part: {part!r}.')
|
|
136
145
|
message = message_cls.from_chunks(chunks)
|
|
@@ -225,19 +225,58 @@ class GeminiConversionTest(unittest.TestCase):
|
|
|
225
225
|
self.assertEqual(
|
|
226
226
|
m.text,
|
|
227
227
|
(
|
|
228
|
-
'What are the common words from <<[[
|
|
229
|
-
'and <<[[
|
|
228
|
+
'What are the common words from <<[[image:dc6e1e43]]>> , '
|
|
229
|
+
'<<[[pdf:4dc12e93]]>> and <<[[video:7e169565]]>> ?'
|
|
230
230
|
)
|
|
231
231
|
)
|
|
232
|
-
self.assertIsInstance(m.
|
|
233
|
-
self.assertEqual(m.
|
|
234
|
-
self.assertEqual(m.
|
|
232
|
+
self.assertIsInstance(m.modalities()[0], lf_modalities.Image)
|
|
233
|
+
self.assertEqual(m.modalities()[0].mime_type, 'image/png')
|
|
234
|
+
self.assertEqual(m.modalities()[0].to_bytes(), image_content)
|
|
235
235
|
|
|
236
|
-
self.assertIsInstance(m.
|
|
237
|
-
self.assertEqual(m.
|
|
236
|
+
self.assertIsInstance(m.modalities()[1], lf_modalities.PDF)
|
|
237
|
+
self.assertEqual(m.modalities()[1].uri, 'https://my.pdf')
|
|
238
238
|
|
|
239
|
-
self.assertIsInstance(m.
|
|
240
|
-
self.assertEqual(
|
|
239
|
+
self.assertIsInstance(m.modalities()[2], lf_modalities.Video)
|
|
240
|
+
self.assertEqual(
|
|
241
|
+
m.modalities()[2].uri,
|
|
242
|
+
'https://www.youtube.com/watch?v=abcd'
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
def test_from_value_with_function_call(self):
|
|
246
|
+
message = lf.Message.from_value(
|
|
247
|
+
{
|
|
248
|
+
'role': 'model',
|
|
249
|
+
'parts': [
|
|
250
|
+
{'text': 'Let me search for that.'},
|
|
251
|
+
{
|
|
252
|
+
'functionCall': {
|
|
253
|
+
'name': 'search',
|
|
254
|
+
'args': {'query': 'test'},
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
},
|
|
259
|
+
format='gemini',
|
|
260
|
+
)
|
|
261
|
+
self.assertEqual(message.text, 'Let me search for that.')
|
|
262
|
+
|
|
263
|
+
def test_from_value_with_function_response(self):
|
|
264
|
+
message = lf.Message.from_value(
|
|
265
|
+
{
|
|
266
|
+
'role': 'user',
|
|
267
|
+
'parts': [
|
|
268
|
+
{
|
|
269
|
+
'functionResponse': {
|
|
270
|
+
'name': 'search',
|
|
271
|
+
'response': {'results': ['a', 'b']},
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
{'text': 'Here are the results.'},
|
|
275
|
+
],
|
|
276
|
+
},
|
|
277
|
+
format='gemini',
|
|
278
|
+
)
|
|
279
|
+
self.assertEqual(message.text, 'Here are the results.')
|
|
241
280
|
|
|
242
281
|
|
|
243
282
|
if __name__ == '__main__':
|