langfun 0.1.2.dev202506190804__tar.gz → 0.1.2.dev202512150805__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 (231) hide show
  1. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/PKG-INFO +15 -36
  2. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/README.md +1 -24
  3. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/__init__.py +1 -1
  4. langfun-0.1.2.dev202512150805/langfun/assistant/capabilities/gui/__init__.py +36 -0
  5. langfun-0.1.2.dev202512150805/langfun/assistant/capabilities/gui/bounding_box_parser.py +195 -0
  6. langfun-0.1.2.dev202512150805/langfun/assistant/capabilities/gui/bounding_box_parser_test.py +313 -0
  7. langfun-0.1.2.dev202512150805/langfun/assistant/capabilities/gui/drawing.py +242 -0
  8. langfun-0.1.2.dev202512150805/langfun/assistant/capabilities/gui/drawing_test.py +103 -0
  9. langfun-0.1.2.dev202512150805/langfun/assistant/capabilities/gui/location.py +288 -0
  10. langfun-0.1.2.dev202512150805/langfun/assistant/capabilities/gui/location_test.py +230 -0
  11. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/__init__.py +9 -0
  12. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/agentic/__init__.py +11 -1
  13. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/agentic/action.py +865 -198
  14. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/agentic/action_eval.py +9 -2
  15. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/agentic/action_test.py +253 -27
  16. langfun-0.1.2.dev202512150805/langfun/core/async_support.py +127 -0
  17. langfun-0.1.2.dev202512150805/langfun/core/async_support_test.py +62 -0
  18. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/coding/python/__init__.py +0 -1
  19. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/coding/python/correction.py +19 -9
  20. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/coding/python/execution.py +14 -12
  21. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/coding/python/generation.py +21 -16
  22. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/coding/python/sandboxing.py +24 -31
  23. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/component.py +42 -3
  24. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/concurrent.py +70 -6
  25. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/concurrent_test.py +17 -6
  26. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/console.py +1 -1
  27. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/data/conversion/anthropic.py +12 -3
  28. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/data/conversion/anthropic_test.py +8 -6
  29. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/data/conversion/gemini.py +11 -2
  30. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/data/conversion/gemini_test.py +48 -9
  31. langfun-0.1.2.dev202512150805/langfun/core/data/conversion/openai.py +245 -0
  32. langfun-0.1.2.dev202512150805/langfun/core/data/conversion/openai_test.py +320 -0
  33. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/base.py +48 -44
  34. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/base_test.py +5 -5
  35. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/matching.py +5 -2
  36. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/patching.py +3 -3
  37. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/scoring.py +4 -3
  38. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/v2/__init__.py +3 -0
  39. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/v2/checkpointing.py +149 -51
  40. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/v2/checkpointing_test.py +33 -20
  41. langfun-0.1.2.dev202512150805/langfun/core/eval/v2/config_saver.py +37 -0
  42. langfun-0.1.2.dev202512150805/langfun/core/eval/v2/config_saver_test.py +36 -0
  43. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/v2/eval_test_helper.py +104 -3
  44. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/v2/evaluation.py +122 -24
  45. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/v2/evaluation_test.py +31 -5
  46. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/v2/example.py +50 -40
  47. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/v2/example_test.py +16 -8
  48. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/v2/experiment.py +108 -20
  49. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/v2/experiment_test.py +26 -7
  50. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/v2/metric_values.py +31 -3
  51. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/v2/metric_values_test.py +32 -0
  52. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/v2/metrics.py +157 -44
  53. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/v2/metrics_test.py +39 -18
  54. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/v2/progress.py +31 -1
  55. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/v2/progress_test.py +27 -0
  56. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/v2/progress_tracking.py +13 -5
  57. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/v2/progress_tracking_test.py +12 -4
  58. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/v2/reporting.py +88 -71
  59. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/v2/reporting_test.py +29 -11
  60. langfun-0.1.2.dev202512150805/langfun/core/eval/v2/runners/__init__.py +30 -0
  61. langfun-0.1.2.dev202506190804/langfun/core/eval/v2/runners.py → langfun-0.1.2.dev202512150805/langfun/core/eval/v2/runners/base.py +74 -180
  62. langfun-0.1.2.dev202512150805/langfun/core/eval/v2/runners/beam.py +354 -0
  63. langfun-0.1.2.dev202512150805/langfun/core/eval/v2/runners/beam_test.py +153 -0
  64. langfun-0.1.2.dev202512150805/langfun/core/eval/v2/runners/ckpt_monitor.py +350 -0
  65. langfun-0.1.2.dev202512150805/langfun/core/eval/v2/runners/ckpt_monitor_test.py +213 -0
  66. langfun-0.1.2.dev202512150805/langfun/core/eval/v2/runners/debug.py +40 -0
  67. langfun-0.1.2.dev202512150805/langfun/core/eval/v2/runners/debug_test.py +76 -0
  68. langfun-0.1.2.dev202512150805/langfun/core/eval/v2/runners/parallel.py +243 -0
  69. langfun-0.1.2.dev202512150805/langfun/core/eval/v2/runners/parallel_test.py +182 -0
  70. langfun-0.1.2.dev202512150805/langfun/core/eval/v2/runners/sequential.py +47 -0
  71. langfun-0.1.2.dev202512150805/langfun/core/eval/v2/runners/sequential_test.py +169 -0
  72. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/langfunc.py +45 -130
  73. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/langfunc_test.py +7 -5
  74. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/language_model.py +304 -46
  75. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/language_model_test.py +128 -17
  76. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/__init__.py +21 -2
  77. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/anthropic.py +157 -2
  78. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/azure_openai.py +29 -17
  79. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/cache/base.py +25 -3
  80. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/cache/in_memory.py +48 -7
  81. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/cache/in_memory_test.py +14 -4
  82. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/compositional.py +26 -2
  83. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/deepseek.py +30 -2
  84. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/fake.py +39 -1
  85. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/fake_test.py +9 -0
  86. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/gemini.py +164 -14
  87. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/gemini_test.py +110 -0
  88. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/google_genai.py +75 -1
  89. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/groq.py +28 -3
  90. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/llama_cpp.py +23 -4
  91. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/openai.py +120 -3
  92. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/openai_compatible.py +148 -27
  93. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/openai_compatible_test.py +207 -20
  94. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/openai_test.py +0 -2
  95. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/rest.py +16 -1
  96. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/vertexai.py +105 -10
  97. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/logging.py +1 -1
  98. langfun-0.1.2.dev202512150805/langfun/core/mcp/__init__.py +10 -0
  99. langfun-0.1.2.dev202512150805/langfun/core/mcp/client.py +177 -0
  100. langfun-0.1.2.dev202512150805/langfun/core/mcp/client_test.py +71 -0
  101. langfun-0.1.2.dev202512150805/langfun/core/mcp/session.py +241 -0
  102. langfun-0.1.2.dev202512150805/langfun/core/mcp/session_test.py +54 -0
  103. langfun-0.1.2.dev202506190804/langfun/core/modalities/pdf.py → langfun-0.1.2.dev202512150805/langfun/core/mcp/testing/simple_mcp_client.py +17 -6
  104. langfun-0.1.2.dev202512150805/langfun/core/mcp/testing/simple_mcp_server.py +33 -0
  105. langfun-0.1.2.dev202512150805/langfun/core/mcp/tool.py +254 -0
  106. langfun-0.1.2.dev202512150805/langfun/core/mcp/tool_test.py +197 -0
  107. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/memory.py +1 -0
  108. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/message.py +162 -59
  109. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/message_test.py +71 -87
  110. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/modalities/__init__.py +8 -0
  111. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/modalities/audio.py +21 -1
  112. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/modalities/audio_test.py +4 -4
  113. langfun-0.1.2.dev202512150805/langfun/core/modalities/image.py +130 -0
  114. langfun-0.1.2.dev202512150805/langfun/core/modalities/image_test.py +224 -0
  115. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/modalities/mime.py +94 -16
  116. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/modalities/mime_test.py +59 -0
  117. langfun-0.1.2.dev202512150805/langfun/core/modalities/pdf.py +40 -0
  118. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/modalities/video.py +21 -1
  119. langfun-0.1.2.dev202512150805/langfun/core/modality.py +271 -0
  120. langfun-0.1.2.dev202512150805/langfun/core/modality_test.py +117 -0
  121. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/natural_language.py +1 -1
  122. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/sampling.py +4 -4
  123. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/sampling_test.py +20 -4
  124. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/structured/__init__.py +9 -24
  125. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/structured/completion.py +62 -44
  126. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/structured/completion_test.py +59 -43
  127. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/structured/description.py +54 -50
  128. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/structured/function_generation.py +29 -12
  129. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/structured/mapping.py +92 -38
  130. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/structured/parsing.py +170 -77
  131. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/structured/parsing_test.py +15 -3
  132. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/structured/querying.py +284 -156
  133. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/structured/querying_test.py +120 -64
  134. langfun-0.1.2.dev202512150805/langfun/core/structured/schema/__init__.py +49 -0
  135. langfun-0.1.2.dev202512150805/langfun/core/structured/schema/base.py +664 -0
  136. langfun-0.1.2.dev202512150805/langfun/core/structured/schema/base_test.py +531 -0
  137. langfun-0.1.2.dev202512150805/langfun/core/structured/schema/json.py +174 -0
  138. langfun-0.1.2.dev202512150805/langfun/core/structured/schema/json_test.py +121 -0
  139. langfun-0.1.2.dev202512150805/langfun/core/structured/schema/python.py +316 -0
  140. langfun-0.1.2.dev202512150805/langfun/core/structured/schema/python_test.py +410 -0
  141. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/structured/schema_generation.py +33 -14
  142. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/structured/scoring.py +74 -35
  143. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/structured/scoring_test.py +8 -0
  144. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/structured/tokenization.py +49 -10
  145. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/structured/tokenization_test.py +8 -0
  146. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/subscription.py +2 -2
  147. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/template.py +176 -51
  148. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/template_test.py +123 -17
  149. langfun-0.1.2.dev202512150805/langfun/env/__init__.py +43 -0
  150. langfun-0.1.2.dev202512150805/langfun/env/base_environment.py +827 -0
  151. langfun-0.1.2.dev202512150805/langfun/env/base_environment_test.py +473 -0
  152. langfun-0.1.2.dev202512150805/langfun/env/base_feature.py +304 -0
  153. langfun-0.1.2.dev202512150805/langfun/env/base_feature_test.py +228 -0
  154. langfun-0.1.2.dev202512150805/langfun/env/base_sandbox.py +842 -0
  155. langfun-0.1.2.dev202512150805/langfun/env/base_sandbox_test.py +1235 -0
  156. langfun-0.1.2.dev202512150805/langfun/env/event_handlers/__init__.py +14 -0
  157. langfun-0.1.2.dev202512150805/langfun/env/event_handlers/chain.py +233 -0
  158. langfun-0.1.2.dev202512150805/langfun/env/event_handlers/chain_test.py +253 -0
  159. langfun-0.1.2.dev202512150805/langfun/env/event_handlers/event_logger.py +472 -0
  160. langfun-0.1.2.dev202512150805/langfun/env/event_handlers/event_logger_test.py +304 -0
  161. langfun-0.1.2.dev202512150805/langfun/env/event_handlers/metric_writer.py +726 -0
  162. langfun-0.1.2.dev202512150805/langfun/env/event_handlers/metric_writer_test.py +214 -0
  163. langfun-0.1.2.dev202512150805/langfun/env/interface.py +1640 -0
  164. langfun-0.1.2.dev202512150805/langfun/env/interface_test.py +153 -0
  165. langfun-0.1.2.dev202512150805/langfun/env/load_balancers.py +59 -0
  166. langfun-0.1.2.dev202512150805/langfun/env/load_balancers_test.py +141 -0
  167. langfun-0.1.2.dev202512150805/langfun/env/test_utils.py +507 -0
  168. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun.egg-info/PKG-INFO +15 -36
  169. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun.egg-info/SOURCES.txt +59 -5
  170. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun.egg-info/requires.txt +10 -9
  171. langfun-0.1.2.dev202506190804/langfun/core/data/conversion/openai.py +0 -131
  172. langfun-0.1.2.dev202506190804/langfun/core/data/conversion/openai_test.py +0 -176
  173. langfun-0.1.2.dev202506190804/langfun/core/eval/v2/runners_test.py +0 -343
  174. langfun-0.1.2.dev202506190804/langfun/core/modalities/image.py +0 -60
  175. langfun-0.1.2.dev202506190804/langfun/core/modalities/image_test.py +0 -108
  176. langfun-0.1.2.dev202506190804/langfun/core/modality.py +0 -133
  177. langfun-0.1.2.dev202506190804/langfun/core/modality_test.py +0 -87
  178. langfun-0.1.2.dev202506190804/langfun/core/structured/schema.py +0 -977
  179. langfun-0.1.2.dev202506190804/langfun/core/structured/schema_test.py +0 -982
  180. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/LICENSE +0 -0
  181. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/agentic/action_eval_test.py +0 -0
  182. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/coding/__init__.py +0 -0
  183. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/coding/python/correction_test.py +0 -0
  184. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/coding/python/execution_test.py +0 -0
  185. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/coding/python/generation_test.py +0 -0
  186. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/coding/python/parsing.py +0 -0
  187. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/coding/python/parsing_test.py +0 -0
  188. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/coding/python/sandboxing_test.py +0 -0
  189. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/component_test.py +0 -0
  190. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/console_test.py +0 -0
  191. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/data/__init__.py +0 -0
  192. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/data/conversion/__init__.py +0 -0
  193. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/__init__.py +0 -0
  194. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/matching_test.py +0 -0
  195. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/patching_test.py +0 -0
  196. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/eval/scoring_test.py +0 -0
  197. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/anthropic_test.py +0 -0
  198. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/azure_openai_test.py +0 -0
  199. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/cache/__init__.py +0 -0
  200. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/compositional_test.py +0 -0
  201. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/deepseek_test.py +0 -0
  202. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/google_genai_test.py +0 -0
  203. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/groq_test.py +0 -0
  204. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/llama_cpp_test.py +0 -0
  205. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/rest_test.py +0 -0
  206. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/llms/vertexai_test.py +0 -0
  207. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/logging_test.py +0 -0
  208. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/memories/__init__.py +0 -0
  209. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/memories/conversation_history.py +0 -0
  210. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/memories/conversation_history_test.py +0 -0
  211. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/modalities/pdf_test.py +0 -0
  212. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/modalities/video_test.py +0 -0
  213. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/natural_language_test.py +0 -0
  214. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/structured/description_test.py +0 -0
  215. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/structured/function_generation_test.py +0 -0
  216. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/structured/mapping_test.py +0 -0
  217. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/structured/schema_generation_test.py +0 -0
  218. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/subscription_test.py +0 -0
  219. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/templates/__init__.py +0 -0
  220. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/templates/completion.py +0 -0
  221. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/templates/completion_test.py +0 -0
  222. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/templates/conversation.py +0 -0
  223. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/templates/conversation_test.py +0 -0
  224. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/templates/demonstration.py +0 -0
  225. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/templates/demonstration_test.py +0 -0
  226. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/templates/selfplay.py +0 -0
  227. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun/core/templates/selfplay_test.py +0 -0
  228. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun.egg-info/dependency_links.txt +0 -0
  229. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/langfun.egg-info/top_level.txt +0 -0
  230. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/setup.cfg +0 -0
  231. {langfun-0.1.2.dev202506190804 → langfun-0.1.2.dev202512150805}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langfun
