{"record":{"id":"8666ee0f67b6563d","repo":"zylon-ai/private-gpt","slug":"no-content-could-be-extracted-from-document-source","errorCode":null,"errorMessage":"No content could be extracted from document source (type={doc_block.source.type!r}).","messagePattern":"No content could be extracted from document source \\(type=(.+?)\\)\\.","errorType":"exception","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"private_gpt/components/chat/processors/chat_history/documents/document_preprocessor.py","lineNumber":62,"sourceCode":"    doc_index: int\n    reference: str | None = None\n    content: str | list[ResultContentBlockType] | None = None\n    error_detail: str | None = None\n\n\nclass DocumentProcessingResponse(BaseModel):\n    message: ChatMessage | None = None\n    processing_status: DocumentProcessingStatus | None = None\n    chat_history: list[ChatMessage] | None = None\n\n\nasync def _process_document(\n    doc_block: DocumentBlock,\n    convert_service: DocumentConverter,\n) -> LITextBlock:\n    text = await asyncio.to_thread(doc_block.source.to_text, convert_service)\n    if not text:\n        raise ValueError(\n            f\"No content could be extracted from document source \"\n            f\"(type={doc_block.source.type!r}).\"\n        )\n\n    parts: list[str] = []\n    if doc_block.title:\n        parts.append(f\"Title: {doc_block.title}\")\n    if doc_block.context:\n        parts.append(f\"Context: {doc_block.context}\")\n    parts.append(text)\n\n    return LITextBlock(text=\"\\n\\n\".join(parts))\n\n\nasync def preprocess_document_message(\n    message: ChatMessage,\n    convert_service: DocumentConverter,\n    max_concurrency: int | None = None,","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/components/chat/processors/chat_history/documents/document_preprocessor.py#L44-L80","documentation":"ValueError from _process_document (chat history preprocessor). It converts a DocumentBlock's source to text via source.to_text(convert_service) on a worker thread; if the result is empty or None, no LITextBlock can be built and it raises, naming the source type. This guards the ingestion path where every document attachment must yield text for the chat history.","triggerScenarios":"Uploading an image/PDF/binary whose conversion yields nothing (empty PDF, scanned image without OCR, corrupt file), a 0-byte file, or a source type whose to_text returns '' for this converter configuration.","commonSituations":"Scanned PDFs with no OCR backend configured; empty or password-protected documents; unsupported formats that the DocumentConverter silently skips; upload endpoints accepting files without content checks.","solutions":["Open the uploaded file locally and confirm it actually contains extractable text (pdftotext, cat).","For scanned PDFs/images, enable an OCR-capable converter or OCR the file before upload.","Reject empty/unsupported files at the API boundary before they reach the chat pipeline (check size and content type).","Check the reported source type in the message to see which converter branch returned nothing."],"exampleFix":"// before\n# upload scanned.pdf (no text layer) -> chat request\nValueError: No content could be extracted from document source (type='file').\n\n// after\n# OCR first, then upload\n$ ocrmypdf in.pdf out.pdf && curl -F file=@out.pdf ...","handlingStrategy":"validation","validationCode":"text = await asyncio.to_thread(doc_block.source.to_text, convert_service)\nif not text or not text.strip():\n    raise HTTPException(422, f\"Document yielded no text (source type={doc_block.source.type!r})\")","typeGuard":"def document_has_extractable_text(path: str) -> bool:\n    import subprocess\n    return subprocess.run([\"pdftotext\", path, \"-\"], capture_output=True).stdout.strip() != b\"\"","tryCatchPattern":"try:\n    block = await _process_document(doc_block, converter)\nexcept ValueError as e:\n    raise HTTPException(422, str(e)) from e","preventionTips":["Reject zero-byte and unsupported uploads at the API boundary.","OCR scanned PDFs/images before ingestion.","Verify extractability client-side for PDFs (pdftotext).","Surface the source type in error reports to speed diagnosis."],"tags":["documents","ingestion","ocr","chat-history","empty-content"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}