langfun 0.1.2.dev202511090804__tar.gz → 0.1.2.dev202512180805__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 (225) hide show
  1. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/PKG-INFO +1 -1
  2. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/__init__.py +1 -0
  3. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/agentic/__init__.py +4 -1
  4. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/agentic/action.py +342 -20
  5. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/agentic/action_test.py +124 -21
  6. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/data/conversion/gemini.py +2 -0
  7. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/data/conversion/gemini_test.py +36 -0
  8. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/base.py +1 -1
  9. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/base_test.py +5 -5
  10. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/v2/__init__.py +3 -0
  11. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/v2/checkpointing.py +120 -44
  12. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/v2/checkpointing_test.py +9 -2
  13. langfun-0.1.2.dev202512180805/langfun/core/eval/v2/config_saver.py +37 -0
  14. langfun-0.1.2.dev202512180805/langfun/core/eval/v2/config_saver_test.py +36 -0
  15. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/v2/eval_test_helper.py +103 -2
  16. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/v2/evaluation.py +42 -5
  17. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/v2/evaluation_test.py +9 -3
  18. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/v2/example.py +29 -30
  19. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/v2/example_test.py +16 -8
  20. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/v2/experiment.py +44 -12
  21. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/v2/experiment_test.py +19 -0
  22. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/v2/metric_values.py +8 -0
  23. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/v2/metric_values_test.py +32 -0
  24. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/v2/metrics.py +124 -40
  25. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/v2/metrics_test.py +39 -18
  26. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/v2/progress.py +22 -0
  27. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/v2/progress_test.py +27 -0
  28. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/v2/progress_tracking.py +13 -5
  29. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/v2/progress_tracking_test.py +3 -1
  30. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/v2/reporting.py +76 -73
  31. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/v2/reporting_test.py +24 -6
  32. langfun-0.1.2.dev202512180805/langfun/core/eval/v2/runners/__init__.py +30 -0
  33. langfun-0.1.2.dev202511090804/langfun/core/eval/v2/runners.py → langfun-0.1.2.dev202512180805/langfun/core/eval/v2/runners/base.py +65 -192
  34. langfun-0.1.2.dev202512180805/langfun/core/eval/v2/runners/beam.py +354 -0
  35. langfun-0.1.2.dev202512180805/langfun/core/eval/v2/runners/beam_test.py +153 -0
  36. langfun-0.1.2.dev202512180805/langfun/core/eval/v2/runners/ckpt_monitor.py +350 -0
  37. langfun-0.1.2.dev202512180805/langfun/core/eval/v2/runners/ckpt_monitor_test.py +213 -0
  38. langfun-0.1.2.dev202512180805/langfun/core/eval/v2/runners/debug.py +40 -0
  39. langfun-0.1.2.dev202512180805/langfun/core/eval/v2/runners/debug_test.py +76 -0
  40. langfun-0.1.2.dev202512180805/langfun/core/eval/v2/runners/parallel.py +243 -0
  41. langfun-0.1.2.dev202512180805/langfun/core/eval/v2/runners/parallel_test.py +182 -0
  42. langfun-0.1.2.dev202512180805/langfun/core/eval/v2/runners/sequential.py +47 -0
  43. langfun-0.1.2.dev202512180805/langfun/core/eval/v2/runners/sequential_test.py +169 -0
  44. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/langfunc_test.py +3 -3
  45. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/language_model.py +38 -5
  46. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/language_model_test.py +45 -0
  47. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/__init__.py +9 -0
  48. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/anthropic.py +59 -0
  49. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/gemini.py +102 -8
  50. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/gemini_test.py +110 -0
  51. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/google_genai.py +29 -0
  52. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/openai.py +27 -0
  53. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/vertexai.py +40 -0
  54. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/mcp/tool.py +1 -3
  55. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/modalities/image.py +54 -2
  56. langfun-0.1.2.dev202512180805/langfun/core/modalities/image_test.py +224 -0
  57. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/modalities/mime.py +16 -1
  58. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/modalities/mime_test.py +59 -0
  59. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/structured/__init__.py +2 -24
  60. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/structured/mapping.py +7 -9
  61. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/structured/parsing.py +5 -5
  62. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/structured/querying.py +1 -1
  63. langfun-0.1.2.dev202512180805/langfun/core/structured/schema/__init__.py +49 -0
  64. langfun-0.1.2.dev202511090804/langfun/core/structured/schema.py → langfun-0.1.2.dev202512180805/langfun/core/structured/schema/base.py +112 -495
  65. langfun-0.1.2.dev202512180805/langfun/core/structured/schema/base_test.py +531 -0
  66. langfun-0.1.2.dev202512180805/langfun/core/structured/schema/json.py +174 -0
  67. langfun-0.1.2.dev202512180805/langfun/core/structured/schema/json_test.py +121 -0
  68. langfun-0.1.2.dev202512180805/langfun/core/structured/schema/python.py +316 -0
  69. langfun-0.1.2.dev202512180805/langfun/core/structured/schema/python_test.py +410 -0
  70. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/structured/scoring.py +2 -2
  71. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/structured/tokenization.py +2 -2
  72. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/env/interface_test.py +2 -0
  73. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/env/load_balancers_test.py +2 -0
  74. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/env/test_utils.py +10 -0
  75. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun.egg-info/PKG-INFO +1 -1
  76. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun.egg-info/SOURCES.txt +21 -4
  77. langfun-0.1.2.dev202511090804/langfun/core/eval/v2/runners_test.py +0 -346
  78. langfun-0.1.2.dev202511090804/langfun/core/modalities/image_test.py +0 -108
  79. langfun-0.1.2.dev202511090804/langfun/core/structured/schema_test.py +0 -982
  80. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/LICENSE +0 -0
  81. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/README.md +0 -0
  82. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/__init__.py +0 -0
  83. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/assistant/capabilities/gui/__init__.py +0 -0
  84. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/assistant/capabilities/gui/bounding_box_parser.py +0 -0
  85. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/assistant/capabilities/gui/bounding_box_parser_test.py +0 -0
  86. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/assistant/capabilities/gui/drawing.py +0 -0
  87. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/assistant/capabilities/gui/drawing_test.py +0 -0
  88. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/assistant/capabilities/gui/location.py +0 -0
  89. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/assistant/capabilities/gui/location_test.py +0 -0
  90. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/agentic/action_eval.py +0 -0
  91. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/agentic/action_eval_test.py +0 -0
  92. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/async_support.py +0 -0
  93. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/async_support_test.py +0 -0
  94. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/coding/__init__.py +0 -0
  95. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/coding/python/__init__.py +0 -0
  96. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/coding/python/correction.py +0 -0
  97. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/coding/python/correction_test.py +0 -0
  98. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/coding/python/execution.py +0 -0
  99. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/coding/python/execution_test.py +0 -0
  100. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/coding/python/generation.py +0 -0
  101. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/coding/python/generation_test.py +0 -0
  102. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/coding/python/parsing.py +0 -0
  103. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/coding/python/parsing_test.py +0 -0
  104. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/coding/python/sandboxing.py +0 -0
  105. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/coding/python/sandboxing_test.py +0 -0
  106. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/component.py +0 -0
  107. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/component_test.py +0 -0
  108. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/concurrent.py +0 -0
  109. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/concurrent_test.py +0 -0
  110. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/console.py +0 -0
  111. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/console_test.py +0 -0
  112. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/data/__init__.py +0 -0
  113. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/data/conversion/__init__.py +0 -0
  114. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/data/conversion/anthropic.py +0 -0
  115. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/data/conversion/anthropic_test.py +0 -0
  116. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/data/conversion/openai.py +0 -0
  117. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/data/conversion/openai_test.py +0 -0
  118. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/__init__.py +0 -0
  119. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/matching.py +0 -0
  120. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/matching_test.py +0 -0
  121. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/patching.py +0 -0
  122. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/patching_test.py +0 -0
  123. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/scoring.py +0 -0
  124. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/eval/scoring_test.py +0 -0
  125. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/langfunc.py +0 -0
  126. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/anthropic_test.py +0 -0
  127. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/azure_openai.py +0 -0
  128. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/azure_openai_test.py +0 -0
  129. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/cache/__init__.py +0 -0
  130. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/cache/base.py +0 -0
  131. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/cache/in_memory.py +0 -0
  132. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/cache/in_memory_test.py +0 -0
  133. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/compositional.py +0 -0
  134. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/compositional_test.py +0 -0
  135. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/deepseek.py +0 -0
  136. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/deepseek_test.py +0 -0
  137. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/fake.py +0 -0
  138. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/fake_test.py +0 -0
  139. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/google_genai_test.py +0 -0
  140. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/groq.py +0 -0
  141. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/groq_test.py +0 -0
  142. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/llama_cpp.py +0 -0
  143. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/llama_cpp_test.py +0 -0
  144. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/openai_compatible.py +0 -0
  145. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/openai_compatible_test.py +0 -0
  146. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/openai_test.py +0 -0
  147. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/rest.py +0 -0
  148. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/rest_test.py +0 -0
  149. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/llms/vertexai_test.py +0 -0
  150. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/logging.py +0 -0
  151. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/logging_test.py +0 -0
  152. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/mcp/__init__.py +0 -0
  153. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/mcp/client.py +0 -0
  154. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/mcp/client_test.py +0 -0
  155. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/mcp/session.py +0 -0
  156. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/mcp/session_test.py +0 -0
  157. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/mcp/testing/simple_mcp_client.py +0 -0
  158. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/mcp/testing/simple_mcp_server.py +0 -0
  159. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/mcp/tool_test.py +0 -0
  160. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/memories/__init__.py +0 -0
  161. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/memories/conversation_history.py +0 -0
  162. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/memories/conversation_history_test.py +0 -0
  163. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/memory.py +0 -0
  164. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/message.py +0 -0
  165. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/message_test.py +0 -0
  166. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/modalities/__init__.py +0 -0
  167. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/modalities/audio.py +0 -0
  168. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/modalities/audio_test.py +0 -0
  169. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/modalities/pdf.py +0 -0
  170. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/modalities/pdf_test.py +0 -0
  171. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/modalities/video.py +0 -0
  172. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/modalities/video_test.py +0 -0
  173. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/modality.py +0 -0
  174. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/modality_test.py +0 -0
  175. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/natural_language.py +0 -0
  176. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/natural_language_test.py +0 -0
  177. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/sampling.py +0 -0
  178. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/sampling_test.py +0 -0
  179. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/structured/completion.py +0 -0
  180. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/structured/completion_test.py +0 -0
  181. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/structured/description.py +0 -0
  182. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/structured/description_test.py +0 -0
  183. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/structured/function_generation.py +0 -0
  184. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/structured/function_generation_test.py +0 -0
  185. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/structured/mapping_test.py +0 -0
  186. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/structured/parsing_test.py +0 -0
  187. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/structured/querying_test.py +0 -0
  188. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/structured/schema_generation.py +0 -0
  189. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/structured/schema_generation_test.py +0 -0
  190. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/structured/scoring_test.py +0 -0
  191. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/structured/tokenization_test.py +0 -0
  192. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/subscription.py +0 -0
  193. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/subscription_test.py +0 -0
  194. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/template.py +0 -0
  195. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/template_test.py +0 -0
  196. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/templates/__init__.py +0 -0
  197. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/templates/completion.py +0 -0
  198. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/templates/completion_test.py +0 -0
  199. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/templates/conversation.py +0 -0
  200. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/templates/conversation_test.py +0 -0
  201. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/templates/demonstration.py +0 -0
  202. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/templates/demonstration_test.py +0 -0
  203. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/templates/selfplay.py +0 -0
  204. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/core/templates/selfplay_test.py +0 -0
  205. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/env/__init__.py +0 -0
  206. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/env/base_environment.py +0 -0
  207. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/env/base_environment_test.py +0 -0
  208. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/env/base_feature.py +0 -0
  209. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/env/base_feature_test.py +0 -0
  210. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/env/base_sandbox.py +0 -0
  211. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/env/base_sandbox_test.py +0 -0
  212. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/env/event_handlers/__init__.py +0 -0
  213. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/env/event_handlers/chain.py +0 -0
  214. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/env/event_handlers/chain_test.py +0 -0
  215. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/env/event_handlers/event_logger.py +0 -0
  216. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/env/event_handlers/event_logger_test.py +0 -0
  217. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/env/event_handlers/metric_writer.py +0 -0
  218. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/env/event_handlers/metric_writer_test.py +0 -0
  219. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/env/interface.py +0 -0
  220. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun/env/load_balancers.py +0 -0
  221. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun.egg-info/dependency_links.txt +0 -0
  222. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun.egg-info/requires.txt +0 -0
  223. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/langfun.egg-info/top_level.txt +0 -0
  224. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/setup.cfg +0 -0
  225. {langfun-0.1.2.dev202511090804 → langfun-0.1.2.dev202512180805}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langfun