3
- Version: 0.1.2.dev202506190804
3
+ Version: 0.1.2.dev202512150805
4
4
  Summary: Langfun: Language as Functions.
5
5
  Home-page: https://github.com/google/langfun
6
6
  Author: Langfun Authors
@@ -21,30 +21,32 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
21
  Classifier: Topic :: Software Development :: Libraries
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
- Requires-Dist: pyglove>=0.4.5.dev202409110000
24
+ Requires-Dist: anyio>=4.7.0
25
25
  Requires-Dist: jinja2>=3.1.2
26
+ Requires-Dist: mcp>=1.17.0
27
+ Requires-Dist: puremagic>=1.20
28
+ Requires-Dist: pyglove>=0.5.0.dev202510170226
26
29
  Requires-Dist: requests>=2.31.0
27
30
  Provides-Extra: all
28
- Requires-Dist: pyglove>=0.4.5.dev202409110000; extra == "all"
31
+ Requires-Dist: anyio>=4.7.0; extra == "all"
29
32
  Requires-Dist: jinja2>=3.1.2; extra == "all"
33
+ Requires-Dist: mcp>=1.17.0; extra == "all"
34
+ Requires-Dist: puremagic>=1.20; extra == "all"
35
+ Requires-Dist: pyglove>=0.5.0.dev202510170226; extra == "all"
30
36
  Requires-Dist: requests>=2.31.0; extra == "all"
