{"record":{"id":"023684f12e07367a","repo":"opendataloader-project/opendataloader-pdf","slug":"hancom-ai-document-layout-with-ocr-returned-empty","errorCode":null,"errorMessage":"Hancom AI DOCUMENT_LAYOUT_WITH_OCR returned empty result — backend unavailable or rejected the document","messagePattern":"Hancom AI DOCUMENT_LAYOUT_WITH_OCR returned empty result — backend unavailable or rejected the document","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"java/opendataloader-pdf-core/src/main/java/org/opendataloader/pdf/hybrid/HancomAIClient.java","lineNumber":209,"sourceCode":"        LOGGER.log(Level.INFO, \"Hancom AI: processing PDF ({0} bytes)\", pdfBytes.length);\n\n        // Crop / page-image destination travels with the request, not the\n        // cached client's config, so the per-document target is correct even\n        // when the client is reused across documents (and is concurrency-safe\n        // since nothing shared is mutated).\n        CropOutput cropOutput = request.getCropOutput();\n\n        try (PageImageCache pageImageCache = createPageImageCache()) {\n            ObjectNode merged = objectMapper.createObjectNode();\n            ObjectNode timingsNode = objectMapper.createObjectNode();\n\n            // Step 1: DLA + OCR. This is required — downstream steps have nothing\n            // to process without it, so treat an empty response as a failure so the\n            // caller can fall back to the Java pipeline instead of silently emitting\n            // an empty document.\n            JsonNode dlaOcrResult = callModule(pdfBytes, \"DOCUMENT_LAYOUT_WITH_OCR\");\n            if (dlaOcrResult == null || !dlaOcrResult.isArray() || dlaOcrResult.size() == 0) {\n                throw new IOException(\n                    \"Hancom AI DOCUMENT_LAYOUT_WITH_OCR returned empty result — \"\n                    + \"backend unavailable or rejected the document\");\n            }\n            merged.set(\"DOCUMENT_LAYOUT_WITH_OCR\", dlaOcrResult);\n            addTimings(timingsNode, \"DOCUMENT_LAYOUT_WITH_OCR\", dlaOcrResult);\n\n            // Step 2: Table Structure — crop each Table region from page image, send to TSR individually\n            long tsrStartMs = System.currentTimeMillis();\n            ArrayNode tsrResults = recognizeTableStructures(pdfBytes, dlaOcrResult, pageImageCache, cropOutput);\n            long tsrMs = System.currentTimeMillis() - tsrStartMs;\n            merged.set(\"TABLE_STRUCTURE_RECOGNITION\", tsrResults);\n\n            ObjectNode tsrTiming = objectMapper.createObjectNode();\n            tsrTiming.put(\"total_ms\", tsrMs);\n            tsrTiming.put(\"count\", tsrResults.size());\n            if (tsrResults.size() > 0) {\n                tsrTiming.put(\"avg_ms\", tsrMs / tsrResults.size());\n            }","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/opendataloader-project/opendataloader-pdf/blob/a7789b8e77dd05e2b8659eb3ea12fc458f80bfb8/java/opendataloader-pdf-core/src/main/java/org/opendataloader/pdf/hybrid/HancomAIClient.java#L191-L227","documentation":"The first Hancom AI pipeline step, DOCUMENT_LAYOUT_WITH_OCR, returned null, a non-array, or an empty array. Because every later step (TSR, figure captioning) depends on DLA+OCR output, an empty result is treated as a hard failure so the caller can fall back to the Java pipeline instead of silently emitting an empty document.","triggerScenarios":"callModule(pdfBytes, \"DOCUMENT_LAYOUT_WITH_OCR\") returns null, a non-array JsonNode, or a zero-length array (the backend returned SUCCESS=false or an empty RESULT, which callModule collapses to an empty array).","commonSituations":"Backend model not loaded / crashed; backend rejected the PDF (corrupt, encrypted, unsupported); backend returned SUCCESS=false with a MSG that callModule only logged at WARNING; backend resource exhaustion.","solutions":["Check the server logs for the DOCUMENT_LAYOUT_WITH_OCR failure reason (callModule logs the MSG at WARNING)","Retry once in case of transient backend unavailability","Decrypt or repair the PDF before sending","Fall back to Java-only processing for this document (--hybrid-fallback)","Restart the Hancom AI backend if its model crashed"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    return client.convert(request);\n} catch (IOException e) {\n    if (e.getMessage().contains(\"DOCUMENT_LAYOUT_WITH_OCR returned empty result\")) {\n        // backend rejected the doc — fall back to Java pipeline\n        log.warn(\"DLA+OCR empty, falling back to Java-only\");\n        return null;\n    }\n    throw e;\n}","preventionTips":["Decrypt/repair PDFs before sending (rejected docs often yield empty DLA)","Watch for the WARNING log from callModule showing the backend MSG","Retry once for transient backend unavailability before falling back"],"tags":["hybrid","hancom-ai","backend-error","pipeline"],"backgroundTag":null,"analyzedSha":"a7789b8e77dd05e2b8659eb3ea12fc458f80bfb8","analyzedAt":"2026-08-14T05:22:03.953Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}