3
- Version: 0.1.2.dev202511090804
3
+ Version: 0.1.2.dev202512180805
4
4
  Summary: Langfun: Language as Functions.
5
5
  Home-page: https://github.com/google/langfun
6
6
  Author: Langfun Authors
@@ -121,6 +121,7 @@ from langfun.core.language_model import LMDebugMode
121
121
  from langfun.core.language_model import LMError
122
122
  from langfun.core.language_model import LMInputError
123
123
  from langfun.core.language_model import ContextLimitError
124
+ from langfun.core.language_model import EmptyGenerationError
124
125
  from langfun.core.language_model import RetryableLMError
125
126
  from langfun.core.language_model import RateLimitError
126
127
  from langfun.core.language_model import TemporaryLMError
@@ -21,9 +21,12 @@ from langfun.core.agentic.action import ActionError
21
21
  from langfun.core.agentic.action import ActionTimeoutError
22
22
 
23
23
  from langfun.core.agentic.action import Action
24
+
25
+ from langfun.core.agentic.action import ExecutionUnit
24
26
  from langfun.core.agentic.action import ActionInvocation
25
- from langfun.core.agentic.action import ExecutionTrace
26
27
  from langfun.core.agentic.action import ParallelExecutions
28
+
29
+ from langfun.core.agentic.action import ExecutionTrace
27
30
  from langfun.core.agentic.action import Session
