langfun 0.1.2.dev202509160805__tar.gz → 0.1.2.dev202509180804__tar.gz
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-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/PKG-INFO +1 -1
- langfun-0.1.2.dev202509180804/langfun/env/__init__.py +39 -0
- langfun-0.1.2.dev202509180804/langfun/env/base_environment.py +491 -0
- langfun-0.1.2.dev202509180804/langfun/env/base_feature.py +165 -0
- langfun-0.1.2.dev202509180804/langfun/env/base_sandbox.py +458 -0
- langfun-0.1.2.dev202509180804/langfun/env/base_test.py +795 -0
- langfun-0.1.2.dev202509180804/langfun/env/interface.py +843 -0
- langfun-0.1.2.dev202509180804/langfun/env/interface_test.py +43 -0
- langfun-0.1.2.dev202509180804/langfun/env/load_balancers.py +59 -0
- langfun-0.1.2.dev202509180804/langfun/env/load_balancers_test.py +157 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun.egg-info/PKG-INFO +1 -1
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun.egg-info/SOURCES.txt +10 -1
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/LICENSE +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/README.md +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/__init__.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/assistant/capabilities/gui/__init__.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/assistant/capabilities/gui/bounding_box_parser.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/assistant/capabilities/gui/bounding_box_parser_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/assistant/capabilities/gui/drawing.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/assistant/capabilities/gui/drawing_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/assistant/capabilities/gui/location.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/assistant/capabilities/gui/location_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/__init__.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/agentic/__init__.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/agentic/action.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/agentic/action_eval.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/agentic/action_eval_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/agentic/action_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/async_support.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/async_support_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/coding/__init__.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/coding/python/__init__.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/coding/python/correction.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/coding/python/correction_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/coding/python/execution.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/coding/python/execution_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/coding/python/generation.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/coding/python/generation_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/coding/python/parsing.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/coding/python/parsing_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/coding/python/sandboxing.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/coding/python/sandboxing_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/component.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/component_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/concurrent.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/concurrent_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/console.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/console_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/data/__init__.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/data/conversion/__init__.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/data/conversion/anthropic.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/data/conversion/anthropic_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/data/conversion/gemini.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/data/conversion/gemini_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/data/conversion/openai.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/data/conversion/openai_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/__init__.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/base.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/base_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/matching.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/matching_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/patching.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/patching_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/scoring.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/scoring_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/__init__.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/checkpointing.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/checkpointing_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/eval_test_helper.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/evaluation.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/evaluation_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/example.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/example_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/experiment.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/experiment_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/metric_values.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/metric_values_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/metrics.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/metrics_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/progress.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/progress_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/progress_tracking.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/progress_tracking_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/reporting.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/reporting_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/runners.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/eval/v2/runners_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/langfunc.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/langfunc_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/language_model.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/language_model_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/__init__.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/anthropic.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/anthropic_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/azure_openai.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/azure_openai_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/cache/__init__.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/cache/base.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/cache/in_memory.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/cache/in_memory_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/compositional.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/compositional_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/deepseek.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/deepseek_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/fake.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/fake_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/gemini.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/gemini_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/google_genai.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/google_genai_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/groq.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/groq_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/llama_cpp.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/llama_cpp_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/openai.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/openai_compatible.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/openai_compatible_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/openai_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/rest.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/rest_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/vertexai.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/llms/vertexai_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/logging.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/logging_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/memories/__init__.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/memories/conversation_history.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/memories/conversation_history_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/memory.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/message.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/message_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/modalities/__init__.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/modalities/audio.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/modalities/audio_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/modalities/image.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/modalities/image_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/modalities/mime.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/modalities/mime_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/modalities/pdf.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/modalities/pdf_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/modalities/video.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/modalities/video_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/modality.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/modality_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/natural_language.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/natural_language_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/sampling.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/sampling_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/structured/__init__.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/structured/completion.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/structured/completion_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/structured/description.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/structured/description_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/structured/function_generation.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/structured/function_generation_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/structured/mapping.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/structured/mapping_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/structured/parsing.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/structured/parsing_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/structured/querying.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/structured/querying_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/structured/schema.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/structured/schema_generation.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/structured/schema_generation_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/structured/schema_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/structured/scoring.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/structured/scoring_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/structured/tokenization.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/structured/tokenization_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/subscription.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/subscription_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/template.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/template_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/templates/__init__.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/templates/completion.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/templates/completion_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/templates/conversation.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/templates/conversation_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/templates/demonstration.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/templates/demonstration_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/templates/selfplay.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun/core/templates/selfplay_test.py +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun.egg-info/dependency_links.txt +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun.egg-info/requires.txt +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/langfun.egg-info/top_level.txt +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/setup.cfg +0 -0
- {langfun-0.1.2.dev202509160805 → langfun-0.1.2.dev202509180804}/setup.py +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
"""Environment for LLM agents."""
|
|
15
|
+
|
|
16
|
+
# pylint: disable=g-importing-member, g-bad-import-order
|
|
17
|
+
from langfun.env.interface import EnvironmentId
|
|
18
|
+
from langfun.env.interface import SandboxId
|
|
19
|
+
|
|
20
|
+
from langfun.env.interface import EnvironmentError # pylint: disable=redefined-builtin
|
|
21
|
+
from langfun.env.interface import EnvironmentOutageError
|
|
22
|
+
from langfun.env.interface import EnvironmentOverloadError
|
|
23
|
+
from langfun.env.interface import SandboxError
|
|
24
|
+
from langfun.env.interface import SandboxStateError
|
|
25
|
+
# from langfun.env.base import SandboxOverloadError
|
|
26
|
+
|
|
27
|
+
from langfun.env.interface import Environment
|
|
28
|
+
from langfun.env.interface import Sandbox
|
|
29
|
+
from langfun.env.interface import Feature
|
|
30
|
+
from langfun.env.interface import call_with_event
|
|
31
|
+
|
|
32
|
+
from langfun.env.base_environment import BaseEnvironment
|
|
33
|
+
from langfun.env.base_sandbox import BaseSandbox
|
|
34
|
+
from langfun.env.base_feature import BaseFeature
|
|
35
|
+
|
|
36
|
+
from langfun.env import load_balancers
|
|
37
|
+
from langfun.env.load_balancers import LoadBalancer
|
|
38
|
+
|
|
39
|
+
# Google-internal imports.
|
|
@@ -0,0 +1,491 @@
|
|
|
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
|
+
"""Common base class for sandbox-based environments.
|
|
15
|
+
|
|
16
|
+
This module provides `BaseEnvironment`, a common base class for sandbox-based
|
|
17
|
+
environments that handles pooling, load balancing, and maintenance.
|
|
18
|
+
|
|
19
|
+
Note that:
|
|
20
|
+
- Environments do not have to inherit from this class, especially if features
|
|
21
|
+
like pooling or load balancing are not needed.
|
|
22
|
+
- `BaseEnvironment` is not required to work with `BaseSandbox`.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import abc
|
|
26
|
+
import functools
|
|
27
|
+
import threading
|
|
28
|
+
import time
|
|
29
|
+
from typing import Annotated, Any
|
|
30
|
+
|
|
31
|
+
import langfun.core as lf
|
|
32
|
+
from langfun.env import interface
|
|
33
|
+
from langfun.env import load_balancers
|
|
34
|
+
import pyglove as pg
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class BaseEnvironment(interface.Environment):
|
|
38
|
+
"""Common base for environments.
|
|
39
|
+
|
|
40
|
+
The base environment provides the common functionalities for sandbox-based
|
|
41
|
+
environments, such as environment pooling, load balancing, and sandbox
|
|
42
|
+
maintenance.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
root_dir: Annotated[
|
|
46
|
+
str | None,
|
|
47
|
+
(
|
|
48
|
+
'The root directory for the environment for writting output files.'
|
|
49
|
+
'If None, no output files will be allowed for the sandboxes.'
|
|
50
|
+
)
|
|
51
|
+
] = None
|
|
52
|
+
|
|
53
|
+
pool_size: Annotated[
|
|
54
|
+
int | tuple[int, int],
|
|
55
|
+
(
|
|
56
|
+
'The (min_size, max_size) of the sandbox pool. If an integer, it '
|
|
57
|
+
'will be used as both min and max size. If 0, sandboxes will be '
|
|
58
|
+
'created on demand and shutdown when user session ends.'
|
|
59
|
+
)
|
|
60
|
+
] = 1
|
|
61
|
+
|
|
62
|
+
load_balancer: Annotated[
|
|
63
|
+
load_balancers.LoadBalancer,
|
|
64
|
+
(
|
|
65
|
+
'The load balancer for the environment.'
|
|
66
|
+
)
|
|
67
|
+
] = load_balancers.RoundRobin()
|
|
68
|
+
|
|
69
|
+
outage_grace_period: Annotated[
|
|
70
|
+
float,
|
|
71
|
+
(
|
|
72
|
+
'The grace period in seconds before the environment is treated '
|
|
73
|
+
'as out of service. When calling `environment.sandbox()`, '
|
|
74
|
+
'wait until the grace period has passed before raising an error.'
|
|
75
|
+
)
|
|
76
|
+
] = 3600.0
|
|
77
|
+
|
|
78
|
+
outage_retry_interval: Annotated[
|
|
79
|
+
float,
|
|
80
|
+
(
|
|
81
|
+
'The retry interval in seconds for environment outage. '
|
|
82
|
+
'When calling `environment.sandbox()`, retry after the interval '
|
|
83
|
+
'if the environment is out of service.'
|
|
84
|
+
)
|
|
85
|
+
] = 10.0
|
|
86
|
+
|
|
87
|
+
stats_report_interval: Annotated[
|
|
88
|
+
float,
|
|
89
|
+
(
|
|
90
|
+
'The interval in seconds for reporting the environment stats. '
|
|
91
|
+
'If 0, stats will not be reported.'
|
|
92
|
+
)
|
|
93
|
+
] = 60.0
|
|
94
|
+
|
|
95
|
+
pool_operation_max_parallelism: Annotated[
|
|
96
|
+
int,
|
|
97
|
+
(
|
|
98
|
+
'The maximum number of threads for bringing up or shutting down '
|
|
99
|
+
'sandboxes in the pool.'
|
|
100
|
+
)
|
|
101
|
+
] = 256
|
|
102
|
+
|
|
103
|
+
def _on_bound(self) -> None:
|
|
104
|
+
super()._on_bound()
|
|
105
|
+
|
|
106
|
+
self._alive = False
|
|
107
|
+
self._start_time = None
|
|
108
|
+
self._sandbox_pool = []
|
|
109
|
+
self._next_pooled_sandbox_id = 0
|
|
110
|
+
|
|
111
|
+
self._maintenance_thread = None
|
|
112
|
+
self._offline_start_time = None
|
|
113
|
+
|
|
114
|
+
#
|
|
115
|
+
# Subclasses must implement:
|
|
116
|
+
#
|
|
117
|
+
|
|
118
|
+
@abc.abstractmethod
|
|
119
|
+
def _create_sandbox(
|
|
120
|
+
self,
|
|
121
|
+
sandbox_id: str,
|
|
122
|
+
reusable: bool
|
|
123
|
+
) -> interface.Sandbox:
|
|
124
|
+
"""Creates a sandbox with the given identifier.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
sandbox_id: The identifier for the sandbox.
|
|
128
|
+
reusable: Whether the sandbox is reusable across user sessions.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
The created sandbox.
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
interface.EnvironmentError: If environment cannot create the sandbox.
|
|
135
|
+
interface.SandboxStateError: If sandbox cannot be started.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
#
|
|
139
|
+
# Subclasses can override:
|
|
140
|
+
#
|
|
141
|
+
|
|
142
|
+
def stats(self) -> dict[str, Any]:
|
|
143
|
+
"""Returns the stats of the environment."""
|
|
144
|
+
num_busy = 0
|
|
145
|
+
num_free = 0
|
|
146
|
+
num_dead = 0
|
|
147
|
+
|
|
148
|
+
for sandbox in self._sandbox_pool:
|
|
149
|
+
if sandbox.is_alive:
|
|
150
|
+
if sandbox.is_busy:
|
|
151
|
+
num_busy += 1
|
|
152
|
+
else:
|
|
153
|
+
num_free += 1
|
|
154
|
+
else:
|
|
155
|
+
num_dead += 1
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
'sandbox': {
|
|
159
|
+
'num_total': len(self._sandbox_pool),
|
|
160
|
+
'num_busy': num_busy,
|
|
161
|
+
'num_free': num_free,
|
|
162
|
+
'num_dead': num_dead,
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
def _start(self) -> None:
|
|
167
|
+
"""Implementation of starting the environment."""
|
|
168
|
+
if self.min_pool_size > 0:
|
|
169
|
+
self._sandbox_pool = [
|
|
170
|
+
sandbox
|
|
171
|
+
for _, sandbox, _ in lf.concurrent_map(
|
|
172
|
+
lambda i: self._bring_up_sandbox_with_retry(sandbox_id=str(i)),
|
|
173
|
+
range(self.min_pool_size),
|
|
174
|
+
silence_on_errors=None,
|
|
175
|
+
max_workers=min(
|
|
176
|
+
self.pool_operation_max_parallelism,
|
|
177
|
+
self.min_pool_size
|
|
178
|
+
),
|
|
179
|
+
)
|
|
180
|
+
]
|
|
181
|
+
self._next_sandbox_id = len(self._sandbox_pool)
|
|
182
|
+
self._alive = True
|
|
183
|
+
self._maintenance_thread = threading.Thread(
|
|
184
|
+
target=self._maintenance_loop, daemon=True
|
|
185
|
+
)
|
|
186
|
+
self._maintenance_count = 0
|
|
187
|
+
self._maintenance_thread.start()
|
|
188
|
+
|
|
189
|
+
def _shutdown(self) -> None:
|
|
190
|
+
"""Implementation of shutting down the environment."""
|
|
191
|
+
if (self._maintenance_thread is not None
|
|
192
|
+
and threading.current_thread() is not self._maintenance_thread):
|
|
193
|
+
self._maintenance_thread.join()
|
|
194
|
+
self._maintenance_thread = None
|
|
195
|
+
|
|
196
|
+
def _shutdown_sandbox(sandbox: interface.Sandbox) -> None:
|
|
197
|
+
sandbox.shutdown()
|
|
198
|
+
|
|
199
|
+
if self._sandbox_pool:
|
|
200
|
+
_ = list(
|
|
201
|
+
lf.concurrent_map(
|
|
202
|
+
_shutdown_sandbox,
|
|
203
|
+
self._sandbox_pool,
|
|
204
|
+
silence_on_errors=None,
|
|
205
|
+
max_workers=min(
|
|
206
|
+
self.pool_operation_max_parallelism,
|
|
207
|
+
len(self._sandbox_pool)
|
|
208
|
+
),
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
self._sandbox_pool = []
|
|
212
|
+
|
|
213
|
+
#
|
|
214
|
+
# Environment basics.
|
|
215
|
+
#
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def sandbox_pool(self) -> list[interface.Sandbox]:
|
|
219
|
+
"""Returns the sandbox pool."""
|
|
220
|
+
return self._sandbox_pool
|
|
221
|
+
|
|
222
|
+
@functools.cached_property
|
|
223
|
+
def working_dir(self) -> str | None:
|
|
224
|
+
"""Returns the working directory for the environment."""
|
|
225
|
+
return self.id.working_dir(self.root_dir)
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def enable_pooling(self) -> bool:
|
|
229
|
+
"""Returns whether the environment enables pooling."""
|
|
230
|
+
return self.min_pool_size > 0
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def is_alive(self) -> bool:
|
|
234
|
+
"""Returns whether the environment is alive."""
|
|
235
|
+
return self._alive
|
|
236
|
+
|
|
237
|
+
@property
|
|
238
|
+
def min_pool_size(self) -> int:
|
|
239
|
+
"""Returns the minimum size of the sandbox pool."""
|
|
240
|
+
if isinstance(self.pool_size, int):
|
|
241
|
+
return self.pool_size
|
|
242
|
+
return self.pool_size[0]
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def max_pool_size(self) -> int:
|
|
246
|
+
"""Returns the maximum size of the sandbox pool."""
|
|
247
|
+
if isinstance(self.pool_size, int):
|
|
248
|
+
return self.pool_size
|
|
249
|
+
return self.pool_size[1]
|
|
250
|
+
|
|
251
|
+
@property
|
|
252
|
+
def start_time(self) -> float | None:
|
|
253
|
+
"""Returns the start time of the environment."""
|
|
254
|
+
return self._start_time
|
|
255
|
+
|
|
256
|
+
@property
|
|
257
|
+
def offline_duration(self) -> float:
|
|
258
|
+
"""Returns the offline duration of the environment."""
|
|
259
|
+
if self._offline_start_time is None:
|
|
260
|
+
return 0.0
|
|
261
|
+
return time.time() - self._offline_start_time
|
|
262
|
+
|
|
263
|
+
#
|
|
264
|
+
# Environment lifecycle.
|
|
265
|
+
#
|
|
266
|
+
|
|
267
|
+
def start(self) -> None:
|
|
268
|
+
"""Starts the environment.
|
|
269
|
+
|
|
270
|
+
Raises:
|
|
271
|
+
interface.EnvironmentOutageError: If the environment is out of service.
|
|
272
|
+
"""
|
|
273
|
+
assert not self._alive
|
|
274
|
+
def _start_impl():
|
|
275
|
+
with pg.timeit('env.start') as t:
|
|
276
|
+
self._start()
|
|
277
|
+
self._start_time = time.time()
|
|
278
|
+
pg.logging.info(
|
|
279
|
+
'[%s]: %s started in %.2f seconds.',
|
|
280
|
+
self.id, self.__class__.__name__, t.elapse
|
|
281
|
+
)
|
|
282
|
+
interface.call_with_event(
|
|
283
|
+
_start_impl, self.on_start,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
def shutdown(self) -> None:
|
|
287
|
+
"""Shuts down the environment.
|
|
288
|
+
|
|
289
|
+
This method should not raise any exceptions.
|
|
290
|
+
"""
|
|
291
|
+
if not self._alive:
|
|
292
|
+
return
|
|
293
|
+
|
|
294
|
+
self._alive = False
|
|
295
|
+
def _shutdown_impl():
|
|
296
|
+
pg.logging.info(
|
|
297
|
+
'[%s]: Shutting down %s...', self.id, self.__class__.__name__
|
|
298
|
+
)
|
|
299
|
+
with pg.timeit('env.shutdown') as t:
|
|
300
|
+
self._shutdown()
|
|
301
|
+
pg.logging.info(
|
|
302
|
+
'[%s]: %s shutdown in %.2f seconds.',
|
|
303
|
+
self.id, self.__class__.__name__, t.elapse
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
interface.call_with_event(
|
|
307
|
+
_shutdown_impl, self.on_shutdown,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
#
|
|
311
|
+
# Environment operations.
|
|
312
|
+
#
|
|
313
|
+
|
|
314
|
+
def acquire(self) -> interface.Sandbox:
|
|
315
|
+
"""Acquires a sandbox from the environment.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
The acquired sandbox.
|
|
319
|
+
|
|
320
|
+
Raises:
|
|
321
|
+
interface.EnvironmentOutageError: If the environment is offline and the
|
|
322
|
+
grace period has passed.
|
|
323
|
+
interface.EnvironmentOverloadError: If the max pool size is reached and
|
|
324
|
+
the grace period has passed.
|
|
325
|
+
"""
|
|
326
|
+
|
|
327
|
+
if not self._alive:
|
|
328
|
+
raise interface.EnvironmentOutageError(
|
|
329
|
+
f'Environment {self.id} is not alive.',
|
|
330
|
+
environment=self,
|
|
331
|
+
offline_duration=self.offline_duration,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
if not self.enable_pooling:
|
|
335
|
+
return self._bring_up_sandbox_with_retry(
|
|
336
|
+
sandbox_id=str(self._increment_sandbox_id()),
|
|
337
|
+
set_pending=True,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
allocation_start_time = time.time()
|
|
341
|
+
while True:
|
|
342
|
+
try:
|
|
343
|
+
# We only append or replace items in the sandbox pool, therefore
|
|
344
|
+
# there is no need to lock the pool.
|
|
345
|
+
return self.load_balancer.acquire(self._sandbox_pool)
|
|
346
|
+
except IndexError:
|
|
347
|
+
if len(self._sandbox_pool) == self.max_pool_size:
|
|
348
|
+
if time.time() - allocation_start_time > self.outage_grace_period:
|
|
349
|
+
raise interface.EnvironmentOverloadError( # pylint: disable=raise-missing-from
|
|
350
|
+
environment=self
|
|
351
|
+
)
|
|
352
|
+
time.sleep(1)
|
|
353
|
+
else:
|
|
354
|
+
try:
|
|
355
|
+
sandbox = self._create_sandbox(
|
|
356
|
+
sandbox_id=str(self._increment_sandbox_id()),
|
|
357
|
+
reusable=self.enable_pooling,
|
|
358
|
+
)
|
|
359
|
+
sandbox.start()
|
|
360
|
+
sandbox.set_pending()
|
|
361
|
+
|
|
362
|
+
# Append is atomic and does not require locking.
|
|
363
|
+
self._sandbox_pool.append(sandbox)
|
|
364
|
+
self._offline_start_time = None
|
|
365
|
+
return sandbox
|
|
366
|
+
except (
|
|
367
|
+
interface.EnvironmentError, interface.SandboxStateError
|
|
368
|
+
) as ex:
|
|
369
|
+
self._report_outage_or_wait(ex)
|
|
370
|
+
|
|
371
|
+
def _bring_up_sandbox_with_retry(
|
|
372
|
+
self,
|
|
373
|
+
sandbox_id: str,
|
|
374
|
+
set_pending: bool = False,
|
|
375
|
+
shutdown_env_upon_outage: bool = True,
|
|
376
|
+
) -> interface.Sandbox:
|
|
377
|
+
"""Brings up a new sandbox with retry until grace period is passed.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
sandbox_id: The ID of the sandbox to bring up.
|
|
381
|
+
set_pending: Whether to mark the sandbox as pending.
|
|
382
|
+
shutdown_env_upon_outage: Whether to shutdown the environment when the
|
|
383
|
+
outage grace period is passed.
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
A new sandbox ready to use.
|
|
387
|
+
|
|
388
|
+
Raises:
|
|
389
|
+
interface.EnvironmentOutageError: If the environment is offline and the
|
|
390
|
+
grace period has passed.
|
|
391
|
+
"""
|
|
392
|
+
while True:
|
|
393
|
+
try:
|
|
394
|
+
sandbox = self._create_sandbox(
|
|
395
|
+
sandbox_id=sandbox_id,
|
|
396
|
+
reusable=self.enable_pooling
|
|
397
|
+
)
|
|
398
|
+
sandbox.start()
|
|
399
|
+
if set_pending:
|
|
400
|
+
sandbox.set_pending()
|
|
401
|
+
return sandbox
|
|
402
|
+
except (interface.EnvironmentError, interface.SandboxStateError) as e:
|
|
403
|
+
self._report_outage_or_wait(e, shutdown_env_upon_outage)
|
|
404
|
+
|
|
405
|
+
def _increment_sandbox_id(self) -> int:
|
|
406
|
+
"""Returns the next pooled sandbox ID."""
|
|
407
|
+
x = self._next_sandbox_id
|
|
408
|
+
self._next_sandbox_id += 1
|
|
409
|
+
return x
|
|
410
|
+
|
|
411
|
+
def _report_outage_or_wait(
|
|
412
|
+
self,
|
|
413
|
+
error: interface.SandboxStateError,
|
|
414
|
+
shutdown_env_upon_outage: bool = True
|
|
415
|
+
):
|
|
416
|
+
"""Raises error if the grace period has passed or wait for retry."""
|
|
417
|
+
if self._offline_start_time is None:
|
|
418
|
+
self._offline_start_time = time.time()
|
|
419
|
+
if self.offline_duration > self.outage_grace_period:
|
|
420
|
+
if shutdown_env_upon_outage:
|
|
421
|
+
self.shutdown()
|
|
422
|
+
raise interface.EnvironmentOutageError(
|
|
423
|
+
environment=self,
|
|
424
|
+
offline_duration=self.offline_duration,
|
|
425
|
+
) from error
|
|
426
|
+
time.sleep(self.outage_retry_interval)
|
|
427
|
+
|
|
428
|
+
#
|
|
429
|
+
# Environment maintenance loop.
|
|
430
|
+
#
|
|
431
|
+
|
|
432
|
+
def _maintenance_loop(self) -> None:
|
|
433
|
+
"""Maintains the server pool."""
|
|
434
|
+
pg.logging.info(
|
|
435
|
+
'[%s]: %s maintenance thread started.', self.id, self.__class__.__name__
|
|
436
|
+
)
|
|
437
|
+
stats_report_time = time.time()
|
|
438
|
+
while self._alive:
|
|
439
|
+
if time.time() - stats_report_time > self.stats_report_interval:
|
|
440
|
+
pg.logging.info(
|
|
441
|
+
'[%s] %s stats: %s.',
|
|
442
|
+
self.id, self.__class__.__name__, self.stats()
|
|
443
|
+
)
|
|
444
|
+
stats_report_time = time.time()
|
|
445
|
+
|
|
446
|
+
dead_pool_indices = [
|
|
447
|
+
i for i, s in enumerate(self._sandbox_pool) if not s.is_alive
|
|
448
|
+
]
|
|
449
|
+
if dead_pool_indices and not self._replace_dead_sandboxes(
|
|
450
|
+
dead_pool_indices
|
|
451
|
+
):
|
|
452
|
+
self.shutdown()
|
|
453
|
+
self._maintenance_count += 1
|
|
454
|
+
break
|
|
455
|
+
self._maintenance_count += 1
|
|
456
|
+
time.sleep(1)
|
|
457
|
+
|
|
458
|
+
def _replace_dead_sandboxes(self, dead_pool_indices: list[int]) -> bool:
|
|
459
|
+
"""Replaces a dead sandbox with a new one.
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
dead_pool_indices: The indices of the dead sandboxes to replace.
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
Whether all of the dead sandboxes are replaced successfully.
|
|
466
|
+
|
|
467
|
+
Raises:
|
|
468
|
+
interface.EnvironmentOutageError: If the XBox sandboxes cannot be created
|
|
469
|
+
within the wait time specified by `xbox_outage_grace_period`.
|
|
470
|
+
"""
|
|
471
|
+
pg.logging.warning(
|
|
472
|
+
'[%s]: %s maintenance: '
|
|
473
|
+
'Replacing %d dead sandbox(es) with new ones...',
|
|
474
|
+
self.id,
|
|
475
|
+
self.__class__.__name__,
|
|
476
|
+
len(dead_pool_indices),
|
|
477
|
+
)
|
|
478
|
+
def _replace(i: int):
|
|
479
|
+
self._sandbox_pool[i] = self._bring_up_sandbox_with_retry(
|
|
480
|
+
str(i), shutdown_env_upon_outage=False
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
return not any([
|
|
484
|
+
error for _, _, error in lf.concurrent_map(
|
|
485
|
+
_replace, dead_pool_indices,
|
|
486
|
+
max_workers=min(
|
|
487
|
+
self.pool_operation_max_parallelism,
|
|
488
|
+
len(dead_pool_indices)
|
|
489
|
+
),
|
|
490
|
+
)
|
|
491
|
+
])
|
|
@@ -0,0 +1,165 @@
|
|
|
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
|
+
"""Common base class for sandbox-based features.
|
|
15
|
+
|
|
16
|
+
This module provides an base class `BaseFeature` for sandbox-based features,
|
|
17
|
+
which provides event handlers for the feature lifecycle events, which can be
|
|
18
|
+
overridden by subclasses to provide custom behaviors. Please note that this base
|
|
19
|
+
class is intended to provide a convenient way to implement features, and not
|
|
20
|
+
all feature implementations need to subclass it. Also `BaseFeature` is not
|
|
21
|
+
coupled with `BaseEnvironment` and `BaseSandbox`, and is expected to work with
|
|
22
|
+
the `Environment` and `Sandbox` interfaces directly.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import functools
|
|
26
|
+
from typing import Annotated
|
|
27
|
+
|
|
28
|
+
from langfun.env import interface
|
|
29
|
+
import pyglove as pg
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BaseFeature(interface.Feature):
|
|
33
|
+
"""Common base class for sandbox-based features."""
|
|
34
|
+
|
|
35
|
+
housekeep_interval: Annotated[
|
|
36
|
+
float | None,
|
|
37
|
+
'Interval in seconds for feature housekeeping.'
|
|
38
|
+
] = None
|
|
39
|
+
|
|
40
|
+
#
|
|
41
|
+
# Subclasses can override:
|
|
42
|
+
#
|
|
43
|
+
|
|
44
|
+
def _setup(self) -> None:
|
|
45
|
+
"""Subclasses can override this for custom setup.
|
|
46
|
+
|
|
47
|
+
NOTE: always call super()._setup() at the beginning of the implementation.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def _teardown(self) -> None:
|
|
51
|
+
"""Subclasses can override this for custom teardown.
|
|
52
|
+
|
|
53
|
+
NOTE: always call super()._teardown() at the end of the implementation.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def _setup_session(self, session_id: str) -> None:
|
|
57
|
+
"""Subclasses can override this for custom setup session.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
session_id: The session ID.
|
|
61
|
+
|
|
62
|
+
NOTE: always call super()._setup_session() at the beginning of the
|
|
63
|
+
implementation.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def _teardown_session(self, session_id: str) -> None:
|
|
67
|
+
"""Subclasses can override this for custom teardown session.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
session_id: The session ID.
|
|
71
|
+
|
|
72
|
+
NOTE: always call super()._teardown_session() at the end of the
|
|
73
|
+
implementation.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def _housekeep(self) -> None:
|
|
77
|
+
"""Performs housekeeping for the feature.
|
|
78
|
+
|
|
79
|
+
NOTE: always call super()._housekeep() at the beginning of the
|
|
80
|
+
implementation.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
#
|
|
84
|
+
# Init and properties
|
|
85
|
+
#
|
|
86
|
+
|
|
87
|
+
def _on_bound(self) -> None:
|
|
88
|
+
"""Called when the feature is bound."""
|
|
89
|
+
super()._on_bound()
|
|
90
|
+
self._sandbox = None
|
|
91
|
+
|
|
92
|
+
@functools.cached_property
|
|
93
|
+
def name(self) -> str:
|
|
94
|
+
"""Returns the name of the feature."""
|
|
95
|
+
assert isinstance(self.sym_parent, dict), 'Feature is not put into a dict.'
|
|
96
|
+
return self.sym_path.key
|
|
97
|
+
|
|
98
|
+
def _on_parent_change(
|
|
99
|
+
self,
|
|
100
|
+
old_parent: pg.Symbolic | None,
|
|
101
|
+
new_parent: pg.Symbolic | None
|
|
102
|
+
) -> None:
|
|
103
|
+
"""Called when the feature is bound."""
|
|
104
|
+
super()._on_parent_change(old_parent, new_parent)
|
|
105
|
+
self.__dict__.pop('name', None)
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def sandbox(self) -> interface.Sandbox:
|
|
109
|
+
"""Returns the sandbox that the feature is running in."""
|
|
110
|
+
assert self._sandbox is not None, 'Feature has not been set up yet.'
|
|
111
|
+
return self._sandbox
|
|
112
|
+
|
|
113
|
+
#
|
|
114
|
+
# Setup and teardown of the feature.
|
|
115
|
+
#
|
|
116
|
+
|
|
117
|
+
def setup(self, sandbox: interface.Sandbox) -> None:
|
|
118
|
+
"""Sets up the feature."""
|
|
119
|
+
self._sandbox = sandbox
|
|
120
|
+
interface.call_with_event(
|
|
121
|
+
action=self._setup,
|
|
122
|
+
event_handler=self.on_setup,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def teardown(self) -> None:
|
|
126
|
+
"""Tears down the feature."""
|
|
127
|
+
# If a sandbox is down during setting up, feature.shutdown might be called
|
|
128
|
+
# before the feature is setup. In this case, we don't need to teardown the
|
|
129
|
+
# feature.
|
|
130
|
+
if self._sandbox is None:
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
interface.call_with_event(
|
|
134
|
+
action=self._teardown,
|
|
135
|
+
event_handler=self.on_teardown,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def setup_session(self, session_id: str) -> None:
|
|
139
|
+
"""Sets up the feature for a user session."""
|
|
140
|
+
interface.call_with_event(
|
|
141
|
+
action=self._setup_session,
|
|
142
|
+
event_handler=self.on_session_setup,
|
|
143
|
+
action_kwargs={'session_id': session_id},
|
|
144
|
+
event_handler_kwargs={'session_id': session_id},
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def teardown_session(self, session_id: str) -> None:
|
|
148
|
+
"""Teardowns the feature for a user session."""
|
|
149
|
+
interface.call_with_event(
|
|
150
|
+
action=self._teardown_session,
|
|
151
|
+
event_handler=self.on_session_teardown,
|
|
152
|
+
action_kwargs={'session_id': session_id},
|
|
153
|
+
event_handler_kwargs={'session_id': session_id},
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
#
|
|
157
|
+
# Housekeeping.
|
|
158
|
+
#
|
|
159
|
+
|
|
160
|
+
def housekeep(self) -> None:
|
|
161
|
+
"""Performs housekeeping for the feature."""
|
|
162
|
+
interface.call_with_event(
|
|
163
|
+
action=self._housekeep,
|
|
164
|
+
event_handler=self.on_housekeep,
|
|
165
|
+
)
|