together 2.0.0a9__tar.gz → 2.0.0a11__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 (248) hide show
  1. together-2.0.0a11/.release-please-manifest.json +3 -0
  2. {together-2.0.0a9 → together-2.0.0a11}/CHANGELOG.md +38 -0
  3. {together-2.0.0a9 → together-2.0.0a11}/PKG-INFO +9 -11
  4. {together-2.0.0a9 → together-2.0.0a11}/README.md +8 -10
  5. {together-2.0.0a9 → together-2.0.0a11}/api.md +3 -1
  6. {together-2.0.0a9 → together-2.0.0a11}/pyproject.toml +1 -1
  7. {together-2.0.0a9 → together-2.0.0a11}/src/together/_base_client.py +8 -2
  8. {together-2.0.0a9 → together-2.0.0a11}/src/together/_types.py +3 -2
  9. {together-2.0.0a9 → together-2.0.0a11}/src/together/_version.py +1 -1
  10. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/cli/api/fine_tuning.py +76 -5
  11. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/cli/api/models.py +1 -6
  12. together-2.0.0a11/src/together/lib/cli/api/utils.py +139 -0
  13. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/constants.py +3 -0
  14. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/resources/files.py +65 -6
  15. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/resources/fine_tuning.py +41 -2
  16. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/types/fine_tuning.py +19 -0
  17. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/audio/transcriptions.py +6 -4
  18. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/audio/translations.py +6 -4
  19. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/chat/completions.py +48 -0
  20. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/fine_tuning.py +213 -5
  21. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/models.py +41 -5
  22. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/__init__.py +3 -0
  23. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/audio/transcription_create_params.py +5 -2
  24. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/audio/translation_create_params.py +5 -2
  25. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/audio/voice_list_response.py +4 -0
  26. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/autoscaling.py +2 -0
  27. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/autoscaling_param.py +2 -0
  28. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/chat/completion_create_params.py +78 -5
  29. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/dedicated_endpoint.py +2 -0
  30. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/endpoint_list_avzones_response.py +2 -0
  31. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/endpoint_list_response.py +2 -0
  32. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/execute_response.py +7 -0
  33. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/fine_tuning_cancel_response.py +20 -0
  34. together-2.0.0a11/src/together/types/fine_tuning_estimate_price_params.py +98 -0
  35. together-2.0.0a11/src/together/types/fine_tuning_estimate_price_response.py +24 -0
  36. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/fine_tuning_list_response.py +20 -0
  37. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/finetune_response.py +17 -2
  38. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/hardware_list_response.py +8 -0
  39. together-2.0.0a11/src/together/types/model_list_params.py +12 -0
  40. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/video_job.py +8 -0
  41. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/chat/test_completions.py +4 -16
  42. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/test_fine_tuning.py +97 -0
  43. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/test_models.py +14 -0
  44. together-2.0.0a11/tests/test_cli_utils.py +414 -0
  45. {together-2.0.0a9 → together-2.0.0a11}/tests/unit/test_fine_tuning_resources.py +8 -8
  46. {together-2.0.0a9 → together-2.0.0a11}/uv.lock +1 -1
  47. together-2.0.0a9/.release-please-manifest.json +0 -3
  48. together-2.0.0a9/src/together/lib/cli/api/utils.py +0 -50
  49. together-2.0.0a9/src/together/lib/resources/models.py +0 -35
  50. {together-2.0.0a9 → together-2.0.0a11}/.gitignore +0 -0
  51. {together-2.0.0a9 → together-2.0.0a11}/CONTRIBUTING.md +0 -0
  52. {together-2.0.0a9 → together-2.0.0a11}/LICENSE +0 -0
  53. {together-2.0.0a9 → together-2.0.0a11}/SECURITY.md +0 -0
  54. {together-2.0.0a9 → together-2.0.0a11}/bin/check-release-environment +0 -0
  55. {together-2.0.0a9 → together-2.0.0a11}/bin/publish-pypi +0 -0
  56. {together-2.0.0a9 → together-2.0.0a11}/examples/.keep +0 -0
  57. {together-2.0.0a9 → together-2.0.0a11}/examples/coqa-small.jsonl +0 -0
  58. {together-2.0.0a9 → together-2.0.0a11}/examples/coqa.jsonl +0 -0
  59. {together-2.0.0a9 → together-2.0.0a11}/examples/embedding.py +0 -0
  60. {together-2.0.0a9 → together-2.0.0a11}/examples/file-upload.py +0 -0
  61. {together-2.0.0a9 → together-2.0.0a11}/examples/files.py +0 -0
  62. {together-2.0.0a9 → together-2.0.0a11}/examples/fine_tuning.py +0 -0
  63. {together-2.0.0a9 → together-2.0.0a11}/examples/image.py +0 -0
  64. {together-2.0.0a9 → together-2.0.0a11}/examples/models.py +0 -0
  65. {together-2.0.0a9 → together-2.0.0a11}/examples/streaming.py +0 -0
  66. {together-2.0.0a9 → together-2.0.0a11}/release-please-config.json +0 -0
  67. {together-2.0.0a9 → together-2.0.0a11}/requirements-dev.lock +0 -0
  68. {together-2.0.0a9 → together-2.0.0a11}/src/together/__init__.py +0 -0
  69. {together-2.0.0a9 → together-2.0.0a11}/src/together/_client.py +0 -0
  70. {together-2.0.0a9 → together-2.0.0a11}/src/together/_compat.py +0 -0
  71. {together-2.0.0a9 → together-2.0.0a11}/src/together/_constants.py +0 -0
  72. {together-2.0.0a9 → together-2.0.0a11}/src/together/_exceptions.py +0 -0
  73. {together-2.0.0a9 → together-2.0.0a11}/src/together/_files.py +0 -0
  74. {together-2.0.0a9 → together-2.0.0a11}/src/together/_models.py +0 -0
  75. {together-2.0.0a9 → together-2.0.0a11}/src/together/_qs.py +0 -0
  76. {together-2.0.0a9 → together-2.0.0a11}/src/together/_resource.py +0 -0
  77. {together-2.0.0a9 → together-2.0.0a11}/src/together/_response.py +0 -0
  78. {together-2.0.0a9 → together-2.0.0a11}/src/together/_streaming.py +0 -0
  79. {together-2.0.0a9 → together-2.0.0a11}/src/together/_utils/__init__.py +0 -0
  80. {together-2.0.0a9 → together-2.0.0a11}/src/together/_utils/_compat.py +0 -0
  81. {together-2.0.0a9 → together-2.0.0a11}/src/together/_utils/_datetime_parse.py +0 -0
  82. {together-2.0.0a9 → together-2.0.0a11}/src/together/_utils/_logs.py +0 -0
  83. {together-2.0.0a9 → together-2.0.0a11}/src/together/_utils/_proxy.py +0 -0
  84. {together-2.0.0a9 → together-2.0.0a11}/src/together/_utils/_reflection.py +0 -0
  85. {together-2.0.0a9 → together-2.0.0a11}/src/together/_utils/_resources_proxy.py +0 -0
  86. {together-2.0.0a9 → together-2.0.0a11}/src/together/_utils/_streams.py +0 -0
  87. {together-2.0.0a9 → together-2.0.0a11}/src/together/_utils/_sync.py +0 -0
  88. {together-2.0.0a9 → together-2.0.0a11}/src/together/_utils/_transform.py +0 -0
  89. {together-2.0.0a9 → together-2.0.0a11}/src/together/_utils/_typing.py +0 -0
  90. {together-2.0.0a9 → together-2.0.0a11}/src/together/_utils/_utils.py +0 -0
  91. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/.keep +0 -0
  92. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/__init__.py +0 -0
  93. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/cli/__init__.py +0 -0
  94. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/cli/api/__init__.py +0 -0
  95. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/cli/api/endpoints.py +0 -0
  96. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/cli/api/evals.py +0 -0
  97. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/cli/api/files.py +0 -0
  98. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/cli/cli.py +0 -0
  99. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/resources/__init__.py +0 -0
  100. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/types/__init__.py +0 -0
  101. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/types/error.py +0 -0
  102. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/utils/__init__.py +0 -0
  103. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/utils/_log.py +0 -0
  104. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/utils/files.py +0 -0
  105. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/utils/serializer.py +0 -0
  106. {together-2.0.0a9 → together-2.0.0a11}/src/together/lib/utils/tools.py +0 -0
  107. {together-2.0.0a9 → together-2.0.0a11}/src/together/py.typed +0 -0
  108. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/__init__.py +0 -0
  109. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/audio/__init__.py +0 -0
  110. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/audio/audio.py +0 -0
  111. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/audio/speech.py +0 -0
  112. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/audio/voices.py +0 -0
  113. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/batches.py +0 -0
  114. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/chat/__init__.py +0 -0
  115. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/chat/chat.py +0 -0
  116. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/code_interpreter/__init__.py +0 -0
  117. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/code_interpreter/code_interpreter.py +0 -0
  118. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/code_interpreter/sessions.py +0 -0
  119. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/completions.py +0 -0
  120. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/embeddings.py +0 -0
  121. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/endpoints.py +0 -0
  122. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/evals.py +0 -0
  123. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/files.py +0 -0
  124. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/hardware.py +0 -0
  125. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/images.py +0 -0
  126. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/jobs.py +0 -0
  127. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/rerank.py +0 -0
  128. {together-2.0.0a9 → together-2.0.0a11}/src/together/resources/videos.py +0 -0
  129. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/audio/__init__.py +0 -0
  130. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/audio/speech_create_params.py +0 -0
  131. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/audio/transcription_create_response.py +0 -0
  132. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/audio/translation_create_response.py +0 -0
  133. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/audio_speech_stream_chunk.py +0 -0
  134. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/batch_create_params.py +0 -0
  135. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/batch_create_response.py +0 -0
  136. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/batch_job.py +0 -0
  137. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/batch_list_response.py +0 -0
  138. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/chat/__init__.py +0 -0
  139. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/chat/chat_completion.py +0 -0
  140. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/chat/chat_completion_chunk.py +0 -0
  141. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/chat/chat_completion_structured_message_image_url_param.py +0 -0
  142. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/chat/chat_completion_structured_message_text_param.py +0 -0
  143. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/chat/chat_completion_structured_message_video_url_param.py +0 -0
  144. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/chat/chat_completion_usage.py +0 -0
  145. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/chat/chat_completion_warning.py +0 -0
  146. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/code_interpreter/__init__.py +0 -0
  147. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/code_interpreter/session_list_response.py +0 -0
  148. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/code_interpreter_execute_params.py +0 -0
  149. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/completion.py +0 -0
  150. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/completion_chunk.py +0 -0
  151. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/completion_create_params.py +0 -0
  152. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/embedding.py +0 -0
  153. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/embedding_create_params.py +0 -0
  154. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/endpoint_create_params.py +0 -0
  155. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/endpoint_list_params.py +0 -0
  156. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/endpoint_update_params.py +0 -0
  157. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/eval_create_params.py +0 -0
  158. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/eval_create_response.py +0 -0
  159. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/eval_list_params.py +0 -0
  160. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/eval_list_response.py +0 -0
  161. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/eval_status_response.py +0 -0
  162. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/evaluation_job.py +0 -0
  163. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/file_delete_response.py +0 -0
  164. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/file_list.py +0 -0
  165. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/file_purpose.py +0 -0
  166. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/file_response.py +0 -0
  167. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/file_type.py +0 -0
  168. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/fine_tuning_content_params.py +0 -0
  169. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/fine_tuning_delete_params.py +0 -0
  170. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/fine_tuning_delete_response.py +0 -0
  171. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/fine_tuning_list_checkpoints_response.py +0 -0
  172. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/fine_tuning_list_events_response.py +0 -0
  173. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/finetune_event.py +0 -0
  174. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/finetune_event_type.py +0 -0
  175. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/hardware_list_params.py +0 -0
  176. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/image_data_b64.py +0 -0
  177. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/image_data_url.py +0 -0
  178. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/image_file.py +0 -0
  179. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/image_generate_params.py +0 -0
  180. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/job_list_response.py +0 -0
  181. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/job_retrieve_response.py +0 -0
  182. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/log_probs.py +0 -0
  183. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/model_list_response.py +0 -0
  184. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/model_object.py +0 -0
  185. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/model_upload_params.py +0 -0
  186. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/model_upload_response.py +0 -0
  187. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/rerank_create_params.py +0 -0
  188. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/rerank_create_response.py +0 -0
  189. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/tool_choice.py +0 -0
  190. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/tool_choice_param.py +0 -0
  191. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/tools_param.py +0 -0
  192. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/training_method_dpo.py +0 -0
  193. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/training_method_sft.py +0 -0
  194. {together-2.0.0a9 → together-2.0.0a11}/src/together/types/video_create_params.py +0 -0
  195. {together-2.0.0a9 → together-2.0.0a11}/tests/__init__.py +0 -0
  196. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/__init__.py +0 -0
  197. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/audio/__init__.py +0 -0
  198. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/audio/test_speech.py +0 -0
  199. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/audio/test_transcriptions.py +0 -0
  200. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/audio/test_translations.py +0 -0
  201. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/audio/test_voices.py +0 -0
  202. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/chat/__init__.py +0 -0
  203. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/code_interpreter/__init__.py +0 -0
  204. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/code_interpreter/test_sessions.py +0 -0
  205. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/test_batches.py +0 -0
  206. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/test_code_interpreter.py +0 -0
  207. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/test_completions.py +0 -0
  208. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/test_embeddings.py +0 -0
  209. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/test_endpoints.py +0 -0
  210. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/test_evals.py +0 -0
  211. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/test_files.py +0 -0
  212. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/test_hardware.py +0 -0
  213. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/test_images.py +0 -0
  214. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/test_jobs.py +0 -0
  215. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/test_rerank.py +0 -0
  216. {together-2.0.0a9 → together-2.0.0a11}/tests/api_resources/test_videos.py +0 -0
  217. {together-2.0.0a9 → together-2.0.0a11}/tests/conftest.py +0 -0
  218. {together-2.0.0a9 → together-2.0.0a11}/tests/integration/__init__.py +0 -0
  219. {together-2.0.0a9 → together-2.0.0a11}/tests/integration/constants.py +0 -0
  220. {together-2.0.0a9 → together-2.0.0a11}/tests/integration/resources/__init__.py +0 -0
  221. {together-2.0.0a9 → together-2.0.0a11}/tests/integration/resources/generate_hyperparameters.py +0 -0
  222. {together-2.0.0a9 → together-2.0.0a11}/tests/integration/resources/test_completion.py +0 -0
  223. {together-2.0.0a9 → together-2.0.0a11}/tests/integration/resources/test_completion_stream.py +0 -0
  224. {together-2.0.0a9 → together-2.0.0a11}/tests/integration/resources/test_files.py +0 -0
  225. {together-2.0.0a9 → together-2.0.0a11}/tests/sample_file.txt +0 -0
  226. {together-2.0.0a9 → together-2.0.0a11}/tests/test_client.py +0 -0
  227. {together-2.0.0a9 → together-2.0.0a11}/tests/test_deepcopy.py +0 -0
  228. {together-2.0.0a9 → together-2.0.0a11}/tests/test_extract_files.py +0 -0
  229. {together-2.0.0a9 → together-2.0.0a11}/tests/test_files.py +0 -0
  230. {together-2.0.0a9 → together-2.0.0a11}/tests/test_models.py +0 -0
  231. {together-2.0.0a9 → together-2.0.0a11}/tests/test_qs.py +0 -0
  232. {together-2.0.0a9 → together-2.0.0a11}/tests/test_required_args.py +0 -0
  233. {together-2.0.0a9 → together-2.0.0a11}/tests/test_response.py +0 -0
  234. {together-2.0.0a9 → together-2.0.0a11}/tests/test_streaming.py +0 -0
  235. {together-2.0.0a9 → together-2.0.0a11}/tests/test_transform.py +0 -0
  236. {together-2.0.0a9 → together-2.0.0a11}/tests/test_utils/test_datetime_parse.py +0 -0
  237. {together-2.0.0a9 → together-2.0.0a11}/tests/test_utils/test_proxy.py +0 -0
  238. {together-2.0.0a9 → together-2.0.0a11}/tests/test_utils/test_typing.py +0 -0
  239. {together-2.0.0a9 → together-2.0.0a11}/tests/unit/test_async_client.py +0 -0
  240. {together-2.0.0a9 → together-2.0.0a11}/tests/unit/test_client.py +0 -0
  241. {together-2.0.0a9 → together-2.0.0a11}/tests/unit/test_code_interpreter.py +0 -0
  242. {together-2.0.0a9 → together-2.0.0a11}/tests/unit/test_files_checks.py +0 -0
  243. {together-2.0.0a9 → together-2.0.0a11}/tests/unit/test_files_resource.py +0 -0
  244. {together-2.0.0a9 → together-2.0.0a11}/tests/unit/test_imports.py +0 -0
  245. {together-2.0.0a9 → together-2.0.0a11}/tests/unit/test_multipart_upload_manager.py +0 -0
  246. {together-2.0.0a9 → together-2.0.0a11}/tests/unit/test_preference_openai.py +0 -0
  247. {together-2.0.0a9 → together-2.0.0a11}/tests/unit/test_video_url.py +0 -0
  248. {together-2.0.0a9 → together-2.0.0a11}/tests/utils.py +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "2.0.0-alpha.11"