28
31
 
29
32
  from langfun.core.agentic.action import SessionEventHandler
@@ -14,9 +14,11 @@
14
14
  """Base classes for agentic actions."""
15
15
 
16
16
  import abc
17
+ import collections
17
18
  import contextlib
18
19
  import dataclasses
19
20
  import functools
21
+ import itertools
20
22
  import threading
21
23
  import time
22
24
  import typing
@@ -286,6 +288,8 @@ class Action(pg.Object):
286
288
 
287
289
  with session.track_action(self, max_execution_time=max_execution_time):
288
290
  try:
291
+ # Early terminate the action if the execution time is exceeded.
292
+ session.check_execution_time()
289
293
  result = self.call(session=session, **kwargs)
290
294
  self._invocation.end(result)
291
295
  except BaseException as e:
@@ -313,12 +317,176 @@ class Action(pg.Object):
313
317
  """
314
318
 
315
319
 
320
+ #
321
+ # Execution tracking.
322
+ #
323
+
324
+
325
+ class ExecutionUnit(pg.Object):
326
+ """Base class for execution units in an agentic trajectory.
327
+
328
+ An `ExecutionUnit` represents a logical step or container in the agent's
329
+ execution flow. It serves as the common interface for top-level executable
330
+ items.
331
+
332
+ The concrete subclasses of `ExecutionUnit` are typically:
333
+ * **`ActionInvocation`**: Represents a single, specific action executed by
334
+ the agent.
335
+ * **`ParallelExecutions`**: Represents a container for a group of
336
+ `ExecutionUnits` that were executed concurrently.
337
+
338
+ Users can retrieve the immediate child execution units from an `ExecutionUnit`
339
+ object. To access the leaf nodes of the execution tree, use `all_actions`,
340
+ `all_queries`, and `all_logs` instead.
341
+
342
+ Each unit exposes a **`position`** property to reveal its specific location
343
+ within the execution hierarchy (e.g., '1.2.3').
344
+
345
+ Users could use **`parent_execution_unit`** to get the parent execution unit
346
+ of the current execution unit.
347
+ """
348
+
349
+ @dataclasses.dataclass
350
+ class Position:
351
+ """The position of an executed unit under current session."""
352
+
353
+ parent: Optional['ExecutionUnit.Position'] = None
354
+ index: int = 0
355
+
356
+ def indices(self) -> tuple[int, ...]:
357
+ """Returns the indices from root to current execution unit."""
358
+ # A deque is efficient for adding items to the front.
359
+ path = collections.deque()
360
+ current_pos = self
361
+
362
+ # Traverse up from the current position to the root.
363
+ while current_pos.parent is not None:
364
+ path.appendleft(current_pos.index)
365
+ current_pos = current_pos.parent
366
+
367
+ path.appendleft(current_pos.index)
368
+ return tuple(path)
369
+
370
+ def to_str(
371
+ self,
372
+ *,
373
+ index_base: int = 1,
374
+ separator: str = '.',
375
+ **kwargs
376
+ ) -> str:
377
+ """Returns a string description of the position."""
378
+ # For root action, we return empty string as it's position descriptor.
379
+ if self.parent is None:
380
+ return ''
381
+ parent_descriptor = self.parent.to_str(
382
+ index_base=index_base,
383
+ separator=separator,
384
+ **kwargs
385
+ )
386
+ if not parent_descriptor:
387
+ return f'{self.index + index_base}'
388
+ return parent_descriptor + separator + f'{self.index + index_base}'
389
+
390
+ def __repr__(self) -> str:
391
+ return f'Position({", ".join(str(x) for x in self.indices())})'
392
+
393
+ def __str__(self) -> str:
394
+ return self.to_str()
395
+
396
+ def __eq__(self, other: 'ExecutionUnit.Position') -> bool:
397
+ if isinstance(other, ExecutionUnit.Position):
398
+ return self.indices() == other.indices()
399
+ if isinstance(other, tuple):
400
+ return self.indices() == other
401
+ if isinstance(other, str):
402
+ return str(self) == other
403
+ return False
404
+
405
+ def __ne__(self, other: 'ExecutionUnit.Position') -> bool:
406
+ return not self == other
407
+
408
+ def __hash__(self) -> int:
409
+ return hash(self.indices())
410
+
411
+ def __lt__(self, other: 'ExecutionUnit.Position') -> bool:
412
+ return self.indices() < other.indices()
413
+
414
+ def __gt__(self, other: 'ExecutionUnit.Position') -> bool:
415
+ return self.indices() > other.indices()
416
+
417
+ def _on_parent_change(self, *args, **kwargs):
418
+ super()._on_parent_change(*args, **kwargs)
419
+ self.__dict__.pop('parent_execution_unit', None)
420
+ self.__dict__.pop('position', None)
421
+
422
+ @functools.cached_property
423
+ def parent_execution_unit(self) -> Optional['ExecutionUnit']:
424
+ """Returns the parent execution unit of the current execution unit."""
425
+ parent_trace = self.sym_ancestor(lambda x: isinstance(x, ExecutionTrace))
426
+ assert isinstance(parent_trace, ExecutionTrace), (
427
+ 'Execution unit is not associated with any `ExecutionTrace`: '
428
+ f'{self}'
429
+ )
430
+ return parent_trace.parent_execution_unit
431
+
432
+ @functools.cached_property
433
+ def position(self) -> Position:
434
+ """Returns the execution position of the action."""
435
+ parent_trace = self.sym_ancestor(lambda x: isinstance(x, ExecutionTrace))
436
+ while parent_trace is not None:
437
+ parent_position = parent_trace.position
438
+ if parent_position is not None:
439
+ return ExecutionUnit.Position(
440
+ parent_position, parent_trace.indexof(self, ExecutionUnit)
441
+ )
442
+ parent_trace = parent_trace.sym_ancestor(
443
+ lambda x: isinstance(x, ExecutionTrace)
444
+ )
445
+ return ExecutionUnit.Position(None, 0)
446
+
447
+ @property
448
+ @abc.abstractmethod
449
+ def execution_units(
450
+ self,
451
+ ) -> list['ExecutionUnit']:
452
+ """Returns immediate child execution items."""
453
+
454
+ @property
455
+ @abc.abstractmethod
456
+ def queries(self) -> list[lf_structured.QueryInvocation]:
457
+ """Returns queries issued by the execution item."""
458
+
459
+ @property
460
+ @abc.abstractmethod
461
+ def actions(self) -> list['ActionInvocation']:
462
+ """Returns immediate child action invocations."""
463
+
464
+ @property
465
+ @abc.abstractmethod
466
+ def logs(self) -> list[lf.logging.LogEntry]:
467
+ """Returns immediate logs under current execution item."""
468
+
469
+ @property
470
+ @abc.abstractmethod
471
+ def all_queries(self) -> list[lf_structured.QueryInvocation]:
472
+ """Returns all queries from the subtree."""
473
+
474
+ @property
475
+ @abc.abstractmethod
476
+ def all_actions(self) -> list['ActionInvocation']:
477
+ """Returns all action invocations from the subtree."""
478
+
479
+ @property
480
+ @abc.abstractmethod
481
+ def all_logs(self) -> list[lf.logging.LogEntry]:
482
+ """Returns all logs from the subtree."""
483
+
484
+
316
485
  # Type definition for traced item during execution.
317
486
  TracedItem = Union[
487
+ ExecutionUnit,
318
488
  lf_structured.QueryInvocation,
319
- 'ActionInvocation',
320
489
  'ExecutionTrace',
321
- 'ParallelExecutions',
322
490
  # NOTE(daiyip): Consider remove log entry once we migrate existing agents.
323
491
  lf.logging.LogEntry,
324
492
  ]
@@ -372,8 +540,14 @@ class ExecutionTrace(pg.Object, pg.views.html.HtmlTreeView.Extension):
372
540
  def _on_parent_change(self, *args, **kwargs):
373
541
  super()._on_parent_change(*args, **kwargs)
374
542
  self.__dict__.pop('id', None)
543
+ self.__dict__.pop('parent_execution_unit', None)
544
+ self.__dict__.pop('position', None)
375
545
 
376
- def indexof(self, item: TracedItem, count_item_cls: Type[Any]) -> int:
546
+ def indexof(
547
+ self,
548
+ item: TracedItem,
549
+ count_item_cls: Type[Any] | tuple[Type[Any], ...]
550
+ ) -> int:
377
551
  """Returns the index of the child item of given type."""
378
552
  pos = 0
379
553
  for x in self._iter_children(count_item_cls):
@@ -382,6 +556,52 @@ class ExecutionTrace(pg.Object, pg.views.html.HtmlTreeView.Extension):
382
556
  pos += 1
383
557
  return -1
384
558
 
559
+ @functools.cached_property
560
+ def parent_execution_unit(self) -> ExecutionUnit:
561
+ """Returns the parent execution unit of the current execution trace."""
562
+ parent = self.sym_parent
563
+ if isinstance(parent, ActionInvocation):
564
+ # Current execution trace is the body of an action.
565
+ return parent
566
+ elif isinstance(parent, pg.List):
567
+ container = parent.sym_parent
568
+ if isinstance(container, ParallelExecutions):
569
+ return container
570
+ elif isinstance(container, ExecutionTrace):
571
+ return container.parent_execution_unit
572
+ assert False, (
573
+ 'Execution trace is not associated with any `ActionInvocation` or '
574
+ f'`ParallelExecutions`: {self}'
575
+ )
576
+
577
+ @functools.cached_property
578
+ def position(self) -> ExecutionUnit.Position | None:
579
+ """Returns the execution position of the execution trace.
580
+
581
+ Returns:
582
+ The execution position of the execution trace, or None if the execution
583
+ trace is either not associated with an execution unit or the execution
584
+ trace is a phase under another execution trace.
585
+ """
586
+ parent = self.sym_parent
587
+ if isinstance(parent, ActionInvocation):
588
+ # Current execution trace is the body of an action.
589
+ return parent.position
590
+ elif isinstance(parent, pg.List):
591
+ container = parent.sym_parent
592
+ if isinstance(container, ParallelExecutions):
593
+ return ExecutionUnit.Position(
594
+ container.position, self.sym_path.key
595
+ )
596
+ elif isinstance(container, ExecutionTrace):
597
+ # When execution trace is a phase under another execution trace,
598
+ # we return None as the position.
599
+ return None
600
+ assert False, (
601
+ 'Execution trace is not associated with any `ActionInvocation` or '
602
+ f'`ParallelExecutions`: {self}'
603
+ )
604
+
385
605
  @functools.cached_property
386
606
  def id(self) -> str:
387
607
  parent = self.sym_parent
@@ -453,6 +673,11 @@ class ExecutionTrace(pg.Object, pg.views.html.HtmlTreeView.Extension):
453
673
  """Returns action invocations from the sequence."""
454
674
  return list(self._iter_children(ActionInvocation))
455
675
 
676
+ @property
677
+ def execution_units(self) -> list[ExecutionUnit]:
678
+ """Returns parallel executions from the sequence."""
679
+ return list(self._iter_children(ExecutionUnit))
680
+
456
681
  @property
457
682
  def logs(self) -> list[lf.logging.LogEntry]:
458
683
  """Returns logs from the sequence."""
@@ -473,7 +698,9 @@ class ExecutionTrace(pg.Object, pg.views.html.HtmlTreeView.Extension):
473
698
  """Returns all logs from current trace and its child execution items."""
474
699
  return list(self._iter_subtree(lf.logging.LogEntry))
475
700
 
476
- def _iter_children(self, item_cls: Type[Any]) -> Iterator[TracedItem]:
701
+ def _iter_children(
702
+ self, item_cls: Type[Any] | tuple[Type[Any], ...]
703
+ ) -> Iterator[TracedItem]:
477
704
  for item in self.items:
478
705
  if isinstance(item, item_cls):
479
706
  yield item
@@ -485,7 +712,10 @@ class ExecutionTrace(pg.Object, pg.views.html.HtmlTreeView.Extension):
485
712
  for x in branch._iter_children(item_cls): # pylint: disable=protected-access
486
713
  yield x
487
714
 
488
- def _iter_subtree(self, item_cls: Type[Any]) -> Iterator[TracedItem]:
715
+ def _iter_subtree(
716
+ self,
717
+ item_cls: Type[Any] | tuple[Type[Any], ...]
718
+ ) -> Iterator[TracedItem]:
489
719
  for item in self.items:
490
720
  if isinstance(item, item_cls):
491
721
  yield item
@@ -798,7 +1028,7 @@ class ExecutionTrace(pg.Object, pg.views.html.HtmlTreeView.Extension):
798
1028
  ]
799
1029
 
800
1030
 
801
- class ParallelExecutions(pg.Object, pg.views.html.HtmlTreeView.Extension):
1031
+ class ParallelExecutions(ExecutionUnit, pg.views.html.HtmlTreeView.Extension):
802
1032
  """A container for multiple parallel execution traces.
