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
@@ -0,0 +1,153 @@
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
+ import contextlib
15
+ from typing import Iterator
16
+ import unittest
17
+ from langfun.env import interface
18
+
19
+
20
+ class IdTest(unittest.TestCase):
21
+
22
+ def test_environment_id(self):
23
+ env_id = interface.Environment.Id('env@1/a b:c#def')
24
+ self.assertEqual(str(env_id), 'env@1/a b:c#def')
25
+ self.assertEqual(
26
+ env_id.working_dir(root_dir='/tmp'),
27
+ '/tmp/env_1/ab_c_def'
28
+ )
29
+ self.assertIsNone(env_id.working_dir(root_dir=None))
30
+
31
+ def test_sandbox_id(self):
32
+ sandbox_id = interface.Sandbox.Id(
33
+ environment_id=interface.Environment.Id('env'),
34
+ image_id='image:2025_01_01_00_00_00',
35
+ sandbox_id='sandbox'
36
+ )
37
+ self.assertEqual(str(sandbox_id), 'env/image:2025_01_01_00_00_00:sandbox')
38
+ self.assertEqual(
39
+ sandbox_id.working_dir(root_dir='/tmp'),
40
+ '/tmp/env/image_2025_01_01_00_00_00/sandbox'
41
+ )
42
+ self.assertIsNone(sandbox_id.working_dir(root_dir=None))
43
+
44
+ def test_feature_id(self):
45
+ # For non-sandboxed feature.
46
+ feature_id = interface.Feature.Id(
47
+ container_id=interface.Environment.Id('env'),
48
+ feature_name='feature'
49
+ )
50
+ self.assertEqual(str(feature_id), 'env/feature')
51
+ self.assertEqual(
52
+ feature_id.working_dir(root_dir='/tmp'),
53
+ '/tmp/env/feature'
54
+ )
55
+ self.assertIsNone(feature_id.working_dir(root_dir=None))
56
+
57
+ # For sandboxed feature.
58
+ feature_id = interface.Feature.Id(
59
+ container_id=interface.Sandbox.Id(
60
+ environment_id=interface.Environment.Id('env'),
61
+ image_id='image1',
62
+ sandbox_id='0'
63
+ ),
64
+ feature_name='feature'
65
+ )
66
+ self.assertEqual(str(feature_id), 'env/image1:0/feature')
67
+ self.assertEqual(
68
+ feature_id.working_dir(root_dir='/tmp'),
69
+ '/tmp/env/image1/0/feature'
70
+ )
71
+ self.assertIsNone(feature_id.working_dir(root_dir=None))
72
+
73
+
74
+ class TestingSandbox(interface.Sandbox):
75
+
76
+ id: interface.Sandbox.Id = interface.Sandbox.Id(
77
+ environment_id=interface.Environment.Id('env'),
78
+ image_id='test_image',
79
+ sandbox_id='0:0'
80
+ )
81
+ image_id: str = 'test_image'
82
+ features: dict[str, interface.Feature] = {}
83
+ status: interface.Sandbox.Status = interface.Sandbox.Status.READY
84
+ session_id: str | None = None
85
+
86
+ __test__ = False
87
+
88
+ def environment(self) -> interface.Environment:
89
+ pass
90
+
91
+ def _on_bound(self) -> None:
92
+ self.activities = []
93
+
94
+ def report_state_error(self, error: interface.SandboxStateError) -> None:
95
+ pass
96
+
97
+ def start(self) -> None:
98
+ pass
99
+
100
+ def shutdown(self) -> None:
101
+ pass
102
+
103
+ def start_session(self, session_id: str) -> None:
104
+ pass
105
+
106
+ def end_session(self, shutdown_sandbox: bool = False) -> None:
107
+ pass
108
+
109
+ @contextlib.contextmanager
110
+ def track_activity(
111
+ self,
112
+ name: str,
113
+ feature: interface.Feature | None = None,
114
+ **kwargs
115
+ ) -> Iterator[None]:
116
+ error = None
117
+ try:
118
+ yield
119
+ except BaseException as e:
120
+ error = e
121
+ raise
122
+ finally:
123
+ self.activities.append((name, error, kwargs))
124
+
125
+
126
+ class DecoratorTest(unittest.TestCase):
127
+
128
+ def test_treat_as_sandbox_state_error(self):
129
+
130
+ class SandboxA(TestingSandbox):
131
+
132
+ @interface.treat_as_sandbox_state_error(errors=(ValueError,))
133
+ def foo(self, bar: str) -> None:
134
+ raise ValueError(bar)
135
+
136
+ with self.assertRaises(interface.SandboxStateError):
137
+ SandboxA().foo('foo')
138
+
139
+ def test_log_sandbox_activity(self):
140
+
141
+ class SandboxB(TestingSandbox):
142
+
143
+ @interface.log_activity()
144
+ def bar(self, x: str) -> None:
145
+ pass
146
+
147
+ sb = SandboxB()
148
+ sb.bar('foo')
149
+ self.assertEqual(sb.activities, [('bar', None, {'x': 'foo'})])
150
+
151
+
152
+ if __name__ == '__main__':
153
+ unittest.main()
@@ -0,0 +1,59 @@
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
+ """Load balancers for environments."""
15
+
16
+ import abc
17
+ import threading
18
+
19
+ from langfun.env import interface
20
+ import pyglove as pg
21
+
22
+
23
+ class LoadBalancer(pg.Object):
24
+ """Base class for load balancers."""
25
+
26
+ @abc.abstractmethod
27
+ def acquire(self, sandbox_pool: list[interface.Sandbox]) -> interface.Sandbox:
28
+ """Acquires a free sandbox from a pool of sandboxes.
29
+
30
+ The load balancer will pick a sandbox from the pool and mark it as pending.
31
+
32
+ Args:
33
+ sandbox_pool: The pool of sandboxes to pick from.
34
+
35
+ Raises:
36
+ IndexError: If all sandboxes in the pool are either busy or dead.
37
+ """
38
+
39
+
40
+ class RoundRobin(LoadBalancer):
41
+ """Round robin load balancer."""
42
+
43
+ def _on_bound(self):
44
+ super()._on_bound()
45
+ self._counter = 0
46
+ self._acquire_lock = threading.Lock()
47
+
48
+ def acquire(self, sandbox_pool: list[interface.Sandbox]) -> interface.Sandbox:
49
+ """Returns a free sandbox from the pool."""
50
+ with self._acquire_lock:
51
+ for _ in range(len(sandbox_pool)):
52
+ sandbox = sandbox_pool[self._counter % len(sandbox_pool)]
53
+ self._counter = self._counter + 1
54
+ if sandbox.status == interface.Sandbox.Status.READY:
55
+ # Mark the sandbox as acquired to prevent it from being acquired by
56
+ # other threads.
57
+ sandbox.set_acquired()
58
+ return sandbox
59
+ raise IndexError('No free sandbox in the pool.')
@@ -0,0 +1,141 @@
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
+ import concurrent.futures
15
+ import contextlib
16
+ import time
17
+ from typing import Any, Iterator
18
+ import unittest
19
+
20
+ from langfun.env import interface
21
+ from langfun.env import load_balancers
22
+
23
+
24
+ class TestingSandbox(interface.Sandbox):
25
+ sandbox_id: str
26
+ status: interface.Sandbox.Status = interface.Sandbox.Status.READY
27
+ image_id: str = 'test_image'
28
+
29
+ __test__ = False
30
+
31
+ def _on_bound(self) -> None:
32
+ super()._on_bound()
33
+ self._session_id = None
34
+
35
+ @property
36
+ def id(self) -> interface.Sandbox.Id:
37
+ return interface.Sandbox.Id(
38
+ environment_id=interface.Environment.Id('testing-env'),
39
+ image_id=self.image_id,
40
+ sandbox_id=self.sandbox_id
41
+ )
42
+
43
+ @property
44
+ def environment(self) -> interface.Environment:
45
+ raise NotImplementedError()
46
+
47
+ @property
48
+ def features(self) -> dict[str, interface.Feature]:
49
+ raise NotImplementedError()
50
+
51
+ @property
52
+ def state_errors(self) -> list[interface.SandboxStateError]:
53
+ return []
54
+
55
+ def report_state_error(self, error: interface.SandboxStateError) -> None:
56
+ pass
57
+
58
+ def set_status(self, status: interface.Sandbox.Status) -> None:
59
+ self.rebind(status=status, skip_notification=True)
60
+
61
+ def set_acquired(self) -> None:
62
+ self.set_status(self.Status.ACQUIRED)
63
+
64
+ def start(self) -> None:
65
+ pass
66
+
67
+ def shutdown(self) -> None:
68
+ pass
69
+
70
+ def start_session(self, session_id: str) -> None:
71
+ self._session_id = session_id
72
+
73
+ def end_session(self, session_id: str) -> None:
74
+ self._session_id = None
75
+
76
+ @property
77
+ def session_id(self) -> str | None:
78
+ return self._session_id
79
+
80
+ @contextlib.contextmanager
81
+ def track_activity(
82
+ self,
83
+ name: str,
84
+ feature: interface.Feature | None = None,
85
+ **kwargs: Any
86
+ ) -> Iterator[None]:
87
+ try:
88
+ yield
89
+ finally:
90
+ pass
91
+
92
+
93
+ class RoundRobinTest(unittest.TestCase):
94
+
95
+ def test_basic(self):
96
+ sandbox_pool = [
97
+ TestingSandbox('0', interface.Sandbox.Status.OFFLINE),
98
+ TestingSandbox('1', interface.Sandbox.Status.SETTING_UP),
99
+ TestingSandbox('2', interface.Sandbox.Status.IN_SESSION),
100
+ TestingSandbox('3', status=interface.Sandbox.Status.READY),
101
+ TestingSandbox('4', status=interface.Sandbox.Status.READY),
102
+ ]
103
+ lb = load_balancers.RoundRobin()
104
+ sandbox = lb.acquire(sandbox_pool)
105
+ self.assertIs(sandbox, sandbox_pool[3])
106
+ self.assertEqual(sandbox.status, interface.Sandbox.Status.ACQUIRED)
107
+
108
+ sandbox = lb.acquire(sandbox_pool)
109
+ self.assertIs(sandbox, sandbox_pool[4])
110
+ self.assertEqual(sandbox.status, interface.Sandbox.Status.ACQUIRED)
111
+
112
+ sandbox_pool[0].set_status(interface.Sandbox.Status.READY)
113
+ sandbox = lb.acquire(sandbox_pool)
114
+ self.assertIs(sandbox, sandbox_pool[0])
115
+ self.assertEqual(sandbox.status, interface.Sandbox.Status.ACQUIRED)
116
+
117
+ with self.assertRaisesRegex(IndexError, 'No free sandbox in the pool.'):
118
+ lb.acquire(sandbox_pool)
119
+
120
+ def test_thread_safety(self):
121
+ sandbox_pool = [TestingSandbox(str(i)) for i in range(64)]
122
+
123
+ lb = load_balancers.RoundRobin()
124
+
125
+ def _thread_func(i):
126
+ sandbox = lb.acquire(sandbox_pool)
127
+ time.sleep(0.1)
128
+ sandbox.set_status(interface.Sandbox.Status.IN_SESSION)
129
+ time.sleep(0.1)
130
+ sandbox.set_status(interface.Sandbox.Status.OFFLINE)
131
+ time.sleep(0.1)
132
+ sandbox.set_status(interface.Sandbox.Status.READY)
133
+ return i
134
+
135
+ with concurrent.futures.ThreadPoolExecutor(max_workers=64) as executor:
136
+ for i, o in enumerate(executor.map(_thread_func, range(1024))):
137
+ self.assertEqual(o, i)
138
+
139
+
140
+ if __name__ == '__main__':
141
+ unittest.main()