langfun 0.1.2.dev202510280805__py3-none-any.whl → 0.1.2.dev202510300805__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.
Potentially problematic release.
This version of langfun might be problematic. Click here for more details.
- langfun/core/agentic/action.py +31 -3
- langfun/core/agentic/action_test.py +25 -0
- langfun/core/llms/gemini.py +3 -3
- langfun/env/__init__.py +4 -0
- langfun/env/base_environment.py +247 -105
- langfun/env/base_feature.py +15 -0
- langfun/env/base_sandbox.py +47 -240
- langfun/env/base_test.py +813 -603
- langfun/env/event_handlers/event_logger_test.py +2 -2
- langfun/env/event_handlers/metric_writer.py +49 -0
- langfun/env/event_handlers/metric_writer_test.py +19 -2
- langfun/env/interface.py +197 -13
- langfun/env/interface_test.py +81 -2
- langfun/env/load_balancers_test.py +19 -0
- langfun/env/test_utils.py +10 -7
- {langfun-0.1.2.dev202510280805.dist-info → langfun-0.1.2.dev202510300805.dist-info}/METADATA +1 -1
- {langfun-0.1.2.dev202510280805.dist-info → langfun-0.1.2.dev202510300805.dist-info}/RECORD +20 -20
- {langfun-0.1.2.dev202510280805.dist-info → langfun-0.1.2.dev202510300805.dist-info}/WHEEL +0 -0
- {langfun-0.1.2.dev202510280805.dist-info → langfun-0.1.2.dev202510300805.dist-info}/licenses/LICENSE +0 -0
- {langfun-0.1.2.dev202510280805.dist-info → langfun-0.1.2.dev202510300805.dist-info}/top_level.txt +0 -0
langfun/env/base_sandbox.py
CHANGED
|
@@ -26,7 +26,7 @@ import contextlib
|
|
|
26
26
|
import functools
|
|
27
27
|
import threading
|
|
28
28
|
import time
|
|
29
|
-
from typing import Annotated, Any,
|
|
29
|
+
from typing import Annotated, Any, Iterator
|
|
30
30
|
|
|
31
31
|
from langfun.env import interface
|
|
32
32
|
from langfun.env.event_handlers import base as event_handler_base
|
|
@@ -41,6 +41,11 @@ class BaseSandbox(interface.Sandbox):
|
|
|
41
41
|
'The identifier for the sandbox.'
|
|
42
42
|
]
|
|
43
43
|
|
|
44
|
+
image_id: Annotated[
|
|
45
|
+
str,
|
|
46
|
+
'The image id for the sandbox.'
|
|
47
|
+
]
|
|
48
|
+
|
|
44
49
|
environment: Annotated[
|
|
45
50
|
pg.Ref[interface.Environment],
|
|
46
51
|
'The parent environment.'
|
|
@@ -95,10 +100,9 @@ class BaseSandbox(interface.Sandbox):
|
|
|
95
100
|
self._status = status
|
|
96
101
|
self._status_start_time = time.time()
|
|
97
102
|
|
|
98
|
-
def
|
|
103
|
+
def report_state_error(self, e: interface.SandboxStateError) -> None:
|
|
99
104
|
"""Reports sandbox state errors."""
|
|
100
|
-
if
|
|
101
|
-
and e not in self._state_errors):
|
|
105
|
+
if e not in self._state_errors:
|
|
102
106
|
self._state_errors.append(e)
|
|
103
107
|
|
|
104
108
|
def _setup_features(self) -> None:
|
|
@@ -136,7 +140,8 @@ class BaseSandbox(interface.Sandbox):
|
|
|
136
140
|
try:
|
|
137
141
|
feature.teardown()
|
|
138
142
|
except BaseException as e: # pylint: disable=broad-except
|
|
139
|
-
|
|
143
|
+
if isinstance(e, interface.SandboxStateError):
|
|
144
|
+
self.report_state_error(e)
|
|
140
145
|
errors[feature.name] = e
|
|
141
146
|
if errors:
|
|
142
147
|
return interface.FeatureTeardownError(sandbox=self, errors=errors)
|
|
@@ -170,7 +175,8 @@ class BaseSandbox(interface.Sandbox):
|
|
|
170
175
|
try:
|
|
171
176
|
feature.teardown_session()
|
|
172
177
|
except BaseException as e: # pylint: disable=broad-except
|
|
173
|
-
|
|
178
|
+
if isinstance(e, interface.SandboxStateError):
|
|
179
|
+
self.report_state_error(e)
|
|
174
180
|
feature_teardown_errors[name] = e
|
|
175
181
|
|
|
176
182
|
return interface.SessionTeardownError(
|
|
@@ -187,10 +193,10 @@ class BaseSandbox(interface.Sandbox):
|
|
|
187
193
|
def _on_bound(self) -> None:
|
|
188
194
|
"""Called when the sandbox is bound."""
|
|
189
195
|
super()._on_bound()
|
|
190
|
-
|
|
191
196
|
self._features = pg.Dict({
|
|
192
197
|
name: pg.clone(feature)
|
|
193
198
|
for name, feature in self.environment.features.items()
|
|
199
|
+
if feature.is_applicable(self.image_id)
|
|
194
200
|
})
|
|
195
201
|
self._event_handlers = []
|
|
196
202
|
self._enable_pre_session_setup = (
|
|
@@ -272,28 +278,6 @@ class BaseSandbox(interface.Sandbox):
|
|
|
272
278
|
"""Returns the features in the sandbox."""
|
|
273
279
|
return self._features
|
|
274
280
|
|
|
275
|
-
def _enter_service_call(self) -> bool:
|
|
276
|
-
"""Enters a service call.
|
|
277
|
-
|
|
278
|
-
Returns:
|
|
279
|
-
True if the service call is at the top of the call stack.
|
|
280
|
-
"""
|
|
281
|
-
v = getattr(self._tls_state, 'service_call_depth', None)
|
|
282
|
-
if v is None:
|
|
283
|
-
v = 0
|
|
284
|
-
setattr(self._tls_state, 'service_call_depth', v + 1)
|
|
285
|
-
return v == 0
|
|
286
|
-
|
|
287
|
-
def _exit_service_call(self) -> bool:
|
|
288
|
-
"""Exits a service call.
|
|
289
|
-
|
|
290
|
-
Returns:
|
|
291
|
-
True if the service call is at the top of the call stack.
|
|
292
|
-
"""
|
|
293
|
-
v = getattr(self._tls_state, 'service_call_depth')
|
|
294
|
-
setattr(self._tls_state, 'service_call_depth', v - 1)
|
|
295
|
-
return v == 1
|
|
296
|
-
|
|
297
281
|
#
|
|
298
282
|
# Sandbox start/shutdown.
|
|
299
283
|
#
|
|
@@ -364,7 +348,8 @@ class BaseSandbox(interface.Sandbox):
|
|
|
364
348
|
'[%s]: Sandbox failed to start in %.2f seconds: %s',
|
|
365
349
|
self.id, duration, e
|
|
366
350
|
)
|
|
367
|
-
|
|
351
|
+
if isinstance(e, interface.SandboxStateError):
|
|
352
|
+
self.report_state_error(e)
|
|
368
353
|
self.on_start(duration, e)
|
|
369
354
|
self.shutdown()
|
|
370
355
|
raise e
|
|
@@ -437,7 +422,8 @@ class BaseSandbox(interface.Sandbox):
|
|
|
437
422
|
shutdown_error = None
|
|
438
423
|
except BaseException as e: # pylint: disable=broad-except
|
|
439
424
|
shutdown_error = e
|
|
440
|
-
|
|
425
|
+
if isinstance(e, interface.SandboxStateError):
|
|
426
|
+
self.report_state_error(e)
|
|
441
427
|
self._set_status(interface.Sandbox.Status.OFFLINE)
|
|
442
428
|
pg.logging.error(
|
|
443
429
|
'[%s]: Sandbox shutdown with error: %s',
|
|
@@ -535,7 +521,8 @@ class BaseSandbox(interface.Sandbox):
|
|
|
535
521
|
self._set_status(self.Status.IN_SESSION)
|
|
536
522
|
self.on_session_start(session_id, time.time() - self._session_start_time)
|
|
537
523
|
except BaseException as e: # pylint: disable=broad-except
|
|
538
|
-
|
|
524
|
+
if isinstance(e, interface.SandboxStateError):
|
|
525
|
+
self.report_state_error(e)
|
|
539
526
|
self.on_session_start(
|
|
540
527
|
session_id, time.time() - self._session_start_time, e
|
|
541
528
|
)
|
|
@@ -621,7 +608,8 @@ class BaseSandbox(interface.Sandbox):
|
|
|
621
608
|
self.id,
|
|
622
609
|
e
|
|
623
610
|
)
|
|
624
|
-
|
|
611
|
+
if isinstance(e, interface.SandboxStateError):
|
|
612
|
+
self.report_state_error(e)
|
|
625
613
|
self.shutdown()
|
|
626
614
|
|
|
627
615
|
# End session before setting up the next session.
|
|
@@ -672,6 +660,30 @@ class BaseSandbox(interface.Sandbox):
|
|
|
672
660
|
and end_session_error.has_non_sandbox_state_error):
|
|
673
661
|
raise end_session_error # pylint: disable=raising-bad-type
|
|
674
662
|
|
|
663
|
+
@contextlib.contextmanager
|
|
664
|
+
def track_activity(
|
|
665
|
+
self,
|
|
666
|
+
name: str,
|
|
667
|
+
feature: interface.Feature | None = None,
|
|
668
|
+
**kwargs: Any
|
|
669
|
+
) -> Iterator[None]:
|
|
670
|
+
"""Tracks an activity for the sandbox."""
|
|
671
|
+
start_time = time.time()
|
|
672
|
+
error = None
|
|
673
|
+
try:
|
|
674
|
+
yield None
|
|
675
|
+
except BaseException as e: # pylint: disable=broad-except
|
|
676
|
+
error = e
|
|
677
|
+
raise
|
|
678
|
+
finally:
|
|
679
|
+
self.on_activity(
|
|
680
|
+
name=name,
|
|
681
|
+
feature=feature,
|
|
682
|
+
duration=time.time() - start_time,
|
|
683
|
+
error=error,
|
|
684
|
+
**kwargs
|
|
685
|
+
)
|
|
686
|
+
|
|
675
687
|
#
|
|
676
688
|
# Housekeeping.
|
|
677
689
|
#
|
|
@@ -717,7 +729,7 @@ class BaseSandbox(interface.Sandbox):
|
|
|
717
729
|
str(e)
|
|
718
730
|
)
|
|
719
731
|
self._housekeep_counter += 1
|
|
720
|
-
self.
|
|
732
|
+
self.report_state_error(e)
|
|
721
733
|
self.on_housekeep(time.time() - housekeep_start, e)
|
|
722
734
|
self.shutdown()
|
|
723
735
|
break
|
|
@@ -739,7 +751,7 @@ class BaseSandbox(interface.Sandbox):
|
|
|
739
751
|
feature.name,
|
|
740
752
|
e,
|
|
741
753
|
)
|
|
742
|
-
self.
|
|
754
|
+
self.report_state_error(e)
|
|
743
755
|
self._housekeep_counter += 1
|
|
744
756
|
self.on_housekeep(time.time() - housekeep_start, e)
|
|
745
757
|
self.shutdown()
|
|
@@ -912,208 +924,3 @@ class BaseSandbox(interface.Sandbox):
|
|
|
912
924
|
handler.on_session_end(
|
|
913
925
|
self.environment, self, session_id, duration, lifetime, error
|
|
914
926
|
)
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
#
|
|
918
|
-
# Sandbox service decorator.
|
|
919
|
-
#
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
def sandbox_service(
|
|
923
|
-
critical_errors: Sequence[
|
|
924
|
-
Type[BaseException] | tuple[Type[BaseException], str]
|
|
925
|
-
] | None = None
|
|
926
|
-
) -> Callable[..., Any]:
|
|
927
|
-
"""Decorator for Sandbox/Feature methods exposed as sandbox services.
|
|
928
|
-
|
|
929
|
-
This decorator will catch errors and map to `SandboxStateError` if the
|
|
930
|
-
error matches any of the critical errors. Consequently, the sandbox will be
|
|
931
|
-
shutdown automatically when the error is raised.
|
|
932
|
-
|
|
933
|
-
Example:
|
|
934
|
-
|
|
935
|
-
```
|
|
936
|
-
with env:
|
|
937
|
-
with env.sandbox() as sb:
|
|
938
|
-
try:
|
|
939
|
-
sb.test_feature.do_something_with_non_state_error()
|
|
940
|
-
except ValueError:
|
|
941
|
-
# sandbox will not be shutdown.
|
|
942
|
-
pass
|
|
943
|
-
|
|
944
|
-
try:
|
|
945
|
-
sb.test_feature.do_something_with_state_error()
|
|
946
|
-
except ValueError:
|
|
947
|
-
assert sb.state == sb.Status.OFFLINE
|
|
948
|
-
```
|
|
949
|
-
|
|
950
|
-
If the decorated method returns a context manager, a wrapper context manager
|
|
951
|
-
will be returned, which will end the session when exiting the context.
|
|
952
|
-
|
|
953
|
-
Example:
|
|
954
|
-
|
|
955
|
-
```
|
|
956
|
-
with env:
|
|
957
|
-
with env.test_feature.do_something_with_context_manager() as result:
|
|
958
|
-
# sandbox will be alive during the whole context manager cycle.
|
|
959
|
-
```
|
|
960
|
-
|
|
961
|
-
For sandbox service methods, an optional `session_id` argument can be passed
|
|
962
|
-
to create a new session for the service call, even its signature does not
|
|
963
|
-
contain a `session_id` argument.
|
|
964
|
-
|
|
965
|
-
Args:
|
|
966
|
-
critical_errors: A sequence of exception types or tuples of exception type
|
|
967
|
-
and error messages (described in regular expression), when matched, treat
|
|
968
|
-
the sandbox as in a bad state, which will trigger a shutdown.
|
|
969
|
-
|
|
970
|
-
Returns:
|
|
971
|
-
The decorator function.
|
|
972
|
-
"""
|
|
973
|
-
critical_errors = critical_errors or []
|
|
974
|
-
|
|
975
|
-
def decorator(func):
|
|
976
|
-
signature = pg.typing.get_signature(func)
|
|
977
|
-
if 'session_id' in signature.arg_names:
|
|
978
|
-
raise ValueError(
|
|
979
|
-
'`session_id` should not be used as argument for sandbox '
|
|
980
|
-
'service method. Please use `self.session_id` instead.'
|
|
981
|
-
)
|
|
982
|
-
|
|
983
|
-
def to_kwargs(*args, **kwargs):
|
|
984
|
-
num_non_self_args = len(signature.arg_names) - 1
|
|
985
|
-
if len(args) > num_non_self_args:
|
|
986
|
-
assert signature.varargs is not None, (signature, args)
|
|
987
|
-
kwargs[signature.varargs.name] = tuple(args[num_non_self_args:])
|
|
988
|
-
args = args[:num_non_self_args]
|
|
989
|
-
for i in range(len(args)):
|
|
990
|
-
# The first argument is `self`.
|
|
991
|
-
kwargs[signature.arg_names[i + 1]] = args[i]
|
|
992
|
-
return kwargs
|
|
993
|
-
|
|
994
|
-
@functools.wraps(func)
|
|
995
|
-
def method_wrapper(self, *args, **kwargs) -> Any:
|
|
996
|
-
"""Helper function to safely execute logics in the sandbox."""
|
|
997
|
-
|
|
998
|
-
assert isinstance(self, (BaseSandbox, interface.Feature)), self
|
|
999
|
-
sandbox = self.sandbox if isinstance(self, interface.Feature) else self
|
|
1000
|
-
|
|
1001
|
-
# We count the service call stack depth so we could shutdown the sandbox
|
|
1002
|
-
# at the top upon sandbox state error.
|
|
1003
|
-
sandbox._enter_service_call() # pylint: disable=protected-access
|
|
1004
|
-
|
|
1005
|
-
# When a capability is directly accessed from the environment,
|
|
1006
|
-
# we create a new session for the capability call. This
|
|
1007
|
-
# prevents the sandbox from being reused for other feature calls.
|
|
1008
|
-
if sandbox.status == interface.Sandbox.Status.ACQUIRED:
|
|
1009
|
-
new_session = True
|
|
1010
|
-
new_session_id = kwargs.get('session_id')
|
|
1011
|
-
if new_session_id is None:
|
|
1012
|
-
new_session_id = sandbox.environment.new_session_id()
|
|
1013
|
-
|
|
1014
|
-
# If it's a feature method called from the environment, start a new
|
|
1015
|
-
# session for the feature call.
|
|
1016
|
-
sandbox.start_session(new_session_id)
|
|
1017
|
-
else:
|
|
1018
|
-
new_session = False
|
|
1019
|
-
|
|
1020
|
-
kwargs.pop('session_id', None)
|
|
1021
|
-
result = None
|
|
1022
|
-
error = None
|
|
1023
|
-
start_time = time.time()
|
|
1024
|
-
|
|
1025
|
-
try:
|
|
1026
|
-
# Execute the service function.
|
|
1027
|
-
result = func(self, *args, **kwargs)
|
|
1028
|
-
|
|
1029
|
-
# If the result is a context manager, wrap it with a context manager
|
|
1030
|
-
# to end the session when exiting.
|
|
1031
|
-
if isinstance(result, contextlib.AbstractContextManager):
|
|
1032
|
-
return _service_context_manager_wrapper(
|
|
1033
|
-
service=result,
|
|
1034
|
-
sandbox_or_feature=self,
|
|
1035
|
-
sandbox=sandbox,
|
|
1036
|
-
name=func.__name__,
|
|
1037
|
-
kwargs=to_kwargs(*args, **kwargs),
|
|
1038
|
-
start_time=start_time,
|
|
1039
|
-
new_session=new_session
|
|
1040
|
-
)
|
|
1041
|
-
|
|
1042
|
-
# Otherwise, return the result and end the session in the finally block.
|
|
1043
|
-
return result
|
|
1044
|
-
except BaseException as e:
|
|
1045
|
-
error = e
|
|
1046
|
-
sandbox.report_maybe_state_error(e)
|
|
1047
|
-
if pg.match_error(e, critical_errors):
|
|
1048
|
-
state_error = interface.SandboxStateError(
|
|
1049
|
-
'Sandbox encountered an unexpected error executing '
|
|
1050
|
-
f'`{func.__name__}` (args={args!r}, kwargs={kwargs!r}): {e}',
|
|
1051
|
-
sandbox=self
|
|
1052
|
-
)
|
|
1053
|
-
sandbox.report_maybe_state_error(state_error)
|
|
1054
|
-
raise state_error from e
|
|
1055
|
-
raise
|
|
1056
|
-
finally:
|
|
1057
|
-
is_topmost_call = sandbox._exit_service_call() # pylint: disable=protected-access
|
|
1058
|
-
if not isinstance(result, contextlib.AbstractContextManager):
|
|
1059
|
-
self.on_activity(
|
|
1060
|
-
name=func.__name__,
|
|
1061
|
-
duration=time.time() - start_time,
|
|
1062
|
-
error=error,
|
|
1063
|
-
**to_kwargs(*args, **kwargs),
|
|
1064
|
-
)
|
|
1065
|
-
if new_session:
|
|
1066
|
-
assert is_topmost_call
|
|
1067
|
-
|
|
1068
|
-
# End the session if it's from a feature method and the result
|
|
1069
|
-
# is not a context manager.
|
|
1070
|
-
sandbox.end_session()
|
|
1071
|
-
|
|
1072
|
-
# Shutdown the sandbox if it is at the top of the service call stack and
|
|
1073
|
-
# has state errors.
|
|
1074
|
-
if (is_topmost_call
|
|
1075
|
-
and sandbox.state_errors
|
|
1076
|
-
# Sandbox service method might be called during shutting down, in
|
|
1077
|
-
# that case we don't want to shutdown the sandbox again.
|
|
1078
|
-
and not sandbox.is_shutting_down):
|
|
1079
|
-
sandbox.shutdown()
|
|
1080
|
-
|
|
1081
|
-
return method_wrapper
|
|
1082
|
-
return decorator
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
@contextlib.contextmanager
|
|
1086
|
-
def _service_context_manager_wrapper(
|
|
1087
|
-
service: contextlib.AbstractContextManager[Any],
|
|
1088
|
-
sandbox_or_feature: BaseSandbox | interface.Feature,
|
|
1089
|
-
sandbox: interface.Sandbox,
|
|
1090
|
-
name: str,
|
|
1091
|
-
kwargs: dict[str, Any],
|
|
1092
|
-
new_session: bool,
|
|
1093
|
-
start_time: float,
|
|
1094
|
-
) -> Iterator[Any]:
|
|
1095
|
-
"""Context manager wrapper for ending a sandbox session when exiting."""
|
|
1096
|
-
error = None
|
|
1097
|
-
sandbox._enter_service_call() # pylint: disable=protected-access
|
|
1098
|
-
|
|
1099
|
-
try:
|
|
1100
|
-
with service as result:
|
|
1101
|
-
yield result
|
|
1102
|
-
except BaseException as e:
|
|
1103
|
-
error = e
|
|
1104
|
-
sandbox.report_maybe_state_error(error)
|
|
1105
|
-
raise
|
|
1106
|
-
finally:
|
|
1107
|
-
sandbox_or_feature.on_activity(
|
|
1108
|
-
name=name,
|
|
1109
|
-
error=error,
|
|
1110
|
-
duration=time.time() - start_time,
|
|
1111
|
-
**kwargs,
|
|
1112
|
-
)
|
|
1113
|
-
is_topmost_call = sandbox._exit_service_call() # pylint: disable=protected-access
|
|
1114
|
-
|
|
1115
|
-
if new_session:
|
|
1116
|
-
assert is_topmost_call
|
|
1117
|
-
sandbox.end_session()
|
|
1118
|
-
elif isinstance(error, interface.SandboxStateError):
|
|
1119
|
-
sandbox.shutdown()
|