803
1033
 
804
1034
  When `session.concurrent_map` is used, it creates a `ParallelExecutions`
@@ -850,8 +1080,64 @@ class ParallelExecutions(pg.Object, pg.views.html.HtmlTreeView.Extension):
850
1080
  self.branches.append(branch)
851
1081
  if self._tab_control is not None:
852
1082
  self._tab_control.append(self._branch_tab(branch))
1083
+
1084
+ # Invalidate cached properties.
1085
+ self.__dict__.pop('all_queries', None)
1086
+ self.__dict__.pop('all_actions', None)
1087
+ self.__dict__.pop('all_logs', None)
853
1088
  return branch
854
1089
 
1090
+ #
1091
+ # ExecutionUnit interface.
1092
+ #
1093
+
1094
+ @property
1095
+ def execution_units(self) -> list[ExecutionUnit]:
1096
+ """Returns immediate child execution items from execution sequence."""
1097
+ return []
1098
+
1099
+ @property
1100
+ def queries(self) -> list[lf_structured.QueryInvocation]:
1101
+ """Returns immediate queries made by the parallel execution."""
1102
+ return []
1103
+
1104
+ @property
1105
+ def actions(self) -> list['ActionInvocation']:
1106
+ """Returns immediate child action invocations."""
1107
+ return []
1108
+
1109
+ @property
1110
+ def logs(self) -> list[lf.logging.LogEntry]:
1111
+ """Returns immediate child logs from execution sequence."""
1112
+ return []
1113
+
1114
+ @functools.cached_property
1115
+ def all_queries(self) -> list[lf_structured.QueryInvocation]:
1116
+ """Returns all queries made by the action and its child execution items."""
1117
+ return list(
1118
+ itertools.chain.from_iterable(
1119
+ branch.all_queries for branch in self.branches
1120
+ )
1121
+ )
1122
+
1123
+ @functools.cached_property
1124
+ def all_actions(self) -> list['ActionInvocation']:
1125
+ """Returns all actions made by the action and its child execution items."""
1126
+ return list(
1127
+ itertools.chain.from_iterable(
1128
+ branch.all_actions for branch in self.branches
1129
+ )
1130
+ )
1131
+
1132
+ @property
1133
+ def all_logs(self) -> list[lf.logging.LogEntry]:
1134
+ """Returns all logs made by the action and its child execution items."""
1135
+ return list(
1136
+ itertools.chain.from_iterable(
1137
+ branch.all_logs for branch in self.branches
1138
+ )
1139
+ )
1140
+
855
1141
  #
