markitdown-pro 2.1.2__tar.gz → 2.2.0__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 (177) hide show
  1. markitdown_pro-2.2.0/.github/workflows/.github/workflows/issues_add_to_ksg_project.yaml +32 -0
  2. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/.github/workflows/test.yml +14 -6
  3. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/PKG-INFO +35 -6
  4. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/README.md +33 -4
  5. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/services/openai_services.py +112 -50
  6. markitdown_pro-2.2.0/odd/tasks/fix-openai-client-cleanup.md +429 -0
  7. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/conftest.py +18 -18
  8. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/test_expectations.yaml +1 -1
  9. markitdown_pro-2.2.0/tests/unit/test_fixture_lifecycle.py +33 -0
  10. markitdown_pro-2.2.0/tests/unit/test_openai_provider.py +509 -0
  11. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/.github/workflows/lint.yaml +0 -0
  12. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/.github/workflows/publish.yaml +0 -0
  13. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/.gitignore +0 -0
  14. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/.vscode/settings.json +0 -0
  15. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/__init__.py +0 -0
  16. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/__init__.py +0 -0
  17. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/common/__init__.py +0 -0
  18. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/common/config.py +0 -0
  19. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/common/isolated_worker.py +0 -0
  20. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/common/logger.py +0 -0
  21. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/common/schemas.py +0 -0
  22. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/common/utils.py +0 -0
  23. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/conversion_pipeline.py +0 -0
  24. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/__init__.py +0 -0
  25. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/base.py +0 -0
  26. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/doc_intel_converter.py +0 -0
  27. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/gotenberg_converter.py +0 -0
  28. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/gpt_vision_converter.py +0 -0
  29. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/markitdown_converter.py +0 -0
  30. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/pymupdf_converter.py +0 -0
  31. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/speech_converter.py +0 -0
  32. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/tabular_converter.py +0 -0
  33. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/unstructured_converter.py +0 -0
  34. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/youtube_converter.py +0 -0
  35. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/__init__.py +0 -0
  36. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/audio_handler.py +0 -0
  37. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/base_handler.py +0 -0
  38. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/email_handler.py +0 -0
  39. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/epub_handler.py +0 -0
  40. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/image_handler.py +0 -0
  41. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/ipynb_handler.py +0 -0
  42. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/markup_handler.py +0 -0
  43. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/office_handler.py +0 -0
  44. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/page_pdf_converter.py +0 -0
  45. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/pdf_handler.py +0 -0
  46. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/pst_handler.py +0 -0
  47. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/tabular_handler.py +0 -0
  48. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/text_handler.py +0 -0
  49. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/services/__init__.py +0 -0
  50. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/services/azure_doc_intelligence.py +0 -0
  51. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/services/azure_speech.py +0 -0
  52. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/pyproject.toml +0 -0
  53. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/__init__.py +0 -0
  54. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/common/__init__.py +0 -0
  55. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/common/test_config.py +0 -0
  56. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/conversion_pipeline/__init__.py +0 -0
  57. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/conversion_pipeline/test_conversion_pipeline.py +0 -0
  58. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/converters/test_azure_doc_intel_wrapper.py +0 -0
  59. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/converters/test_azure_speech_wrapper.py +0 -0
  60. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/converters/test_doc_intel_lifecycle.py +0 -0
  61. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/converters/test_gpt_vision_config.py +0 -0
  62. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/converters/test_gpt_vision_wrapper.py +0 -0
  63. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/converters/test_pymupdf_wrapper.py +0 -0
  64. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/converters/test_pymupdf_wrapper_async.py +0 -0
  65. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/converters/test_unstructured_io_wrapper.py +0 -0
  66. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/audio/the-power-of-small-steps_cn.wav +0 -0
  67. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/audio/the-power-of-small-steps_en.mp3 +0 -0
  68. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/audio/the-power-of-small-steps_en.wav +0 -0
  69. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/audio/the-power-of-small-steps_es.mp3 +0 -0
  70. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/audio/the-power-of-small-steps_es.wav +0 -0
  71. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/audio/the-power-of-small-steps_it.wav +0 -0
  72. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/audio/the-power-of-small-steps_jp.mp3 +0 -0
  73. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/audio/the-power-of-small-steps_jp.wav +0 -0
  74. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/eml/email-with-image.eml +0 -0
  75. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/eml/mime-different-plain-html.eml +0 -0
  76. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/epub/sample1.epub +0 -0
  77. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/images/flower.jpg +0 -0
  78. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/images/geometry_colors.bmp +0 -0
  79. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/images/handwriting.jpg +0 -0
  80. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/images/scanned_text.heic +0 -0
  81. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/images/scanned_text.png +0 -0
  82. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/images/text_with_image.jpg +0 -0
  83. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/markup/blog.html +0 -0
  84. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/markup/factbook.xml +0 -0
  85. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/markup/simple.json +0 -0
  86. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/markup/simple.yaml +0 -0
  87. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/msg/fake-email-multiple-attachments.msg +0 -0
  88. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/msg/fake-email.msg +0 -0
  89. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/pdf/geometry-colors.pdf +0 -0
  90. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/pdf/lorem-ipsum-only-images.pdf +0 -0
  91. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/pdf/lorem-ipsum-only-text.pdf +0 -0
  92. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/pdf/lorem-ipsum-scanned-text.pdf +0 -0
  93. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/pdf/lorem-ipsum-text-with-image.pdf +0 -0
  94. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/powerpoint/geometry-colors.pptx +0 -0
  95. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/powerpoint/lorem-ipsum-only-text.pptx +0 -0
  96. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/powerpoint/lorem-ipsum-scanned-text.pptx +0 -0
  97. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/powerpoint/lorem-ipsum-text-with-image.pptx +0 -0
  98. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/powerpoint/lorem-ipsum-text-with-scanned-text.pptx +0 -0
  99. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/powerpoint-legacy/geometry-colors.ppt +0 -0
  100. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/powerpoint-legacy/lorem-ipsum-only-text.ppt +0 -0
  101. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/powerpoint-legacy/lorem-ipsum-scanned-text.ppt +0 -0
  102. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/powerpoint-legacy/lorem-ipsum-text-with-image.ppt +0 -0
  103. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/powerpoint-legacy/lorem-ipsum-text-with-scanned-text.ppt +0 -0
  104. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/tabular/2023-half-year-analyses-by-segment.xlsx +0 -0
  105. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/tabular/stanley-cups.csv +0 -0
  106. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/tabular/stanley-cups.tsv +0 -0
  107. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/tabular/stanley-cups.xlsx +0 -0
  108. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/text/book-war-and-peace-1p.txt +0 -0
  109. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/text/fake-email.txt +0 -0
  110. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/text/logger.py +0 -0
  111. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word/geometry-colors.docx +0 -0
  112. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word/lorem-ipsum-only-text.docx +0 -0
  113. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word/lorem-ipsum-scanned-text.docx +0 -0
  114. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word/lorem-ipsum-text-with-image.docx +0 -0
  115. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word/lorem-ipsum-text-with-scanned-text.docx +0 -0
  116. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/geometry-colors.doc +0 -0
  117. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/geometry-colors.odt +0 -0
  118. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/geometry-colors.rtf +0 -0
  119. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-only-text.doc +0 -0
  120. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-only-text.odt +0 -0
  121. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-only-text.rtf +0 -0
  122. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-scanned-text.doc +0 -0
  123. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-scanned-text.odt +0 -0
  124. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-scanned-text.rtf +0 -0
  125. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-text-with-image.doc +0 -0
  126. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-text-with-image.odt +0 -0
  127. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-text-with-image.rtf +0 -0
  128. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-text-with-scanned-text.doc +0 -0
  129. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-text-with-scanned-text.odt +0 -0
  130. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-text-with-scanned-text.rtf +0 -0
  131. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/fixtures.py +0 -0
  132. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/__init__.py +0 -0
  133. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_email_handler.py +0 -0
  134. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_epub_handler.py +0 -0
  135. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_image_handler.py +0 -0
  136. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_ipynb_handler.py +0 -0
  137. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_markitdown_handler.py +0 -0
  138. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_markitdown_validation.py +0 -0
  139. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_markup_handler.py +0 -0
  140. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_page_pdf_converter.py +0 -0
  141. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_pdf_handler.py +0 -0
  142. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_pst_handler.py +0 -0
  143. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_tabular_handler.py +0 -0
  144. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_text_handler.py +0 -0
  145. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/__init__.py +0 -0
  146. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/cheat_sheet.py +0 -0
  147. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/runner.py +0 -0
  148. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_audio_handler.py +0 -0
  149. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_doc_intelligence_converter.py +0 -0
  150. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_email_handler.py +0 -0
  151. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_gotenberg_converter.py +0 -0
  152. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_gpt_vision_converter.py +0 -0
  153. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_legacy_office_formats.py +0 -0
  154. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_markitdown_converter.py +0 -0
  155. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_markup_handler.py +0 -0
  156. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_pdf_handler.py +0 -0
  157. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_pymupdf_converter.py +0 -0
  158. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_tabular_handler.py +0 -0
  159. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_text_handler.py +0 -0
  160. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/page_count/__init__.py +0 -0
  161. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/page_count/test_pdf_page_count.py +0 -0
  162. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/__init__.py +0 -0
  163. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_audio_handler.py +0 -0
  164. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_base_converter.py +0 -0
  165. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_conversion_pipeline.py +0 -0
  166. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_doc_intel_converter.py +0 -0
  167. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_gotenberg_converter.py +0 -0
  168. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_gpt_vision_converter.py +0 -0
  169. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_image_handler.py +0 -0
  170. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_markitdown_converter.py +0 -0
  171. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_office_handler.py +0 -0
  172. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_page_pdf_converter.py +0 -0
  173. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_pdf_handler.py +0 -0
  174. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_pymupdf_converter.py +0 -0
  175. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_speech_converter.py +0 -0
  176. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/utils.py +0 -0
  177. {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/uv.lock +0 -0
@@ -0,0 +1,32 @@
1
+ name: Add new issues to VIVI project
2
+
3
+ # When an issue is opened in this repo:
4
+ # 1. Apply the `VIVI` label.
5
+ # 2. Add the issue to the org project (Kinetic-Solutions-Group / project #1).
6
+ #
7
+ # Step 2 targets an *organization* project, which the default GITHUB_TOKEN
8
+ # cannot write to. It requires a PAT stored as the `ADD_ISSUE_TO_PROJECT_PAT`
9
+ # secret with project read/write scope. See the repo README / setup notes.
10
+
11
+ on:
12
+ issues:
13
+ types: [opened]
14
+
15
+ permissions:
16
+ issues: write
17
+
18
+ jobs:
19
+ add-to-vivi-project:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - name: Apply MKDWN label
23
+ env:
24
+ GH_TOKEN: ${{ github.token }}
25
+ GH_REPO: ${{ github.repository }}
26
+ run: gh issue edit "${{ github.event.issue.number }}" --add-label "MKDWNPRO"
27
+
28
+ - name: Add issue to VIVI project
29
+ uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0
30
+ with:
31
+ project-url: https://github.com/orgs/Kinetic-Solutions-Group/projects/1
32
+ github-token: ${{ secrets.ADD_ISSUE_TO_PROJECT_PAT }}
@@ -36,10 +36,14 @@ jobs:
36
36
  run: uv run pytest tests/unit/ -v
37
37
 
38
38
  integration-tests:
39
- name: Integration Tests (Azure + Gotenberg)
39
+ name: Integration Tests (${{ matrix.provider }} + Gotenberg)
40
40
  runs-on: ubuntu-latest
41
41
  environment: ci
42
42
  needs: unit-tests
43
+ strategy:
44
+ fail-fast: false
45
+ matrix:
46
+ provider: [azure_openai, openai]
43
47
  services:
44
48
  gotenberg:
45
49
  image: gotenberg/gotenberg:8
@@ -74,8 +78,10 @@ jobs:
74
78
  cat <<EOF > .test.env
75
79
  DEBUG=True
76
80
  LOG_LEVEL=10
77
- AZURE_OPENAI_ENDPOINT=${{ secrets.AZURE_OPENAI_ENDPOINT }}
78
- AZURE_OPENAI_API_KEY=${{ secrets.AZURE_OPENAI_API_KEY }}
81
+ OPEN_AI_PROVIDER=${{ matrix.provider }}
82
+ OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}
83
+ AZURE_OPENAI_ENDPOINT=${{ secrets.AZURE_OPEN_AI_EASTUS2_ENDPOINT }}
84
+ AZURE_OPENAI_API_KEY=${{ secrets.AZURE_OPEN_AI_EASTUS2_KEY }}
79
85
  AZURE_OPENAI_API_VERSION=${{ secrets.AZURE_OPENAI_API_VERSION }}
