DeepFabric 4.4.1__tar.gz → 4.5.1__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.
Files changed (213) hide show
  1. {deepfabric-4.4.1 → deepfabric-4.5.1}/.gitignore +1 -0
  2. {deepfabric-4.4.1 → deepfabric-4.5.1}/CLAUDE.md +2 -1
  3. {deepfabric-4.4.1 → deepfabric-4.5.1}/PKG-INFO +6 -3
  4. {deepfabric-4.4.1 → deepfabric-4.5.1}/README.md +5 -2
  5. deepfabric-4.5.1/assets/df-demo.gif +0 -0
  6. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/builders_agent.py +16 -4
  7. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/cli.py +3 -3
  8. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/backends/__init__.py +2 -0
  9. deepfabric-4.5.1/deepfabric/evaluation/backends/llm_eval_backend.py +527 -0
  10. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/backends/ollama_backend.py +3 -3
  11. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/backends/tool_call_parsers.py +7 -7
  12. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/backends/transformers_backend.py +73 -16
  13. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/evaluator.py +41 -7
  14. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/evaluators/builtin/tool_calling.py +13 -8
  15. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/inference.py +77 -5
  16. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/metrics.py +4 -0
  17. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/reporters/cloud_reporter.py +1 -1
  18. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/generator.py +4 -0
  19. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/hf_hub.py +1 -1
  20. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/schemas.py +2 -2
  21. deepfabric-4.5.1/deepfabric/training/__init__.py +54 -0
  22. deepfabric-4.5.1/deepfabric/training/dataset_utils.py +223 -0
  23. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/cli/index.md +6 -3
  24. deepfabric-4.5.1/docs/cli/upload-hf.md +89 -0
  25. deepfabric-4.5.1/docs/cli/upload-kaggle.md +82 -0
  26. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/evaluation/index.md +20 -1
  27. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/evaluation/running.md +129 -8
  28. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/index.md +41 -23
  29. deepfabric-4.5.1/docs/training/dataset-preparation.md +193 -0
  30. deepfabric-4.5.1/docs/training/frameworks.md +489 -0
  31. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/training/index.md +15 -6
  32. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/training/loading.md +30 -2
  33. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/agent-tools-multi.yaml +1 -1
  34. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/reasoning.yaml +3 -0
  35. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/blender/spin-blender.yaml +5 -4
  36. {deepfabric-4.4.1 → deepfabric-4.5.1}/mkdocs.yml +2 -1
  37. deepfabric-4.5.1/notebooks/dataset-compatibility-check.ipynb +616 -0
  38. deepfabric-4.5.1/notebooks/evaluations.ipynb +161 -0
  39. {deepfabric-4.4.1 → deepfabric-4.5.1}/pyproject.toml +1 -1
  40. {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_cli.py +1 -1
  41. deepfabric-4.5.1/tests/unit/test_llm_eval_backend.py +471 -0
  42. {deepfabric-4.4.1 → deepfabric-4.5.1}/uv.lock +1 -1
  43. deepfabric-4.4.1/assets/df-demo.gif +0 -0
  44. deepfabric-4.4.1/deepfabric/training/__init__.py +0 -35
  45. deepfabric-4.4.1/docs/cli/upload.md +0 -153
  46. deepfabric-4.4.1/docs/training/frameworks.md +0 -155
  47. deepfabric-4.4.1/notebooks/trl_sft_training.ipynb +0 -602
  48. {deepfabric-4.4.1 → deepfabric-4.5.1}/.github/config.yml +0 -0
  49. {deepfabric-4.4.1 → deepfabric-4.5.1}/.github/dependabot.yml +0 -0
  50. {deepfabric-4.4.1 → deepfabric-4.5.1}/.github/workflows/docs.yml +0 -0
  51. {deepfabric-4.4.1 → deepfabric-4.5.1}/.github/workflows/integration.yml +0 -0
  52. {deepfabric-4.4.1 → deepfabric-4.5.1}/.github/workflows/publish.yml +0 -0
  53. {deepfabric-4.4.1 → deepfabric-4.5.1}/.github/workflows/test.yml +0 -0
  54. {deepfabric-4.4.1 → deepfabric-4.5.1}/.github/workflows/tools-sdk-docker.yml +0 -0
  55. {deepfabric-4.4.1 → deepfabric-4.5.1}/LICENSE +0 -0
  56. {deepfabric-4.4.1 → deepfabric-4.5.1}/Makefile +0 -0
  57. {deepfabric-4.4.1 → deepfabric-4.5.1}/assets/logo-light-hols.png +0 -0
  58. {deepfabric-4.4.1 → deepfabric-4.5.1}/assets/logo-light.png +0 -0
  59. {deepfabric-4.4.1 → deepfabric-4.5.1}/assets/star.gif +0 -0
  60. {deepfabric-4.4.1 → deepfabric-4.5.1}/coverage.xml +0 -0
  61. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/__init__.py +0 -0
  62. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/__main__.py +0 -0
  63. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/auth.py +0 -0
  64. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/builders.py +0 -0
  65. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/config.py +0 -0
  66. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/config_manager.py +0 -0
  67. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/constants.py +0 -0
  68. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/dataset_manager.py +0 -0
  69. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/error_codes.py +0 -0
  70. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/__init__.py +0 -0
  71. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/evaluators/__init__.py +0 -0
  72. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/evaluators/base.py +0 -0
  73. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/evaluators/builtin/__init__.py +0 -0
  74. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/evaluators/registry.py +0 -0
  75. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/parser.py +0 -0
  76. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/reporters/__init__.py +0 -0
  77. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/reporters/base.py +0 -0
  78. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/reporters/file_reporter.py +0 -0
  79. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/evaluation/reporters/multi_reporter.py +0 -0
  80. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/exceptions.py +0 -0
  81. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/factory.py +0 -0
  82. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/graph.py +0 -0
  83. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/kaggle_hub.py +0 -0
  84. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/llm/__init__.py +0 -0
  85. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/llm/api_key_verifier.py +0 -0
  86. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/llm/client.py +0 -0
  87. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/llm/errors.py +0 -0
  88. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/llm/rate_limit_config.py +0 -0
  89. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/llm/rate_limit_detector.py +0 -0
  90. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/llm/retry_handler.py +0 -0
  91. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/metrics.py +0 -0
  92. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/progress.py +0 -0
  93. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/prompts.py +0 -0
  94. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/spin/__init__.py +0 -0
  95. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/spin/client.py +0 -0
  96. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/spin/models.py +0 -0
  97. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/stream_simulator.py +0 -0
  98. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/tools/__init__.py +0 -0
  99. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/tools/defaults.py +0 -0
  100. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/tools/loader.py +0 -0
  101. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/tools/mcp_client.py +0 -0
  102. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/topic_manager.py +0 -0
  103. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/topic_model.py +0 -0
  104. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/training/api_key_prompt.py +0 -0
  105. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/training/callback.py +0 -0
  106. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/training/metrics_sender.py +0 -0
  107. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/tree.py +0 -0
  108. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/tui.py +0 -0
  109. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/update_checker.py +0 -0
  110. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/utils.py +0 -0
  111. {deepfabric-4.4.1 → deepfabric-4.5.1}/deepfabric/validation.py +0 -0
  112. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/api/config.md +0 -0
  113. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/api/generator.md +0 -0
  114. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/api/graph.md +0 -0
  115. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/api/index.md +0 -0
  116. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/api/tree.md +0 -0
  117. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/cli/format.md +0 -0
  118. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/cli/generate.md +0 -0
  119. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/cli/import-tools.md +0 -0
  120. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/cli/validate.md +0 -0
  121. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/cli/visualize.md +0 -0
  122. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/dataset-generation/agent.md +0 -0
  123. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/dataset-generation/basic.md +0 -0
  124. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/dataset-generation/configuration.md +0 -0
  125. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/dataset-generation/index.md +0 -0
  126. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/dataset-generation/rate-limiting.md +0 -0
  127. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/dataset-generation/reasoning.md +0 -0
  128. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/evaluation/metrics.md +0 -0
  129. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/getting-started/index.md +0 -0
  130. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/images/logo-light.png +0 -0
  131. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/tools/custom.md +0 -0
  132. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/tools/index.md +0 -0
  133. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/tools/mcp.md +0 -0
  134. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/tools/mock.md +0 -0
  135. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/tools/spin.md +0 -0
  136. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/tools/vfs.md +0 -0
  137. {deepfabric-4.4.1 → deepfabric-4.5.1}/docs/training/chat-templates.md +0 -0
  138. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/agent-tools-single.yaml +0 -0
  139. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/basic-anthropic.yaml +0 -0
  140. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/basic-gemini.yaml +0 -0
  141. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/basic-graph.yaml +0 -0
  142. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/basic-ollama.yaml +0 -0
  143. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/basic-openai.yaml +0 -0
  144. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/basic-openrouter.yaml +0 -0
  145. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/basic-tree.yaml +0 -0
  146. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/coding-agent.yaml +0 -0
  147. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/complete.yaml +0 -0
  148. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/custom-tools.yaml +0 -0
  149. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/github-mock-tools.yaml +0 -0
  150. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/spin-vfs-tools.yaml +0 -0
  151. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/blender/blender-mock-data.json +0 -0
  152. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/blender/load-blender-mock-data.sh +0 -0
  153. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/figma/figma-mock-data.json +0 -0
  154. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/figma/load-figma-mock-data.sh +0 -0
  155. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/figma/spin-figma.yaml +0 -0
  156. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/github/github-mock-data.json +0 -0
  157. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/github/load-github-mock-data.sh +0 -0
  158. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/github/spin-github-tools.yaml +0 -0
  159. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/kubernetes/kubernetes-mock-data.json +0 -0
  160. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/kubernetes/load-kubernetes-mock-data.sh +0 -0
  161. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/kubernetes/spin-kubernetes.yaml +0 -0
  162. {deepfabric-4.4.1 → deepfabric-4.5.1}/examples/tools-sdk-examples/kubernetes/tools.json +0 -0
  163. {deepfabric-4.4.1 → deepfabric-4.5.1}/misc/test_update_manual.py +0 -0
  164. {deepfabric-4.4.1 → deepfabric-4.5.1}/test-run/01-alpaca.txt +0 -0
  165. {deepfabric-4.4.1 → deepfabric-4.5.1}/test-run/01-chatml.txt +0 -0
  166. {deepfabric-4.4.1 → deepfabric-4.5.1}/test-run/01-grpo.txt +0 -0
  167. {deepfabric-4.4.1 → deepfabric-4.5.1}/test-run/01-xlam_v2.txt +0 -0
  168. {deepfabric-4.4.1 → deepfabric-4.5.1}/test-run/02-trl2.txt +0 -0
  169. {deepfabric-4.4.1 → deepfabric-4.5.1}/test-run/02-xlam_v2.txt +0 -0
  170. {deepfabric-4.4.1 → deepfabric-4.5.1}/test-run/04-agent-tool-conversations.jsnl +0 -0
  171. {deepfabric-4.4.1 → deepfabric-4.5.1}/test-run/04-single-agent-tools +0 -0
  172. {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/__init__.py +0 -0
  173. {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/integration/__init__.py +0 -0
  174. {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/integration/test_tree_integration.py +0 -0
  175. {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/__init__.py +0 -0
  176. {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/conftest.py +0 -0
  177. {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_api_key_validation.py +0 -0
  178. {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_config.py +0 -0
  179. {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_error_codes.py +0 -0
  180. {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_gemini_schema.py +0 -0
  181. {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_generator.py +0 -0
  182. {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_hf_hub.py +0 -0
  183. {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_kaggle_hub.py +0 -0
  184. {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_modular_config.py +0 -0
  185. {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_rate_limiting.py +0 -0
  186. {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_schemas.py +0 -0
  187. {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_tool_call_parsers.py +0 -0
  188. {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_topic_graph.py +0 -0
  189. {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_training_callback.py +0 -0
  190. {deepfabric-4.4.1 → deepfabric-4.5.1}/tests/unit/test_update_checker.py +0 -0
  191. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools/extract_messages.py +0 -0
  192. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools/function.py +0 -0
  193. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools/yaml_to_openai_tools.py +0 -0
  194. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/.dockerignore +0 -0
  195. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/Dockerfile +0 -0
  196. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/README.md +0 -0
  197. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/github/README.md +0 -0
  198. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/github/app.py +0 -0
  199. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/github/github.wasm +0 -0
  200. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/github/pyproject.toml +0 -0
  201. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/github/requirements.txt +0 -0
  202. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/github/spin.toml +0 -0
  203. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/github/uv.lock +0 -0
  204. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/mock/Cargo.lock +0 -0
  205. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/mock/Cargo.toml +0 -0
  206. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/mock/README.md +0 -0
  207. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/mock/src/lib.rs +0 -0
  208. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/vfs/Cargo.lock +0 -0
  209. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/vfs/Cargo.toml +0 -0
  210. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/components/vfs/src/lib.rs +0 -0
  211. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/docker-compose.yaml +0 -0
  212. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/runtime-config.toml +0 -0
  213. {deepfabric-4.4.1 → deepfabric-4.5.1}/tools-sdk/spin.toml +0 -0
@@ -133,3 +133,4 @@ formats/*
133
133
  # coverage files
134
134
  .coverage.*
135
135
  tools-sdk/.spin/*
136
+ notebooks/lora-output/*
@@ -97,4 +97,5 @@ deepfabric start config.yaml --model gpt-4 --temperature 0.8 --hf-repo user/data
97
97
  - Bandit for security analysis
98
98
  - Python 3.11+ required
99
99
  - Google-style docstrings preferred
100
- - do not place imports anywhere but the top of the file
100
+ - do not place imports anywhere but the top of the file
101
+ - When updating `docs/` documentation, if new Markdown files are added or removed, consider updating `mkdocs.yml`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: DeepFabric
3
- Version: 4.4.1
3
+ Version: 4.5.1
4
4
  Summary: Curate High Quality Datasets, Train, Evaluate and Ship
5
5
  Author-email: Luke Hinds <luke@alwaysfurther.ai>
6
6
  License-File: LICENSE
@@ -77,6 +77,9 @@ Description-Content-Type: text/markdown
77
77
  <a href="https://discord.gg/pPcjYzGvbS">
78
78
  <img src="https://img.shields.io/discord/1384081906773131274?color=7289da&label=Discord&logo=discord&logoColor=white" alt="Discord"/>
79
79
  </a>
80
+ <a href="https://www.reddit.com/r/deepfabric/">
81
+ <img src="https://img.shields.io/badge/Reddit-r%2Fdeepfabric-FF4500?logo=reddit&logoColor=white" alt="Reddit"/>
82
+ </a>
80
83
  </p>
81
84
  </div>
82
85
 
@@ -86,7 +89,7 @@ What sets DeepFabric apart from other dataset generation tools is its ability to
86
89
 
87
90
  <img src="/assets/df-demo.gif" width="100%" height="100%"/>
88
91
 
89
- Constrained decoding and response validation, along with real tool executions within isolated webassembly environments, ensure that generated samples strictly adhere to structured schema, variable constraints, and execution correctness, ensuring datasets have exact syntax and structure for use in model training pipelines. Tool definations can be either directly imported from MCP (Model Context Protocol) server schemas and automatically mocked, real life interfaces along with a standard set of common tools (`list_files()`, 'read_file()` etc)
92
+ Constrained decoding and response validation, along with real tool executions within isolated webassembly environments, ensure that generated samples strictly adhere to structured schema, variable constraints, and execution correctness, ensuring datasets have exact syntax and structure for use in model training pipelines. Tool definations can be either directly imported from MCP (Model Context Protocol) server schemas and automatically mocked, real life interfaces along with a standard set of common tools (`list_files()`, `'read_file()` etc)
90
93
 
91
94
  Once your dataset is generated, it can be automatically uploaded to Hugging Face and directly imported into popular training frameworks like TRL, Unsloth, and Axolotl.
92
95
 
@@ -215,7 +218,7 @@ deepfabric generate config.yaml --output-save-as dataset.jsonl
215
218
  Or upload to HuggingFace Hub:
216
219
 
217
220
  ```bash
218
- deepfabric upload dataset.jsonl --repo your-username/my-dataset
221
+ deepfabric upload-hf dataset.jsonl --repo your-username/my-dataset
219
222
  ```
220
223
 
221
224
  ### 2. Load and Split for Training
@@ -33,6 +33,9 @@
33
33
  <a href="https://discord.gg/pPcjYzGvbS">
34
34
  <img src="https://img.shields.io/discord/1384081906773131274?color=7289da&label=Discord&logo=discord&logoColor=white" alt="Discord"/>
35
35
  </a>
36
+ <a href="https://www.reddit.com/r/deepfabric/">
37
+ <img src="https://img.shields.io/badge/Reddit-r%2Fdeepfabric-FF4500?logo=reddit&logoColor=white" alt="Reddit"/>
38
+ </a>
36
39
  </p>
37
40
  </div>
38
41
 
@@ -42,7 +45,7 @@ What sets DeepFabric apart from other dataset generation tools is its ability to
42
45
 
43
46
  <img src="/assets/df-demo.gif" width="100%" height="100%"/>
44
47
 
45
- Constrained decoding and response validation, along with real tool executions within isolated webassembly environments, ensure that generated samples strictly adhere to structured schema, variable constraints, and execution correctness, ensuring datasets have exact syntax and structure for use in model training pipelines. Tool definations can be either directly imported from MCP (Model Context Protocol) server schemas and automatically mocked, real life interfaces along with a standard set of common tools (`list_files()`, 'read_file()` etc)
48
+ Constrained decoding and response validation, along with real tool executions within isolated webassembly environments, ensure that generated samples strictly adhere to structured schema, variable constraints, and execution correctness, ensuring datasets have exact syntax and structure for use in model training pipelines. Tool definations can be either directly imported from MCP (Model Context Protocol) server schemas and automatically mocked, real life interfaces along with a standard set of common tools (`list_files()`, `'read_file()` etc)
46
49
 
47
50
  Once your dataset is generated, it can be automatically uploaded to Hugging Face and directly imported into popular training frameworks like TRL, Unsloth, and Axolotl.
48
51
 
@@ -171,7 +174,7 @@ deepfabric generate config.yaml --output-save-as dataset.jsonl
171
174
  Or upload to HuggingFace Hub:
172
175
 
173
176
  ```bash
174
- deepfabric upload dataset.jsonl --repo your-username/my-dataset
177
+ deepfabric upload-hf dataset.jsonl --repo your-username/my-dataset
175
178
  ```
176
179
 
177
180
  ### 2. Load and Split for Training
Binary file
@@ -763,8 +763,14 @@ Remember: You have access to the tools listed above and have used them in this c
763
763
  # Insert system message if configured
764
764
  self._insert_system_message_if_configured(messages)
765
765
 
766
- # Convert tools to OpenAI format
767
- tools_openai = [tool.to_openai() for tool in self.tool_registry.tools]
766
+ # Convert tools to OpenAI format, filtering based on inclusion strategy
767
+ if self.config.tool_inclusion_strategy == "used_only" and tool_results:
768
+ used_names = {te.function_name for te in tool_results}
769
+ tools_openai = [
770
+ tool.to_openai() for tool in self.tool_registry.tools if tool.name in used_names
771
+ ]
772
+ else:
773
+ tools_openai = [tool.to_openai() for tool in self.tool_registry.tools]
768
774
 
769
775
  return Conversation(
770
776
  messages=messages,
@@ -1291,8 +1297,14 @@ Is the user's original task/goal from the scenario fully completed?
1291
1297
  # Insert system message if configured
1292
1298
  self._insert_system_message_if_configured(messages)
1293
1299
 
1294
- # Convert tools to OpenAI format
1295
- tools_openai = [tool.to_openai() for tool in self.tool_registry.tools]
1300
+ # Convert tools to OpenAI format, filtering based on inclusion strategy
1301
+ if self.config.tool_inclusion_strategy == "used_only" and all_executions:
1302
+ used_names = {te.function_name for te in all_executions}
1303
+ tools_openai = [
1304
+ tool.to_openai() for tool in self.tool_registry.tools if tool.name in used_names
1305
+ ]
1306
+ else:
1307
+ tools_openai = [tool.to_openai() for tool in self.tool_registry.tools]
1296
1308
 
1297
1309
  return Conversation(
1298
1310
  messages=messages,
@@ -557,7 +557,7 @@ def generate( # noqa: PLR0913
557
557
  sys.exit(1)
558
558
 
559
559
 
560
- @cli.command()
560
+ @cli.command("upload-hf")
561
561
  @click.argument("dataset_file", type=click.Path(exists=True))
562
562
  @click.option(
563
563
  "--repo",
@@ -573,14 +573,14 @@ def generate( # noqa: PLR0913
573
573
  multiple=True,
574
574
  help="Tags for the dataset (can be specified multiple times)",
575
575
  )
576
- def upload(
576
+ def upload_hf(
577
577
  dataset_file: str,
578
578
  repo: str,
579
579
  token: str | None = None,
580
580
  tags: list[str] | None = None,
581
581
  ) -> None:
582
582
  """Upload a dataset to Hugging Face Hub."""
583
- trace("cli_upload", {"has_tags": len(tags) > 0 if tags else False})
583
+ trace("cli_upload_hf", {"has_tags": len(tags) > 0 if tags else False})
584
584
 
585
585
  try:
586
586
  # Get token from CLI arg or env var
@@ -1,5 +1,6 @@
1
1
  """Inference backend implementations."""
2
2
 
3
+ from .llm_eval_backend import LLMEvalBackend
3
4
  from .ollama_backend import OllamaBackend
4
5
  from .tool_call_parsers import (
5
6
  GenericToolCallParser,
@@ -18,6 +19,7 @@ from .transformers_backend import TransformersBackend
18
19
  __all__ = [
19
20
  "TransformersBackend",
20
21
  "OllamaBackend",
22
+ "LLMEvalBackend",
21
23
  # Tool call parsers
22
24
  "ToolCallParser",
23
25
  "ToolCallParserRegistry",
@@ -0,0 +1,527 @@
1
+ """LLM Evaluation Backend for cloud providers.
2
+
3
+ Supports OpenAI, Anthropic, Gemini, and OpenRouter for tool calling evaluation.
4
+ Uses async clients internally with sync wrapper for compatibility with Evaluator.
5
+ """
6
+
7
+ import asyncio
8
+ import json
9
+ import logging
10
+ import os
11
+
12
+ from typing import Any
13
+
14
+ from deepfabric.evaluation.inference import (
15
+ InferenceBackend,
16
+ InferenceConfig,
17
+ ModelResponse,
18
+ )
19
+ from deepfabric.llm.errors import handle_provider_error
20
+ from deepfabric.llm.rate_limit_config import (
21
+ RateLimitConfig,
22
+ create_rate_limit_config,
23
+ get_default_rate_limit_config,
24
+ )
25
+ from deepfabric.llm.retry_handler import RetryHandler
26
+ from deepfabric.schemas import ToolDefinition
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ class LLMEvalBackend(InferenceBackend):
32
+ """Inference backend using cloud LLM providers for evaluation.
33
+
34
+ Supports:
35
+ - OpenAI (GPT-4, GPT-4o, etc.)
36
+ - Anthropic (Claude models)
37
+ - Gemini (gemini-2.0-flash, etc.)
38
+ - OpenRouter (OpenAI-compatible API)
39
+
40
+ Uses async clients internally with sync wrapper for compatibility.
41
+ """
42
+
43
+ def __init__(self, config: InferenceConfig) -> None:
44
+ """Initialize LLM evaluation backend.
45
+
46
+ Args:
47
+ config: Inference configuration with provider and model details
48
+ """
49
+ super().__init__(config)
50
+
51
+ if config.provider is None:
52
+ msg = "provider must be specified for LLM backend"
53
+ raise ValueError(msg)
54
+
55
+ self.provider = config.provider
56
+ self.model_name = config.model
57
+
58
+ # Initialize rate limiting
59
+ self.rate_limit_config: RateLimitConfig
60
+ if config.rate_limit_config:
61
+ self.rate_limit_config = create_rate_limit_config(
62
+ self.provider, config.rate_limit_config
63
+ )
64
+ else:
65
+ self.rate_limit_config = get_default_rate_limit_config(self.provider)
66
+
67
+ self.retry_handler = RetryHandler(self.rate_limit_config, self.provider)
68
+
69
+ # Initialize provider-specific async client
70
+ self._client = self._create_client()
71
+
72
+ def _create_client(self) -> Any:
73
+ """Create the appropriate async client for the provider."""
74
+ client_creators = {
75
+ "openai": self._create_openai_client,
76
+ "anthropic": self._create_anthropic_client,
77
+ "gemini": self._create_gemini_client,
78
+ "openrouter": self._create_openrouter_client,
79
+ }
80
+ if creator := client_creators.get(self.provider):
81
+ return creator()
82
+ msg = f"Unsupported provider: {self.provider}"
83
+ raise ValueError(msg)
84
+
85
+ def _create_openai_client(self) -> Any:
86
+ """Create async OpenAI client."""
87
+ import openai # noqa: PLC0415
88
+
89
+ api_key = self.config.api_key or os.getenv("OPENAI_API_KEY")
90
+ if not api_key:
91
+ msg = "OPENAI_API_KEY environment variable is not set"
92
+ raise ValueError(msg)
93
+
94
+ kwargs: dict[str, Any] = {"api_key": api_key}
95
+ if self.config.base_url:
96
+ kwargs["base_url"] = self.config.base_url
97
+
98
+ return openai.AsyncOpenAI(**kwargs)
99
+
100
+ def _create_anthropic_client(self) -> Any:
101
+ """Create async Anthropic client."""
102
+ import anthropic # noqa: PLC0415
103
+
104
+ api_key = self.config.api_key or os.getenv("ANTHROPIC_API_KEY")
105
+ if not api_key:
106
+ msg = "ANTHROPIC_API_KEY environment variable is not set"
107
+ raise ValueError(msg)
108
+
109
+ return anthropic.AsyncAnthropic(api_key=api_key)
110
+
111
+ def _create_gemini_client(self) -> Any:
112
+ """Create Gemini client (uses aio namespace for async)."""
113
+ from google import genai # noqa: PLC0415
114
+
115
+ api_key = self.config.api_key or os.getenv("GOOGLE_API_KEY") or os.getenv("GEMINI_API_KEY")
116
+ if not api_key:
117
+ msg = "GOOGLE_API_KEY or GEMINI_API_KEY environment variable is not set"
118
+ raise ValueError(msg)
119
+
120
+ return genai.Client(api_key=api_key)
121
+
122
+ def _create_openrouter_client(self) -> Any:
123
+ """Create async OpenRouter client (OpenAI-compatible)."""
124
+ import openai # noqa: PLC0415
125
+
126
+ api_key = self.config.api_key or os.getenv("OPENROUTER_API_KEY")
127
+ if not api_key:
128
+ msg = "OPENROUTER_API_KEY environment variable is not set"
129
+ raise ValueError(msg)
130
+
131
+ base_url = self.config.base_url or "https://openrouter.ai/api/v1"
132
+ return openai.AsyncOpenAI(api_key=api_key, base_url=base_url)
133
+
134
+ def generate(
135
+ self,
136
+ messages: list[dict[str, str]],
137
+ tools: list[ToolDefinition] | None = None,
138
+ ) -> ModelResponse:
139
+ """Generate response with optional tool calling (sync wrapper).
140
+
141
+ Args:
142
+ messages: List of message dicts with 'role' and 'content'
143
+ tools: Optional list of available tools for function calling
144
+
145
+ Returns:
146
+ ModelResponse with generated content and parsed tool calls
147
+ """
148
+ return asyncio.run(self.generate_async(messages, tools))
149
+
150
+ async def generate_async(
151
+ self,
152
+ messages: list[dict[str, str]],
153
+ tools: list[ToolDefinition] | None = None,
154
+ ) -> ModelResponse:
155
+ """Generate response with optional tool calling (async).
156
+
157
+ Args:
158
+ messages: List of message dicts with 'role' and 'content'
159
+ tools: Optional list of available tools for function calling
160
+
161
+ Returns:
162
+ ModelResponse with generated content and parsed tool calls
163
+ """
164
+ return await self._generate_with_retry(messages, tools)
165
+
166
+ async def _generate_with_retry(
167
+ self,
168
+ messages: list[dict[str, str]],
169
+ tools: list[ToolDefinition] | None = None,
170
+ ) -> ModelResponse:
171
+ """Generate with retry logic."""
172
+ attempt = 0
173
+ max_retries = self.rate_limit_config.max_retries
174
+
175
+ while attempt <= max_retries:
176
+ try:
177
+ return await self._do_generate(messages, tools)
178
+ except Exception as e:
179
+ if not self.retry_handler.should_retry(e):
180
+ raise handle_provider_error(e, self.provider, self.model_name) from e
181
+
182
+ if attempt >= max_retries:
183
+ self.retry_handler.on_giveup_handler({"exception": e, "tries": attempt + 1})
184
+ raise handle_provider_error(e, self.provider, self.model_name) from e
185
+
186
+ delay = self.retry_handler.calculate_delay(attempt, e)
187
+ self.retry_handler.on_backoff_handler(
188
+ {
189
+ "exception": e,
190
+ "wait": delay,
191
+ "tries": attempt + 1,
192
+ }
193
+ )
194
+ await asyncio.sleep(delay)
195
+ attempt += 1
196
+
197
+ msg = "Unexpected state in retry logic"
198
+ raise RuntimeError(msg)
199
+
200
+ async def _do_generate(
201
+ self,
202
+ messages: list[dict[str, str]],
203
+ tools: list[ToolDefinition] | None = None,
204
+ ) -> ModelResponse:
205
+ """Execute generation based on provider."""
206
+ generators = {
207
+ "openai": self._generate_openai_async,
208
+ "openrouter": self._generate_openai_async,
209
+ "anthropic": self._generate_anthropic_async,
210
+ "gemini": self._generate_gemini_async,
211
+ }
212
+ if generator := generators.get(self.provider):
213
+ return await generator(messages, tools)
214
+ msg = f"Unsupported provider: {self.provider}"
215
+ raise ValueError(msg)
216
+
217
+ async def _generate_openai_async(
218
+ self,
219
+ messages: list[dict[str, str]],
220
+ tools: list[ToolDefinition] | None = None,
221
+ ) -> ModelResponse:
222
+ """Generate using OpenAI/OpenRouter API."""
223
+ kwargs: dict[str, Any] = {
224
+ "model": self.model_name,
225
+ "messages": messages,
226
+ "temperature": self.config.temperature,
227
+ "max_tokens": self.config.max_tokens,
228
+ "top_p": self.config.top_p,
229
+ }
230
+
231
+ if tools:
232
+ kwargs["tools"] = [tool.to_openai() for tool in tools]
233
+ kwargs["tool_choice"] = "auto"
234
+
235
+ response = await self._client.chat.completions.create(**kwargs)
236
+ message = response.choices[0].message
237
+
238
+ # Parse tool calls
239
+ tool_call = None
240
+ tool_calls = None
241
+ if message.tool_calls:
242
+ tool_calls = []
243
+ for tc in message.tool_calls:
244
+ # Parse arguments from JSON string
245
+ try:
246
+ args = json.loads(tc.function.arguments)
247
+ except json.JSONDecodeError as e:
248
+ logger.warning(
249
+ "Failed to parse tool call arguments as JSON: %s (%s)",
250
+ tc.function.arguments,
251
+ e,
252
+ )
253
+ args = {}
254
+
255
+ parsed = {
256
+ "name": tc.function.name,
257
+ "arguments": args,
258
+ }
259
+ tool_calls.append(parsed)
260
+ tool_call = tool_calls[0] if tool_calls else None
261
+
262
+ return ModelResponse(
263
+ content=message.content or "",
264
+ tool_call=tool_call,
265
+ tool_calls=tool_calls,
266
+ raw_output=message.content or "",
267
+ finish_reason=response.choices[0].finish_reason,
268
+ )
269
+
270
+ async def _generate_anthropic_async(
271
+ self,
272
+ messages: list[dict[str, str]],
273
+ tools: list[ToolDefinition] | None = None,
274
+ ) -> ModelResponse:
275
+ """Generate using Anthropic API."""
276
+ # Convert messages to Anthropic format (system message separate)
277
+ system_message = None
278
+ anthropic_messages = []
279
+ for msg in messages:
280
+ if msg["role"] == "system":
281
+ system_message = msg["content"]
282
+ else:
283
+ anthropic_messages.append(
284
+ {
285
+ "role": msg["role"],
286
+ "content": msg["content"],
287
+ }
288
+ )
289
+
290
+ # Anthropic doesn't allow both temperature and top_p together
291
+ # Use temperature only (the more commonly configured parameter)
292
+ kwargs: dict[str, Any] = {
293
+ "model": self.model_name,
294
+ "messages": anthropic_messages,
295
+ "max_tokens": self.config.max_tokens,
296
+ "temperature": self.config.temperature,
297
+ }
298
+
299
+ if system_message:
300
+ kwargs["system"] = system_message
301
+
302
+ if tools:
303
+ kwargs["tools"] = [self._convert_tool_to_anthropic(tool) for tool in tools]
304
+
305
+ response = await self._client.messages.create(**kwargs)
306
+
307
+ # Parse response - Anthropic uses content blocks
308
+ content = ""
309
+ tool_call = None
310
+ tool_calls: list[dict[str, Any]] = []
311
+
312
+ for block in response.content:
313
+ if block.type == "text":
314
+ content += block.text
315
+ elif block.type == "tool_use":
316
+ parsed = {
317
+ "name": block.name,
318
+ "arguments": block.input,
319
+ }
320
+ tool_calls.append(parsed)
321
+
322
+ tool_call = tool_calls[0] if tool_calls else None
323
+
324
+ return ModelResponse(
325
+ content=content,
326
+ tool_call=tool_call,
327
+ tool_calls=tool_calls if tool_calls else None,
328
+ raw_output=content,
329
+ finish_reason=response.stop_reason,
330
+ )
331
+
332
+ def _convert_tool_to_anthropic(self, tool: ToolDefinition) -> dict[str, Any]:
333
+ """Convert ToolDefinition to Anthropic tool format."""
334
+ openai_format = tool.to_openai()
335
+ func = openai_format["function"]
336
+
337
+ return {
338
+ "name": func["name"],
339
+ "description": func["description"],
340
+ "input_schema": func["parameters"],
341
+ }
342
+
343
+ async def _generate_gemini_async(
344
+ self,
345
+ messages: list[dict[str, str]],
346
+ tools: list[ToolDefinition] | None = None,
347
+ ) -> ModelResponse:
348
+ """Generate using Gemini API."""
349
+ from google.genai import types # noqa: PLC0415
350
+
351
+ # Convert messages to Gemini format
352
+ gemini_contents: list[types.Content] = []
353
+ system_instruction = None
354
+
355
+ for msg in messages:
356
+ role = msg["role"]
357
+ if role == "system":
358
+ # Gemini uses system_instruction parameter
359
+ system_instruction = msg["content"]
360
+ elif role == "assistant":
361
+ gemini_contents.append(
362
+ types.Content(role="model", parts=[types.Part(text=msg["content"])])
363
+ )
364
+ else:
365
+ gemini_contents.append(
366
+ types.Content(role="user", parts=[types.Part(text=msg["content"])])
367
+ )
368
+
369
+ # Prepare tools for Gemini
370
+ gemini_tools = None
371
+ if tools:
372
+ function_declarations = []
373
+ for tool in tools:
374
+ openai_format = tool.to_openai()
375
+ func = openai_format["function"]
376
+
377
+ # Gemini uses slightly different schema format
378
+ params = self._convert_schema_for_gemini(func["parameters"])
379
+
380
+ function_declarations.append(
381
+ types.FunctionDeclaration(
382
+ name=func["name"],
383
+ description=func["description"],
384
+ parameters=params,
385
+ )
386
+ )
387
+
388
+ gemini_tools = [types.Tool(function_declarations=function_declarations)]
389
+
390
+ # Configure generation
391
+ generation_config = types.GenerateContentConfig(
392
+ temperature=self.config.temperature,
393
+ max_output_tokens=self.config.max_tokens,
394
+ top_p=self.config.top_p,
395
+ system_instruction=system_instruction,
396
+ tools=gemini_tools,
397
+ )
398
+
399
+ response = await self._client.aio.models.generate_content(
400
+ model=self.model_name,
401
+ contents=gemini_contents,
402
+ config=generation_config,
403
+ )
404
+
405
+ # Parse response
406
+ content = ""
407
+ tool_call = None
408
+ tool_calls: list[dict[str, Any]] = []
409
+
410
+ if response.candidates:
411
+ for part in response.candidates[0].content.parts:
412
+ if part.text:
413
+ content += part.text
414
+ elif part.function_call:
415
+ fc = part.function_call
416
+ parsed = {
417
+ "name": fc.name,
418
+ "arguments": dict(fc.args) if fc.args else {},
419
+ }
420
+ tool_calls.append(parsed)
421
+
422
+ tool_call = tool_calls[0] if tool_calls else None
423
+ finish_reason = response.candidates[0].finish_reason.name if response.candidates else None
424
+
425
+ return ModelResponse(
426
+ content=content,
427
+ tool_call=tool_call,
428
+ tool_calls=tool_calls if tool_calls else None,
429
+ raw_output=content,
430
+ finish_reason=finish_reason,
431
+ )
432
+
433
+ def _convert_schema_for_gemini(self, schema: dict[str, Any]) -> dict[str, Any]:
434
+ """Convert JSON Schema to Gemini-compatible format.
435
+
436
+ Gemini has specific requirements:
437
+ - Does not support additionalProperties
438
+ - Requires 'items' field for array types
439
+ - Handles nested schemas in anyOf, oneOf, allOf
440
+ """
441
+ if not isinstance(schema, dict):
442
+ return schema
443
+
444
+ result = dict(schema)
445
+
446
+ # Remove additionalProperties (not supported by Gemini)
447
+ if "additionalProperties" in result:
448
+ del result["additionalProperties"]
449
+
450
+ # Ensure array types have items defined (Gemini requires this)
451
+ # Check both explicit type and type within anyOf/oneOf
452
+ is_array = result.get("type") == "array"
453
+ if not is_array and "type" in result and isinstance(result["type"], list):
454
+ is_array = "array" in result["type"]
455
+
456
+ if is_array and "items" not in result:
457
+ result["items"] = {"type": "string"} # Default to string array
458
+
459
+ # Recursively process nested schemas in properties
460
+ if "properties" in result and isinstance(result["properties"], dict):
461
+ for prop_name, prop_schema in result["properties"].items():
462
+ if isinstance(prop_schema, dict):
463
+ result["properties"][prop_name] = self._convert_schema_for_gemini(prop_schema)
464
+
465
+ # Process items in arrays
466
+ if "items" in result and isinstance(result["items"], dict):
467
+ result["items"] = self._convert_schema_for_gemini(result["items"])
468
+
469
+ # Process anyOf, oneOf, allOf schemas
470
+ for key in ("anyOf", "oneOf", "allOf"):
471
+ if key in result and isinstance(result[key], list):
472
+ result[key] = [
473
+ self._convert_schema_for_gemini(sub_schema)
474
+ for sub_schema in result[key]
475
+ if isinstance(sub_schema, dict)
476
+ ]
477
+ # If any sub-schema is an array type, ensure it has items
478
+ for sub_schema in result[key]:
479
+ if isinstance(sub_schema, dict):
480
+ sub_is_array = sub_schema.get("type") == "array"
481
+ if sub_is_array and "items" not in sub_schema:
482
+ sub_schema["items"] = {"type": "string"}
483
+
484
+ # Process nested definitions/defs
485
+ for key in ("definitions", "$defs"):
486
+ if key in result and isinstance(result[key], dict):
487
+ result[key] = {
488
+ name: self._convert_schema_for_gemini(def_schema)
489
+ for name, def_schema in result[key].items()
490
+ if isinstance(def_schema, dict)
491
+ }
492
+
493
+ return result
494
+
495
+ def generate_batch(
496
+ self,
497
+ batch_messages: list[list[dict[str, str]]],
498
+ tools: list[ToolDefinition] | None = None,
499
+ ) -> list[ModelResponse]:
500
+ """Generate responses for a batch of message lists.
501
+
502
+ Uses asyncio.gather for parallel execution with rate limiting.
503
+
504
+ Args:
505
+ batch_messages: List of message lists
506
+ tools: Optional list of available tools
507
+
508
+ Returns:
509
+ List of ModelResponse objects
510
+ """
511
+ return asyncio.run(self._generate_batch_async(batch_messages, tools))
512
+
513
+ async def _generate_batch_async(
514
+ self,
515
+ batch_messages: list[list[dict[str, str]]],
516
+ tools: list[ToolDefinition] | None = None,
517
+ ) -> list[ModelResponse]:
518
+ """Generate batch responses using asyncio.gather."""
519
+ tasks = [self.generate_async(messages, tools) for messages in batch_messages]
520
+ return list(await asyncio.gather(*tasks))
521
+
522
+ def cleanup(self) -> None:
523
+ """Clean up resources.
524
+
525
+ Cloud clients don't typically need explicit cleanup.
526
+ """
527
+ pass