3
+ }
@@ -1,5 +1,43 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.0-alpha.11 (2025-12-16)
4
+
5
+ Full Changelog: [v2.0.0-alpha.10...v2.0.0-alpha.11](https://github.com/togethercomputer/together-py/compare/v2.0.0-alpha.10...v2.0.0-alpha.11)
6
+
7
+ ### Features
8
+
9
+ * **api:** api update ([17ad3ec](https://github.com/togethercomputer/together-py/commit/17ad3ec91a06a7e886252d4b688c3a9e217a3799))
10
+ * **api:** api update ([ebc3414](https://github.com/togethercomputer/together-py/commit/ebc3414e28db0309fef5aeed456e242048b5d13c))
11
+ * **files:** add support for string alternative to file upload type ([db59ed6](https://github.com/togethercomputer/together-py/commit/db59ed6235f2e18db100a72084c2fefc22354d15))
12
+
13
+
14
+ ### Chores
15
+
16
+ * **internal:** add missing files argument to base client ([6977285](https://github.com/togethercomputer/together-py/commit/69772856908b8378c74eed382735523e91011d90))
17
+
18
+ ## 2.0.0-alpha.10 (2025-12-15)
19
+
20
+ Full Changelog: [v2.0.0-alpha.9...v2.0.0-alpha.10](https://github.com/togethercomputer/together-py/compare/v2.0.0-alpha.9...v2.0.0-alpha.10)
21
+
22
+ ### Features
23
+
24
+ * **api:** Add fine_tuning.estimate_price api ([1582cc4](https://github.com/togethercomputer/together-py/commit/1582cc498e17562a3a23ae5120dfff2d39ae1e41))
25
+ * **api:** api update ([5341347](https://github.com/togethercomputer/together-py/commit/53413475daeeec382968407d47688cf7926f643c))
26
+ * **api:** api update ([96fc9b3](https://github.com/togethercomputer/together-py/commit/96fc9b3b1218bcf0c8dd13a28b8eab5c9690c6fd))
27
+ * **api:** api update ([e5cfa45](https://github.com/togethercomputer/together-py/commit/e5cfa45f476c77965a9249e9ae41b55b029abfaa))
28
+
29
+
30
+ ### Bug Fixes
31
+
32
+ * **types:** allow pyright to infer TypedDict types within SequenceNotStr ([048f2b7](https://github.com/togethercomputer/together-py/commit/048f2b7d347aa2ab09a4b49c2770cbf15a70c3e4))
33
+
34
+
35
+ ### Chores
36
+
37
+ * add missing docstrings ([a1c8329](https://github.com/togethercomputer/together-py/commit/a1c8329a0c2562bcdbd22c262eb7a995bfbd0deb))
38
+ * **internal:** avoid using unstable Python versions in tests ([6268112](https://github.com/togethercomputer/together-py/commit/62681124a807a4f718e1711039242d2b9037e33b))
39
+ * Update model list CLI to use api parameter for dedicated filtering ([#195](https://github.com/togethercomputer/together-py/issues/195)) ([95cc672](https://github.com/togethercomputer/together-py/commit/95cc672583e2a908f54dd557cd0f22465da26a4b))
40
+
3
41
  ## 2.0.0-alpha.9 (2025-12-03)
4
42
 
5
43
  Full Changelog: [v2.0.0-alpha.8...v2.0.0-alpha.9](https://github.com/togethercomputer/together-py/compare/v2.0.0-alpha.8...v2.0.0-alpha.9)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: together
3
- Version: 2.0.0a9
3
+ Version: 2.0.0a11
4
4
  Summary: The official Python library for the together API
5
5
  Project-URL: Homepage, https://github.com/togethercomputer/together-py
6
6
  Project-URL: Repository, https://github.com/togethercomputer/together-py
@@ -232,17 +232,15 @@ from together import Together
232
232
 
233
233
  client = Together()
234
234
 
235
- chat_completion = client.chat.completions.create(
236
- messages=[
237
- {
238
- "content": "content",
239
- "role": "system",
240
- }
241
- ],
242
- model="meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo",
243
- response_format={},
235
+ dedicated_endpoint = client.endpoints.create(
236
+ autoscaling={
237
+ "max_replicas": 5,
238
+ "min_replicas": 2,
239
+ },
240
+ hardware="1x_nvidia_a100_80gb_sxm",
241
+ model="meta-llama/Llama-3-8b-chat-hf",
244
242
  )
245
- print(chat_completion.response_format)
243
+ print(dedicated_endpoint.autoscaling)
246
244
  ```
247
245
 
248
246
  The async client uses the exact same interface. If you pass a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance, the file contents will be read asynchronously automatically.
@@ -186,17 +186,15 @@ from together import Together
186
186
 
187
187
  client = Together()
188
188
 
189
- chat_completion = client.chat.completions.create(
190
- messages=[
191
- {
192
- "content": "content",
193
- "role": "system",
194
- }
195
- ],
196
- model="meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo",
197
- response_format={},
189
+ dedicated_endpoint = client.endpoints.create(
190
+ autoscaling={
191
+ "max_replicas": 5,
192
+ "min_replicas": 2,
193
+ },
194
+ hardware="1x_nvidia_a100_80gb_sxm",
195
+ model="meta-llama/Llama-3-8b-chat-hf",
198
196
  )
199
- print(chat_completion.response_format)
197
+ print(dedicated_endpoint.autoscaling)
200
198
  ```
201
199
 
202
200
  The async client uses the exact same interface. If you pass a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance, the file contents will be read asynchronously automatically.
@@ -78,6 +78,7 @@ from together.types import (
78
78
  FineTuningListResponse,
79
79
  FineTuningDeleteResponse,
80
80
  FineTuningCancelResponse,
81
+ FineTuningEstimatePriceResponse,
81
82
  FineTuningListCheckpointsResponse,
82
83
  FineTuningListEventsResponse,
83
84
  )
@@ -90,6 +91,7 @@ Methods:
90
91
  - <code title="delete /fine-tunes/{id}">client.fine_tuning.<a href="./src/together/resources/fine_tuning.py">delete</a>(id, \*\*<a href="src/together/types/fine_tuning_delete_params.py">params</a>) -> <a href="./src/together/types/fine_tuning_delete_response.py">FineTuningDeleteResponse</a></code>
91
92
  - <code title="post /fine-tunes/{id}/cancel">client.fine_tuning.<a href="./src/together/resources/fine_tuning.py">cancel</a>(id) -> <a href="./src/together/types/fine_tuning_cancel_response.py">FineTuningCancelResponse</a></code>
92
93
  - <code title="get /finetune/download">client.fine_tuning.<a href="./src/together/resources/fine_tuning.py">content</a>(\*\*<a href="src/together/types/fine_tuning_content_params.py">params</a>) -> BinaryAPIResponse</code>
94
+ - <code title="post /fine-tunes/estimate-price">client.fine_tuning.<a href="./src/together/resources/fine_tuning.py">estimate_price</a>(\*\*<a href="src/together/types/fine_tuning_estimate_price_params.py">params</a>) -> <a href="./src/together/types/fine_tuning_estimate_price_response.py">FineTuningEstimatePriceResponse</a></code>
93
95
  - <code title="get /fine-tunes/{id}/checkpoints">client.fine_tuning.<a href="./src/together/resources/fine_tuning.py">list_checkpoints</a>(id) -> <a href="./src/together/types/fine_tuning_list_checkpoints_response.py">FineTuningListCheckpointsResponse</a></code>
94
96
  - <code title="get /fine-tunes/{id}/events">client.fine_tuning.<a href="./src/together/resources/fine_tuning.py">list_events</a>(id) -> <a href="./src/together/types/fine_tuning_list_events_response.py">FineTuningListEventsResponse</a></code>
95
97
 
@@ -202,7 +204,7 @@ from together.types import ModelObject, ModelListResponse, ModelUploadResponse
202
204
 
203
205
  Methods:
204
206
 
205
- - <code title="get /models">client.models.<a href="./src/together/resources/models.py">list</a>() -> <a href="./src/together/types/model_list_response.py">ModelListResponse</a></code>
207
+ - <code title="get /models">client.models.<a href="./src/together/resources/models.py">list</a>(\*\*<a href="src/together/types/model_list_params.py">params</a>) -> <a href="./src/together/types/model_list_response.py">ModelListResponse</a></code>
206
208
  - <code title="post /models">client.models.<a href="./src/together/resources/models.py">upload</a>(\*\*<a href="src/together/types/model_upload_params.py">params</a>) -> <a href="./src/together/types/model_upload_response.py">ModelUploadResponse</a></code>
207
209
 
208
210
  # Jobs
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "together"
3
- version = "2.0.0-alpha.9"
3
+ version = "2.0.0-alpha.11"
4
4
  description = "The official Python library for the together API"
5
5
  dynamic = ["readme"]
6
6
  license = "Apache-2.0"
@@ -1247,9 +1247,12 @@ class SyncAPIClient(BaseClient[httpx.Client, Stream[Any]]):
1247
1247
  *,
1248
1248
  cast_to: Type[ResponseT],
1249
1249
  body: Body | None = None,
1250
+ files: RequestFiles | None = None,
1250
1251
  options: RequestOptions = {},
1251
1252
  ) -> ResponseT:
1252
- opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options)
1253
+ opts = FinalRequestOptions.construct(
1254
+ method="patch", url=path, json_data=body, files=to_httpx_files(files), **options
1255
+ )
1253
1256
  return self.request(cast_to, opts)
1254
1257
 
1255
1258
  def put(
@@ -1767,9 +1770,12 @@ class AsyncAPIClient(BaseClient[httpx.AsyncClient, AsyncStream[Any]]):
1767
1770
  *,
1768
1771
  cast_to: Type[ResponseT],
1769
1772
  body: Body | None = None,
1773
+ files: RequestFiles | None = None,
1770
1774
  options: RequestOptions = {},
1771
1775
  ) -> ResponseT:
1772
- opts = FinalRequestOptions.construct(method="patch", url=path, json_data=body, **options)
1776
+ opts = FinalRequestOptions.construct(
1777
+ method="patch", url=path, json_data=body, files=to_httpx_files(files), **options
1778
+ )
1773
1779
  return await self.request(cast_to, opts)
1774
1780
 
1775
1781
  async def put(
@@ -243,6 +243,9 @@ _T_co = TypeVar("_T_co", covariant=True)
243
243
  if TYPE_CHECKING:
244
244
  # This works because str.__contains__ does not accept object (either in typeshed or at runtime)
245
245
  # https://github.com/hauntsaninja/useful_types/blob/5e9710f3875107d068e7679fd7fec9cfab0eff3b/useful_types/__init__.py#L285
246
+ #
247
+ # Note: index() and count() methods are intentionally omitted to allow pyright to properly
248
+ # infer TypedDict types when dict literals are used in lists assigned to SequenceNotStr.
246
249
  class SequenceNotStr(Protocol[_T_co]):
247
250
  @overload
248
251
  def __getitem__(self, index: SupportsIndex, /) -> _T_co: ...
@@ -251,8 +254,6 @@ if TYPE_CHECKING:
251
254
  def __contains__(self, value: object, /) -> bool: ...
252
255
  def __len__(self) -> int: ...
253
256
  def __iter__(self) -> Iterator[_T_co]: ...
254
- def index(self, value: Any, start: int = 0, stop: int = ..., /) -> int: ...
255
- def count(self, value: Any, /) -> int: ...
256
257
  def __reversed__(self) -> Iterator[_T_co]: ...
257
258
  else:
258
259
  # just point this to a normal `Sequence` at runtime to avoid having to special case
@@ -1,4 +1,4 @@
1
1
  # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
2
 
3
3
  __title__ = "together"
4
- __version__ = "2.0.0-alpha.9" # x-release-please-version
4
+ __version__ = "2.0.0-alpha.11" # x-release-please-version
@@ -10,13 +10,15 @@ from textwrap import wrap
10
10
  import click
11
11
  from rich import print as rprint
12
12
  from tabulate import tabulate
13
+ from rich.json import JSON
13
14
  from click.core import ParameterSource # type: ignore[attr-defined]
14
15
 
15
16
  from together import Together
17
+ from together.types import fine_tuning_estimate_price_params as pe_params
16
18
  from together._types import NOT_GIVEN, NotGiven
17
19
  from together.lib.utils import log_warn
18
20
  from together.lib.utils.tools import format_timestamp, finetune_price_to_dollars
19
- from together.lib.cli.api.utils import INT_WITH_MAX, BOOL_WITH_AUTO
21
+ from together.lib.cli.api.utils import INT_WITH_MAX, BOOL_WITH_AUTO, generate_progress_bar
20
22
  from together.lib.resources.files import DownloadManager
21
23
  from together.lib.utils.serializer import datetime_serializer
22
24
  from together.types.finetune_response import TrainingTypeFullTrainingType, TrainingTypeLoRaTrainingType
@@ -24,13 +26,21 @@ from together.lib.resources.fine_tuning import get_model_limits
24
26
 
25
27
  _CONFIRMATION_MESSAGE = (
26
28
  "You are about to create a fine-tuning job. "
27
- "The cost of your job will be determined by the model size, the number of tokens "
29
+ "The estimated price of this job is {price}. "
30
+ "The actual cost of your job will be determined by the model size, the number of tokens "
28
31
  "in the training file, the number of tokens in the validation file, the number of epochs, and "
29
- "the number of evaluations. Visit https://www.together.ai/pricing to get a price estimate.\n"
32
+ "the number of evaluations. Visit https://www.together.ai/pricing to learn more about pricing.\n"
33
+ "{warning}"
30
34
  "You can pass `-y` or `--confirm` to your command to skip this message.\n\n"
31
35
  "Do you want to proceed?"
32
36
  )
33
37
 
38
+ _WARNING_MESSAGE_INSUFFICIENT_FUNDS = (
39
+ "The estimated price of this job is significantly greater than your current credit limit and balance combined. "
40
+ "It will likely get cancelled due to insufficient funds. "
41
+ "Consider increasing your credit limit at https://api.together.xyz/settings/profile\n"
42
+ )
43
+
34
44
  _FT_JOB_WITH_STEP_REGEX = r"^ft-[\dabcdef-]+:\d+$"
35
45
 
36
46
 
@@ -323,7 +333,60 @@ def create(
323
333
  elif n_evals > 0 and not validation_file:
324
334
  raise click.BadParameter("You have specified a number of evaluation loops but no validation file.")
325
335
 
326
- if confirm or click.confirm(_CONFIRMATION_MESSAGE, default=True, show_default=True):
336
+ training_type_cls: pe_params.TrainingType
337
+ if lora:
338
+ training_type_cls = pe_params.TrainingTypeLoRaTrainingType(
339
+ lora_alpha=int(lora_alpha or 0),
340
+ lora_r=lora_r or 0,
341
+ lora_dropout=lora_dropout or 0,
342
+ lora_trainable_modules=lora_trainable_modules or "all-linear",
343
+ type="Lora",
344
+ )
345
+ else:
346
+ training_type_cls = pe_params.TrainingTypeFullTrainingType(
347
+ type="Full",
348
+ )
349
+
350
+ training_method_cls: pe_params.TrainingMethod
351
+ if training_method == "sft":
352
+ training_method_cls = pe_params.TrainingMethodTrainingMethodSft(
353
+ method="sft",
354
+ train_on_inputs=train_on_inputs or "auto",
355
+ )
356
+ else:
357
+ training_method_cls = pe_params.TrainingMethodTrainingMethodDpo(
358
+ method="dpo",
359
+ dpo_beta=dpo_beta or 0,
360
+ dpo_normalize_logratios_by_length=dpo_normalize_logratios_by_length or False,
361
+ dpo_reference_free=False,
362
+ rpo_alpha=rpo_alpha or 0,
363
+ simpo_gamma=simpo_gamma or 0,
364
+ )
365
+
366
+ finetune_price_estimation_result = client.fine_tuning.estimate_price(
367
+ training_file=training_file,
368
+ validation_file=validation_file,
369
+ model=model or "",
370
+ n_epochs=n_epochs,
371
+ n_evals=n_evals,
372
+ training_type=training_type_cls,
373
+ training_method=training_method_cls,
374
+ )
375
+ price = click.style(
376
+ f"${finetune_price_estimation_result.estimated_total_price:.2f}",
377
+ bold=True,
378
+ )
379
+ if not finetune_price_estimation_result.allowed_to_proceed:
380
+ warning = click.style(_WARNING_MESSAGE_INSUFFICIENT_FUNDS, fg="red", bold=True)
381
+ else:
382
+ warning = ""
383
+
384
+ confirmation_message = _CONFIRMATION_MESSAGE.format(
385
+ price=price,
386
+ warning=warning,
387
+ )
388
+
389
+ if confirm or click.confirm(confirmation_message, default=True, show_default=True):
327
390
  response = client.fine_tuning.create(
328
391
  **training_args,
329
392
  verbose=True,
@@ -363,6 +426,9 @@ def list(ctx: click.Context) -> None:
363
426
  "Price": f"""${
364
427
  finetune_price_to_dollars(float(str(i.total_price)))
365
428
  }""", # convert to string for mypy typing
429
+ "Progress": generate_progress_bar(
430
+ i, datetime.now().astimezone(), use_rich=False
431
+ ),
366
432
  }
367
433
  )
368
434
  table = tabulate(display_list, headers="keys", tablefmt="grid", showindex=True)
@@ -382,7 +448,12 @@ def retrieve(ctx: click.Context, fine_tune_id: str) -> None:
382
448
  # remove events from response for cleaner output
383
449
  response.events = None
384
450
 
385
- click.echo(json.dumps(response.model_dump(exclude_none=True), indent=4))
451
+ rprint(JSON.from_data(response.model_json_schema()))
452
+ progress_text = generate_progress_bar(
453
+ response, datetime.now().astimezone(), use_rich=True
454
+ )
455
+ prefix = f"Status: [bold]{response.status}[/bold],"
456
+ rprint(f"{prefix} {progress_text}")
386
457
 
387
458
 
388
459
  @fine_tuning.command()
@@ -7,7 +7,6 @@ from tabulate import tabulate
7
7
  from together import Together, omit
8
8
  from together._models import BaseModel
9
9
  from together._response import APIResponse as APIResponse
10
- from together.lib.resources.models import filter_by_dedicated_models
11
10
  from together.types.model_upload_response import ModelUploadResponse
12
11
 
13
12
 
@@ -34,11 +33,7 @@ def list(ctx: click.Context, type: Optional[str], json: bool) -> None:
34
33
  """List models"""
35
34
  client: Together = ctx.obj
36
35
 
37
- response = client.models.list()
38
- models_list = response
39
-
40
- if type == "dedicated":
41
- models_list = filter_by_dedicated_models(client, models_list)
36
+ models_list = client.models.list(dedicated=type == "dedicated" if type else omit)
42
37
 
43
38
  display_list: List[Dict[str, Any]] = []
44
39
  model: BaseModel
@@ -0,0 +1,139 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import math
5
+ from typing import List, Union, Literal
6
+ from gettext import gettext as _
7
+ from datetime import datetime
8
+
9
+ import click
10
+
11
+ from together.lib.types.fine_tuning import COMPLETED_STATUSES, FinetuneResponse
12
+ from together.types.finetune_response import FinetuneResponse as _FinetuneResponse
13
+ from together.types.fine_tuning_list_response import Data
14
+
15
+ _PROGRESS_BAR_WIDTH = 40
16
+
17
+
18
+ class AutoIntParamType(click.ParamType):
19
+ name = "integer_or_max"
20
+ _number_class = int
21
+
22
+ def convert( # pyright: ignore[reportImplicitOverride]
23
+ self, value: str, param: click.Parameter | None, ctx: click.Context | None
24
+ ) -> int | Literal["max"] | None:
25
+ if value == "max":
26
+ return "max"
27
+ try:
28
+ return int(value)
29
+ except ValueError:
30
+ self.fail(
31
+ _("{value!r} is not a valid {number_type}.").format(
32
+ value=value, number_type=self.name
33
+ ),
34
+ param,
35
+ ctx,
36
+ )
37
+
38
+
39
+ class BooleanWithAutoParamType(click.ParamType):
40
+ name = "boolean_or_auto"
41
+
42
+ def convert( # pyright: ignore[reportImplicitOverride]
43
+ self, value: str, param: click.Parameter | None, ctx: click.Context | None
44
+ ) -> bool | Literal["auto"] | None:
45
+ if value == "auto":
46
+ return "auto"
47
+ try:
48
+ return bool(value)
49
+ except ValueError:
50
+ self.fail(
51
+ _("{value!r} is not a valid {type}.").format(
52
+ value=value, type=self.name
53
+ ),
54
+ param,
55
+ ctx,
56
+ )
57
+
58
+
59
+ INT_WITH_MAX = AutoIntParamType()
60
+ BOOL_WITH_AUTO = BooleanWithAutoParamType()
61
+
62
+
63
+ def _human_readable_time(timedelta: float) -> str:
64
+ """Convert a timedelta to a compact human-readble string
65
+ Examples:
66
+ 00:00:10 -> 10s
67
+ 01:23:45 -> 1h 23min 45s
68
+ 1 Month 23 days 04:56:07 -> 1month 23d 4h 56min 7s
69
+ Args:
70
+ timedelta (float): The timedelta in seconds to convert.
71
+ Returns:
72
+ A string representing the timedelta in a human-readable format.
73
+ """
74
+ units = [
75
+ (30 * 24 * 60 * 60, "month"), # 30 days
76
+ (24 * 60 * 60, "d"),
77
+ (60 * 60, "h"),
78
+ (60, "min"),
79
+ (1, "s"),
80
+ ]
81
+
82
+ total_seconds = int(timedelta)
83
+ parts: List[str] = []
84
+
85
+ for unit_seconds, unit_name in units:
86
+ if total_seconds >= unit_seconds:
87
+ value = total_seconds // unit_seconds
88
+ total_seconds %= unit_seconds
89
+ parts.append(f"{value}{unit_name}")
90
+
91
+ return " ".join(parts) if parts else "0s"
92
+
93
+
94
+ def generate_progress_bar(
95
+ finetune_job: Union[Data, FinetuneResponse, _FinetuneResponse], current_time: datetime, use_rich: bool = False
96
+ ) -> str:
97
+ """Generate a progress bar for a finetune job.
98
+ Args:
99
+ finetune_job: The finetune job to generate a progress bar for.
100
+ current_time: The current time.
101
+ use_rich: Whether to use rich formatting.
102
+ Returns:
103
+ A string representing the progress bar.
104
+ """
105
+ progress = "Progress: [bold red]unavailable[/bold red]"
106
+ if finetune_job.status in COMPLETED_STATUSES:
107
+ progress = "Progress: [bold green]completed[/bold green]"
108
+ elif finetune_job.updated_at is not None:
109
+ update_at = finetune_job.updated_at.astimezone()
110
+
111
+ if finetune_job.progress is not None:
112
+ if current_time < update_at:
113
+ return progress
114
+
115
+ if not finetune_job.progress.estimate_available:
116
+ return progress
117
+
118
+ if finetune_job.progress.seconds_remaining <= 0:
119
+ return progress
120
+
121
+ elapsed_time = (current_time - update_at).total_seconds()
122
+ ratio_filled = min(
123
+ elapsed_time / finetune_job.progress.seconds_remaining, 1.0
124
+ )
125
+ percentage = ratio_filled * 100
126
+ filled = math.ceil(ratio_filled * _PROGRESS_BAR_WIDTH)
127
+ bar = "█" * filled + "░" * (_PROGRESS_BAR_WIDTH - filled)
128
+ time_left = "N/A"
129
+ if finetune_job.progress.seconds_remaining > elapsed_time:
130
+ time_left = _human_readable_time(
131
+ finetune_job.progress.seconds_remaining - elapsed_time
132
+ )
133
+ time_text = f"{time_left} left"
134
+ progress = f"Progress: {bar} [bold]{percentage:>3.0f}%[/bold] [yellow]{time_text}[/yellow]"
135
+
136
+ if use_rich:
137
+ return progress
138
+
139
+ return re.sub(r"\[/?[^\]]+\]", "", progress)
@@ -14,6 +14,9 @@ import enum
14
14
  # Download defaults
15
15
  DOWNLOAD_BLOCK_SIZE = 10 * 1024 * 1024 # 10 MB
16
16
  DISABLE_TQDM = False
17
+ MAX_DOWNLOAD_RETRIES = 5 # Maximum retries for download failures
18
+ DOWNLOAD_INITIAL_RETRY_DELAY = 1.0 # Initial retry delay in seconds
19
+ DOWNLOAD_MAX_RETRY_DELAY = 30.0 # Maximum retry delay in seconds
17
20
 
18
21
  # Upload defaults
19
22
  MAX_CONCURRENT_PARTS = 4 # Maximum concurrent parts for multipart upload
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import os
4
4
  import math
5
5
  import stat
6
+ import time
6
7
  import uuid
7
8
  import shutil
8
9
  import asyncio
@@ -29,12 +30,15 @@ from ..constants import (
29
30
  MAX_MULTIPART_PARTS,
30
31
  TARGET_PART_SIZE_MB,
31
32
  MAX_CONCURRENT_PARTS,
33
+ MAX_DOWNLOAD_RETRIES,
32
34
  MULTIPART_THRESHOLD_GB,
35
+ DOWNLOAD_MAX_RETRY_DELAY,
33
36
  MULTIPART_UPLOAD_TIMEOUT,
37
+ DOWNLOAD_INITIAL_RETRY_DELAY,
34
38
  )
35
39
  from ..._resource import SyncAPIResource, AsyncAPIResource
36
40
  from ..types.error import DownloadError, FileTypeError
37
- from ..._exceptions import APIStatusError, AuthenticationError
41
+ from ..._exceptions import APIStatusError, APIConnectionError, AuthenticationError
38
42
 
39
43
  log: logging.Logger = logging.getLogger(__name__)
40
44
 
@@ -198,6 +202,11 @@ class DownloadManager(SyncAPIResource):
198
202
 
199
203
  assert file_size != 0, "Unable to retrieve remote file."
200
204
 
205
+ # Download with retry logic
206
+ bytes_downloaded = 0
207
+ retry_count = 0
208
+ retry_delay = DOWNLOAD_INITIAL_RETRY_DELAY
209
+
201
210
  with tqdm(
202
211
  total=file_size,
203
212
  unit="B",
@@ -205,14 +214,64 @@ class DownloadManager(SyncAPIResource):
205
214
  desc=f"Downloading file {file_path.name}",
206
215
  disable=bool(DISABLE_TQDM),
207
216
  ) as pbar:
208
- for chunk in response.iter_bytes(DOWNLOAD_BLOCK_SIZE):
209
- pbar.update(len(chunk))
210
- temp_file.write(chunk) # type: ignore
217
+ while bytes_downloaded < file_size:
218
+ try:
219
+ # If this is a retry, close the previous response and create a new one with Range header
220
+ if bytes_downloaded > 0:
221
+ response.close()
222
+
223
+ log.info(f"Resuming download from byte {bytes_downloaded}")
224
+ response = self._client.get(
225
+ path=url,
226
+ cast_to=httpx.Response,
227
+ stream=True,
228
+ options=RequestOptions(
229
+ headers={"Range": f"bytes={bytes_downloaded}-"},
230
+ ),
231
+ )
232
+
233
+ # Download chunks
234
+ for chunk in response.iter_bytes(DOWNLOAD_BLOCK_SIZE):
235
+ temp_file.write(chunk) # type: ignore
236
+ bytes_downloaded += len(chunk)
237
+ pbar.update(len(chunk))
238
+
239
+ # Successfully completed download
240
+ break
241
+
242
+ except (httpx.RequestError, httpx.StreamError, APIConnectionError) as e:
243
+ if retry_count >= MAX_DOWNLOAD_RETRIES:
244
+ log.error(f"Download failed after {retry_count} retries")
245
+ raise DownloadError(
246
+ f"Download failed after {retry_count} retries. Last error: {str(e)}"
247
+ ) from e
248
+
249
+ retry_count += 1
250
+ log.warning(
251
+ f"Download interrupted at {bytes_downloaded}/{file_size} bytes. "
252
+ f"Retry {retry_count}/{MAX_DOWNLOAD_RETRIES} in {retry_delay}s..."
253
+ )
254
+ time.sleep(retry_delay)
255
+
256
+ # Exponential backoff with max delay cap
257
+ retry_delay = min(retry_delay * 2, DOWNLOAD_MAX_RETRY_DELAY)
258
+
259
+ except APIStatusError as e:
260
+ # For API errors, don't retry
261
+ log.error(f"API error during download: {e}")
262
+ raise APIStatusError(
263
+ "Error downloading file",
264
+ response=e.response,
265
+ body=e.response,
266
+ ) from e
267
+
268
+ # Close the response
269
+ response.close()
211
270
 
212
271
  # Raise exception if remote file size does not match downloaded file size
213
272
  if os.stat(temp_file.name).st_size != file_size:
214
- DownloadError(
215
- f"Downloaded file size `{pbar.n}` bytes does not match remote file size `{file_size}` bytes."
273
+ raise DownloadError(
274
+ f"Downloaded file size `{bytes_downloaded}` bytes does not match remote file size `{file_size}` bytes."
216
275
  )
217
276
 
218
277
  # Moves temp file to output file path