80
86
  AZURE_DOCINTEL_ENDPOINT=${{ secrets.AZURE_DOCINTEL_ENDPOINT }}
81
87
  AZURE_DOCINTEL_KEY=${{ secrets.AZURE_DOCINTEL_KEY }}
@@ -91,8 +97,10 @@ jobs:
91
97
  cat <<EOF > .env
92
98
  DEBUG=True
93
99
  LOG_LEVEL=10
94
- AZURE_OPENAI_ENDPOINT=${{ secrets.AZURE_OPENAI_ENDPOINT }}
95
- AZURE_OPENAI_API_KEY=${{ secrets.AZURE_OPENAI_API_KEY }}
100
+ OPEN_AI_PROVIDER=${{ matrix.provider }}
101
+ OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}
102
+ AZURE_OPENAI_ENDPOINT=${{ secrets.AZURE_OPEN_AI_EASTUS2_ENDPOINT }}
103
+ AZURE_OPENAI_API_KEY=${{ secrets.AZURE_OPEN_AI_EASTUS2_KEY }}
96
104
  AZURE_OPENAI_API_VERSION=${{ secrets.AZURE_OPENAI_API_VERSION }}
97
105
  AZURE_DOCINTEL_ENDPOINT=${{ secrets.AZURE_DOCINTEL_ENDPOINT }}
