langfun 0.1.2.dev202509150805__tar.gz → 0.1.2.dev202509170804__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.

Files changed (186) hide show
  1. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/PKG-INFO +1 -1
  2. langfun-0.1.2.dev202509170804/langfun/env/__init__.py +37 -0
  3. langfun-0.1.2.dev202509170804/langfun/env/base_environment.py +491 -0
  4. langfun-0.1.2.dev202509170804/langfun/env/base_feature.py +158 -0
  5. langfun-0.1.2.dev202509170804/langfun/env/base_sandbox.py +444 -0
  6. langfun-0.1.2.dev202509170804/langfun/env/base_test.py +795 -0
  7. langfun-0.1.2.dev202509170804/langfun/env/interface.py +843 -0
  8. langfun-0.1.2.dev202509170804/langfun/env/interface_test.py +43 -0
  9. langfun-0.1.2.dev202509170804/langfun/env/load_balancers.py +59 -0
  10. langfun-0.1.2.dev202509170804/langfun/env/load_balancers_test.py +157 -0
  11. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun.egg-info/PKG-INFO +1 -1
  12. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun.egg-info/SOURCES.txt +10 -1
  13. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/LICENSE +0 -0
  14. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/README.md +0 -0
  15. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/__init__.py +0 -0
  16. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/assistant/capabilities/gui/__init__.py +0 -0
  17. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/assistant/capabilities/gui/bounding_box_parser.py +0 -0
  18. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/assistant/capabilities/gui/bounding_box_parser_test.py +0 -0
  19. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/assistant/capabilities/gui/drawing.py +0 -0
  20. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/assistant/capabilities/gui/drawing_test.py +0 -0
  21. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/assistant/capabilities/gui/location.py +0 -0
  22. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/assistant/capabilities/gui/location_test.py +0 -0
  23. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/__init__.py +0 -0
  24. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/agentic/__init__.py +0 -0
  25. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/agentic/action.py +0 -0
  26. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/agentic/action_eval.py +0 -0
  27. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/agentic/action_eval_test.py +0 -0
  28. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/agentic/action_test.py +0 -0
  29. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/async_support.py +0 -0
  30. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/async_support_test.py +0 -0
  31. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/coding/__init__.py +0 -0
  32. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/coding/python/__init__.py +0 -0
  33. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/coding/python/correction.py +0 -0
  34. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/coding/python/correction_test.py +0 -0
  35. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/coding/python/execution.py +0 -0
  36. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/coding/python/execution_test.py +0 -0
  37. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/coding/python/generation.py +0 -0
  38. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/coding/python/generation_test.py +0 -0
  39. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/coding/python/parsing.py +0 -0
  40. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/coding/python/parsing_test.py +0 -0
  41. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/coding/python/sandboxing.py +0 -0
  42. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/coding/python/sandboxing_test.py +0 -0
  43. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/component.py +0 -0
  44. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/component_test.py +0 -0
  45. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/concurrent.py +0 -0
  46. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/concurrent_test.py +0 -0
  47. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/console.py +0 -0
  48. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/console_test.py +0 -0
  49. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/data/__init__.py +0 -0
  50. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/data/conversion/__init__.py +0 -0
  51. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/data/conversion/anthropic.py +0 -0
  52. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/data/conversion/anthropic_test.py +0 -0
  53. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/data/conversion/gemini.py +0 -0
  54. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/data/conversion/gemini_test.py +0 -0
  55. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/data/conversion/openai.py +0 -0
  56. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/data/conversion/openai_test.py +0 -0
  57. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/__init__.py +0 -0
  58. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/base.py +0 -0
  59. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/base_test.py +0 -0
  60. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/matching.py +0 -0
  61. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/matching_test.py +0 -0
  62. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/patching.py +0 -0
  63. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/patching_test.py +0 -0
  64. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/scoring.py +0 -0
  65. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/scoring_test.py +0 -0
  66. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/__init__.py +0 -0
  67. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/checkpointing.py +0 -0
  68. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/checkpointing_test.py +0 -0
  69. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/eval_test_helper.py +0 -0
  70. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/evaluation.py +0 -0
  71. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/evaluation_test.py +0 -0
  72. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/example.py +0 -0
  73. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/example_test.py +0 -0
  74. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/experiment.py +0 -0
  75. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/experiment_test.py +0 -0
  76. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/metric_values.py +0 -0
  77. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/metric_values_test.py +0 -0
  78. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/metrics.py +0 -0
  79. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/metrics_test.py +0 -0
  80. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/progress.py +0 -0
  81. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/progress_test.py +0 -0
  82. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/progress_tracking.py +0 -0
  83. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/progress_tracking_test.py +0 -0
  84. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/reporting.py +0 -0
  85. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/reporting_test.py +0 -0
  86. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/runners.py +0 -0
  87. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/eval/v2/runners_test.py +0 -0
  88. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/langfunc.py +0 -0
  89. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/langfunc_test.py +0 -0
  90. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/language_model.py +0 -0
  91. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/language_model_test.py +0 -0
  92. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/__init__.py +0 -0
  93. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/anthropic.py +0 -0
  94. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/anthropic_test.py +0 -0
  95. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/azure_openai.py +0 -0
  96. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/azure_openai_test.py +0 -0
  97. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/cache/__init__.py +0 -0
  98. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/cache/base.py +0 -0
  99. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/cache/in_memory.py +0 -0
  100. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/cache/in_memory_test.py +0 -0
  101. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/compositional.py +0 -0
  102. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/compositional_test.py +0 -0
  103. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/deepseek.py +0 -0
  104. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/deepseek_test.py +0 -0
  105. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/fake.py +0 -0
  106. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/fake_test.py +0 -0
  107. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/gemini.py +0 -0
  108. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/gemini_test.py +0 -0
  109. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/google_genai.py +0 -0
  110. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/google_genai_test.py +0 -0
  111. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/groq.py +0 -0
  112. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/groq_test.py +0 -0
  113. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/llama_cpp.py +0 -0
  114. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/llama_cpp_test.py +0 -0
  115. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/openai.py +0 -0
  116. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/openai_compatible.py +0 -0
  117. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/openai_compatible_test.py +0 -0
  118. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/openai_test.py +0 -0
  119. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/rest.py +0 -0
  120. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/rest_test.py +0 -0
  121. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/vertexai.py +0 -0
  122. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/llms/vertexai_test.py +0 -0
  123. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/logging.py +0 -0
  124. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/logging_test.py +0 -0
  125. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/memories/__init__.py +0 -0
  126. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/memories/conversation_history.py +0 -0
  127. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/memories/conversation_history_test.py +0 -0
  128. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/memory.py +0 -0
  129. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/message.py +0 -0
  130. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/message_test.py +0 -0
  131. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/modalities/__init__.py +0 -0
  132. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/modalities/audio.py +0 -0
  133. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/modalities/audio_test.py +0 -0
  134. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/modalities/image.py +0 -0
  135. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/modalities/image_test.py +0 -0
  136. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/modalities/mime.py +0 -0
  137. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/modalities/mime_test.py +0 -0
  138. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/modalities/pdf.py +0 -0
  139. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/modalities/pdf_test.py +0 -0
  140. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/modalities/video.py +0 -0
  141. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/modalities/video_test.py +0 -0
  142. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/modality.py +0 -0
  143. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/modality_test.py +0 -0
  144. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/natural_language.py +0 -0
  145. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/natural_language_test.py +0 -0
  146. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/sampling.py +0 -0
  147. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/sampling_test.py +0 -0
  148. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/structured/__init__.py +0 -0
  149. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/structured/completion.py +0 -0
  150. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/structured/completion_test.py +0 -0
  151. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/structured/description.py +0 -0
  152. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/structured/description_test.py +0 -0
  153. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/structured/function_generation.py +0 -0
  154. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/structured/function_generation_test.py +0 -0
  155. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/structured/mapping.py +0 -0
  156. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/structured/mapping_test.py +0 -0
  157. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/structured/parsing.py +0 -0
  158. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/structured/parsing_test.py +0 -0
  159. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/structured/querying.py +0 -0
  160. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/structured/querying_test.py +0 -0
  161. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/structured/schema.py +0 -0
  162. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/structured/schema_generation.py +0 -0
  163. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/structured/schema_generation_test.py +0 -0
  164. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/structured/schema_test.py +0 -0
  165. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/structured/scoring.py +0 -0
  166. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/structured/scoring_test.py +0 -0
  167. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/structured/tokenization.py +0 -0
  168. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/structured/tokenization_test.py +0 -0
  169. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/subscription.py +0 -0
  170. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/subscription_test.py +0 -0
  171. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/template.py +0 -0
  172. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/template_test.py +0 -0
  173. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/templates/__init__.py +0 -0
  174. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/templates/completion.py +0 -0
  175. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/templates/completion_test.py +0 -0
  176. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/templates/conversation.py +0 -0
  177. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/templates/conversation_test.py +0 -0
  178. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/templates/demonstration.py +0 -0
  179. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/templates/demonstration_test.py +0 -0
  180. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/templates/selfplay.py +0 -0
  181. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun/core/templates/selfplay_test.py +0 -0
  182. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun.egg-info/dependency_links.txt +0 -0
  183. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun.egg-info/requires.txt +0 -0
  184. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/langfun.egg-info/top_level.txt +0 -0
  185. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/setup.cfg +0 -0
  186. {langfun-0.1.2.dev202509150805 → langfun-0.1.2.dev202509170804}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langfun