856
1142
  # HTML views.
857
1143
  #
@@ -892,7 +1178,7 @@ class ParallelExecutions(pg.Object, pg.views.html.HtmlTreeView.Extension):
892
1178
  )
893
1179
 
894
1180
 
895
- class ActionInvocation(pg.Object, pg.views.html.HtmlTreeView.Extension):
1181
+ class ActionInvocation(ExecutionUnit, pg.views.html.HtmlTreeView.Extension):
896
1182
  """An invocation of an action, capturing its execution and result.
897
1183
 
898
1184
  `ActionInvocation` represents a single call to an `Action`. It contains
@@ -953,6 +1239,7 @@ class ActionInvocation(pg.Object, pg.views.html.HtmlTreeView.Extension):
953
1239
  def _on_parent_change(self, *args, **kwargs):
954
1240
  super()._on_parent_change(*args, **kwargs)
955
1241
  self.__dict__.pop('id', None)
1242
+ self.__dict__.pop('position', None)
956
1243
 
957
1244
  @property
958
1245
  def parent_action(self) -> Optional['ActionInvocation']:
@@ -989,6 +1276,15 @@ class ActionInvocation(pg.Object, pg.views.html.HtmlTreeView.Extension):
989
1276
  """Returns the state of the action."""
990
1277
  return self.action.state
991
1278
 
1279
+ #
1280
+ # Implement `ExecutionUnit` interface.
1281
+ #
1282
+
1283
+ @property
1284
+ def execution_units(self) -> list[ExecutionUnit]:
1285
+ """Returns immediate child execution items from execution sequence."""
1286
+ return self.execution.execution_units
1287
+
992
1288
  @property
993
1289
  def logs(self) -> list[lf.logging.LogEntry]:
994
1290
  """Returns immediate child logs from execution sequence."""
@@ -1040,10 +1336,12 @@ class ActionInvocation(pg.Object, pg.views.html.HtmlTreeView.Extension):
1040
1336
  metadata: dict[str, Any] | None = None,
1041
1337
  ) -> None:
1042
1338
  """Ends the execution of the action with result and metadata."""
1043
- rebind_dict = dict(result=result, error=error)
1044
- if metadata is not None:
1045
- rebind_dict['metadata'] = metadata
1046
- self.rebind(**rebind_dict, skip_notification=True, raise_on_no_change=False)
1339
+ with pg.notify_on_change(False):
1340
+ self.result = result
1341
+ self.error = error
1342
+ if metadata:
1343
+ self.metadata.update(metadata)
1344
+
1047
1345
  self.execution.stop()
1048
1346
  if self._tab_control is not None:
1049
1347
  if self.metadata:
@@ -1201,6 +1499,19 @@ class RootAction(Action):
1201
1499
  class SessionEventHandler:
1202
1500
  """Interface for handling session events."""
1203
1501
 
1502
+ def get(
1503
+ self,
1504
+ session_cls: type['SessionEventHandler']
1505
+ ) -> Optional['SessionEventHandler']:
1506
+ """Returns this or a child event handler for the given session class."""
1507
+ if isinstance(self, session_cls):
1508
+ return self
1509
+ elif isinstance(self, SessionEventHandlerChain):
1510
+ for handler in self.handlers:
1511
+ if v := handler.get(session_cls):
1512
+ return v
1513
+ return None
1514
+
1204
1515
  def on_session_start(
1205
1516
  self,
1206
1517
  session: 'Session'
@@ -1485,11 +1796,6 @@ class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
1485
1796
  'An optional identifier for the session, which will be used for logging.'
1486
1797
  ]
1487
1798
 
1488
- event_handler: Annotated[
1489
- SessionEventHandler,
1490
- 'Event handler for the session.'
1491
- ]
1492
-
1493
1799
  @pg.explicit_method_override
1494
1800
  def __init__(
1495
1801
  self,
@@ -1503,14 +1809,33 @@ class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
1503
1809
  super().__init__(
1504
1810
  id=id,
1505
1811
  root=root or ActionInvocation(RootAction()),
1506
- event_handler=event_handler or SessionLogging(verbose=verbose),
1507
1812
  **kwargs
1508
1813
  )
1814
+ self._event_handler = event_handler or SessionLogging(verbose=verbose)
1815
+
1816
+ @property
1817
+ def event_handler(self) -> SessionEventHandler:
1818
+ """Returns the event handler for the session."""
1819
+ return self._event_handler
1820
+
1821
+ def _sym_clone(self, deep: bool, memo: Any = None) -> 'Session':
1822
+ other = super()._sym_clone(deep=deep, memo=memo)
1823
+ if deep:
1824
+ event_handler = pg.clone(self.event_handler, deep=deep, memo=memo)
1825
+ else:
1826
+ event_handler = self.event_handler
1827
+ other._event_handler = event_handler # pylint: disable=protected-access
1828
+ return other
1509
1829
 
1510
1830
  #
1511
1831
  # Shortcut methods for accessing the root action invocation.
1512
1832
  #
1513
1833
 
1834
+ @property
1835
+ def metadata(self) -> dict[str, Any]:
1836
+ """Returns metadata associated with the root of the session."""
1837
+ return self.root.metadata
1838
+
1514
1839
  @property
1515
1840
  def all_queries(self) -> list[lf_structured.QueryInvocation]:
1516
1841
  """Returns all queries made by the session."""
@@ -1682,9 +2007,6 @@ class Session(pg.Object, pg.views.html.HtmlTreeView.Extension):
1682
2007
  'signal the start and end of the session.'
1683
2008
  )
1684
2009
 
1685
- # Early terminate the action if the execution time is exceeded.
1686
- self.check_execution_time()
1687
-
1688
2010
  invocation = ActionInvocation(
1689
2011
  pg.maybe_ref(action),
1690
2012
  max_execution_time=self._child_max_execution_time(max_execution_time)