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.
- markitdown_pro-2.2.0/.github/workflows/.github/workflows/issues_add_to_ksg_project.yaml +32 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/.github/workflows/test.yml +14 -6
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/PKG-INFO +35 -6
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/README.md +33 -4
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/services/openai_services.py +112 -50
- markitdown_pro-2.2.0/odd/tasks/fix-openai-client-cleanup.md +429 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/conftest.py +18 -18
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/test_expectations.yaml +1 -1
- markitdown_pro-2.2.0/tests/unit/test_fixture_lifecycle.py +33 -0
- markitdown_pro-2.2.0/tests/unit/test_openai_provider.py +509 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/.github/workflows/lint.yaml +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/.github/workflows/publish.yaml +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/.gitignore +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/.vscode/settings.json +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/__init__.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/__init__.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/common/__init__.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/common/config.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/common/isolated_worker.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/common/logger.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/common/schemas.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/common/utils.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/conversion_pipeline.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/__init__.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/base.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/doc_intel_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/gotenberg_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/gpt_vision_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/markitdown_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/pymupdf_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/speech_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/tabular_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/unstructured_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/converters/youtube_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/__init__.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/audio_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/base_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/email_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/epub_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/image_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/ipynb_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/markup_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/office_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/page_pdf_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/pdf_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/pst_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/tabular_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/handlers/text_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/services/__init__.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/services/azure_doc_intelligence.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/markitdown_pro/services/azure_speech.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/pyproject.toml +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/__init__.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/common/__init__.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/common/test_config.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/conversion_pipeline/__init__.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/conversion_pipeline/test_conversion_pipeline.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/converters/test_azure_doc_intel_wrapper.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/converters/test_azure_speech_wrapper.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/converters/test_doc_intel_lifecycle.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/converters/test_gpt_vision_config.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/converters/test_gpt_vision_wrapper.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/converters/test_pymupdf_wrapper.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/converters/test_pymupdf_wrapper_async.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/converters/test_unstructured_io_wrapper.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/audio/the-power-of-small-steps_cn.wav +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/audio/the-power-of-small-steps_en.mp3 +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/audio/the-power-of-small-steps_en.wav +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/audio/the-power-of-small-steps_es.mp3 +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/audio/the-power-of-small-steps_es.wav +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/audio/the-power-of-small-steps_it.wav +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/audio/the-power-of-small-steps_jp.mp3 +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/audio/the-power-of-small-steps_jp.wav +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/eml/email-with-image.eml +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/eml/mime-different-plain-html.eml +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/epub/sample1.epub +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/images/flower.jpg +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/images/geometry_colors.bmp +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/images/handwriting.jpg +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/images/scanned_text.heic +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/images/scanned_text.png +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/images/text_with_image.jpg +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/markup/blog.html +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/markup/factbook.xml +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/markup/simple.json +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/markup/simple.yaml +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/msg/fake-email-multiple-attachments.msg +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/msg/fake-email.msg +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/pdf/geometry-colors.pdf +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/pdf/lorem-ipsum-only-images.pdf +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/pdf/lorem-ipsum-only-text.pdf +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/pdf/lorem-ipsum-scanned-text.pdf +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/pdf/lorem-ipsum-text-with-image.pdf +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/powerpoint/geometry-colors.pptx +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/powerpoint/lorem-ipsum-only-text.pptx +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/powerpoint/lorem-ipsum-scanned-text.pptx +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/powerpoint/lorem-ipsum-text-with-image.pptx +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/powerpoint/lorem-ipsum-text-with-scanned-text.pptx +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/powerpoint-legacy/geometry-colors.ppt +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/powerpoint-legacy/lorem-ipsum-only-text.ppt +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/powerpoint-legacy/lorem-ipsum-scanned-text.ppt +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/powerpoint-legacy/lorem-ipsum-text-with-image.ppt +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/powerpoint-legacy/lorem-ipsum-text-with-scanned-text.ppt +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/tabular/2023-half-year-analyses-by-segment.xlsx +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/tabular/stanley-cups.csv +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/tabular/stanley-cups.tsv +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/tabular/stanley-cups.xlsx +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/text/book-war-and-peace-1p.txt +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/text/fake-email.txt +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/text/logger.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word/geometry-colors.docx +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word/lorem-ipsum-only-text.docx +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word/lorem-ipsum-scanned-text.docx +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word/lorem-ipsum-text-with-image.docx +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word/lorem-ipsum-text-with-scanned-text.docx +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/geometry-colors.doc +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/geometry-colors.odt +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/geometry-colors.rtf +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-only-text.doc +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-only-text.odt +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-only-text.rtf +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-scanned-text.doc +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-scanned-text.odt +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-scanned-text.rtf +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-text-with-image.doc +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-text-with-image.odt +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-text-with-image.rtf +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-text-with-scanned-text.doc +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-text-with-scanned-text.odt +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/data/word-legacy/lorem-ipsum-text-with-scanned-text.rtf +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/fixtures.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/__init__.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_email_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_epub_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_image_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_ipynb_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_markitdown_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_markitdown_validation.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_markup_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_page_pdf_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_pdf_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_pst_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_tabular_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/handlers/test_text_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/__init__.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/cheat_sheet.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/runner.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_audio_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_doc_intelligence_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_email_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_gotenberg_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_gpt_vision_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_legacy_office_formats.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_markitdown_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_markup_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_pdf_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_pymupdf_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_tabular_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/integration/test_text_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/page_count/__init__.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/page_count/test_pdf_page_count.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/__init__.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_audio_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_base_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_conversion_pipeline.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_doc_intel_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_gotenberg_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_gpt_vision_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_image_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_markitdown_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_office_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_page_pdf_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_pdf_handler.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_pymupdf_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/unit/test_speech_converter.py +0 -0
- {markitdown_pro-2.1.2 → markitdown_pro-2.2.0}/tests/utils.py +0 -0
- {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 (
|
|
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
|
-
|
|
78
|
-
|
|
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
|
-
|
|
95
|
-
|
|
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.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: markitdown-pro
|
|
3
|
-
Version: 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
|
-
#
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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:
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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
|
|
282
|
-
f"{
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
524
|
-
|
|
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
|
-
|
|
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
|
-
|
|
533
|
-
|
|
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
|
-
|
|
539
|
-
|
|
540
|
-
client.close()
|
|
606
|
+
if self._http_client is not None:
|
|
607
|
+
self._http_client.close()
|
|
541
608
|
|
|
542
|
-
|
|
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(
|