98
106
  AZURE_DOCINTEL_KEY=${{ secrets.AZURE_DOCINTEL_KEY }}
@@ -117,5 +125,5 @@ jobs:
117
125
  if: always()
118
126
  uses: actions/upload-artifact@v4
119
127
  with:
120
- name: test-results
128
+ name: test-results-${{ matrix.provider }}
121
129
  path: tests/data/
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: markitdown-pro
3
- Version: 2.1.2
3
+ Version: 2.2.0
4
4
  Summary: A package that converts almost any file format to Markdown.
5
5
  Author: Developer
6
6
  License-Expression: MIT
@@ -32,7 +32,7 @@ Description-Content-Type: text/markdown
32
32
 
33
33
  # MarkItDown-Pro
34
34
 
35
- **MarkItDown-Pro** is a Python library that converts **50+ document formats into Markdown**, built to power RAG (Retrieval-Augmented Generation) pipelines for semantic search. It extends [Microsoft MarkItDown](https://github.com/microsoft/markitdown) with Azure AI services, per-page OCR, and customizable converter pipelines.
35
+ **MarkItDown-Pro** is a Python library that converts **50+ document formats into Markdown**, built to power RAG (Retrieval-Augmented Generation) pipelines for semantic search. It extends [Microsoft MarkItDown](https://github.com/microsoft/markitdown) with Azure AI and OpenAI services, per-page OCR, and customizable converter pipelines.
36
36
 
37
37
  ---
38
38
 
@@ -41,7 +41,7 @@ Description-Content-Type: text/markdown
41
41
  - **Async-first API** -- all public methods are `async`, designed for concurrent document processing
42
42
  - **Per-page PDF routing** -- classifies each page as text or image, extracts text locally and OCRs only image pages
43
43
  - **Customizable pipelines** -- inject your own converter order per handler to optimize for quality, speed, or cost
44
- - **GPT Vision OCR** -- concurrent page-by-page OCR via Azure OpenAI (gpt-5.4-mini default)
44
+ - **GPT Vision OCR** -- concurrent page-by-page OCR via Azure OpenAI (default) or OpenAI (gpt-5.4-mini default model)
45
45
  - **Gotenberg integration** -- convert Office files to PDF for full OCR via [Gotenberg](https://gotenberg.dev) HTTP API
46
46
  - **Azure Document Intelligence** -- layout-aware text extraction with the `prebuilt-layout` model
47
47
  - **Azure Speech-to-Text** -- audio transcription with automatic language detection
@@ -117,7 +117,10 @@ uv sync --dev
117
117
  Create a `.env` file in the project root:
118
118
 
119
119
  ```bash
120
- # Azure OpenAI (required for GPT Vision OCR)
120
+ # GPT Vision OCR provider (optional; defaults to azure_openai)
121
+ OPEN_AI_PROVIDER="azure_openai"
122
+
123
+ # Azure OpenAI credentials (used only when the provider is azure_openai)
121
124
  AZURE_OPENAI_ENDPOINT="https://<resource>.openai.azure.com"
122
125
  AZURE_OPENAI_API_KEY="your-key"
123
126
  AZURE_OPENAI_API_VERSION="2024-12-01-preview"
@@ -144,7 +147,33 @@ MARKITDOWN_MIN_IMAGE_AREA="150000"
144
147
  LOG_LEVEL=20 # 10=DEBUG, 20=INFO, 30=WARNING
145
148
  ```
146
149
 
147
- All services are optional -- the library degrades gracefully when credentials are missing.
150
+ To use OpenAI instead of Azure OpenAI for OCR, set:
151
+
152
+ ```bash
153
+ OPEN_AI_PROVIDER="openai"
154
+ OPENAI_API_KEY="your-openai-key"
155
+ # Optional; defaults to the public OpenAI API
156
+ OPENAI_BASE_URL="https://api.openai.com/v1"
157
+ MARKITDOWN_OCR_MODEL="gpt-5.4-mini"
158
+ MARKITDOWN_OCR_FALLBACK_MODEL="gpt-5.4"
159
+ ```
160
+
161
+ `OPEN_AI_PROVIDER` accepts exactly `azure_openai` or `openai`. Omitting it preserves
162
+ Azure behavior and existing `AZURE_OPENAI_*` names. Invalid values raise `ValueError`;
163
+ credentials never implicitly switch the provider. OpenAI does not require Azure credentials
164
+ or an Azure API version. Set these variables before creating converters; the OCR model
165
+ variables are read when the library is imported.
166
+
167
+ Direct OpenAI uses `OPENAI_API_KEY` and the optional `OPENAI_BASE_URL`.
168
+
169
+ `MARKITDOWN_OCR_MODEL` and `MARKITDOWN_OCR_FALLBACK_MODEL` identify Azure deployments or
170
+ OpenAI model IDs, depending on the selected provider. Choose vision-capable models available
171
+ to your account. Both the primary and fallback model use the same selected provider.
172
+ This applies to image OCR, scanned PDF pages, and Office files routed through PDF OCR.
173
+ Azure Speech, Document Intelligence, and Gotenberg configuration are unchanged.
174
+
175
+ All services are optional -- missing selected-provider credentials leave GPT Vision unavailable
176
+ so other converters can still run; there is no automatic fallback to the other provider.
148
177
 
149
178
  ## Usage
150
179
 
@@ -1,6 +1,6 @@
1
1
  # MarkItDown-Pro
2
2
 
3
- **MarkItDown-Pro** is a Python library that converts **50+ document formats into Markdown**, built to power RAG (Retrieval-Augmented Generation) pipelines for semantic search. It extends [Microsoft MarkItDown](https://github.com/microsoft/markitdown) with Azure AI services, per-page OCR, and customizable converter pipelines.
3
+ **MarkItDown-Pro** is a Python library that converts **50+ document formats into Markdown**, built to power RAG (Retrieval-Augmented Generation) pipelines for semantic search. It extends [Microsoft MarkItDown](https://github.com/microsoft/markitdown) with Azure AI and OpenAI services, per-page OCR, and customizable converter pipelines.
4
4
 
5
5
  ---
6
6
 
@@ -9,7 +9,7 @@
9
9
  - **Async-first API** -- all public methods are `async`, designed for concurrent document processing
10
10
  - **Per-page PDF routing** -- classifies each page as text or image, extracts text locally and OCRs only image pages
11
11
  - **Customizable pipelines** -- inject your own converter order per handler to optimize for quality, speed, or cost
12
- - **GPT Vision OCR** -- concurrent page-by-page OCR via Azure OpenAI (gpt-5.4-mini default)
12
+ - **GPT Vision OCR** -- concurrent page-by-page OCR via Azure OpenAI (default) or OpenAI (gpt-5.4-mini default model)
13
13
  - **Gotenberg integration** -- convert Office files to PDF for full OCR via [Gotenberg](https://gotenberg.dev) HTTP API
14
14
  - **Azure Document Intelligence** -- layout-aware text extraction with the `prebuilt-layout` model
15
15
  - **Azure Speech-to-Text** -- audio transcription with automatic language detection
@@ -85,7 +85,10 @@ uv sync --dev
85
85
  Create a `.env` file in the project root:
86
86
 
87
87
  ```bash
88
- # Azure OpenAI (required for GPT Vision OCR)
88
+ # GPT Vision OCR provider (optional; defaults to azure_openai)
89
+ OPEN_AI_PROVIDER="azure_openai"
90
+
91
+ # Azure OpenAI credentials (used only when the provider is azure_openai)
89
92
  AZURE_OPENAI_ENDPOINT="https://<resource>.openai.azure.com"
90
93
  AZURE_OPENAI_API_KEY="your-key"
91
94
  AZURE_OPENAI_API_VERSION="2024-12-01-preview"
@@ -112,7 +115,33 @@ MARKITDOWN_MIN_IMAGE_AREA="150000"
112
115
  LOG_LEVEL=20 # 10=DEBUG, 20=INFO, 30=WARNING
113
116
  ```
114
117
 
115
- All services are optional -- the library degrades gracefully when credentials are missing.
118
+ To use OpenAI instead of Azure OpenAI for OCR, set:
119
+
120
+ ```bash
121
+ OPEN_AI_PROVIDER="openai"
122
+ OPENAI_API_KEY="your-openai-key"
123
+ # Optional; defaults to the public OpenAI API
124
+ OPENAI_BASE_URL="https://api.openai.com/v1"
125
+ MARKITDOWN_OCR_MODEL="gpt-5.4-mini"
126
+ MARKITDOWN_OCR_FALLBACK_MODEL="gpt-5.4"
127
+ ```
128
+
129
+ `OPEN_AI_PROVIDER` accepts exactly `azure_openai` or `openai`. Omitting it preserves
130
+ Azure behavior and existing `AZURE_OPENAI_*` names. Invalid values raise `ValueError`;
131
+ credentials never implicitly switch the provider. OpenAI does not require Azure credentials
132
+ or an Azure API version. Set these variables before creating converters; the OCR model
133
+ variables are read when the library is imported.
134
+
135
+ Direct OpenAI uses `OPENAI_API_KEY` and the optional `OPENAI_BASE_URL`.
136
+
137
+ `MARKITDOWN_OCR_MODEL` and `MARKITDOWN_OCR_FALLBACK_MODEL` identify Azure deployments or
138
+ OpenAI model IDs, depending on the selected provider. Choose vision-capable models available
139
+ to your account. Both the primary and fallback model use the same selected provider.
140
+ This applies to image OCR, scanned PDF pages, and Office files routed through PDF OCR.
141
+ Azure Speech, Document Intelligence, and Gotenberg configuration are unchanged.
142
+
143
+ All services are optional -- missing selected-provider credentials leave GPT Vision unavailable
144
+ so other converters can still run; there is no automatic fallback to the other provider.
116
145
 
117
146
  ## Usage
118
147
 
@@ -1,5 +1,5 @@
1
1
  """
2
- Asynchronous PDF → image → OCR pipeline using Azure OpenAI (Vision via Chat Completions).
2
+ Asynchronous PDF → image → OCR pipeline using Azure OpenAI or OpenAI (Vision via Chat Completions).
3
3
 
4
4
  Key features
5
5
  ------------
@@ -37,10 +37,11 @@ from pathlib import Path
37
37
  from typing import TypeVar
38
38
 
39
39
  import fitz
40
+ import httpx
40
41
 
41
42
  # from docling.datamodel.pipeline_options import PictureDescriptionApiOptions
42
43
  from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage
43
- from langchain_openai import AzureChatOpenAI
44
+ from langchain_openai import AzureChatOpenAI, ChatOpenAI
44
45
  from PIL import Image as PILImage
45
46
  from pydantic import SecretStr
46
47
 
@@ -54,13 +55,19 @@ T = TypeVar("T")
54
55
  _PNG_DPI = 150
55
56
  _DEFAULT_CONTENT_TYPE = "image/jpeg"
56
57
  _RETRY_HTTP_STATUSES = (408, 409, 429, 500, 502, 503, 504)
58
+ _EXCEPTION_CHAIN_LIMIT = 5
59
+ _OPENAI_HTTP_LIMITS = httpx.Limits(
60
+ max_connections=1000,
61
+ max_keepalive_connections=100,
62
+ keepalive_expiry=5.0,
63
+ )
57
64
 
58
65
 
59
66
  class GPTVision:
60
67
  """
61
- High-level async wrapper around AzureChatOpenAI for image OCR of PDF pages.
68
+ High-level async wrapper around Azure OpenAI or OpenAI for image OCR of PDF pages.
62
69
 
63
- This class renders PDF pages to PNG, sends them to an Azure OpenAI vision model,
70
+ This class renders PDF pages to PNG, sends them to the selected provider's vision model,
64
71
  and returns markdown text per page. It is designed for high-throughput,
65
72
  robust processing with timeouts and retries.
66
73
  """
@@ -80,9 +87,9 @@ class GPTVision:
80
87
  Parameters
81
88
  ----------
82
89
  model_name : str
83
- Azure OpenAI deployment name (e.g., "gpt-4.1-mini").
90
+ Azure OpenAI deployment name or OpenAI model ID (e.g., "gpt-4.1-mini").
84
91
  api_version : str
85
- Azure OpenAI API version (e.g., "2024-09-01-preview").
92
+ Azure OpenAI API version; ignored for OpenAI.
86
93
  completion_tokens : int
87
94
  Max completion token budget per call (output-side).
88
95
  max_concurrency : int
@@ -94,28 +101,59 @@ class GPTVision:
94
101
  page_timeout_s : float
95
102
  Hard timeout for a single page end-to-end (wrapping process_image).
96
103
  """
97
- azure_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT", "")
98
- if not azure_endpoint:
99
- logger.error("GPTVision: AZURE_OPENAI_ENDPOINT environment variable is not set.")
100
- self.lang_client = None
101
- return
104
+ self._llm = None
105
+ self.lang_client = None
106
+ self._http_client: httpx.Client | None = None
107
+ self._http_async_client: httpx.AsyncClient | None = None
108
+
109
+ provider = os.getenv("OPEN_AI_PROVIDER", "azure_openai")
110
+ if provider not in {"azure_openai", "openai"}:
111
+ raise ValueError("OPEN_AI_PROVIDER must be 'azure_openai' or 'openai'.")
112
+
113
+ if provider == "azure_openai":
114
+ azure_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT", "")
115
+ if not azure_endpoint:
116
+ logger.error("GPTVision: AZURE_OPENAI_ENDPOINT environment variable is not set.")
117
+ return
118
+ key_variable = "AZURE_OPENAI_API_KEY"
119
+ api_key = os.getenv(key_variable, "")
120
+ else:
121
+ key_variable = "OPENAI_API_KEY"
122
+ api_key = os.getenv(key_variable, "")
123
+ base_url = os.getenv("OPENAI_BASE_URL") or "https://api.openai.com/v1"
102
124
 
103
- api_key = os.getenv("AZURE_OPENAI_API_KEY", "")
104
125
  if not api_key:
105
- logger.error("GPTVision: AZURE_OPENAI_API_KEY environment variable is not set.")
106
- self.lang_client = None
126
+ logger.error(f"GPTVision: {key_variable} environment variable is not set.")
107
127
  return
108
128
 
109
129
  try:
110
- self._llm = AzureChatOpenAI(
111
- azure_endpoint=azure_endpoint,
112
- api_key=SecretStr(api_key),
113
- api_version=api_version,
114
- model=model_name,
115
- model_kwargs={"max_completion_tokens": completion_tokens},
116
- temperature=0,
117
- cache=False,
118
- )
130
+ http_client_options = {
131
+ "limits": _OPENAI_HTTP_LIMITS,
132
+ "follow_redirects": True,
133
+ }
134
+ self._http_client = httpx.Client(**http_client_options)
135
+ self._http_async_client = httpx.AsyncClient(**http_client_options)
136
+ client_options = {
137
+ "api_key": SecretStr(api_key),
138
+ "model": model_name,
139
+ "model_kwargs": {"max_completion_tokens": completion_tokens},
140
+ "temperature": 0,
141
+ "cache": False,
142
+ "http_client": self._http_client,
143
+ "http_async_client": self._http_async_client,
144
+ "openai_proxy": "",
145
+ }
146
+ if provider == "azure_openai":
147
+ self._llm = AzureChatOpenAI(
148
+ azure_endpoint=azure_endpoint,
149
+ api_version=api_version,
150
+ **client_options,
151
+ )
152
+ else:
153
+ self._llm = ChatOpenAI(
154
+ base_url=base_url,
155
+ **client_options,
156
+ )
119
157
  # In this version we use the raw LLM response (string markdown),
120
158
  # not structured output. Keep a separate client if you later switch.
121
159
  self.lang_client = self._llm
@@ -134,13 +172,26 @@ class GPTVision:
134
172
 
135
173
  logger.info("GPTVision: GPT-vision client initialized successfully.")
136
174
  except Exception as e:
137
- logger.warning(f"GPTVision: Failed to initialize GPT-vision client: {e}")
175
+ self._close_clients_after_initialization_failure()
176
+ logger.warning(
177
+ f"GPTVision: Failed to initialize {provider} client ({type(e).__name__})."
178
+ )
138
179
  self.lang_client = None
139
180
  self._semaphore = asyncio.Semaphore(1)
140
181
  self.request_timeout_s = 60.0
141
182
  self.acquire_timeout_s = 30.0
142
183
  self.page_timeout_s = 120.0
143
184
 
185
+ def _close_clients_after_initialization_failure(self) -> None:
186
+ """Close the sync transport and release the unused async transport reference."""
187
+ http_client = self._http_client
188
+ self._http_client = None
189
+ self._http_async_client = None
190
+
191
+ if http_client is not None:
192
+ with contextlib.suppress(Exception):
193
+ http_client.close()
194
+
144
195
  # -------------------------
145
196
  # Async offloading helpers
146
197
  # -------------------------
@@ -276,13 +327,35 @@ class GPTVision:
276
327
  raise last_err
277
328
  cap = min(max_delay, base_delay * (2 ** (attempt - 1)))
278
329
  sleep_for = random.uniform(0, cap)
279
- label = context_label or file_or_url
280
330
  logger.warning(
281
- f"GPTVision: ainvoke transient error {label}: attempt {attempt}/{max_retries}: "
282
- f"{type(last_err).__name__}: {last_err}. Retrying in {sleep_for:.2f}s"
331
+ f"GPTVision: ainvoke transient error: attempt {attempt}/{max_retries}: "
332
+ f"error_chain={self._format_exception_chain(last_err)}. "
333
+ f"Retrying in {sleep_for:.2f}s"
283
334
  )
284
335
  await asyncio.sleep(sleep_for)
285
336
 
337
+ @staticmethod
338
+ def _format_exception_chain(error: BaseException) -> str:
339
+ """Return bounded transport diagnostics without exception payload text."""
340
+ parts: list[str] = []
341
+ seen: set[int] = set()
342
+ current: BaseException | None = error
343
+
344
+ while current is not None and len(parts) < _EXCEPTION_CHAIN_LIMIT:
345
+ identity = id(current)
346
+ if identity in seen:
347
+ break
348
+ seen.add(identity)
349
+
350
+ name = type(current).__name__
351
+ errno = getattr(current, "errno", None)
352
+ if isinstance(errno, int) and not isinstance(errno, bool):
353
+ name = f"{name}(errno={errno})"
354
+ parts.append(name)
355
+ current = current.__cause__ or current.__context__
356
+
357
+ return " -> ".join(parts)
358
+
286
359
  @staticmethod
287
360
  def _is_transient_error(e: Exception, retry_http_statuses: tuple[int, ...]) -> bool:
288
361
  """
@@ -300,6 +373,7 @@ class GPTVision:
300
373
  "temporarily unavailable",
301
374
  "timeout",
302
375
  "timed out",
376
+ "connection error",
303
377
  "connection reset",
304
378
  )
305
379
  ):
@@ -394,7 +468,8 @@ class GPTVision:
394
468
  return None
395
469
  except Exception as e:
396
470
  logger.error(
397
- f"GPTVision: process_image: Error during GPT-vision image OCR for {file_or_url}: {e}"
471
+ "GPTVision: process_image: Error during GPT-vision image OCR: "
472
+ f"error_chain={self._format_exception_chain(e)}"
398
473
  )
399
474
  return None
400
475
 
@@ -517,34 +592,21 @@ class GPTVision:
517
592
 
518
593
  async def aclose(self) -> None:
519
594
  """
520
- Idempotently close any async/sync clients created under the hood so they
521
- don't try to close after the event loop is gone (pytest teardown).
595
+ Idempotently close the async/sync transports owned by this GPTVision.
522
596
 
523
- LangChain's AzureChatOpenAI lazily creates httpx AsyncClient instances.
524
- We close them explicitly here to prevent 'Event loop is closed' errors
525
- during garbage collection.
597
+ GPTVision creates these httpx clients explicitly and closes them before
598
+ the event loop is gone to prevent teardown errors during garbage collection.
526
599
  """
527
- if not hasattr(self, "_llm") or self._llm is None:
528
- return
529
-
530
- # Close the async OpenAI client (wraps httpx.AsyncClient)
600
+ # Close only the transports created and owned by this GPTVision instance.
531
601
  with contextlib.suppress(Exception):
532
- async_client = getattr(self._llm, "async_client", None)
533
- if async_client and hasattr(async_client, "close"):
534
- await async_client.close()
602
+ if self._http_async_client is not None:
603
+ await self._http_async_client.aclose()
535
604
 
536
- # Close the sync OpenAI client
537
605
  with contextlib.suppress(Exception):
538
- client = getattr(self._llm, "client", None)
539
- if client and hasattr(client, "close"):
540
- client.close()
606
+ if self._http_client is not None:
607
+ self._http_client.close()
541
608
 
542
- # Close httpx clients buried inside the OpenAI SDK
543
- with contextlib.suppress(Exception):
544
- async_client = getattr(self._llm, "async_client", None)
545
- http_client = getattr(async_client, "_client", None)
546
- if http_client and hasattr(http_client, "aclose"):
547
- await http_client.aclose()
609
+ self.lang_client = None
548
610
 
549
611
 
550
612
  # def azure_openai_vlm_options(