Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ RUN --mount=type=cache,id=ragtime-pip-cache,target=/root/.cache/pip,sharing=lock
else \
pip install -r /tmp/requirements.app.txt; \
fi && \
python -c "import chonkie, pandas"
python -c "import anydoc, chonkie; assert callable(anydoc.to_markdown_bytes); assert anydoc.format_from_extension('csv') is not None"

# =============================================================================
# Stage 3: Python CI base
Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ RUN pip install --no-cache-dir --upgrade pip && \
python /tmp/install_deps_from_pyproject.py /ragtime/pyproject.toml app /tmp/requirements.app.txt && \
python /tmp/install_deps_from_pyproject.py /ragtime/pyproject.toml test /tmp/requirements.test.txt && \
pip install --no-cache-dir -r /tmp/requirements.app.txt -r /tmp/requirements.test.txt && \
python -c "import chonkie, pandas"
python -c "import anydoc, chonkie"

# Copy frontend package.json for npm install layer caching
COPY ragtime/frontend/package.json ragtime/frontend/package-lock.json* /ragtime/ragtime/frontend/
Expand Down
2 changes: 2 additions & 0 deletions docker/Dockerfile.runtime
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,15 @@ RUN --mount=type=cache,id=ragtime-pip-cache,target=/root/.cache/pip,sharing=lock
pip install --upgrade pip && \
python /tmp/install_deps_from_pyproject.py /runtime/pyproject.toml runtime /tmp/requirements.runtime.txt && \
pip install -r /tmp/requirements.runtime.txt && \
python -c "import anydoc; assert callable(anydoc.to_markdown_bytes); assert anydoc.format_from_extension('csv') is not None" && \
pip install poetry pipenv uv && \
curl -fsSL https://bun.sh/install | bash

COPY runtime/ /runtime/runtime/
RUN mkdir -p /runtime/ragtime/core
COPY ragtime/__init__.py /runtime/ragtime/__init__.py
COPY ragtime/core/__init__.py /runtime/ragtime/core/__init__.py
COPY ragtime/core/document_conversion.py /runtime/ragtime/core/document_conversion.py
COPY ragtime/core/file_constants.py /runtime/ragtime/core/file_constants.py
COPY docker/entrypoint.runtime.sh /entrypoint.runtime.sh

