langfun 0.1.1.dev20240827__py3-none-any.whl → 0.1.1.dev202408282153__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/core/concurrent.py +148 -21
- langfun/core/concurrent_test.py +58 -8
- langfun/core/eval/base.py +7 -7
- langfun/core/llms/google_genai.py +40 -13
- langfun/core/llms/openai.py +34 -17
- langfun/core/llms/vertexai.py +30 -9
- langfun/core/modalities/image.py +17 -5
- langfun/core/modalities/mime.py +13 -4
- langfun/core/modalities/ms_office.py +17 -8
- langfun/core/text_formatting.py +7 -1
- langfun/core/text_formatting_test.py +18 -0
- {langfun-0.1.1.dev20240827.dist-info → langfun-0.1.1.dev202408282153.dist-info}/METADATA +78 -14
- {langfun-0.1.1.dev20240827.dist-info → langfun-0.1.1.dev202408282153.dist-info}/RECORD +16 -16
- {langfun-0.1.1.dev20240827.dist-info → langfun-0.1.1.dev202408282153.dist-info}/WHEEL +1 -1
- {langfun-0.1.1.dev20240827.dist-info → langfun-0.1.1.dev202408282153.dist-info}/LICENSE +0 -0
- {langfun-0.1.1.dev20240827.dist-info → langfun-0.1.1.dev202408282153.dist-info}/top_level.txt +0 -0
langfun/core/concurrent.py
CHANGED
@@ -13,17 +13,30 @@
|
|
13
13
|
# limitations under the License.
|
14
14
|
"""Utility library for handling concurrency in langfun."""
|
15
15
|
|
16
|
+
import abc
|
16
17
|
import collections
|
17
18
|
import concurrent.futures
|
18
19
|
import dataclasses
|
20
|
+
import io
|
19
21
|
import random
|
22
|
+
import sys
|
20
23
|
import threading
|
21
24
|
import time
|
22
25
|
from typing import Any, Callable, Iterable, Iterator, Literal, Sequence, Tuple, Type, Union
|
23
26
|
|
24
27
|
from langfun.core import component
|
28
|
+
from langfun.core import text_formatting
|
25
29
|
import pyglove as pg
|
26
|
-
|
30
|
+
|
31
|
+
|
32
|
+
progress_bar: Literal['tqdm', 'console', None] = None
|
33
|
+
|
34
|
+
try:
|
35
|
+
from tqdm import auto as tqdm # pylint: disable=g-import-not-at-top
|
36
|
+
progress_bar = 'tqdm'
|
37
|
+
except ImportError as e:
|
38
|
+
progress_bar = 'console'
|
39
|
+
tqdm = None
|
27
40
|
|
28
41
|
|
29
42
|
def with_context_access(func: Callable[..., Any]) -> Callable[..., Any]:
|
@@ -142,7 +155,6 @@ def with_retry(
|
|
142
155
|
attempt = 1
|
143
156
|
return base_interval() * (2 ** (attempt - 1))
|
144
157
|
|
145
|
-
wait_interval = None
|
146
158
|
wait_intervals = []
|
147
159
|
errors = []
|
148
160
|
while True:
|
@@ -356,17 +368,17 @@ class ProgressBar:
|
|
356
368
|
label: str | None
|
357
369
|
total: int
|
358
370
|
color: str | None = None
|
359
|
-
|
371
|
+
status: dict[str, Any] | None = None
|
360
372
|
|
361
373
|
@dataclasses.dataclass
|
362
374
|
class Update:
|
363
375
|
"""Progress bar update."""
|
364
376
|
bar_id: int
|
365
377
|
delta: int
|
366
|
-
|
378
|
+
status: Union[dict[str, Any], str, None] = None
|
367
379
|
color: str | None = None
|
368
380
|
|
369
|
-
_progress_bars: dict[int,
|
381
|
+
_progress_bars: dict[int, '_ProgressControl'] = {}
|
370
382
|
_install_requests: list[tuple[int, Settings]] = []
|
371
383
|
_updates: collections.deque[Update] = collections.deque()
|
372
384
|
_uninstall_requests: list[int] = []
|
@@ -378,11 +390,11 @@ class ProgressBar:
|
|
378
390
|
label: str | None,
|
379
391
|
total: int,
|
380
392
|
color: str | None = None,
|
381
|
-
|
393
|
+
status: dict[str, Any] | None = None,
|
382
394
|
) -> int:
|
383
395
|
"""Installs a progress bar and returns a reference id."""
|
384
396
|
with cls._lock:
|
385
|
-
settings = ProgressBar.Settings(label, total, color,
|
397
|
+
settings = ProgressBar.Settings(label, total, color, status)
|
386
398
|
bar_id = id(settings)
|
387
399
|
cls._install_requests.append((bar_id, settings))
|
388
400
|
return bar_id
|
@@ -392,15 +404,17 @@ class ProgressBar:
|
|
392
404
|
cls,
|
393
405
|
bar_id: int,
|
394
406
|
delta: int = 0,
|
395
|
-
|
407
|
+
status: Union[dict[str, Any], str, None] = None,
|
396
408
|
color: str | None = None,
|
397
409
|
refresh: bool = True,
|
398
410
|
) -> None:
|
399
411
|
"""Report the progress for a label."""
|
412
|
+
if status is not None and not isinstance(status, (str, dict)):
|
413
|
+
raise ValueError(f'Unsupported status: {status}')
|
400
414
|
with cls._lock:
|
401
415
|
cls._updates.append(
|
402
416
|
ProgressBar.Update(
|
403
|
-
bar_id=bar_id, delta=delta,
|
417
|
+
bar_id=bar_id, delta=delta, status=status, color=color,
|
404
418
|
)
|
405
419
|
)
|
406
420
|
if refresh:
|
@@ -422,11 +436,11 @@ class ProgressBar:
|
|
422
436
|
# Process install requests.
|
423
437
|
if cls._install_requests:
|
424
438
|
for bar_id, settings in cls._install_requests:
|
425
|
-
cls._progress_bars[bar_id] =
|
439
|
+
cls._progress_bars[bar_id] = _progress_control(
|
426
440
|
total=settings.total,
|
427
|
-
|
428
|
-
|
429
|
-
|
441
|
+
label=settings.label,
|
442
|
+
color=settings.color,
|
443
|
+
status=settings.status)
|
430
444
|
cls._install_requests.clear()
|
431
445
|
|
432
446
|
# Process updates.
|
@@ -441,15 +455,11 @@ class ProgressBar:
|
|
441
455
|
if update.delta > 0:
|
442
456
|
bar.update(update.delta)
|
443
457
|
|
444
|
-
if
|
445
|
-
bar.
|
446
|
-
elif isinstance(update.postfix, dict):
|
447
|
-
bar.set_postfix(update.postfix, refresh=False)
|
448
|
-
elif update.postfix is not None:
|
449
|
-
raise ValueError(f'Unsupported postfix: {update.postfix}')
|
458
|
+
if update.status is not None:
|
459
|
+
bar.set_status(update.status)
|
450
460
|
|
451
461
|
if update.color is not None:
|
452
|
-
bar.
|
462
|
+
bar.set_color(update.color)
|
453
463
|
updated_bars.add(bar)
|
454
464
|
|
455
465
|
# Refresh each updated bar just once.
|
@@ -603,7 +613,7 @@ def concurrent_map(
|
|
603
613
|
if len(error_text) >= 64:
|
604
614
|
error_text = error_text[:64] + '...'
|
605
615
|
status['LastError'] = error_text
|
606
|
-
ProgressBar.update(bar_id, delta=1,
|
616
|
+
ProgressBar.update(bar_id, delta=1, status=status)
|
607
617
|
|
608
618
|
try:
|
609
619
|
if ordered:
|
@@ -729,5 +739,122 @@ class ExecutorPool:
|
|
729
739
|
raise ValueError(f'Unsupported value: {maybe_executor}.')
|
730
740
|
|
731
741
|
|
742
|
+
class _ProgressControl(pg.Object):
|
743
|
+
"""Abstract progress control."""
|
744
|
+
# Disable symbolic comparison so the hash is based on object address.
|
745
|
+
use_symbolic_comparison = False
|
746
|
+
|
747
|
+
total: int
|
748
|
+
label: str | None
|
749
|
+
color: str | None
|
750
|
+
status: str | dict[str, Any] | None
|
751
|
+
|
752
|
+
def set_color(self, color: str | None):
|
753
|
+
with pg.notify_on_change(False):
|
754
|
+
self.rebind(color=color)
|
755
|
+
|
756
|
+
def set_status(self, status: str | dict[str, Any] | None):
|
757
|
+
with pg.notify_on_change(False):
|
758
|
+
self.rebind(status=status)
|
759
|
+
|
760
|
+
@abc.abstractmethod
|
761
|
+
def update(self, delta):
|
762
|
+
"""Update progress."""
|
763
|
+
|
764
|
+
@abc.abstractmethod
|
765
|
+
def refresh(self) -> None:
|
766
|
+
"""Refresh progress bar."""
|
767
|
+
|
768
|
+
|
769
|
+
class _TqdmProgressControl(_ProgressControl):
|
770
|
+
"""Tqdm-based progress control."""
|
771
|
+
|
772
|
+
def _on_bound(self):
|
773
|
+
super()._on_bound()
|
774
|
+
assert tqdm is not None
|
775
|
+
self._tqdm = tqdm.tqdm(
|
776
|
+
total=self.total,
|
777
|
+
desc=self.label,
|
778
|
+
colour=self.color,
|
779
|
+
postfix=self.status,
|
780
|
+
)
|
781
|
+
|
782
|
+
def update(self, delta: int) -> None:
|
783
|
+
self._tqdm.update(delta)
|
784
|
+
|
785
|
+
def refresh(self):
|
786
|
+
self._tqdm.set_description(self.label, refresh=False)
|
787
|
+
if isinstance(self.status, str):
|
788
|
+
self._tqdm.set_postfix_str(self.status, refresh=False)
|
789
|
+
else:
|
790
|
+
self._tqdm.set_postfix(self.status, refresh=False)
|
791
|
+
self._tqdm.colour = self.color
|
792
|
+
self._tqdm.refresh()
|
793
|
+
|
794
|
+
|
795
|
+
class _ConsoleProgressControl(_ProgressControl):
|
796
|
+
"""Simple progress control by printing the status to the console."""
|
797
|
+
|
798
|
+
def _on_bound(self):
|
799
|
+
super()._on_bound()
|
800
|
+
self._progress = 0
|
801
|
+
|
802
|
+
def update(self, delta: int) -> None:
|
803
|
+
self._progress += delta
|
804
|
+
|
805
|
+
def refresh(self):
|
806
|
+
s = io.StringIO()
|
807
|
+
if self.label is not None:
|
808
|
+
s.write(text_formatting.colored(self.label, 'red', styles=['bold']))
|
809
|
+
s.write(': ')
|
810
|
+
s.write(
|
811
|
+
text_formatting.colored(
|
812
|
+
'%d%% (%d/%d)' %
|
813
|
+
(
|
814
|
+
self._progress * 100 // self.total,
|
815
|
+
self._progress,
|
816
|
+
self.total,
|
817
|
+
),
|
818
|
+
color=self.color or 'green'
|
819
|
+
)
|
820
|
+
)
|
821
|
+
if self.status is not None:
|
822
|
+
status = repr(self.status) if isinstance(
|
823
|
+
self.status, dict) else self.status
|
824
|
+
s.write(f' : {status}')
|
825
|
+
sys.stderr.write(s.getvalue() + '\n')
|
826
|
+
|
827
|
+
|
828
|
+
class _NoopProgressControl(_ProgressControl):
|
829
|
+
"""No-op progress control."""
|
830
|
+
|
831
|
+
def update(self, delta: int) -> None:
|
832
|
+
pass
|
833
|
+
|
834
|
+
def refresh(self) -> None:
|
835
|
+
pass
|
836
|
+
|
837
|
+
|
838
|
+
def _progress_control(
|
839
|
+
total: int,
|
840
|
+
label: str | None,
|
841
|
+
color: str | None,
|
842
|
+
status: str | dict[str, Any] | None,
|
843
|
+
) -> _ProgressControl:
|
844
|
+
"""Creates a process control."""
|
845
|
+
if progress_bar == 'tqdm':
|
846
|
+
if not tqdm:
|
847
|
+
raise RuntimeError(
|
848
|
+
'Please install package "tqdm" to use `tqdm` progress bar.'
|
849
|
+
)
|
850
|
+
return _TqdmProgressControl(total, label, color, status)
|
851
|
+
elif progress_bar == 'console':
|
852
|
+
return _ConsoleProgressControl(total, label, color, status)
|
853
|
+
elif progress_bar is None:
|
854
|
+
return _NoopProgressControl(total, label, color, status)
|
855
|
+
else:
|
856
|
+
raise ValueError(f'Unsupported progress bar type: {progress_bar}')
|
857
|
+
|
858
|
+
|
732
859
|
# The global executor pool based on resource IDs.
|
733
860
|
_executor_pool = ExecutorPool()
|
langfun/core/concurrent_test.py
CHANGED
@@ -233,6 +233,57 @@ class ProgressTest(unittest.TestCase):
|
|
233
233
|
self.assertIs(p.last_error, job2.error)
|
234
234
|
|
235
235
|
|
236
|
+
class ProgressControlTest(unittest.TestCase):
|
237
|
+
|
238
|
+
def test_noop(self):
|
239
|
+
concurrent.progress_bar = None
|
240
|
+
ctrl = concurrent._progress_control(100, 'noop', 'blue', None)
|
241
|
+
self.assertIsInstance(ctrl, concurrent._NoopProgressControl)
|
242
|
+
string_io = io.StringIO()
|
243
|
+
with contextlib.redirect_stderr(string_io):
|
244
|
+
ctrl.update(1)
|
245
|
+
ctrl.refresh()
|
246
|
+
self.assertEqual(string_io.getvalue(), '')
|
247
|
+
concurrent.progress_bar = 'tqdm'
|
248
|
+
|
249
|
+
def test_console(self):
|
250
|
+
concurrent.progress_bar = 'console'
|
251
|
+
ctrl = concurrent._progress_control(100, 'foo', 'blue', None)
|
252
|
+
self.assertIsInstance(ctrl, concurrent._ConsoleProgressControl)
|
253
|
+
string_io = io.StringIO()
|
254
|
+
with contextlib.redirect_stderr(string_io):
|
255
|
+
ctrl.set_status('bar')
|
256
|
+
ctrl.update(10)
|
257
|
+
ctrl.refresh()
|
258
|
+
self.assertEqual(
|
259
|
+
string_io.getvalue(),
|
260
|
+
'\x1b[1m\x1b[31mfoo\x1b[0m: \x1b[34m10% (10/100)\x1b[0m : bar\n'
|
261
|
+
)
|
262
|
+
concurrent.progress_bar = 'tqdm'
|
263
|
+
|
264
|
+
def test_tqdm(self):
|
265
|
+
concurrent.progress_bar = 'tqdm'
|
266
|
+
string_io = io.StringIO()
|
267
|
+
with contextlib.redirect_stderr(string_io):
|
268
|
+
ctrl = concurrent._progress_control(100, 'foo', 'blue', None)
|
269
|
+
self.assertIsInstance(ctrl, concurrent._TqdmProgressControl)
|
270
|
+
ctrl.update(10)
|
271
|
+
ctrl.refresh()
|
272
|
+
self.assertIn('10/100', string_io.getvalue())
|
273
|
+
|
274
|
+
tqdm = concurrent.tqdm
|
275
|
+
concurrent.tqdm = None
|
276
|
+
with self.assertRaisesRegex(RuntimeError, 'install package "tqdm"'):
|
277
|
+
_ = concurrent._progress_control(100, 'foo', 'blue', None)
|
278
|
+
concurrent.tqdm = tqdm
|
279
|
+
|
280
|
+
def test_unsupported(self):
|
281
|
+
concurrent.progress_bar = 'unknown'
|
282
|
+
with self.assertRaisesRegex(ValueError, 'Unsupported progress bar type'):
|
283
|
+
_ = concurrent._progress_control(100, 'foo', 'blue', None)
|
284
|
+
concurrent.progress_bar = 'tqdm'
|
285
|
+
|
286
|
+
|
236
287
|
class ProgressBarTest(unittest.TestCase):
|
237
288
|
|
238
289
|
def test_multithread_support(self):
|
@@ -241,13 +292,12 @@ class ProgressBarTest(unittest.TestCase):
|
|
241
292
|
bar_id = concurrent.ProgressBar.install(None, 5)
|
242
293
|
def fun(x):
|
243
294
|
del x
|
244
|
-
concurrent.ProgressBar.update(bar_id, 1,
|
295
|
+
concurrent.ProgressBar.update(bar_id, 1, status=None)
|
245
296
|
|
246
297
|
for _ in concurrent.concurrent_execute(fun, range(5)):
|
247
298
|
concurrent.ProgressBar.refresh()
|
248
299
|
concurrent.ProgressBar.uninstall(bar_id)
|
249
300
|
output_str = string_io.getvalue()
|
250
|
-
print(output_str)
|
251
301
|
self.assertIn('100%', output_str)
|
252
302
|
self.assertIn('5/5', output_str)
|
253
303
|
|
@@ -255,12 +305,12 @@ class ProgressBarTest(unittest.TestCase):
|
|
255
305
|
string_io = io.StringIO()
|
256
306
|
with contextlib.redirect_stderr(string_io):
|
257
307
|
bar_id = concurrent.ProgressBar.install(None, 4)
|
258
|
-
concurrent.ProgressBar.update(bar_id, 1,
|
259
|
-
concurrent.ProgressBar.update(bar_id, 1,
|
260
|
-
concurrent.ProgressBar.update(bar_id, color='
|
261
|
-
concurrent.ProgressBar.update(bar_id, 2,
|
262
|
-
with self.assertRaisesRegex(ValueError, 'Unsupported
|
263
|
-
concurrent.ProgressBar.update(bar_id, 0,
|
308
|
+
concurrent.ProgressBar.update(bar_id, 1, status=None)
|
309
|
+
concurrent.ProgressBar.update(bar_id, 1, status='hello')
|
310
|
+
concurrent.ProgressBar.update(bar_id, color='green')
|
311
|
+
concurrent.ProgressBar.update(bar_id, 2, status=dict(x=1))
|
312
|
+
with self.assertRaisesRegex(ValueError, 'Unsupported status'):
|
313
|
+
concurrent.ProgressBar.update(bar_id, 0, status=1)
|
264
314
|
concurrent.ProgressBar.uninstall(bar_id)
|
265
315
|
self.assertIn('1/4', string_io.getvalue())
|
266
316
|
self.assertIn('2/4', string_io.getvalue())
|
langfun/core/eval/base.py
CHANGED
@@ -242,7 +242,7 @@ class Evaluable(lf.Component):
|
|
242
242
|
):
|
243
243
|
if show_progress:
|
244
244
|
lf.concurrent.ProgressBar.update(
|
245
|
-
progress_bar,
|
245
|
+
progress_bar, status='LOADING SAVED RESULTS...', color='yellow'
|
246
246
|
)
|
247
247
|
if self.try_load_result():
|
248
248
|
run_status = 'CACHED'
|
@@ -271,7 +271,7 @@ class Evaluable(lf.Component):
|
|
271
271
|
if should_save:
|
272
272
|
if show_progress:
|
273
273
|
lf.concurrent.ProgressBar.update(
|
274
|
-
progress_bar,
|
274
|
+
progress_bar, status='SAVING RESULTS...', color='yellow'
|
275
275
|
)
|
276
276
|
|
277
277
|
# Save evaluation results.
|
@@ -284,7 +284,7 @@ class Evaluable(lf.Component):
|
|
284
284
|
if show_progress:
|
285
285
|
lf.concurrent.ProgressBar.update(
|
286
286
|
progress_bar,
|
287
|
-
|
287
|
+
status=self._completion_status(run_status),
|
288
288
|
color='green',
|
289
289
|
)
|
290
290
|
else:
|
@@ -340,7 +340,7 @@ class Evaluable(lf.Component):
|
|
340
340
|
f'[#{leaf.index} - {leaf.node.id}]',
|
341
341
|
total=leaf.node.num_examples if leaf.enabled else 0,
|
342
342
|
color='cyan' if leaf.enabled else 'yellow',
|
343
|
-
|
343
|
+
status=None if leaf.enabled else 'SKIPPED.')
|
344
344
|
|
345
345
|
# Run leaf groups in parallel.
|
346
346
|
try:
|
@@ -354,7 +354,7 @@ class Evaluable(lf.Component):
|
|
354
354
|
# Save results for non-leaf nodes.
|
355
355
|
lf.concurrent.ProgressBar.update(
|
356
356
|
overview_bar,
|
357
|
-
|
357
|
+
status='SAVING RESULTS...',
|
358
358
|
color='yellow')
|
359
359
|
|
360
360
|
for node in self.nonleaf_nodes:
|
@@ -364,7 +364,7 @@ class Evaluable(lf.Component):
|
|
364
364
|
|
365
365
|
if should_save and summary:
|
366
366
|
lf.concurrent.ProgressBar.update(
|
367
|
-
overview_bar,
|
367
|
+
overview_bar, status='FINALIZING SUMMARY...'
|
368
368
|
)
|
369
369
|
|
370
370
|
summary.save(os.path.join(self.root_dir, Evaluable.SUMMARY_HTML))
|
@@ -378,7 +378,7 @@ class Evaluable(lf.Component):
|
|
378
378
|
# Signal all task completed by making the bar green.
|
379
379
|
lf.concurrent.ProgressBar.update(
|
380
380
|
overview_bar,
|
381
|
-
|
381
|
+
status='COMPLETED',
|
382
382
|
color='green')
|
383
383
|
|
384
384
|
finally:
|
@@ -18,12 +18,29 @@ import functools
|
|
18
18
|
import os
|
19
19
|
from typing import Annotated, Any, Literal
|
20
20
|
|
21
|
-
import google.generativeai as genai
|
22
21
|
import langfun.core as lf
|
23
22
|
from langfun.core import modalities as lf_modalities
|
24
23
|
import pyglove as pg
|
25
24
|
|
26
25
|
|
26
|
+
try:
|
27
|
+
import google.generativeai as genai # pylint: disable=g-import-not-at-top
|
28
|
+
BlobDict = genai.types.BlobDict
|
29
|
+
GenerativeModel = genai.GenerativeModel
|
30
|
+
Completion = genai.types.Completion
|
31
|
+
GenerationConfig = genai.GenerationConfig
|
32
|
+
GenerateContentResponse = genai.types.GenerateContentResponse
|
33
|
+
ChatResponse = genai.types.ChatResponse
|
34
|
+
except ImportError:
|
35
|
+
genai = None
|
36
|
+
BlobDict = Any
|
37
|
+
GenerativeModel = Any
|
38
|
+
Completion = Any
|
39
|
+
GenerationConfig = Any
|
40
|
+
GenerateContentResponse = Any
|
41
|
+
ChatResponse = Any
|
42
|
+
|
43
|
+
|
27
44
|
@lf.use_init_args(['model'])
|
28
45
|
class GenAI(lf.LanguageModel):
|
29
46
|
"""Language models provided by Google GenAI."""
|
@@ -59,10 +76,16 @@ class GenAI(lf.LanguageModel):
|
|
59
76
|
|
60
77
|
def _on_bound(self):
|
61
78
|
super()._on_bound()
|
79
|
+
if genai is None:
|
80
|
+
raise RuntimeError(
|
81
|
+
'Please install "langfun[llm-google-genai]" to use '
|
82
|
+
'Google Generative AI models.'
|
83
|
+
)
|
62
84
|
self.__dict__.pop('_api_initialized', None)
|
63
85
|
|
64
86
|
@functools.cached_property
|
65
87
|
def _api_initialized(self):
|
88
|
+
assert genai is not None
|
66
89
|
api_key = self.api_key or os.environ.get('GOOGLE_API_KEY', None)
|
67
90
|
if not api_key:
|
68
91
|
raise ValueError(
|
@@ -78,6 +101,7 @@ class GenAI(lf.LanguageModel):
|
|
78
101
|
@classmethod
|
79
102
|
def dir(cls) -> list[str]:
|
80
103
|
"""Lists generative models."""
|
104
|
+
assert genai is not None
|
81
105
|
return [
|
82
106
|
m.name.lstrip('models/')
|
83
107
|
for m in genai.list_models()
|
@@ -100,7 +124,7 @@ class GenAI(lf.LanguageModel):
|
|
100
124
|
|
101
125
|
def _generation_config(self, options: lf.LMSamplingOptions) -> dict[str, Any]:
|
102
126
|
"""Creates generation config from langfun sampling options."""
|
103
|
-
return
|
127
|
+
return GenerationConfig(
|
104
128
|
candidate_count=options.n,
|
105
129
|
temperature=options.temperature,
|
106
130
|
top_p=options.top_p,
|
@@ -111,7 +135,7 @@ class GenAI(lf.LanguageModel):
|
|
111
135
|
|
112
136
|
def _content_from_message(
|
113
137
|
self, prompt: lf.Message
|
114
|
-
) -> list[str |
|
138
|
+
) -> list[str | BlobDict]:
|
115
139
|
"""Gets Evergreen formatted content from langfun message."""
|
116
140
|
formatted = lf.UserMessage(prompt.text)
|
117
141
|
formatted.source = prompt
|
@@ -131,7 +155,7 @@ class GenAI(lf.LanguageModel):
|
|
131
155
|
if modality.is_text:
|
132
156
|
chunk = modality.to_text()
|
133
157
|
else:
|
134
|
-
chunk =
|
158
|
+
chunk = BlobDict(
|
135
159
|
data=modality.to_bytes(),
|
136
160
|
mime_type=modality.mime_type
|
137
161
|
)
|
@@ -143,7 +167,7 @@ class GenAI(lf.LanguageModel):
|
|
143
167
|
return chunks
|
144
168
|
|
145
169
|
def _response_to_result(
|
146
|
-
self, response:
|
170
|
+
self, response: GenerateContentResponse | pg.Dict
|
147
171
|
) -> lf.LMSamplingResult:
|
148
172
|
"""Parses generative response into message."""
|
149
173
|
samples = []
|
@@ -182,8 +206,8 @@ class _LegacyGenerativeModel(pg.Object):
|
|
182
206
|
|
183
207
|
def generate_content(
|
184
208
|
self,
|
185
|
-
input_content: list[str |
|
186
|
-
generation_config:
|
209
|
+
input_content: list[str | BlobDict],
|
210
|
+
generation_config: GenerationConfig,
|
187
211
|
) -> pg.Dict:
|
188
212
|
"""Generate content."""
|
189
213
|
segments = []
|
@@ -195,7 +219,7 @@ class _LegacyGenerativeModel(pg.Object):
|
|
195
219
|
|
196
220
|
@abc.abstractmethod
|
197
221
|
def generate(
|
198
|
-
self, prompt: str, generation_config:
|
222
|
+
self, prompt: str, generation_config: GenerationConfig) -> pg.Dict:
|
199
223
|
"""Generate response based on prompt."""
|
200
224
|
|
201
225
|
|
@@ -203,9 +227,10 @@ class _LegacyCompletionModel(_LegacyGenerativeModel):
|
|
203
227
|
"""Legacy GenAI completion model."""
|
204
228
|
|
205
229
|
def generate(
|
206
|
-
self, prompt: str, generation_config:
|
230
|
+
self, prompt: str, generation_config: GenerationConfig
|
207
231
|
) -> pg.Dict:
|
208
|
-
|
232
|
+
assert genai is not None
|
233
|
+
completion: Completion = genai.generate_text(
|
209
234
|
model=f'models/{self.model}',
|
210
235
|
prompt=prompt,
|
211
236
|
temperature=generation_config.temperature,
|
@@ -227,9 +252,10 @@ class _LegacyChatModel(_LegacyGenerativeModel):
|
|
227
252
|
"""Legacy GenAI chat model."""
|
228
253
|
|
229
254
|
def generate(
|
230
|
-
self, prompt: str, generation_config:
|
255
|
+
self, prompt: str, generation_config: GenerationConfig
|
231
256
|
) -> pg.Dict:
|
232
|
-
|
257
|
+
assert genai is not None
|
258
|
+
response: ChatResponse = genai.chat(
|
233
259
|
model=f'models/{self.model}',
|
234
260
|
messages=prompt,
|
235
261
|
temperature=generation_config.temperature,
|
@@ -253,8 +279,9 @@ class _ModelHub:
|
|
253
279
|
|
254
280
|
def get(
|
255
281
|
self, model_name: str
|
256
|
-
) ->
|
282
|
+
) -> GenerativeModel | _LegacyGenerativeModel:
|
257
283
|
"""Gets a generative model by model id."""
|
284
|
+
assert genai is not None
|
258
285
|
model = self._model_cache.get(model_name, None)
|
259
286
|
if model is None:
|
260
287
|
model_info = genai.get_model(f'models/{model_name}')
|
langfun/core/llms/openai.py
CHANGED
@@ -16,15 +16,31 @@
|
|
16
16
|
import collections
|
17
17
|
import functools
|
18
18
|
import os
|
19
|
-
from typing import Annotated, Any
|
19
|
+
from typing import Annotated, Any
|
20
20
|
|
21
21
|
import langfun.core as lf
|
22
22
|
from langfun.core import modalities as lf_modalities
|
23
|
-
import openai
|
24
|
-
from openai import error as openai_error
|
25
|
-
from openai import openai_object
|
26
23
|
import pyglove as pg
|
27
24
|
|
25
|
+
try:
|
26
|
+
import openai # pylint: disable=g-import-not-at-top
|
27
|
+
|
28
|
+
if hasattr(openai, 'error'):
|
29
|
+
# For lower versions.
|
30
|
+
ServiceUnavailableError = openai.error.ServiceUnavailableError
|
31
|
+
RateLimitError = openai.error.RateLimitError
|
32
|
+
APITimeoutError = (
|
33
|
+
openai.error.APIError,
|
34
|
+
'.*The server had an error processing your request'
|
35
|
+
)
|
36
|
+
else:
|
37
|
+
# For higher versions.
|
38
|
+
ServiceUnavailableError = getattr(openai, 'InternalServerError')
|
39
|
+
RateLimitError = getattr(openai, 'RateLimitError')
|
40
|
+
APITimeoutError = getattr(openai, 'APITimeoutError')
|
41
|
+
except ImportError:
|
42
|
+
openai = None
|
43
|
+
|
28
44
|
|
29
45
|
# From https://platform.openai.com/settings/organization/limits
|
30
46
|
_DEFAULT_TPM = 250000
|
@@ -119,6 +135,10 @@ class OpenAI(lf.LanguageModel):
|
|
119
135
|
def _on_bound(self):
|
120
136
|
super()._on_bound()
|
121
137
|
self.__dict__.pop('_api_initialized', None)
|
138
|
+
if openai is None:
|
139
|
+
raise RuntimeError(
|
140
|
+
'Please install "langfun[llm-openai]" to use OpenAI models.'
|
141
|
+
)
|
122
142
|
|
123
143
|
@functools.cached_property
|
124
144
|
def _api_initialized(self):
|
@@ -149,6 +169,7 @@ class OpenAI(lf.LanguageModel):
|
|
149
169
|
|
150
170
|
@classmethod
|
151
171
|
def dir(cls):
|
172
|
+
assert openai is not None
|
152
173
|
return openai.Model.list()
|
153
174
|
|
154
175
|
@property
|
@@ -195,11 +216,11 @@ class OpenAI(lf.LanguageModel):
|
|
195
216
|
) -> list[lf.LMSamplingResult]:
|
196
217
|
|
197
218
|
def _open_ai_completion(prompts):
|
219
|
+
assert openai is not None
|
198
220
|
response = openai.Completion.create(
|
199
221
|
prompt=[p.text for p in prompts],
|
200
222
|
**self._get_request_args(self.sampling_options),
|
201
223
|
)
|
202
|
-
response = cast(openai_object.OpenAIObject, response)
|
203
224
|
# Parse response.
|
204
225
|
samples_by_index = collections.defaultdict(list)
|
205
226
|
for choice in response.choices:
|
@@ -222,12 +243,9 @@ class OpenAI(lf.LanguageModel):
|
|
222
243
|
_open_ai_completion,
|
223
244
|
[prompts],
|
224
245
|
retry_on_errors=(
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
# https://platform.openai.com/docs/guides/error-codes/error-codes
|
229
|
-
(openai_error.APIError,
|
230
|
-
'.*The server had an error processing your request'),
|
246
|
+
ServiceUnavailableError,
|
247
|
+
RateLimitError,
|
248
|
+
APITimeoutError,
|
231
249
|
),
|
232
250
|
)[0]
|
233
251
|
|
@@ -292,10 +310,8 @@ class OpenAI(lf.LanguageModel):
|
|
292
310
|
)
|
293
311
|
messages.append(dict(role='user', content=_content_from_message(prompt)))
|
294
312
|
|
295
|
-
|
296
|
-
|
297
|
-
openai.ChatCompletion.create(messages=messages, **request_args)
|
298
|
-
)
|
313
|
+
assert openai is not None
|
314
|
+
response = openai.ChatCompletion.create(messages=messages, **request_args)
|
299
315
|
|
300
316
|
samples = []
|
301
317
|
for choice in response.choices:
|
@@ -330,8 +346,9 @@ class OpenAI(lf.LanguageModel):
|
|
330
346
|
_open_ai_chat_completion,
|
331
347
|
prompts,
|
332
348
|
retry_on_errors=(
|
333
|
-
|
334
|
-
|
349
|
+
ServiceUnavailableError,
|
350
|
+
RateLimitError,
|
351
|
+
APITimeoutError
|
335
352
|
),
|
336
353
|
)
|
337
354
|
|
langfun/core/llms/vertexai.py
CHANGED
@@ -17,11 +17,28 @@ import functools
|
|
17
17
|
import os
|
18
18
|
from typing import Annotated, Any
|
19
19
|
|
20
|
-
from google.auth import credentials as credentials_lib
|
21
20
|
import langfun.core as lf
|
22
21
|
from langfun.core import modalities as lf_modalities
|
23
22
|
import pyglove as pg
|
24
23
|
|
24
|
+
try:
|
25
|
+
# pylint: disable=g-import-not-at-top
|
26
|
+
from google.auth import credentials as credentials_lib
|
27
|
+
import vertexai
|
28
|
+
from google.cloud.aiplatform import models as aiplatform_models
|
29
|
+
from vertexai import generative_models
|
30
|
+
from vertexai import language_models
|
31
|
+
# pylint: enable=g-import-not-at-top
|
32
|
+
|
33
|
+
Credentials = credentials_lib.Credentials
|
34
|
+
except ImportError:
|
35
|
+
credentials_lib = None # pylint: disable=invalid-name
|
36
|
+
vertexai = None
|
37
|
+
generative_models = None
|
38
|
+
language_models = None
|
39
|
+
aiplatform_models = None
|
40
|
+
Credentials = Any
|
41
|
+
|
25
42
|
|
26
43
|
SUPPORTED_MODELS_AND_SETTINGS = {
|
27
44
|
'gemini-1.5-pro-001': pg.Dict(api='gemini', rpm=50),
|
@@ -78,7 +95,7 @@ class VertexAI(lf.LanguageModel):
|
|
78
95
|
] = None
|
79
96
|
|
80
97
|
credentials: Annotated[
|
81
|
-
|
98
|
+
Credentials | None,
|
82
99
|
(
|
83
100
|
'Credentials to use. If None, the default credentials to the '
|
84
101
|
'environment will be used.'
|
@@ -93,6 +110,10 @@ class VertexAI(lf.LanguageModel):
|
|
93
110
|
def _on_bound(self):
|
94
111
|
super()._on_bound()
|
95
112
|
self.__dict__.pop('_api_initialized', None)
|
113
|
+
if generative_models is None:
|
114
|
+
raise RuntimeError(
|
115
|
+
'Please install "langfun[llm-google-vertex]" to use Vertex AI models.'
|
116
|
+
)
|
96
117
|
|
97
118
|
@functools.cached_property
|
98
119
|
def _api_initialized(self):
|
@@ -112,7 +133,7 @@ class VertexAI(lf.LanguageModel):
|
|
112
133
|
|
113
134
|
credentials = self.credentials
|
114
135
|
# Placeholder for Google-internal credentials.
|
115
|
-
|
136
|
+
assert vertexai is not None
|
116
137
|
vertexai.init(project=project, location=location, credentials=credentials)
|
117
138
|
return True
|
118
139
|
|
@@ -138,7 +159,7 @@ class VertexAI(lf.LanguageModel):
|
|
138
159
|
self, prompt: lf.Message, options: lf.LMSamplingOptions
|
139
160
|
) -> Any: # generative_models.GenerationConfig
|
140
161
|
"""Creates generation config from langfun sampling options."""
|
141
|
-
|
162
|
+
assert generative_models is not None
|
142
163
|
# Users could use `metadata_json_schema` to pass additional
|
143
164
|
# request arguments.
|
144
165
|
json_schema = prompt.metadata.get('json_schema')
|
@@ -169,7 +190,7 @@ class VertexAI(lf.LanguageModel):
|
|
169
190
|
self, prompt: lf.Message
|
170
191
|
) -> list[str | Any]:
|
171
192
|
"""Gets generation input from langfun message."""
|
172
|
-
|
193
|
+
assert generative_models is not None
|
173
194
|
chunks = []
|
174
195
|
|
175
196
|
for lf_chunk in prompt.chunk():
|
@@ -296,8 +317,8 @@ class VertexAI(lf.LanguageModel):
|
|
296
317
|
|
297
318
|
def _sample_endpoint_model(self, prompt: lf.Message) -> lf.LMSamplingResult:
|
298
319
|
"""Samples a text generation model."""
|
299
|
-
|
300
|
-
model =
|
320
|
+
assert aiplatform_models is not None
|
321
|
+
model = aiplatform_models.Endpoint(self.endpoint_name)
|
301
322
|
# TODO(chengrun): Add support for stop_sequences.
|
302
323
|
predict_options = dict(
|
303
324
|
temperature=self.sampling_options.temperature
|
@@ -337,7 +358,7 @@ class _ModelHub:
|
|
337
358
|
"""Gets a generative model by model id."""
|
338
359
|
model = self._generative_model_cache.get(model_id, None)
|
339
360
|
if model is None:
|
340
|
-
|
361
|
+
assert generative_models is not None
|
341
362
|
model = generative_models.GenerativeModel(model_id)
|
342
363
|
self._generative_model_cache[model_id] = model
|
343
364
|
return model
|
@@ -348,7 +369,7 @@ class _ModelHub:
|
|
348
369
|
"""Gets a text generation model by model id."""
|
349
370
|
model = self._text_generation_model_cache.get(model_id, None)
|
350
371
|
if model is None:
|
351
|
-
|
372
|
+
assert language_models is not None
|
352
373
|
model = language_models.TextGenerationModel.from_pretrained(model_id)
|
353
374
|
self._text_generation_model_cache[model_id] = model
|
354
375
|
return model
|
langfun/core/modalities/image.py
CHANGED
@@ -15,9 +15,21 @@
|
|
15
15
|
|
16
16
|
import functools
|
17
17
|
import io
|
18
|
+
from typing import Any
|
18
19
|
|
19
20
|
from langfun.core.modalities import mime
|
20
|
-
|
21
|
+
|
22
|
+
try:
|
23
|
+
from PIL import Image as pil_image # pylint: disable=g-import-not-at-top
|
24
|
+
PILImage = pil_image.Image
|
25
|
+
pil_open = pil_image.open
|
26
|
+
except ImportError:
|
27
|
+
PILImage = Any
|
28
|
+
|
29
|
+
def pil_open(*unused_args, **unused_kwargs):
|
30
|
+
raise RuntimeError(
|
31
|
+
'Please install "langfun[mime-pil]" to enable PIL image support.'
|
32
|
+
)
|
21
33
|
|
22
34
|
|
23
35
|
class Image(mime.Mime):
|
@@ -34,14 +46,14 @@ class Image(mime.Mime):
|
|
34
46
|
|
35
47
|
@functools.cached_property
|
36
48
|
def size(self) -> tuple[int, int]:
|
37
|
-
img =
|
49
|
+
img = pil_open(io.BytesIO(self.to_bytes()))
|
38
50
|
return img.size
|
39
51
|
|
40
|
-
def to_pil_image(self) ->
|
41
|
-
return
|
52
|
+
def to_pil_image(self) -> PILImage: # pytype: disable=invalid-annotation
|
53
|
+
return pil_open(io.BytesIO(self.to_bytes()))
|
42
54
|
|
43
55
|
@classmethod
|
44
|
-
def from_pil_image(cls, img:
|
56
|
+
def from_pil_image(cls, img: PILImage) -> 'Image': # pytype: disable=invalid-annotation
|
45
57
|
buf = io.BytesIO()
|
46
58
|
img.save(buf, format='PNG')
|
47
59
|
return cls.from_bytes(buf.getvalue())
|
langfun/core/modalities/mime.py
CHANGED
@@ -17,11 +17,20 @@ import base64
|
|
17
17
|
import functools
|
18
18
|
from typing import Annotated, Iterable, Type, Union
|
19
19
|
import langfun.core as lf
|
20
|
-
import magic
|
21
20
|
import pyglove as pg
|
22
21
|
import requests
|
23
22
|
|
24
23
|
|
24
|
+
try:
|
25
|
+
import magic # pylint: disable=g-import-not-at-top
|
26
|
+
from_buffer = magic.from_buffer
|
27
|
+
except ImportError:
|
28
|
+
def from_buffer(*unused_args, **unused_kwargs):
|
29
|
+
raise RuntimeError(
|
30
|
+
'Please install "langfun[mime-auto]" to enable automatic MIME support.'
|
31
|
+
)
|
32
|
+
|
33
|
+
|
25
34
|
class Mime(lf.Modality):
|
26
35
|
"""Base for MIME data."""
|
27
36
|
|
@@ -38,7 +47,7 @@ class Mime(lf.Modality):
|
|
38
47
|
@functools.cached_property
|
39
48
|
def mime_type(self) -> str:
|
40
49
|
"""Returns the MIME type."""
|
41
|
-
mime =
|
50
|
+
mime = from_buffer((self.to_bytes()), mime=True)
|
42
51
|
if (
|
43
52
|
self.MIME_PREFIX
|
44
53
|
and not mime.lower().startswith(self.MIME_PREFIX)
|
@@ -136,14 +145,14 @@ class Mime(lf.Modality):
|
|
136
145
|
def from_uri(cls, uri: str, **kwargs) -> 'Mime':
|
137
146
|
if cls is Mime:
|
138
147
|
content = cls.download(uri)
|
139
|
-
mime =
|
148
|
+
mime = from_buffer(content, mime=True).lower()
|
140
149
|
return cls.class_from_mime_type(mime)(uri=uri, content=content, **kwargs)
|
141
150
|
return cls(uri=uri, content=None, **kwargs)
|
142
151
|
|
143
152
|
@classmethod
|
144
153
|
def from_bytes(cls, content: bytes | str, **kwargs) -> 'Mime':
|
145
154
|
if cls is Mime:
|
146
|
-
mime =
|
155
|
+
mime = from_buffer(content, mime=True).lower()
|
147
156
|
return cls.class_from_mime_type(mime)(content=content, **kwargs)
|
148
157
|
return cls(content=content, **kwargs)
|
149
158
|
|
@@ -30,10 +30,15 @@ class Xlsx(mime.Mime):
|
|
30
30
|
)
|
31
31
|
|
32
32
|
def to_html(self) -> str:
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
try:
|
34
|
+
import pandas as pd # pylint: disable=g-import-not-at-top
|
35
|
+
import openpyxl # pylint: disable=g-import-not-at-top, unused-import
|
36
|
+
df = pd.read_excel(io.BytesIO(self.to_bytes()))
|
37
|
+
return df.to_html()
|
38
|
+
except ImportError as e:
|
39
|
+
raise RuntimeError(
|
40
|
+
'Please install "langfun[mime-xlsx]" to enable XLSX support.'
|
41
|
+
) from e
|
37
42
|
|
38
43
|
def _repr_html_(self) -> str:
|
39
44
|
return self.to_html()
|
@@ -58,10 +63,14 @@ class Docx(mime.Mime):
|
|
58
63
|
)
|
59
64
|
|
60
65
|
def to_xml(self) -> str:
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
66
|
+
try:
|
67
|
+
import docx # pylint: disable=g-import-not-at-top
|
68
|
+
doc = docx.Document(io.BytesIO(self.to_bytes()))
|
69
|
+
return str(doc.element.xml)
|
70
|
+
except ImportError as e:
|
71
|
+
raise RuntimeError(
|
72
|
+
'Please install "langfun[mime-docx]" to enable Docx support.'
|
73
|
+
) from e
|
65
74
|
|
66
75
|
def _repr_html_(self) -> str:
|
67
76
|
return self.to_xml()
|
langfun/core/text_formatting.py
CHANGED
@@ -16,7 +16,11 @@
|
|
16
16
|
import io
|
17
17
|
import re
|
18
18
|
from typing import Any
|
19
|
-
|
19
|
+
|
20
|
+
try:
|
21
|
+
import termcolor # pylint: disable=g-import-not-at-top
|
22
|
+
except ImportError:
|
23
|
+
termcolor = None
|
20
24
|
|
21
25
|
|
22
26
|
# Regular expression for ANSI color characters.
|
@@ -49,6 +53,8 @@ def colored(
|
|
49
53
|
Returns:
|
50
54
|
A string with ANSI color characters embracing the entire text.
|
51
55
|
"""
|
56
|
+
if not termcolor:
|
57
|
+
return text
|
52
58
|
return termcolor.colored(
|
53
59
|
text,
|
54
60
|
color=color,
|
@@ -42,6 +42,24 @@ class TextFormattingTest(unittest.TestCase):
|
|
42
42
|
)
|
43
43
|
self.assertEqual(text_formatting.decolored(colored_text), original_text)
|
44
44
|
|
45
|
+
def test_colored_without_termcolor(self):
|
46
|
+
termcolor = text_formatting.termcolor
|
47
|
+
text_formatting.termcolor = None
|
48
|
+
original_text = inspect.cleandoc("""
|
49
|
+
Hi {{ foo }}
|
50
|
+
{# print x if x is present #}
|
51
|
+
{% if x %}
|
52
|
+
{{ x }}
|
53
|
+
{% endif %}
|
54
|
+
""")
|
55
|
+
|
56
|
+
colored_text = text_formatting.colored_template(
|
57
|
+
text_formatting.colored(original_text, color='blue')
|
58
|
+
)
|
59
|
+
self.assertEqual(colored_text, original_text)
|
60
|
+
self.assertEqual(text_formatting.decolored(colored_text), original_text)
|
61
|
+
text_formatting.termcolor = termcolor
|
62
|
+
|
45
63
|
|
46
64
|
if __name__ == '__main__':
|
47
65
|
unittest.main()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: langfun
|
3
|
-
Version: 0.1.1.
|
3
|
+
Version: 0.1.1.dev202408282153
|
4
4
|
Summary: Langfun: Language as Functions.
|
5
5
|
Home-page: https://github.com/google/langfun
|
6
6
|
Author: Langfun Authors
|
@@ -21,19 +21,54 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
21
|
Classifier: Topic :: Software Development :: Libraries
|
22
22
|
Description-Content-Type: text/markdown
|
23
23
|
License-File: LICENSE
|
24
|
-
Requires-Dist:
|
25
|
-
Requires-Dist: google-generativeai>=0.3.2
|
24
|
+
Requires-Dist: pyglove>=0.4.5.dev20240824
|
26
25
|
Requires-Dist: jinja2>=3.1.2
|
27
|
-
Requires-Dist: openai==0.27.2
|
28
|
-
Requires-Dist: openpyxl>=3.1.0
|
29
|
-
Requires-Dist: pandas>=2.0.3
|
30
|
-
Requires-Dist: pyglove>=0.4.5.dev20240423
|
31
|
-
Requires-Dist: python-docx>=0.8.11
|
32
|
-
Requires-Dist: python-magic>=0.4.27
|
33
26
|
Requires-Dist: requests>=2.31.0
|
34
|
-
|
35
|
-
Requires-Dist:
|
36
|
-
Requires-Dist:
|
27
|
+
Provides-Extra: all
|
28
|
+
Requires-Dist: pyglove>=0.4.5.dev20240824; extra == "all"
|
29
|
+
Requires-Dist: jinja2>=3.1.2; extra == "all"
|
30
|
+
Requires-Dist: requests>=2.31.0; extra == "all"
|
31
|
+
Requires-Dist: termcolor==1.1.0; extra == "all"
|
32
|
+
Requires-Dist: tqdm>=4.64.1; extra == "all"
|
33
|
+
Requires-Dist: google-cloud-aiplatform>=1.5.0; extra == "all"
|
34
|
+
Requires-Dist: google-generativeai>=0.3.2; extra == "all"
|
35
|
+
Requires-Dist: openai>=0.27.2; extra == "all"
|
36
|
+
Requires-Dist: python-magic>=0.4.27; extra == "all"
|
37
|
+
Requires-Dist: python-docx>=0.8.11; extra == "all"
|
38
|
+
Requires-Dist: pillow>=10.0.0; extra == "all"
|
39
|
+
Requires-Dist: openpyxl>=3.1.0; extra == "all"
|
40
|
+
Requires-Dist: pandas>=2.0.3; extra == "all"
|
41
|
+
Provides-Extra: llm
|
42
|
+
Requires-Dist: google-cloud-aiplatform>=1.5.0; extra == "llm"
|
43
|
+
Requires-Dist: google-generativeai>=0.3.2; extra == "llm"
|
44
|
+
Requires-Dist: openai>=0.27.2; extra == "llm"
|
45
|
+
Provides-Extra: llm-google
|
46
|
+
Requires-Dist: google-cloud-aiplatform>=1.5.0; extra == "llm-google"
|
47
|
+
Requires-Dist: google-generativeai>=0.3.2; extra == "llm-google"
|
48
|
+
Provides-Extra: llm-google-genai
|
49
|
+
Requires-Dist: google-generativeai>=0.3.2; extra == "llm-google-genai"
|
50
|
+
Provides-Extra: llm-google-vertex
|
51
|
+
Requires-Dist: google-cloud-aiplatform>=1.5.0; extra == "llm-google-vertex"
|
52
|
+
Provides-Extra: llm-openai
|
53
|
+
Requires-Dist: openai>=0.27.2; extra == "llm-openai"
|
54
|
+
Provides-Extra: mime
|
55
|
+
Requires-Dist: python-magic>=0.4.27; extra == "mime"
|
56
|
+
Requires-Dist: python-docx>=0.8.11; extra == "mime"
|
57
|
+
Requires-Dist: pillow>=10.0.0; extra == "mime"
|
58
|
+
Requires-Dist: openpyxl>=3.1.0; extra == "mime"
|
59
|
+
Requires-Dist: pandas>=2.0.3; extra == "mime"
|
60
|
+
Provides-Extra: mime-auto
|
61
|
+
Requires-Dist: python-magic>=0.4.27; extra == "mime-auto"
|
62
|
+
Provides-Extra: mime-docx
|
63
|
+
Requires-Dist: python-docx>=0.8.11; extra == "mime-docx"
|
64
|
+
Provides-Extra: mime-pil
|
65
|
+
Requires-Dist: pillow>=10.0.0; extra == "mime-pil"
|
66
|
+
Provides-Extra: mime-xlsx
|
67
|
+
Requires-Dist: openpyxl>=3.1.0; extra == "mime-xlsx"
|
68
|
+
Requires-Dist: pandas>=2.0.3; extra == "mime-xlsx"
|
69
|
+
Provides-Extra: ui
|
70
|
+
Requires-Dist: termcolor==1.1.0; extra == "ui"
|
71
|
+
Requires-Dist: tqdm>=4.64.1; extra == "ui"
|
37
72
|
|
38
73
|
<div align="center">
|
39
74
|
<img src="https://raw.githubusercontent.com/google/langfun/main/docs/_static/logo.svg" width="520px" alt="logo"></img>
|
@@ -152,16 +187,45 @@ See [Langfun 101](https://colab.research.google.com/github/google/langfun/blob/m
|
|
152
187
|
|
153
188
|
## Install
|
154
189
|
|
190
|
+
Langfun offers a range of features through [Extras](https://packaging.python.org/en/latest/tutorials/installing-packages/#installing-extras), allowing users to install only what they need. The minimal installation of Langfun requires only [PyGlove](https://github.com/google/pyglove), [Jinja2](https://github.com/pallets/jinja/), and [requests](https://github.com/psf/requests). To install Langfun with its minimal dependencies, use:
|
191
|
+
|
155
192
|
```
|
156
193
|
pip install langfun
|
157
194
|
```
|
158
195
|
|
159
|
-
|
196
|
+
For a complete installation with all dependencies, use:
|
160
197
|
|
161
198
|
```
|
162
|
-
pip install langfun
|
199
|
+
pip install langfun[all]
|
163
200
|
```
|
164
201
|
|
202
|
+
To install a nightly build, include the `--pre` flag, like this:
|
203
|
+
|
204
|
+
```
|
205
|
+
pip install langfun[all] --pre
|
206
|
+
```
|
165
207
|
|
208
|
+
If you want to customize your installation, you can select specific features using package names like `langfun[X1, X2, ..., Xn]`, where `Xi` corresponds to a tag from the list below:
|
209
|
+
|
210
|
+
| Tag | Description |
|
211
|
+
| ------------------- | ---------------------------------------- |
|
212
|
+
| all | All Langfun features. |
|
213
|
+
| llm | All supported LLMs. |
|
214
|
+
| llm-google | All supported Google-powered LLMs. |
|
215
|
+
| llm-google-vertexai | LLMs powered by Google Cloud VertexAI |
|
216
|
+
| llm-google-genai | LLMs powered by Google Generative AI API |
|
217
|
+
| llm-openai | LLMs powered by OpenAI |
|
218
|
+
| mime | All MIME supports. |
|
219
|
+
| mime-auto | Automatic MIME type detection. |
|
220
|
+
| mime-docx | DocX format support. |
|
221
|
+
| mime-pil | Image support for PIL. |
|
222
|
+
| mime-xlsx | XlsX format support. |
|
223
|
+
| ui | UI enhancements |
|
224
|
+
|
225
|
+
|
226
|
+
For example, to install a nightly build that includes Google-powered LLMs, full modality support, and UI enhancements, use:
|
227
|
+
```
|
228
|
+
pip install langfun[llm-google,mime,ui] --pre
|
229
|
+
```
|
166
230
|
|
167
231
|
*Disclaimer: this is not an officially supported Google product.*
|
@@ -2,8 +2,8 @@ langfun/__init__.py,sha256=ZqERg4fvvtFwr5L41Lfev9FSuCBm8PbQrZmcKvvloxE,2274
|
|
2
2
|
langfun/core/__init__.py,sha256=r86kuy-BiJIveqnXx5OklUUXtMG3q79nWRBum6zFOCQ,4835
|
3
3
|
langfun/core/component.py,sha256=Icyoj9ICoJoK2r2PHbrFXbxnseOr9QZZOvKWklLWNo8,10276
|
4
4
|
langfun/core/component_test.py,sha256=q15Xn51cVTu2RKxZ9U5VQgT3bm6RQ4638bKhWBtvW5o,8220
|
5
|
-
langfun/core/concurrent.py,sha256=
|
6
|
-
langfun/core/concurrent_test.py,sha256=
|
5
|
+
langfun/core/concurrent.py,sha256=qZteOkA7jzLt7a5AVmNqDc5ZEvj_hwiLECqGMVYdzSU,27756
|
6
|
+
langfun/core/concurrent_test.py,sha256=ULVrE6JGzsClRBLHAlqLF1A2mfxMO2X0245qYTYQ76o,16943
|
7
7
|
langfun/core/console.py,sha256=bk5rNPNm9rMGW5YT2HixxU04p2umnoabn5SDz6Dqe88,2317
|
8
8
|
langfun/core/console_test.py,sha256=5SYJdxpJGLgdSSQqqMPoA1X6jpsLD8rgcyk-EgI65oE,1077
|
9
9
|
langfun/core/langfunc.py,sha256=G50YgoVZ0y1GFw2ev41MlOqr6qa8YakbvNC0h_E0PiA,11140
|
@@ -27,8 +27,8 @@ langfun/core/subscription.py,sha256=euawEuSZP-BHydaT-AQpfYFL0m5pWPGcW0upFhrojqc,
|
|
27
27
|
langfun/core/subscription_test.py,sha256=Y4ZdbZEwm83YNZBxHff0QR4QUa4rdaNXA3_jfIcArBo,8717
|
28
28
|
langfun/core/template.py,sha256=UhNNGUDJ4StUhPBKzHmjym36khxHOGWGr9MDxBwgxQA,22284
|
29
29
|
langfun/core/template_test.py,sha256=cb005qM_3dvJikaPDB3rirfIrMAIIVRpoiXGREGJ43o,15468
|
30
|
-
langfun/core/text_formatting.py,sha256=
|
31
|
-
langfun/core/text_formatting_test.py,sha256=
|
30
|
+
langfun/core/text_formatting.py,sha256=d7t9vaY6aCn1dkfkikpNYnBy5E_i93vHbfyDWFclGZU,5284
|
31
|
+
langfun/core/text_formatting_test.py,sha256=ck0Xzdd4YF4CtCUj7VE0GybfbAyKQ8p3xkM1FBGrqIk,2096
|
32
32
|
langfun/core/coding/__init__.py,sha256=5utju_fwEsImaiftx4oXKl9FAM8p281k8-Esdh_-m1w,835
|
33
33
|
langfun/core/coding/python/__init__.py,sha256=MJ-vubliz-ebrZH3OBRKBwMi0S9-FrhGCp8YQLR6_I4,1776
|
34
34
|
langfun/core/coding/python/correction.py,sha256=a2aFUt9ocbXTCR6Z6OGNjQZDI1LfU0PBkSe7hJB8dEM,6589
|
@@ -44,7 +44,7 @@ langfun/core/coding/python/parsing_test.py,sha256=9vAWF484kWIm6JZq8NFiMgKUDhXV-d
|
|
44
44
|
langfun/core/coding/python/permissions.py,sha256=1QWGHvzL8MM0Ok_auQ9tURqZHtdOfJaDpBzZ29GUE-c,2544
|
45
45
|
langfun/core/coding/python/permissions_test.py,sha256=w5EDb8QxpxgJyZkojyzVWQvDfg366zn99-g__6TbPQ0,2699
|
46
46
|
langfun/core/eval/__init__.py,sha256=Ogdr9OtTywhhLPHi3AZzOD2mXX2oyaHWflrSTMm96uA,1899
|
47
|
-
langfun/core/eval/base.py,sha256=
|
47
|
+
langfun/core/eval/base.py,sha256=YU_R9pKITKWyWa5-dSY8Q5O9pK8xioWX1ZdqYVOZcK8,74751
|
48
48
|
langfun/core/eval/base_test.py,sha256=VEraWaRybSxOCOcZrZouNkiroDEPR6uyFBJoAz-1pQg,26930
|
49
49
|
langfun/core/eval/matching.py,sha256=9GX8HfO9jKxgNLAivgy5K88Xhoh6Z7Pptq65pe7vht8,9762
|
50
50
|
langfun/core/eval/matching_test.py,sha256=f7iVyXH5KGJBWt4Wp14Bt9J3X59A6Ayfog9MbuFvPew,5532
|
@@ -57,17 +57,17 @@ langfun/core/llms/anthropic.py,sha256=Gon3fOi31RhZFgNd0ijyTnKnUdp9hrWrCoSXyO4UaL
|
|
57
57
|
langfun/core/llms/anthropic_test.py,sha256=T-swuMkfnlgs8Fpif4rtXs579exGk0TsbLMirXDZCkg,5533
|
58
58
|
langfun/core/llms/fake.py,sha256=gCHBYBLvBCsC78HI1hpoqXCS-p1FMTgY1P1qh_sGBPk,3070
|
59
59
|
langfun/core/llms/fake_test.py,sha256=sIl_Mg7nFVjaN7AJhYCpA_qzDJpSnJzkazepGXpfQQg,7338
|
60
|
-
langfun/core/llms/google_genai.py,sha256=
|
60
|
+
langfun/core/llms/google_genai.py,sha256=OYrlKIEY57CFWYwndvEdhXLwffNvDwKXyy9ULZTNeYI,10885
|
61
61
|
langfun/core/llms/google_genai_test.py,sha256=iTISk3tJ4-3gjWmzcKQhEbH3ke4AkEiCu8rAGtB7SvU,7535
|
62
62
|
langfun/core/llms/groq.py,sha256=pqtyOZ_1_OJMOg8xATWT_B_SVbuT9nMRf4VkH9GzW8g,6308
|
63
63
|
langfun/core/llms/groq_test.py,sha256=GYF_Qtq5S1H1TrKH38t6_lkdroqT7v-joYLDKnmS9e0,5274
|
64
64
|
langfun/core/llms/llama_cpp.py,sha256=9tXQntSCDtjTF3bnyJrAPCr4N6wycy5nXYvp9uduygE,2843
|
65
65
|
langfun/core/llms/llama_cpp_test.py,sha256=MWO_qaOeKjRniGjcaWPDScd7HPaIJemqUZoslrt4FPs,1806
|
66
|
-
langfun/core/llms/openai.py,sha256=
|
66
|
+
langfun/core/llms/openai.py,sha256=aZ5RgRBomVhBtzJtGYu9EM-qm07WEDcJr127VGpc_Sg,16161
|
67
67
|
langfun/core/llms/openai_test.py,sha256=02KeysRppXcAwA4SBUow8hKolFiEU9_lTSdlVHZletM,17518
|
68
68
|
langfun/core/llms/rest.py,sha256=laopuq-zD8V-3Y6eFDngftHEbE66VlUkCD2-rvvRaLU,3388
|
69
69
|
langfun/core/llms/rest_test.py,sha256=NZ3Nf0XQVpT9kLP5cBVo_yBHLI7vWTYhWQxYEJVMGs4,3472
|
70
|
-
langfun/core/llms/vertexai.py,sha256=
|
70
|
+
langfun/core/llms/vertexai.py,sha256=mEQVwO3Kf3rGRmsI-qKrV6vg0hYy6OH1lEVOM81cb3U,15134
|
71
71
|
langfun/core/llms/vertexai_test.py,sha256=EPR-mB2hNUpvpf7E8m_k5bh04epdQTVUuYU6hPgZyu8,10321
|
72
72
|
langfun/core/llms/cache/__init__.py,sha256=QAo3InUMDM_YpteNnVCSejI4zOsnjSMWKJKzkb3VY64,993
|
73
73
|
langfun/core/llms/cache/base.py,sha256=rt3zwmyw0y9jsSGW-ZbV1vAfLxQ7_3AVk0l2EySlse4,3918
|
@@ -79,11 +79,11 @@ langfun/core/memories/conversation_history_test.py,sha256=AaW8aNoFjxNusanwJDV0r3
|
|
79
79
|
langfun/core/modalities/__init__.py,sha256=F8P72IwFiTpEseTR2tYEJyQMlDW7fd9csvGJquLKJNg,1269
|
80
80
|
langfun/core/modalities/audio.py,sha256=Qxo7bYjLKQ1gVJVomr9RqR2SvxY826QgXhTzzk437Sk,952
|
81
81
|
langfun/core/modalities/audio_test.py,sha256=gWCB9h3FyrdGqro3ajBXqkw0lU0W1sBjOOq6wZbl7Fg,2027
|
82
|
-
langfun/core/modalities/image.py,sha256=
|
82
|
+
langfun/core/modalities/image.py,sha256=DyVV17Ii-vJmwA7Mo23B40m4VxZg_WXc0KPKN8Q4Mlc,1756
|
83
83
|
langfun/core/modalities/image_test.py,sha256=A0aYblxpnKABDgm2OVPxF8iRq4qFVanM9TPcaewkAgA,3774
|
84
|
-
langfun/core/modalities/mime.py,sha256
|
84
|
+
langfun/core/modalities/mime.py,sha256=-BEIAcIGzPdPgwUZ2yqFj6NGsBYGCiqeNqIWVeaNnTI,6164
|
85
85
|
langfun/core/modalities/mime_test.py,sha256=kmRiPP-3Py02NnKPu0oPexSIJ-MXaaE2UYY82ga0TV8,2913
|
86
|
-
langfun/core/modalities/ms_office.py,sha256=
|
86
|
+
langfun/core/modalities/ms_office.py,sha256=_dUgUCfK-hsEMOjgA_q3hUgKC4n8iAufKy7Sh2Gs1Jo,3780
|
87
87
|
langfun/core/modalities/ms_office_test.py,sha256=d_NZ0QU23NydenYZgNj6YxgO5ZYzjg-HCbglsVJGp04,87866
|
88
88
|
langfun/core/modalities/pdf.py,sha256=mfaeCbUA4JslFVTARiJh8hW7imvL4tLVw9gUhO5bAZA,727
|
89
89
|
langfun/core/modalities/pdf_test.py,sha256=KE40zJD3Whe6ty2OULkp1J8jwLmB4ZjGXlGekluTP48,1952
|
@@ -119,8 +119,8 @@ langfun/core/templates/demonstration.py,sha256=vCrgYubdZM5Umqcgp8NUVGXgr4P_c-fik
|
|
119
119
|
langfun/core/templates/demonstration_test.py,sha256=SafcDQ0WgI7pw05EmPI2S4v1t3ABKzup8jReCljHeK4,2162
|
120
120
|
langfun/core/templates/selfplay.py,sha256=yhgrJbiYwq47TgzThmHrDQTF4nDrTI09CWGhuQPNv-s,2273
|
121
121
|
langfun/core/templates/selfplay_test.py,sha256=rBW2Qr8yi-aWYwoTwRR-n1peKyMX9QXPZXURjLgoiRs,2264
|
122
|
-
langfun-0.1.1.
|
123
|
-
langfun-0.1.1.
|
124
|
-
langfun-0.1.1.
|
125
|
-
langfun-0.1.1.
|
126
|
-
langfun-0.1.1.
|
122
|
+
langfun-0.1.1.dev202408282153.dist-info/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
|
123
|
+
langfun-0.1.1.dev202408282153.dist-info/METADATA,sha256=QF_FZsBIPmWG9chJNbkoDrgRq_jnT75KFLaXKN44dp8,8825
|
124
|
+
langfun-0.1.1.dev202408282153.dist-info/WHEEL,sha256=UvcQYKBHoFqaQd6LKyqHw9fxEolWLQnlzP0h_LgJAfI,91
|
125
|
+
langfun-0.1.1.dev202408282153.dist-info/top_level.txt,sha256=RhlEkHxs1qtzmmtWSwYoLVJAc1YrbPtxQ52uh8Z9VvY,8
|
126
|
+
langfun-0.1.1.dev202408282153.dist-info/RECORD,,
|
File without changes
|
{langfun-0.1.1.dev20240827.dist-info → langfun-0.1.1.dev202408282153.dist-info}/top_level.txt
RENAMED
File without changes
|