3
- Version: 0.1.2.dev202509150805
3
+ Version: 0.1.2.dev202509170804
4
4
  Summary: Langfun: Language as Functions.
5
5
  Home-page: https://github.com/google/langfun
6
6
  Author: Langfun Authors
@@ -0,0 +1,37 @@
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
@@ -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,158 @@
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 | None:
109
+ """Returns the sandbox that the feature is running in."""
110
+ return self._sandbox
111
+
112
+ #
113
+ # Setup and teardown of the feature.
114
+ #
115
+
116
+ def setup(self, sandbox: interface.Sandbox) -> None:
117
+ """Sets up the feature."""
118
+ self._sandbox = sandbox
119
+ interface.call_with_event(
120
+ action=self._setup,
121
+ event_handler=self.on_setup,
122
+ )
123
+
124
+ def teardown(self) -> None:
125
+ """Tears down the feature."""
126
+ interface.call_with_event(
127
+ action=self._teardown,
128
+ event_handler=self.on_teardown,
129
+ )
130
+
131
+ def setup_session(self, session_id: str) -> None:
132
+ """Sets up the feature for a user session."""
133
+ interface.call_with_event(
134
+ action=self._setup_session,
135
+ event_handler=self.on_session_setup,
136
+ action_kwargs={'session_id': session_id},
137
+ event_handler_kwargs={'session_id': session_id},
138
+ )
139
+
140
+ def teardown_session(self, session_id: str) -> None:
141
+ """Teardowns the feature for a user session."""
142
+ interface.call_with_event(
143
+ action=self._teardown_session,
144
+ event_handler=self.on_session_teardown,
145
+ action_kwargs={'session_id': session_id},
146
+ event_handler_kwargs={'session_id': session_id},
147
+ )
148
+
149
+ #
150
+ # Housekeeping.
151
+ #
152
+
153
+ def housekeep(self) -> None:
154
+ """Performs housekeeping for the feature."""
155
+ interface.call_with_event(
156
+ action=self._housekeep,
157
+ event_handler=self.on_housekeep,
158
+ )