langfun 0.1.2.dev202509120804__py3-none-any.whl → 0.1.2.dev202512040805__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 +2 -0
- langfun/core/eval/v2/checkpointing.py +76 -7
- 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 +92 -17
- 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 +84 -15
- 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 +90 -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} +72 -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 +294 -0
- langfun/core/eval/v2/runners/ckpt_monitor_test.py +162 -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 +12 -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 +64 -12
- langfun/core/llms/gemini_test.py +110 -0
- langfun/core/llms/google_genai.py +34 -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 +58 -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 +64 -3
- langfun/core/modalities/mime_test.py +11 -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.dev202512040805.dist-info}/METADATA +7 -3
- langfun-0.1.2.dev202512040805.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.dev202512040805.dist-info}/WHEEL +0 -0
- {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512040805.dist-info}/licenses/LICENSE +0 -0
- {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512040805.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,410 @@
|
|
|
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 dataclasses
|
|
15
|
+
import inspect
|
|
16
|
+
import typing
|
|
17
|
+
import unittest
|
|
18
|
+
|
|
19
|
+
import langfun.core as lf
|
|
20
|
+
from langfun.core.llms import fake
|
|
21
|
+
from langfun.core.structured.schema import base
|
|
22
|
+
from langfun.core.structured.schema import python
|
|
23
|
+
import pyglove as pg
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Activity(pg.Object):
|
|
27
|
+
description: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Itinerary(pg.Object):
|
|
31
|
+
"""A travel itinerary for a day."""
|
|
32
|
+
|
|
33
|
+
day: pg.typing.Int[1, None]
|
|
34
|
+
type: pg.typing.Enum['daytime', 'nighttime']
|
|
35
|
+
activities: list[Activity]
|
|
36
|
+
hotel: pg.typing.Annotated[
|
|
37
|
+
pg.typing.Str['.*Hotel'] | None,
|
|
38
|
+
'Hotel to stay if applicable.'
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class PlaceOfInterest(pg.Object):
|
|
43
|
+
"""The name of a place of interest.
|
|
44
|
+
|
|
45
|
+
A place of interest is a place that people ususally visit during their
|
|
46
|
+
travels.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
name: str
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
Itinerary.__serialization_key__ = 'Itinerary'
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Node(pg.Object):
|
|
56
|
+
children: list['Node']
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class SchemaReprTest(unittest.TestCase):
|
|
60
|
+
|
|
61
|
+
def test_class_definition(self):
|
|
62
|
+
self.assertEqual(
|
|
63
|
+
python.class_definition(Activity, allowed_dependencies=set()),
|
|
64
|
+
'class Activity:\n description: str\n',
|
|
65
|
+
)
|
|
66
|
+
self.assertEqual(
|
|
67
|
+
python.class_definition(Itinerary),
|
|
68
|
+
inspect.cleandoc("""
|
|
69
|
+
class Itinerary(Object):
|
|
70
|
+
\"\"\"A travel itinerary for a day.\"\"\"
|
|
71
|
+
day: int(min=1)
|
|
72
|
+
type: Literal['daytime', 'nighttime']
|
|
73
|
+
activities: list[Activity]
|
|
74
|
+
# Hotel to stay if applicable.
|
|
75
|
+
hotel: str(regex='.*Hotel') | None
|
|
76
|
+
""") + '\n',
|
|
77
|
+
)
|
|
78
|
+
self.assertEqual(
|
|
79
|
+
python.class_definition(
|
|
80
|
+
PlaceOfInterest, allowed_dependencies=set()
|
|
81
|
+
),
|
|
82
|
+
inspect.cleandoc("""
|
|
83
|
+
class PlaceOfInterest:
|
|
84
|
+
\"\"\"The name of a place of interest.
|
|
85
|
+
|
|
86
|
+
A place of interest is a place that people ususally visit during their
|
|
87
|
+
travels.
|
|
88
|
+
\"\"\"
|
|
89
|
+
name: str
|
|
90
|
+
""") + '\n',
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
class A(pg.Object):
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
self.assertEqual(
|
|
97
|
+
python.class_definition(A, allowed_dependencies=set()),
|
|
98
|
+
'class A:\n pass\n',
|
|
99
|
+
)
|
|
100
|
+
self.assertEqual(
|
|
101
|
+
python.class_definition(A),
|
|
102
|
+
'class A(Object):\n pass\n',
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
class C(pg.Object):
|
|
106
|
+
x: str
|
|
107
|
+
__kwargs__: typing.Any
|
|
108
|
+
|
|
109
|
+
self.assertEqual(
|
|
110
|
+
python.class_definition(C), 'class C(Object):\n x: str\n'
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
class D(pg.Object):
|
|
114
|
+
x: str
|
|
115
|
+
@python.include_method_in_prompt
|
|
116
|
+
def __call__(self, y: int) -> int:
|
|
117
|
+
return len(self.x) + y
|
|
118
|
+
|
|
119
|
+
self.assertEqual(
|
|
120
|
+
python.class_definition(D),
|
|
121
|
+
inspect.cleandoc(
|
|
122
|
+
"""
|
|
123
|
+
class D(Object):
|
|
124
|
+
x: str
|
|
125
|
+
|
|
126
|
+
def __call__(self, y: int) -> int:
|
|
127
|
+
return len(self.x) + y
|
|
128
|
+
""") + '\n'
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
class E(pg.Object):
|
|
132
|
+
x: str
|
|
133
|
+
y: typing.Annotated[int, 'y', dict(exclude_from_prompt=True)]
|
|
134
|
+
|
|
135
|
+
self.assertEqual(
|
|
136
|
+
python.class_definition(E),
|
|
137
|
+
inspect.cleandoc(
|
|
138
|
+
"""
|
|
139
|
+
class E(Object):
|
|
140
|
+
x: str
|
|
141
|
+
""") + '\n'
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def test_repr(self):
|
|
145
|
+
class Foo(pg.Object):
|
|
146
|
+
x: int
|
|
147
|
+
|
|
148
|
+
@dataclasses.dataclass(frozen=True)
|
|
149
|
+
class Bar:
|
|
150
|
+
"""Class Bar."""
|
|
151
|
+
y: str
|
|
152
|
+
|
|
153
|
+
@dataclasses.dataclass(frozen=True)
|
|
154
|
+
class Baz(Bar): # pylint: disable=unused-variable
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
class A(pg.Object):
|
|
158
|
+
foo: Foo
|
|
159
|
+
|
|
160
|
+
@python.include_method_in_prompt
|
|
161
|
+
def foo_value(self) -> int:
|
|
162
|
+
return self.foo.x
|
|
163
|
+
|
|
164
|
+
def baz_value(self) -> str:
|
|
165
|
+
return 'baz'
|
|
166
|
+
|
|
167
|
+
class B(A):
|
|
168
|
+
bar: Bar
|
|
169
|
+
foo2: Foo
|
|
170
|
+
|
|
171
|
+
@python.include_method_in_prompt
|
|
172
|
+
def bar_value(self) -> str:
|
|
173
|
+
return self.bar.y
|
|
174
|
+
|
|
175
|
+
schema = base.Schema([B])
|
|
176
|
+
self.assertEqual(
|
|
177
|
+
python.PythonPromptingProtocol().class_definitions(schema),
|
|
178
|
+
inspect.cleandoc('''
|
|
179
|
+
class Foo:
|
|
180
|
+
x: int
|
|
181
|
+
|
|
182
|
+
class Bar:
|
|
183
|
+
"""Class Bar."""
|
|
184
|
+
y: str
|
|
185
|
+
|
|
186
|
+
class Baz(Bar):
|
|
187
|
+
"""Baz(y: str)"""
|
|
188
|
+
y: str
|
|
189
|
+
|
|
190
|
+
class B:
|
|
191
|
+
foo: Foo
|
|
192
|
+
bar: Bar
|
|
193
|
+
foo2: Foo
|
|
194
|
+
|
|
195
|
+
def bar_value(self) -> str:
|
|
196
|
+
return self.bar.y
|
|
197
|
+
|
|
198
|
+
def foo_value(self) -> int:
|
|
199
|
+
return self.foo.x
|
|
200
|
+
''') + '\n',
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
self.assertEqual(
|
|
204
|
+
python.PythonPromptingProtocol().result_definition(schema), 'list[B]'
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
self.assertEqual(
|
|
208
|
+
base.schema_repr(schema),
|
|
209
|
+
inspect.cleandoc('''
|
|
210
|
+
list[B]
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
class Foo:
|
|
214
|
+
x: int
|
|
215
|
+
|
|
216
|
+
class Bar:
|
|
217
|
+
"""Class Bar."""
|
|
218
|
+
y: str
|
|
219
|
+
|
|
220
|
+
class Baz(Bar):
|
|
221
|
+
"""Baz(y: str)"""
|
|
222
|
+
y: str
|
|
223
|
+
|
|
224
|
+
class B:
|
|
225
|
+
foo: Foo
|
|
226
|
+
bar: Bar
|
|
227
|
+
foo2: Foo
|
|
228
|
+
|
|
229
|
+
def bar_value(self) -> str:
|
|
230
|
+
return self.bar.y
|
|
231
|
+
|
|
232
|
+
def foo_value(self) -> int:
|
|
233
|
+
return self.foo.x
|
|
234
|
+
```
|
|
235
|
+
'''),
|
|
236
|
+
)
|
|
237
|
+
self.assertEqual(
|
|
238
|
+
python.PythonPromptingProtocol().schema_repr(
|
|
239
|
+
schema,
|
|
240
|
+
include_result_definition=False,
|
|
241
|
+
markdown=False,
|
|
242
|
+
),
|
|
243
|
+
inspect.cleandoc('''
|
|
244
|
+
class Foo:
|
|
245
|
+
x: int
|
|
246
|
+
|
|
247
|
+
class Bar:
|
|
248
|
+
"""Class Bar."""
|
|
249
|
+
y: str
|
|
250
|
+
|
|
251
|
+
class Baz(Bar):
|
|
252
|
+
"""Baz(y: str)"""
|
|
253
|
+
y: str
|
|
254
|
+
|
|
255
|
+
class B:
|
|
256
|
+
foo: Foo
|
|
257
|
+
bar: Bar
|
|
258
|
+
foo2: Foo
|
|
259
|
+
|
|
260
|
+
def bar_value(self) -> str:
|
|
261
|
+
return self.bar.y
|
|
262
|
+
|
|
263
|
+
def foo_value(self) -> int:
|
|
264
|
+
return self.foo.x
|
|
265
|
+
'''),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class ValuePythonReprTest(unittest.TestCase):
|
|
270
|
+
|
|
271
|
+
def test_repr(self):
|
|
272
|
+
class Foo(pg.Object):
|
|
273
|
+
x: int
|
|
274
|
+
|
|
275
|
+
class A(pg.Object):
|
|
276
|
+
foo: list[Foo]
|
|
277
|
+
y: str | None
|
|
278
|
+
|
|
279
|
+
self.assertEqual(
|
|
280
|
+
base.value_repr(1, base.Schema(int)),
|
|
281
|
+
'```python\n1\n```'
|
|
282
|
+
)
|
|
283
|
+
self.assertEqual(
|
|
284
|
+
base.value_repr(lf.Template('hi, {{a}}', a='foo')),
|
|
285
|
+
'hi, foo'
|
|
286
|
+
)
|
|
287
|
+
self.assertEqual(
|
|
288
|
+
base.value_repr(
|
|
289
|
+
A([Foo(1), Foo(2)], 'bar'), base.Schema(A), markdown=False,
|
|
290
|
+
),
|
|
291
|
+
"A(foo=[Foo(x=1), Foo(x=2)], y='bar')",
|
|
292
|
+
)
|
|
293
|
+
self.assertEqual(
|
|
294
|
+
base.value_repr(
|
|
295
|
+
A([Foo(1), Foo(2)], 'bar'),
|
|
296
|
+
base.Schema(A),
|
|
297
|
+
markdown=True,
|
|
298
|
+
compact=False,
|
|
299
|
+
assign_to_var='output',
|
|
300
|
+
),
|
|
301
|
+
inspect.cleandoc("""
|
|
302
|
+
```python
|
|
303
|
+
output = A(
|
|
304
|
+
foo=[
|
|
305
|
+
Foo(
|
|
306
|
+
x=1
|
|
307
|
+
),
|
|
308
|
+
Foo(
|
|
309
|
+
x=2
|
|
310
|
+
)
|
|
311
|
+
],
|
|
312
|
+
y='bar'
|
|
313
|
+
)
|
|
314
|
+
```
|
|
315
|
+
"""),
|
|
316
|
+
)
|
|
317
|
+
self.assertEqual(
|
|
318
|
+
base.value_repr(A),
|
|
319
|
+
inspect.cleandoc("""
|
|
320
|
+
```python
|
|
321
|
+
class Foo(Object):
|
|
322
|
+
x: int
|
|
323
|
+
|
|
324
|
+
class A(Object):
|
|
325
|
+
foo: list[Foo]
|
|
326
|
+
y: str | None
|
|
327
|
+
```
|
|
328
|
+
"""),
|
|
329
|
+
)
|
|
330
|
+
self.assertEqual(python.source_form(int), 'int')
|
|
331
|
+
|
|
332
|
+
def test_parse(self):
|
|
333
|
+
class Foo(pg.Object):
|
|
334
|
+
x: int
|
|
335
|
+
|
|
336
|
+
class A(pg.Object):
|
|
337
|
+
foo: list[Foo]
|
|
338
|
+
y: str | None
|
|
339
|
+
|
|
340
|
+
self.assertEqual(
|
|
341
|
+
base.parse_value(
|
|
342
|
+
"A(foo=[Foo(x=1), Foo(x=2)], y='bar')", base.Schema(A)
|
|
343
|
+
),
|
|
344
|
+
A([Foo(1), Foo(2)], y='bar'),
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
def test_parse_with_correction(self):
|
|
348
|
+
class Foo(pg.Object):
|
|
349
|
+
x: int
|
|
350
|
+
|
|
351
|
+
class A(pg.Object):
|
|
352
|
+
foo: list[Foo]
|
|
353
|
+
y: str | None
|
|
354
|
+
|
|
355
|
+
self.assertEqual(
|
|
356
|
+
base.parse_value(
|
|
357
|
+
"A(foo=[Foo(x=1), Foo(x=2)], y='bar'",
|
|
358
|
+
base.Schema(A),
|
|
359
|
+
autofix=1,
|
|
360
|
+
autofix_lm=fake.StaticResponse(
|
|
361
|
+
inspect.cleandoc(
|
|
362
|
+
"""
|
|
363
|
+
CorrectedCode(
|
|
364
|
+
corrected_code='A(foo=[Foo(x=1), Foo(x=2)], y=\\\'bar\\\')',
|
|
365
|
+
)
|
|
366
|
+
"""
|
|
367
|
+
)
|
|
368
|
+
),
|
|
369
|
+
),
|
|
370
|
+
A([Foo(1), Foo(2)], y='bar'),
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
def test_parse_class_def(self):
|
|
374
|
+
self.assertTrue(
|
|
375
|
+
inspect.isclass(
|
|
376
|
+
base.parse_value(
|
|
377
|
+
"""
|
|
378
|
+
class A:
|
|
379
|
+
x: Dict[str, Any]
|
|
380
|
+
y: Optional[Sequence[str]]
|
|
381
|
+
z: Union[int, List[int], Tuple[int]]
|
|
382
|
+
""",
|
|
383
|
+
permission=pg.coding.CodePermission.ALL,
|
|
384
|
+
)
|
|
385
|
+
)
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
class StructureFromPythonTest(unittest.TestCase):
|
|
390
|
+
|
|
391
|
+
def test_parse_class_def(self):
|
|
392
|
+
|
|
393
|
+
class B:
|
|
394
|
+
pass
|
|
395
|
+
|
|
396
|
+
schema = base.Schema([B])
|
|
397
|
+
v = python.structure_from_python(
|
|
398
|
+
"""
|
|
399
|
+
class C(B):
|
|
400
|
+
pass
|
|
401
|
+
""",
|
|
402
|
+
global_vars=dict(B=B),
|
|
403
|
+
permission=pg.coding.CodePermission.ALL,
|
|
404
|
+
)
|
|
405
|
+
self.assertEqual(v.__module__, 'builtins')
|
|
406
|
+
self.assertEqual(schema.class_dependencies(), [B])
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
if __name__ == '__main__':
|
|
410
|
+
unittest.main()
|
|
@@ -90,16 +90,35 @@ def generate_class(
|
|
|
90
90
|
skip_lm: bool = False,
|
|
91
91
|
**kwargs,
|
|
92
92
|
) -> Type[Any] | lf.Message:
|
|
93
|
-
"""
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
93
|
+
"""Generates a Python class dynamically from a prompt using an LLM.
|
|
94
|
+
|
|
95
|
+
`lf.structured.generate_class` takes a class name and a natural language
|
|
96
|
+
description (prompt) and uses a language model to generate a Python class
|
|
97
|
+
(inheriting from `pg.Object`) that matches the description.
|
|
98
|
+
This is useful for creating structured data types on-the-fly based on
|
|
99
|
+
dynamic requirements.
|
|
100
|
+
|
|
101
|
+
**Example:**
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
import langfun as lf
|
|
105
|
+
import pyglove as pg
|
|
106
|
+
|
|
107
|
+
trip_plan_cls = lf.structured.generate_class(
|
|
108
|
+
'TripPlan',
|
|
109
|
+
'A trip plan to visit San Francisco, including a list of destinations,'
|
|
110
|
+
'start date, end date, and total budget.',
|
|
111
|
+
lm=lf.llms.Gemini25Flash())
|
|
112
|
+
|
|
113
|
+
# This might generate a class like:
|
|
114
|
+
# class TripPlan(pg.Object):
|
|
115
|
+
# destinations: list[str]
|
|
116
|
+
# start_date: str
|
|
117
|
+
# end_date: str
|
|
118
|
+
# total_budget: float
|
|
119
|
+
|
|
120
|
+
print(lf.Schema.from_value(trip_plan_cls).schema_str('python'))
|
|
121
|
+
```
|
|
103
122
|
|
|
104
123
|
Args:
|
|
105
124
|
name: Class name to be generated.
|
|
@@ -108,17 +127,17 @@ def generate_class(
|
|
|
108
127
|
lm: The language model to use. If not specified, the language model from
|
|
109
128
|
`lf.context` context manager will be used.
|
|
110
129
|
examples: An optional list of fewshot examples for helping class generation.
|
|
111
|
-
If None, a default single
|
|
112
|
-
`lf.structured.classgen_example` to generate
|
|
130
|
+
If None, a default single-shot example will be used. Use
|
|
131
|
+
`lf.structured.classgen_example` to generate examples.
|
|
113
132
|
returns_message: If True, returns `lf.Message` as the output, instead of
|
|
114
133
|
returning the structured `message.result`.
|
|
115
134
|
skip_lm: If True, returns the rendered prompt as a UserMessage object.
|
|
116
|
-
otherwise
|
|
135
|
+
otherwise returns the LLM response based on the rendered prompt.
|
|
117
136
|
**kwargs: Template variables passed to `prompt` and keyword arguments passed
|
|
118
137
|
to `lf.structured.GenerateClass`.
|
|
119
138
|
|
|
120
139
|
Returns:
|
|
121
|
-
|
|
140
|
+
The generated Python class, or `lf.Message` if `returns_message` is True.
|
|
122
141
|
|
|
123
142
|
Raises:
|
|
124
143
|
CodeError: if generation failed.
|
|
@@ -31,42 +31,54 @@ def score(
|
|
|
31
31
|
*,
|
|
32
32
|
lm: lf.LanguageModel | None = None,
|
|
33
33
|
examples: list[mapping.MappingExample] | None = None,
|
|
34
|
-
protocol:
|
|
34
|
+
protocol: str = 'python',
|
|
35
35
|
return_scoring_results: bool = False,
|
|
36
36
|
**kwargs,
|
|
37
37
|
) -> list[float] | list[lf.LMScoringResult]:
|
|
38
|
-
"""Scores
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
38
|
+
"""Scores completions based on a prompt using a language model.
|
|
39
|
+
|
|
40
|
+
`lf.score` computes the likelihood of each completion being generated given
|
|
41
|
+
a prompt, according to the specified language model. It can score text
|
|
42
|
+
completions or structured objects. If `schema` is provided, Langfun
|
|
43
|
+
formats the prompt and completions appropriately before scoring.
|
|
44
|
+
|
|
45
|
+
**Example 1: Score text completions**
|
|
46
|
+
```python
|
|
47
|
+
import langfun as lf
|
|
48
|
+
scores = lf.score(
|
|
49
|
+
'1 + 1 =',
|
|
50
|
+
['2', '3', '4'],
|
|
51
|
+
lm=lf.llms.Gemini25Flash())
|
|
52
|
+
print([f'{s:.3f}' for s in scores])
|
|
53
|
+
# Output: ['-0.001', '-2.345', '-3.456']
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Example 2: Score structured completions**
|
|
57
|
+
```python
|
|
58
|
+
import langfun as lf
|
|
59
|
+
import pyglove as pg
|
|
60
|
+
|
|
61
|
+
class Answer(pg.Object):
|
|
62
|
+
result: int
|
|
63
|
+
|
|
64
|
+
scores = lf.score(
|
|
65
|
+
'1 + 1 =',
|
|
66
|
+
[Answer(result=2), Answer(result=3), Answer(result=4)],
|
|
67
|
+
lm=lf.llms.Gemini25Flash())
|
|
68
|
+
print([f'{s:.3f}' for s in scores])
|
|
69
|
+
# Output: ['-0.001', '-2.345', '-3.456']
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Example 3: Score multiple prompt/completion pairs**
|
|
73
|
+
```python
|
|
74
|
+
import langfun as lf
|
|
75
|
+
scores = lf.score(
|
|
76
|
+
['1 + 1 =', '2 + 2 ='],
|
|
77
|
+
['2', '4'],
|
|
78
|
+
lm=lf.llms.Gemini25Flash())
|
|
79
|
+
print([f'{s:.3f}' for s in scores])
|
|
80
|
+
# Output: ['-0.001', '-0.002']
|
|
81
|
+
```
|
|
70
82
|
|
|
71
83
|
Args:
|
|
72
84
|
prompt: The prompt(s) based on which each completion will be scored.
|
|
@@ -74,8 +86,7 @@ def score(
|
|
|
74
86
|
schema: The schema as the output type. If None, it will be inferred from
|
|
75
87
|
the completions.
|
|
76
88
|
lm: The language model used for scoring.
|
|
77
|
-
examples:
|
|
78
|
-
completions.
|
|
89
|
+
examples: Few-shot examples used to construct the prompt for scoring.
|
|
79
90
|
protocol: The protocol for formulating the prompt based on objects.
|
|
80
91
|
return_scoring_results: If True, returns a list of `lf.LMScoringResult`,
|
|
81
92
|
otherwise returns a list of floats as the scores of each completion.
|
|
@@ -190,7 +201,7 @@ async def ascore(
|
|
|
190
201
|
*,
|
|
191
202
|
lm: lf.LanguageModel | None = None,
|
|
192
203
|
examples: list[mapping.MappingExample] | None = None,
|
|
193
|
-
protocol:
|
|
204
|
+
protocol: str = 'python',
|
|
194
205
|
return_scoring_results: bool = False,
|
|
195
206
|
**kwargs,
|
|
196
207
|
) -> list[float] | list[lf.LMScoringResult]:
|
|
@@ -23,30 +23,45 @@ import pyglove as pg
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def tokenize(
|
|
26
|
-
prompt: Union[str, pg.Symbolic
|
|
26
|
+
prompt: Union[str, pg.Symbolic, list[str | pg.Symbolic]],
|
|
27
27
|
schema: Union[
|
|
28
28
|
schema_lib.Schema, Type[Any], list[Type[Any]], dict[str, Any], None
|
|
29
29
|
] = None,
|
|
30
30
|
*,
|
|
31
31
|
lm: lf.LanguageModel | None = None,
|
|
32
32
|
examples: list[mapping.MappingExample] | None = None,
|
|
33
|
-
protocol:
|
|
33
|
+
protocol: str = 'python',
|
|
34
34
|
**kwargs,
|
|
35
35
|
) -> list[tuple[str | bytes, int]]:
|
|
36
|
-
"""
|
|
36
|
+
"""Renders a prompt and tokenizes it using a language model.
|
|
37
|
+
|
|
38
|
+
`lf.tokenize` first renders a prompt based on the provided `prompt`,
|
|
39
|
+
`schema`, and `examples`, similar to `lf.query`, and then uses the
|
|
40
|
+
specified language model (`lm`) to tokenize the resulting message.
|
|
41
|
+
This is useful for understanding how a prompt is seen by the model or
|
|
42
|
+
for estimating token counts before sending requests.
|
|
43
|
+
|
|
44
|
+
**Example:**
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import langfun as lf
|
|
48
|
+
tokens = lf.tokenize('Hello world!', lm=lf.llms.Gpt4())
|
|
49
|
+
print(tokens)
|
|
50
|
+
# Output might look like: [('Hello', 15339), (' world', 1917), ('!', 0)]
|
|
51
|
+
```
|
|
37
52
|
|
|
38
53
|
Args:
|
|
39
|
-
prompt: The prompt
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
54
|
+
prompt: The prompt to render and tokenize. Can be a string, `pg.Symbolic`,
|
|
55
|
+
or `lf.Template`.
|
|
56
|
+
schema: The schema for formatting the prompt, if `prompt` is structured or
|
|
57
|
+
if schema-based formatting is needed.
|
|
58
|
+
lm: The language model to use for tokenization.
|
|
59
|
+
examples: Few-shot examples to include in the rendered prompt.
|
|
45
60
|
protocol: The protocol for formulating the prompt based on objects.
|
|
46
61
|
**kwargs: Keyword arguments that are referred by the prompt.
|
|
47
62
|
|
|
48
63
|
Returns:
|
|
49
|
-
A list of (
|
|
64
|
+
A list of (token_str, token_id) tuples representing the tokenized prompt.
|
|
50
65
|
"""
|
|
51
66
|
input_message = querying.query_prompt(
|
|
52
67
|
prompt,
|
|
@@ -72,7 +87,7 @@ async def atokenize(
|
|
|
72
87
|
*,
|
|
73
88
|
lm: lf.LanguageModel | None = None,
|
|
74
89
|
examples: list[mapping.MappingExample] | None = None,
|
|
75
|
-
protocol:
|
|
90
|
+
protocol: str = 'python',
|
|
76
91
|
**kwargs,
|
|
77
92
|
) -> list[tuple[str | bytes, int]]:
|
|
78
93
|
"""Async version of `lf.tokenize`."""
|
langfun/core/subscription.py
CHANGED
|
@@ -35,7 +35,7 @@ EventType = TypeVar('EventType')
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
class EventHandler(Generic[EventType], metaclass=abc.ABCMeta):
|
|
38
|
-
"""Interface for event
|
|
38
|
+
"""Interface for event handler."""
|
|
39
39
|
|
|
40
40
|
@classmethod
|
|
41
41
|
@functools.cache
|
|
@@ -51,7 +51,7 @@ class EventHandler(Generic[EventType], metaclass=abc.ABCMeta):
|
|
|
51
51
|
|
|
52
52
|
@classmethod
|
|
53
53
|
def accepts(cls, event: Event[Any]) -> bool:
|
|
54
|
-
"""Returns True if current event handler class can
|
|
54
|
+
"""Returns True if current event handler class can accept an event."""
|
|
55
55
|
return isinstance(event, cls.event_type())
|
|
56
56
|
|
|
57
57
|
@abc.abstractmethod
|