31
- Requires-Dist: termcolor==1.1.0; extra == "all"
32
- Requires-Dist: tqdm>=4.64.1; extra == "all"
33
37
  Requires-Dist: google-auth>=2.16.0; extra == "all"
34
- Requires-Dist: python-magic>=0.4.27; extra == "all"
35
38
  Requires-Dist: pillow>=10.0.0; extra == "all"
36
- Provides-Extra: ui
37
- Requires-Dist: termcolor==1.1.0; extra == "ui"
38
- Requires-Dist: tqdm>=4.64.1; extra == "ui"
39
+ Requires-Dist: termcolor==1.1.0; extra == "all"
40
+ Requires-Dist: tqdm>=4.64.1; extra == "all"
39
41
  Provides-Extra: vertexai
40
42
  Requires-Dist: google-auth>=2.16.0; extra == "vertexai"
41
43
  Provides-Extra: mime
42
- Requires-Dist: python-magic>=0.4.27; extra == "mime"
43
44
  Requires-Dist: pillow>=10.0.0; extra == "mime"
44
- Provides-Extra: mime-auto
45
- Requires-Dist: python-magic>=0.4.27; extra == "mime-auto"
46
45
  Provides-Extra: mime-pil
47
46
  Requires-Dist: pillow>=10.0.0; extra == "mime-pil"