Expand Down
13 changes: 2 additions & 11 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,11 @@ app = [
"python-jose[cryptography]>=3.3.0,<4.0.0",
"webauthn>=2.0.0,<3.0.0",
"slowapi>=0.1.9,<1.0.0",
"pypdf>=4.0.0,<5.0.0",
"firecrawl-anydoc==0.2.4",
"python-docx>=1.1.0,<2.0.0",
"openpyxl>=3.1.0,<4.0.0",
"pandas>=2.2.0,<3.0.0",
"python-pptx>=0.6.21,<1.0.0",
"odfpy>=1.4.1,<2.0.0",
"xlrd>=2.0.1,<3.0.0",
"chonkie[code]>=1.3.1,<2.0.0",
"striprtf>=0.0.26,<1.0.0",
"ebooklib>=0.18,<1.0.0",
"beautifulsoup4>=4.12.0,<5.0.0",
"lxml>=5.0.0,<6.0.0",
"extract-msg>=0.48.0,<1.0.0",
Expand All @@ -72,7 +67,7 @@ runtime = [
"pydantic>=2.5.0,<3.0.0",
"httpx>=0.26.0,<1.0.0",
"mcp>=1.0.0,<2.0.0",
"pypdf>=4.0.0,<5.0.0",
"firecrawl-anydoc==0.2.4",
]
test = [
"mypy>=1.15.0,<2.0.0",
Expand Down Expand Up @@ -166,10 +161,6 @@ warn_unused_ignores = false

[[tool.mypy.overrides]]
module = [
"ebooklib",
"odf",
"odf.*",
"pptx",
"pytesseract",
]
ignore_missing_imports = true
Expand Down
78 changes: 78 additions & 0 deletions ragtime/core/document_conversion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""Shared AnyDoc conversion adapter."""

import os
import threading
from dataclasses import dataclass
from enum import Enum


class DocumentConversionFailure(str, Enum):
UNSUPPORTED = "unsupported"
NEEDS_OCR = "needs_ocr"
MALFORMED = "malformed"
ENCRYPTED = "encrypted"
RESOURCE_LIMIT = "resource_limit"
MISSING_PART = "missing_part"
DEPENDENCY = "dependency"
UNEXPECTED = "unexpected"


@dataclass(frozen=True)
class DocumentConversionResult:
text: str
failure: DocumentConversionFailure | None = None
detail: str | None = None


_MAX_CONCURRENT_CONVERSIONS = max(1, min(8, os.cpu_count() or 1))
_CONVERSION_SEMAPHORE = threading.Semaphore(_MAX_CONCURRENT_CONVERSIONS)


def convert_document_bytes(content: bytes, suffix: str) -> DocumentConversionResult:
try:
import anydoc
except ImportError as exc:
return DocumentConversionResult(
text="",
failure=DocumentConversionFailure.DEPENDENCY,
detail=str(exc),
)

try:
detected_format = anydoc.format_from_bytes(content)
normalized_format = detected_format or anydoc.format_from_extension(suffix)
if not normalized_format:
return DocumentConversionResult(
text="",
failure=DocumentConversionFailure.UNSUPPORTED,
detail=f"Unsupported document format for {suffix or 'content'}",
)

with _CONVERSION_SEMAPHORE:
markdown = anydoc.to_markdown_bytes(content, normalized_format)
except ImportError as exc:
return DocumentConversionResult(
text="",
failure=DocumentConversionFailure.DEPENDENCY,
detail=str(exc),
)
except anydoc.UnsupportedError as exc:
return DocumentConversionResult(text="", failure=DocumentConversionFailure.UNSUPPORTED, detail=str(exc))
except anydoc.NeedsOcrError as exc:
return DocumentConversionResult(text="", failure=DocumentConversionFailure.NEEDS_OCR, detail=str(exc))
except anydoc.MalformedError as exc:
return DocumentConversionResult(text="", failure=DocumentConversionFailure.MALFORMED, detail=str(exc))
except anydoc.EncryptedError as exc:
return DocumentConversionResult(text="", failure=DocumentConversionFailure.ENCRYPTED, detail=str(exc))
except anydoc.ResourceLimitError as exc:
return DocumentConversionResult(text="", failure=DocumentConversionFailure.RESOURCE_LIMIT, detail=str(exc))
except anydoc.MissingPartError as exc:
return DocumentConversionResult(text="", failure=DocumentConversionFailure.MISSING_PART, detail=str(exc))
except Exception as exc:
return DocumentConversionResult(text="", failure=DocumentConversionFailure.UNEXPECTED, detail=str(exc))

if isinstance(markdown, bytes):
text = markdown.decode("utf-8", errors="replace")
else:
text = str(markdown)
return DocumentConversionResult(text=text)
56 changes: 29 additions & 27 deletions ragtime/core/file_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,27 +108,36 @@
# =============================================================================
# PARSEABLE DOCUMENT EXTENSIONS
# =============================================================================
# Documents that require special parsers (PDF, Office, OpenDocument, etc.).
# Both the filesystem indexer and git/upload indexer can parse these using
# document_parser.py extractors.
PARSEABLE_DOCUMENT_EXTENSIONS: set[str] = {
# Office documents
# AnyDoc README-supported document formats. Keep this as the canonical
# AnyDoc taxonomy so parser-facing sets can reuse it without local drift.
ANYDOC_DOCUMENT_EXTENSIONS: set[str] = {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The canonical AnyDoc taxonomy is expanded here, but the filesystem-index UI defaults and User Space binary-prefetch gate are not updated in this PR. Most newly advertised formats are therefore omitted by default or treated as editor text. Please propagate this matrix to those consumer gates and pin parity with focused tests.

".pdf",
".doc",
".docx",
".xls",
".xlsx",
".docm",
".ppt",
".pps",
".pot",
".pptx",
# OpenDocument
".pptm",
".ppsx",
".ppsm",
".xls",
".xlsx",
".xlsm",
".xlsb",
".odt",
".ods",
".odp",
# Rich text
".rtf",
# Ebooks
".epub",
# Email
".csv",
}

# Documents that require special parsers (PDF, Office, OpenDocument, etc.).
# Both the filesystem indexer and git/upload indexer can parse these using
# document_parser.py extractors.
PARSEABLE_DOCUMENT_EXTENSIONS: set[str] = ANYDOC_DOCUMENT_EXTENSIONS | {
".eml",
".msg",
}
Expand Down Expand Up @@ -180,22 +189,7 @@
".txt",
".md",
".rst",
".csv",
# Office documents - parsed by document_parser.py
".pdf",
".doc",
".docx",
".xls",
".xlsx",
".ppt",
".pptx",
# OpenDocument formats
".odt",
".ods",
".odp",
".rtf",
# Ebooks
".epub",
*ANYDOC_DOCUMENT_EXTENSIONS,
# Email
".eml",
".msg",
Expand Down Expand Up @@ -485,10 +479,18 @@
".pdf": None,
".doc": None,
".docx": None,
".docm": None,
".xls": None,
".xlsx": None,
".xlsm": None,
".xlsb": None,
".ppt": None,
".pptx": None,
".pps": None,
".pot": None,
".pptm": None,
".ppsx": None,
".ppsm": None,
".rtf": None,
# OpenDocument
".odt": None,
Expand Down
34 changes: 34 additions & 0 deletions ragtime/frontend/src/components/ToolWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2556,9 +2556,26 @@ export function ToolWizard({
'**/*.txt',
'**/*.md',
'**/*.pdf',
'**/*.doc',
'**/*.docx',
'**/*.docm',
'**/*.xls',
'**/*.xlsx',
'**/*.xlsm',
'**/*.xlsb',
'**/*.ppt',
'**/*.pptx',
'**/*.pptm',
'**/*.pps',
'**/*.ppsx',
'**/*.ppsm',
'**/*.pot',
'**/*.odt',
'**/*.ods',
'**/*.odp',
'**/*.rtf',
'**/*.epub',
'**/*.csv',
'**/*.py',
'**/*.json',
'**/*.png',
Expand Down Expand Up @@ -2608,9 +2625,26 @@ export function ToolWizard({
'**/*.txt',
'**/*.md',
'**/*.pdf',
'**/*.doc',
'**/*.docx',
'**/*.docm',
'**/*.xls',
'**/*.xlsx',
'**/*.xlsm',
'**/*.xlsb',
'**/*.ppt',
'**/*.pptx',
'**/*.pptm',
'**/*.pps',
'**/*.ppsx',
'**/*.ppsm',
'**/*.pot',
'**/*.odt',
'**/*.ods',
'**/*.odp',
'**/*.rtf',
'**/*.epub',
'**/*.csv',
'**/*.py',
'**/*.json',
'**/*.png',
Expand Down
8 changes: 8 additions & 0 deletions ragtime/frontend/src/utils/userspacePrefetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,23 @@ const NON_PREFETCHABLE_USER_SPACE_FILE_EXTENSIONS = [
'.pdf',
'.doc',
'.docx',
'.docm',
'.ppt',
'.pptx',
'.pptm',
'.pps',
'.ppsx',
'.ppsm',
'.pot',
'.xls',
'.xlsx',
'.xlsm',
'.xlsb',
'.odt',
'.ods',
'.odp',
'.rtf',
'.epub',
'.msg',
// binary/tabular data formats
'.parquet',
Expand Down
Loading
Loading