47
+ Provides-Extra: ui
48
+ Requires-Dist: termcolor==1.1.0; extra == "ui"
49
+ Requires-Dist: tqdm>=4.64.1; extra == "ui"
48
50
  Dynamic: author
49
51
  Dynamic: author-email
50
52
  Dynamic: classifier
@@ -202,37 +204,14 @@ a tag from the list below:
202
204
  | all | All Langfun features. |
203
205
  | vertexai | VertexAI access. |
204
206
  | mime | All MIME supports. |
205
- | mime-auto | Automatic MIME type detection. |
206
207
  | mime-pil | Image support for PIL. |
207
208
  | ui | UI enhancements |
208
209
 
209
210
  For example, to install a nightly build that includes VertexAI access, full
210
211
  modality support, and UI enhancements, use:
211
- ```
212
- pip install langfun[vertexai,mime,ui] --pre
213
- ```
214
-
215
- ### Solving import issue with `libmagic`
216
-
217
- Langfun utilizes `libmagic` for automatic MIME type detection to support
218
- multi-modal functionalities. However, `pip install libmagic` may not work
219
- out-of-the-box on all operation systems, sometimes leading to an
220
- `'ImportError: failed to find libmagic.'` error after Langfun installation.
221
-
222
- If you encounter this error, you will need to follow the recommendations below
223
- to fix the installation of `libmagic` library.
224
212
 
225
- #### OSX
226
-
227
- ```
228
- conda install conda-forge::libmagic
229
213
  ```
230
-
231
- #### Windows:
232
- ```
233
- pip install python-magic
234
- pip uninstall python-magic-bin
235
- pip install python-magic-bin
214
+ pip install langfun[vertexai,mime,ui] --pre
236
215
  ```
237
216
 
238
217
  *Disclaimer: this is not an officially supported Google product.*
@@ -142,37 +142,14 @@ a tag from the list below:
142
142
  | all | All Langfun features. |
143
143
  | vertexai | VertexAI access. |
144
144
  | mime | All MIME supports. |
145
- | mime-auto | Automatic MIME type detection. |
146
145
  | mime-pil | Image support for PIL. |
147
146
  | ui | UI enhancements |
148
147
 
149
148
  For example, to install a nightly build that includes VertexAI access, full
150
149
  modality support, and UI enhancements, use:
151
- ```
152
- pip install langfun[vertexai,mime,ui] --pre
153
- ```
154
-
155
- ### Solving import issue with `libmagic`
156
-
157
- Langfun utilizes `libmagic` for automatic MIME type detection to support
158
- multi-modal functionalities. However, `pip install libmagic` may not work
159
- out-of-the-box on all operation systems, sometimes leading to an
160
- `'ImportError: failed to find libmagic.'` error after Langfun installation.
161
-
162
- If you encounter this error, you will need to follow the recommendations below
163
- to fix the installation of `libmagic` library.
164
150
 
165
- #### OSX
166
-
167
- ```
168
- conda install conda-forge::libmagic
169
151
  ```
170
-
171
- #### Windows:
172
- ```
173
- pip install python-magic
174
- pip uninstall python-magic-bin
175
- pip install python-magic-bin
152
+ pip install langfun[vertexai,mime,ui] --pre
176
153
  ```
177
154
 
178
155
  *Disclaimer: this is not an officially supported Google product.*
@@ -66,8 +66,8 @@ from langfun.core import agentic
66
66
  Action = agentic.Action
67
67
  Session = agentic.Session
68
68
 
69
+ from langfun.core import mcp
69
70
  from langfun.core import memories
70
-
71
71
  from langfun.core import modalities
72
72
 
73
73
  Mime = modalities.Mime
@@ -0,0 +1,36 @@
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
+ """GUI understanding."""
15
+
16
+ # pylint: disable=g-bad-import-order
17
+ # pylint: disable=g-importing-member
18
+ # pylint: disable=g-import-not-at-top
19
+
20
+ from langfun.assistant.capabilities.gui.location import Coordinate
21
+ from langfun.assistant.capabilities.gui.location import BBox
22
+ from langfun.assistant.capabilities.gui import bounding_box_parser
23
+ from langfun.assistant.capabilities.gui import drawing
24
+ import pyglove as pg
25
+
26
+ # For backward compatibility.
27
+ pg.JSONConvertible.add_module_alias(
28
+ 'langfun.assistant.capabilities.gui', (
29
+ 'langfun.agents.gui',
30
+ 'langfun.agents.tools.gui',
31
+ )
32
+ )
33
+
34
+ # pylint: enable=g-bad-import-order
35
+ # pylint: enable=g-importing-member
36
+ # pylint: enable=g-import-not-at-top
@@ -0,0 +1,195 @@
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
+ """Customized bounding box related to Gemini Object Detection."""
15
+
16
+ import json
17
+ import re
18
+ from typing import Any, Dict, List, Tuple, Union
19
+
20
+ from langfun.assistant.capabilities.gui import location
21
+ import pyglove as pg
22
+
23
+
24
+ class GeminiBBox(pg.Object):
25
+ """Customized bounding box.
26
+
27
+ Note: GeminiPro returns a unique JSON response structure for object detection
28
+ tasks, which differs from the standard location.BBox format. To accommodate
29
+ this, we've created this custom class to adapt to GeminiPro's specific
30
+ response structure.
31
+ """
32
+
33
+ ymin: int
34
+ xmin: int
35
+ ymax: int
36
+ xmax: int
37
+
38
+ def scale(self,
39
+ target_size: tuple[int, int],
40
+ source_size: tuple[int, int] = (1000, 1000)) -> 'GeminiBBox':
41
+
42
+ def _scale_x(v):
43
+ return (v * target_size[0]) // source_size[0]
44
+
45
+ def _scale_y(v):
46
+ return (v * target_size[1]) // source_size[1]
47
+
48
+ return GeminiBBox(
49
+ ymin=_scale_y(self.ymin),
50
+ xmin=_scale_x(self.xmin),
51
+ ymax=_scale_y(self.ymax),
52
+ xmax=_scale_x(self.xmax),
53
+ )
54
+
55
+ def resize(
56
+ self, image_size: Tuple[int, int], resize_factor: int = 3
57
+ ) -> 'GeminiBBox':
58
+ """Resize a bounding box from its center.
59
+
60
+ Args:
61
+ image_size: A tuple (width, height) representing the size of the image.
62
+ resize_factor: The factor by which to resize the bounding box. Defaults
63
+ to 3.
64
+
65
+ Returns:
66
+ A bounding box with the expanded dimensions.
67
+ """
68
+ center_x = (self.xmin + self.xmax) // 2
69
+ center_y = (self.ymin + self.ymax) // 2
70
+ width = self.xmax - self.xmin
71
+ height = self.ymax - self.ymin
72
+
73
+ new_width = width * resize_factor
74
+ new_height = height * resize_factor
75
+
76
+ new_xmin = max(0, int(center_x - new_width // 2))
77
+ new_ymin = max(0, int(center_y - new_height // 2))
78
+ new_xmax = min(image_size[0], int(center_x + new_width // 2))
79
+ new_ymax = min(image_size[1], int(center_y + new_height // 2))
80
+
81
+ return GeminiBBox(
82
+ xmin=new_xmin, ymin=new_ymin, xmax=new_xmax, ymax=new_ymax
83
+ )
84
+
85
+ def to_gui_bbox(self) -> location.BBox | None:
86
+ try:
87
+ return location.BBox(
88
+ x=self.xmin, y=self.ymin, right=self.xmax, bottom=self.ymax
89
+ )
90
+ except AssertionError:
91
+ # If the bounding box is not valid, return None.
92
+ return None
93
+
94
+
95
+ def extract_json_candidate_from_text(raw_text: str) -> str:
96
+ """Extracts a JSON candidate string from raw text."""
97
+ # Try to find content within ```json ... ```
98
+ match = re.search(r'```json\s*([\s\S]+?)\s*```', raw_text, re.IGNORECASE)
99
+ if match:
100
+ return match.group(1).strip()
101
+
102
+ # Try to find content within ``` ... ```
103
+ match = re.search(r'```\s*([\s\S]+?)\s*```', raw_text)
104
+ if match:
105
+ return match.group(1).strip()
106
+
107
+ # If no code blocks, return the stripped raw text.
108
+ return raw_text.strip()
109
+
110
+
111
+ def parse_and_convert_json(
112
+ text: str, screen_size: Tuple[int, int] = (1000, 1000)
113
+ ) -> Dict[str, location.BBox | None]:
114
+ """Parse and convert json to bounding box.
115
+
116
+ Args:
117
+ text: The text to parse.
118
+ screen_size: The screen size to scale the bounding box.
119
+
120
+ Returns:
121
+ A dictionary of bounding boxes.
122
+
123
+ Example:
124
+ >>> json_text = '```{"search button": [10, 20, 100, 200]}```'
125
+ >>> bboxes = parse_and_convert_json(json_text, screen_size=(800, 600))
126
+ >>> print(bboxes)
127
+ {'search button': BBox(x=16, y=6, right=160, bottom=60)}
128
+ """
129
+
130
+ def parse_json(t: str) -> Union[Dict[str, str], List[str], None]:
131
+ """Parse text to json."""
132
+ try:
133
+ return json.loads(t)
134
+ except json.JSONDecodeError:
135
+ return None
136
+
137
+ def can_cast_to_int(obj: Any) -> bool:
138
+ try:
139
+ int(obj)
140
+ return True
141
+ except (ValueError, TypeError):
142
+ return False
143
+
144
+ def convert_to_bbox(
145
+ data: Union[Dict[str, Any], List[Any], None], screen_size: Tuple[int, int]
146
+ ) -> Dict[str, location.BBox | None]:
147
+ """Convert data to bounding box."""
148
+ result = {}
149
+ if not data:
150
+ return result
151
+
152
+ if isinstance(data, list):
153
+ if len(data) == 4 and all(can_cast_to_int(item) for item in data):
154
+ return {
155
+ 'element': (
156
+ GeminiBBox(
157
+ xmin=int(data[1]),
158
+ ymin=int(data[0]),
159
+ xmax=int(data[3]),
160
+ ymax=int(data[2]),
161
+ )
162
+ .scale(screen_size)
163
+ .to_gui_bbox()
164
+ )
165
+ }
166
+ for item in data:
167
+ if isinstance(item, dict):
168
+ result.update(convert_to_bbox(item, screen_size))
169
+ return result
170
+
171
+ for key, value in data.items():
172
+ if not value:
173
+ continue
174
+
175
+ if isinstance(value, list):
176
+ if len(value) == 4 and all(can_cast_to_int(item) for item in value):
177
+ result[key] = (
178
+ GeminiBBox(
179
+ xmin=int(value[1]),
180
+ ymin=int(value[0]),
181
+ xmax=int(value[3]),
182
+ ymax=int(value[2]),
183
+ )
184
+ .scale(screen_size)
185
+ .to_gui_bbox()
186
+ )
187
+ elif isinstance(value, dict):
188
+ result.update(convert_to_bbox(value, screen_size))
189
+ return result
190
+
191
+ parsed_data = parse_json(extract_json_candidate_from_text(text))
192
+ if parsed_data is None:
193
+ return {}
194
+
195
+ return convert_to_bbox(parsed_data, screen_size)
@@ -0,0 +1,313 @@
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
+ from typing import Dict, Tuple
15
+ import unittest
16
+
17
+ from langfun.assistant.capabilities.gui import bounding_box_parser
18
+ from langfun.assistant.capabilities.gui import location
19
+
20
+
21
+ class BoundingBoxTest(unittest.TestCase):
22
+
23
+ def assert_bbox_equal(
24
+ self,
25
+ expected: Dict[str, Tuple[int, int, int, int]],
26
+ actual: Dict[str, location.BBox],
27
+ ):
28
+ self.assertEqual(len(expected), len(actual))
29
+ for key, value in expected.items():
30
+ self.assertIn(key, actual)
31
+ self.assertEqual(value[0], actual[key].x)
32
+ self.assertEqual(value[1], actual[key].y)
33
+ self.assertEqual(value[2], actual[key].right)
34
+ self.assertEqual(value[3], actual[key].bottom)
35
+
36
+ def test_bbox_basic_functionality(self):
37
+ bbox = bounding_box_parser.GeminiBBox(xmin=10, ymin=20, xmax=50, ymax=80)
38
+
39
+ self.assertEqual(bbox.xmin, 10)
40
+ self.assertEqual(bbox.ymin, 20)
41
+ self.assertEqual(bbox.xmax, 50)
42
+ self.assertEqual(bbox.ymax, 80)
43
+
44
+ # Test the `scale` method
45
+ target_size = (800, 600)
46
+ source_size = (1000, 1000)
47
+ scaled_bbox = bbox.scale(target_size, source_size)
48
+
49
+ self.assertEqual(scaled_bbox.xmin, 8)
50
+ self.assertEqual(scaled_bbox.ymin, 12)
51
+ self.assertEqual(scaled_bbox.xmax, 40)
52
+ self.assertEqual(scaled_bbox.ymax, 48)
53
+
54
+ # Test the `resize` method
55
+ resized_bbox = bbox.resize((1200, 800), 2)
56
+
57
+ self.assertEqual(resized_bbox.xmin, 0)
58
+ self.assertEqual(resized_bbox.ymin, 0)
59
+ self.assertEqual(resized_bbox.xmax, 70)
60
+ self.assertEqual(resized_bbox.ymax, 110)
61
+
62
+ # Test the `to_gui_bbox` method
63
+ gui_bbox = bbox.to_gui_bbox()
64
+ self.assertEqual(gui_bbox.x, 10)
65
+ self.assertEqual(gui_bbox.y, 20)
66
+ self.assertEqual(gui_bbox.right, 50)
67
+ self.assertEqual(gui_bbox.bottom, 80)
68
+
69
+ def test_simple_json(self):
70
+ json_text = '{"search button": [10, 20, 100, 200]}'
71
+ expected = {'search button': (16, 6, 160, 60)}
72
+ result = bounding_box_parser.parse_and_convert_json(
73
+ json_text, screen_size=(800, 600)
74
+ )
75
+ self.assert_bbox_equal(expected, result)
76
+
77
+ def test_multiple_objects(self):
78
+ json_text = (
79
+ '{"button1": [10, 20, 100, 200], "button2": [30, 40, 130, 240]}'
80
+ )
81
+ expected = {'button1': (16, 6, 160, 60), 'button2': (32, 18, 192, 78)}
82
+ result = bounding_box_parser.parse_and_convert_json(
83
+ json_text, screen_size=(800, 600)
84
+ )
85
+ self.assert_bbox_equal(expected, result)
86
+
87
+ def test_nested_json(self):
88
+ json_text = (
89
+ '{"buttons": {"search": [10, 20, 100, 200], "cancel": [30, 40, 130,'
90
+ ' 240]}}'
91
+ )
92
+ expected = {'search': (16, 6, 160, 60), 'cancel': (32, 18, 192, 78)}
93
+ result = bounding_box_parser.parse_and_convert_json(
94
+ json_text, screen_size=(800, 600)
95
+ )
96
+ self.assert_bbox_equal(expected, result)
97
+
98
+ def test_json_in_code_block(self):
99
+ json_text = '```\n{"search button": [10, 20, 100, 200]}\n```'
100
+ expected = {'search button': (16, 6, 160, 60)}
101
+ result = bounding_box_parser.parse_and_convert_json(
102
+ json_text, screen_size=(800, 600)
103
+ )
104
+ self.assert_bbox_equal(expected, result)
105
+
106
+ def test_extract_json_candidate_from_text(self):
107
+ test_cases = [
108
+ (
109
+ 'Some text before ```json\n{"key": "value"}\n``` and after',
110
+ '{"key": "value"}',
111
+ ),
112
+ (
113
+ 'Some text before ```\n{"key": "value"}\n``` and after',
114
+ '{"key": "value"}',
115
+ ),
116
+ ('{"key": "value"}', '{"key": "value"}'),
117
+ (
118
+ '```json\n{\n "name": "Test",\n "version": 1\n}\n```',
119
+ '{\n "name": "Test",\n "version": 1\n}',
120
+ ),
121
+ (
122
+ '```\n{\n "name": "Test",\n "version": 1\n}\n```',
123
+ '{\n "name": "Test",\n "version": 1\n}',
124
+ ),
125
+ (
126
+ ' ```json\n {"spaced_json": true} \n``` ',
127
+ '{"spaced_json": true}',
128
+ ),
129
+ (
130
+ 'No code block here, just plain text.',
131
+ 'No code block here, just plain text.',
132
+ ),
133
+ (
134
+ '```JSON\n{"case_test": "uppercase_json_tag"}\n```',
135
+ '{"case_test": "uppercase_json_tag"}',
136
+ ),
137
+ ('', ''),
138
+ (' ', ''),
139
+ (
140
+ (
141
+ 'First block: ```json\n{"first": true}\n``` Second block:'
142
+ ' ```json\n{"second": false}\n```'
143
+ ),
144
+ '{"first": true}',
145
+ ),
146
+ (
147
+ (
148
+ 'First block: ```\n{"first_code": true}\n``` Second block:'
149
+ ' ```\n{"second_code": false}\n```'
150
+ ),
151
+ '{"first_code": true}',
152
+ ),
153
+ (
154
+ '```json \n {"leading_trailing_space_in_block": "test"} \n ```',
155
+ '{"leading_trailing_space_in_block": "test"}',
156
+ ),
157
+ (
158
+ (
159
+ '```\n {"leading_trailing_space_in_block_no_json_tag": "test"}'
160
+ ' \n ```'
161
+ ),
162
+ '{"leading_trailing_space_in_block_no_json_tag": "test"}',
163
+ ),
164
+ ]
165
+
166
+ for raw_text, expected_json_str in test_cases:
167
+ with self.subTest(raw_text=raw_text):
168
+ self.assertEqual(
169
+ bounding_box_parser.extract_json_candidate_from_text(raw_text),
170
+ expected_json_str,
171
+ )
172
+
173
+ def test_dict_in_list(self):
174
+ json_text = '```json\n[\n {"box_2d": [61, 22, 160, 95]}\n]\n```'
175
+ expected = {'box_2d': (22, 61, 95, 160)}
176
+ result = bounding_box_parser.parse_and_convert_json(json_text)
177
+ self.assert_bbox_equal(expected, result)
178
+
179
+ def test_dict_with_label(self):
180
+ json_text = """```json
181
+ [
182
+ {"box_2d": [634, 416, 820, 482], "label": "the inner vertical side of the rightmost lower protrusion of the green polygon"},
183
+ {"box_2d": [820, 328, 872, 352], "label": "the purple number '1' located to its left"}
184
+ ]
185
+ ```"""
186
+ expected = {'box_2d': (328, 820, 352, 872)}
187
+ result = bounding_box_parser.parse_and_convert_json(json_text)
188
+ print('result: ', result)
189
+ self.assert_bbox_equal(expected, result)
190
+
191
+ def test_invalid_json(self):
192
+ json_text = 'This is not a valid JSON'
193
+ result = bounding_box_parser.parse_and_convert_json(json_text)
194
+ self.assertEqual({}, result)
195
+
196
+ def test_empty_input(self):
197
+ json_text = ''
198
+ result = bounding_box_parser.parse_and_convert_json(json_text)
199
+ self.assertEqual({}, result)
200
+
201
+ def test_invalid_list_length(self):
202
+ # Test with a list of length 3
203
+ json_text = '{"button": [10, 20, 100]}'
204
+ result = bounding_box_parser.parse_and_convert_json(json_text)
205
+ self.assertEqual({}, result)
206
+
207
+ # Test with a list of length 5
208
+ json_text = '{"button": [10, 20, 100, 200, 300]}'
209
+ result = bounding_box_parser.parse_and_convert_json(json_text)
210
+ self.assertEqual({}, result)
211
+
212
+ # Test with an empty list
213
+ json_text = '{"button": []}'
214
+ result = bounding_box_parser.parse_and_convert_json(json_text)
215
+ self.assertEqual({}, result)
216
+
217
+ def test_list_input(self):
218
+ json_text = '[10, 20, 100, 200]'
219
+ expected = {'element': (20, 10, 200, 100)}
220
+ result = bounding_box_parser.parse_and_convert_json(json_text)
221
+ self.assert_bbox_equal(expected, result)
222
+
223
+ def test_default_screen_size(self):
224
+ json_text = '{"button": [10, 20, 100, 200]}'
225
+ expected = {'button': (20, 10, 200, 100)}
226
+ result = bounding_box_parser.parse_and_convert_json(json_text)
227
+ self.assert_bbox_equal(expected, result)
228
+
229
+ def test_float_numbers(self):
230
+ json_text = '{"button": [10.5, 20.2, 100.7, 200.9]}'
231
+ expected = {'button': (20, 10, 200, 100)} # Expected integer coordinates
232
+ result = bounding_box_parser.parse_and_convert_json(
233
+ json_text, screen_size=(1000, 1000)
234
+ )
235
+ self.assert_bbox_equal(expected, result)
236
+
237
+ def test_type_error_handling(self):
238
+ json_text = '{"button": ["text", 20, 100, 200]}'
239
+ result = bounding_box_parser.parse_and_convert_json(
240
+ json_text, screen_size=(1000, 1000)
241
+ )
242
+ self.assertEqual({}, result)
243
+ json_text = '{"button": [None, 20, 100, 200]}'
244
+ result = bounding_box_parser.parse_and_convert_json(
245
+ json_text, screen_size=(1000, 1000)
246
+ )
247
+ self.assertEqual({}, result)
248
+
249
+ def test_malformed_json(self):
250
+ # Missing quotes around keys
251
+ json_text = '{search button: [10, 20, 100, 200]}'
252
+ result = bounding_box_parser.parse_and_convert_json(json_text)
253
+ self.assertEqual({}, result)
254
+
255
+ # Unbalanced brackets
256
+ json_text = '{"search button": [10, 20, 100, 200}'
257
+ result = bounding_box_parser.parse_and_convert_json(json_text)
258
+ self.assertEqual({}, result)
259
+
260
+ # Incorrect comma usage
261
+ json_text = '{"search button", [10, 20, 100, 200]}'
262
+ result = bounding_box_parser.parse_and_convert_json(json_text)
263
+ self.assertEqual({}, result)
264
+
265
+ def test_mixed_data_types(self):
266
+ # String values in coordinates
267
+ json_text = '{"button": ["10", "20", "100", "200"]}'
268
+ expected = {'button': (20, 10, 200, 100)}
269
+ result = bounding_box_parser.parse_and_convert_json(
270
+ json_text, screen_size=(1000, 1000)
271
+ )
272
+ self.assert_bbox_equal(expected, result)
273
+
274
+ def test_none_values(self):
275
+ json_text = '{"button": [null, 20, 100, 200]}'
276
+ result = bounding_box_parser.parse_and_convert_json(
277
+ json_text, screen_size=(1000, 1000)
278
+ )
279
+ self.assertEqual({}, result)
280
+
281
+ json_text = '{"button": [10, null, 100, 200]}'
282
+ result = bounding_box_parser.parse_and_convert_json(
283
+ json_text, screen_size=(1000, 1000)
284
+ )
285
+ self.assertEqual({}, result)
286
+
287
+ def test_large_coordinates(self):
288
+ json_text = '{"button": [10000, 20000, 100000, 200000]}'
289
+ expected = {'button': (20000, 10000, 200000, 100000)}
290
+ result = bounding_box_parser.parse_and_convert_json(
291
+ json_text, screen_size=(1000, 1000)
292
+ )
293
+ self.assert_bbox_equal(expected, result)
294
+
295
+ def test_different_string_formats(self):
296
+ # Newlines
297
+ json_text = '{\n"search button": [10, 20, 100, 200]\n}'
298
+ expected = {'search button': (16, 6, 160, 60)}
299
+ result = bounding_box_parser.parse_and_convert_json(
300
+ json_text, screen_size=(800, 600)
301
+ )
302
+ self.assert_bbox_equal(expected, result)
303
+
304
+ def test_deeply_nested_json(self):
305
+ json_text = (
306
+ '{"layer1": {"layer2": {"layer3": {"button": [10, 20, 100, 200]}}}}'
307
+ )
308
+ expected = {'button': (20, 10, 200, 100)}
309
+ result = bounding_box_parser.parse_and_convert_json(json_text)
310
+ self.assert_bbox_equal(expected, result)
311
+
312
+ if __name__ == '__main__':
313
+ unittest.main()