{"id":517,"date":"2026-05-21T15:33:14","date_gmt":"2026-05-21T13:33:14","guid":{"rendered":"https:\/\/www.rothamel.com\/?p=517"},"modified":"2026-05-21T15:33:14","modified_gmt":"2026-05-21T13:33:14","slug":"ai-based-document-processing-with-camunda-8","status":"publish","type":"post","link":"https:\/\/www.rothamel.com\/index.php\/2026\/05\/21\/ai-based-document-processing-with-camunda-8\/","title":{"rendered":"AI based document processing with Camunda 8"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">This tutorial guides you through setting up a <strong>local, privacy-compliant Intelligent Document Processing (IDP) pipeline<\/strong> using <strong>Camunda 8 (Desktop\/Run)<\/strong>, <strong>Ollama<\/strong> (for the LLM), and a <strong>Python External Task Worker<\/strong> (for OCR and file operations).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The pipeline reads PDF documents using OCR, analyzes the content with an Agentic AI (local LLM) to determine the sender and document type, and automatically reorganizes and renames the files within your local file system.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Architecture Overview<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91; Input Folder ] \u2794 (Python Worker: OCR) \u2794 &#91; Camunda 8 Engine ]\n                                               \u2502\n &#91; Local Directory ] \u2b8c (Python Worker) \u2b80 &#91; AI Agent Connector (Ollama) ]\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">1. Prerequisites &amp; Local Infrastructure<\/h2>\n\n\n\n<h2 class=\"wp-block-heading\">Install Ollama &amp; LLM<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Download and install Ollama.<\/li>\n\n\n\n<li>Open your terminal and download an LLM well-suited for structured JSON output and tool calling (e.g., <code>Llama 3<\/code> or <code>Mistral<\/code>):<code>ollama run llama3 <\/code><em>Note: Ollama exposes an OpenAI-compatible API endpoint by default at <code>http:\/\/localhost:11434\/v1<\/code>.<\/em><\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Set Up Python Environment<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Create a project directory and install the required dependencies, including the official <a href=\"https:\/\/docs.camunda.io\/docs\/apis-tools\/python-sdk\/\">Camunda 8 Python SDK<\/a>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pip install camunda-orchestration-sdk pypdf pytesseract pdf2image OpenAI\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><em>(Ensure that Tesseract OCR is installed on your system and added to your system environment variables\/PATH).<\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">2. Camunda BPMN Process Modeling<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Create a new BPMN diagram in the <strong>Camunda Desktop Modeler<\/strong> or <strong>Web Modeler<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Process Steps Definition<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Service Task 1<\/strong>: &#8220;Extract Document Text via OCR&#8221;\n<ul class=\"wp-block-list\">\n<li><strong>Type (Job Worker Key)<\/strong>: <code>ocr-extract<\/code><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>AI Agent Task (Connector)<\/strong>: &#8220;Analyze Document Content&#8221;\n<ul class=\"wp-block-list\">\n<li>Use the <strong>AI Agent Connector<\/strong> available in Camunda 8.<\/li>\n\n\n\n<li><strong>Configuration<\/strong>:\n<ul class=\"wp-block-list\">\n<li><strong>Provider<\/strong>: <code>OpenAI-compatible<\/code> (or Custom REST depending on your connector version)<\/li>\n\n\n\n<li><strong>Endpoint<\/strong>: <code>http:\/\/localhost:11434\/v1<\/code><\/li>\n\n\n\n<li><strong>Model ID<\/strong>: <code>llama3<\/code><\/li>\n\n\n\n<li><strong>System Prompt<\/strong>:<code>You are a precise document classifier. Analyze the text and extract the sender (company or person) and the document type (e.g., Invoice, Contract, Cancellation, Cover Letter). Respond exclusively in valid JSON format: {\"sender\": \"Name\", \"type\": \"Type\"}.<\/code><\/li>\n\n\n\n<li><strong>Prompt<\/strong>: <code>Context Text: {{ocr_text}}<\/code><\/li>\n\n\n\n<li><strong>Result Variable<\/strong>: <code>analysis_result<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Service Task 3<\/strong>: &#8220;Move and Rename File&#8221;\n<ul class=\"wp-block-list\">\n<li><strong>Type (Job Worker Key)<\/strong>: <code>file-organizer<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">3. Implementing the Python Job Worker<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Create a file named <code>app.py<\/code>. This worker handles the extraction of the PDF content and the final local file system operations.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import os\nimport json\nimport shutil\nfrom pathlib import Path\nfrom pypdf import PdfReader\nimport pytesseract\nfrom pdf2image import convert_from_path\nfrom camunda.client import CamundaClient\n\n<em># Configuration<\/em>\nINPUT_DIR = Path(\".\/input\")\nSTORAGE_ROOT = Path(\".\/archive\")\n\n<em># Initialize local directory structures<\/em>\nINPUT_DIR.mkdir(exist_ok=True)\nSTORAGE_ROOT.mkdir(exist_ok=True)\n\n<em># Initialize Camunda Client (Default configurations for Self-Managed \/ Local Run)<\/em>\nclient = CamundaClient() \n\ndef extract_text_from_pdf(pdf_path):\n    \"\"\"Extracts native text, falls back to OCR if the PDF is a scan.\"\"\"\n    text = \"\"\n    reader = PdfReader(pdf_path)\n    for page in reader.pages:\n        text += page.extract_text() or \"\"\n    \n    <em># Fallback to OCR if no native text is found<\/em>\n    if not text.strip():\n        images = convert_from_path(pdf_path)\n        for img in images:\n            text += pytesseract.image_to_string(img)\n            \n    return text\n\n<em># 1. WORKER: OCR &amp; Text Extraction<\/em>\n@client.worker(type=\"ocr-extract\")\ndef handle_ocr(job):\n    file_name = job.variables.get(\"file_name\")\n    file_path = INPUT_DIR \/ file_name\n    \n    if not file_path.exists():\n        return {\"error\": \"File not found\"}\n        \n    extracted_text = extract_text_from_pdf(file_path)\n    \n    <em># Restrict context length to fit local LLM context windows smoothly<\/em>\n    return {\"ocr_text\": extracted_text&#91;:4000]} \n\n<em># 2. WORKER: File Organization &amp; Renaming<\/em>\n@client.worker(type=\"file-organizer\")\ndef handle_organization(job):\n    file_name = job.variables.get(\"file_name\")\n    analysis_raw = job.variables.get(\"analysis_result\")\n    \n    <em># Parse the JSON string returned by the LLM<\/em>\n    try:\n        data = json.loads(analysis_raw)\n        sender = data.get(\"sender\", \"Unknown\").replace(\" \", \"_\")\n        doc_type = data.get(\"type\", \"Other\").replace(\" \", \"_\")\n    except Exception:\n        sender, doc_type = \"Unknown\", \"Unclassified\"\n\n    <em># Define target directory structure (e.g., .\/archive\/Acme_Corp\/Invoice\/)<\/em>\n    target_dir = STORAGE_ROOT \/ sender \/ doc_type\n    target_dir.mkdir(parents=True, exist_ok=True)\n    \n    <em># Generate new standardized filename (e.g., Invoice_Acme_Corp.pdf)<\/em>\n    file_extension = Path(file_name).suffix\n    new_file_name = f\"{doc_type}_{sender}{file_extension}\"\n    target_path = target_dir \/ new_file_name\n    \n    <em># Execute file movement<\/em>\n    source_path = INPUT_DIR \/ file_name\n    shutil.move(str(source_path), str(target_path))\n    \n    print(f\"File successfully archived: {target_path}\")\n    return {\"status\": \"success\", \"archived_path\": str(target_path)}\n\nif __name__ == \"__main__\":\n    print(\"Python Camunda Workers active. Awaiting jobs...\")\n    client.start_workers()\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">4. Triggering and Testing the Pipeline<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To feed documents into the system, you can use a secondary helper script that scans your input directory and instantiates a process instance in Camunda for each discovered file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em># trigger_process.py<\/em>\nimport os\nfrom camunda.client import CamundaClient\n\nclient = CamundaClient()\n\ndef check_for_new_files():\n    for file in os.listdir(\".\/input\"):\n        if file.endswith(\".pdf\"):\n            <em># Pass the filename into the process variables<\/em>\n            client.start_process_instance(\n                process_definition_key=\"Process_Document_Pipeline\", \n                variables={\"file_name\": file}\n            )\n            print(f\"Process instance started for file: {file}\")\n\nif __name__ == \"__main__\":\n    check_for_new_files()\n<\/code><\/pre>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Place a sample PDF document inside the <code>.\/input<\/code> folder.<\/li>\n\n\n\n<li>Run <code>python app.py<\/code> in your terminal to keep your workers listening.<\/li>\n\n\n\n<li>In a separate terminal window, run <code>python trigger_process.py<\/code>.<\/li>\n\n\n\n<li>Your file will process through Camunda, get parsed by <strong>Ollama<\/strong>, and land perfectly organized under <code>.\/archive\/[Sender]\/[Type]\/<\/code>.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This tutorial guides you through setting up a local, privacy-compliant Intelligent Document Processing (IDP) pipeline using Camunda 8 (Desktop\/Run), Ollama (for the LLM), and a Python External Task Worker (for OCR and file operations). The pipeline reads PDF documents using OCR, analyzes the content with an Agentic AI (local LLM) to determine the sender and [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[5],"tags":[],"class_list":["post-517","post","type-post","status-publish","format-standard","hentry","category-java"],"jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p9W9h9-8l","jetpack-related-posts":[{"id":77,"url":"https:\/\/www.rothamel.com\/index.php\/2018\/10\/27\/liferay-development-for-beginners-part-two-creation-of-a-development-environment\/","url_meta":{"origin":517,"position":0},"title":"Liferay-Development for beginners \u2013 Part two \u2013 Creation of a development environment","author":"Rolf Rothamel","date":"2018-10-27","format":false,"excerpt":"The development for the Liferay portal is quite nice by using a local develop environment with Eclipse, Mysql and Liferay. Therefore you should download the following components: Liferay (http:\/\/sourceforge.net\/projects\/lportal\/files\/Liferay%20Portal\/) Liferay Portal bundled with Tomcat Liferay Portal sources Liferay Portal Documentation Liferay Portal SDK Eclipse Eclipse Liferay IDE (downloadable via Eclipse\u2026","rel":"","context":"In &quot;Java&quot;","block_context":{"text":"Java","link":"https:\/\/www.rothamel.com\/index.php\/category\/java\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":355,"url":"https:\/\/www.rothamel.com\/index.php\/2022\/11\/13\/migration-einer-linux-vms-von-vmware-workstation-zu-hyper-v\/","url_meta":{"origin":517,"position":1},"title":"Migrating a Linux VM from VMWare Workstation to Hyper-V","author":"Rolf Rothamel","date":"2022-11-13","format":false,"excerpt":"The following is a quick guide to migrating an OpenSuse Linux VMs on VMWare Workstation to Hyper-V. Preparations Create snapshot (as a backup in case something goes wrong) Remove VMWare Tools add dracut Config: \/etc\/dracut.conf.d\/00-custom.conf and add the following line. Do not forget the spaces before and after the modules!\u2026","rel":"","context":"In &quot;Linux&quot;","block_context":{"text":"Linux","link":"https:\/\/www.rothamel.com\/index.php\/category\/linux\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":219,"url":"https:\/\/www.rothamel.com\/index.php\/2020\/07\/15\/docker-teil-1-tomcat-im-docker-container\/","url_meta":{"origin":517,"position":2},"title":"Container &#8211; Part 1: Tomcat im Docker-Container","author":"Rolf Rothamel","date":"2020-07-15","format":false,"excerpt":"Docker represents a pleasant possibility to create clearly defined, lightweight runtime environments (containers) and to distribute them if necessary. In the following, the creation and possible use is shown using the example of a container with Tomcat and OpenSuse.","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/www.rothamel.com\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":75,"url":"https:\/\/www.rothamel.com\/index.php\/2018\/10\/27\/liferay-development-for-beginners-part-one-setup-your-own-instance\/","url_meta":{"origin":517,"position":3},"title":"Liferay-Development for beginners &#8211; Part One \u2013 Setup your own instance","author":"Rolf Rothamel","date":"2018-10-27","format":false,"excerpt":"This little tutorial is written for users that are familiar with the following points, so they are not explained in detail: Working with linux (or any other OS of your choice), an editor like vi, etc. Install and configure a Mysql instance Creation of schema and assignment of user and\u2026","rel":"","context":"In &quot;Java&quot;","block_context":{"text":"Java","link":"https:\/\/www.rothamel.com\/index.php\/category\/java\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":349,"url":"https:\/\/www.rothamel.com\/index.php\/2023\/06\/03\/neato_d3_d4_d5_d6_d7_update\/","url_meta":{"origin":517,"position":4},"title":"Neato Botvac D3 &#8211; D7 Firmware Update 4.5.3_189","author":"Rolf Rothamel","date":"2023-06-03","format":false,"excerpt":"Created 2022-11-13, updated 2023-06-03 Instructions for manually updating the firmware of Neato Botvac robots (D3, D4, D5, D6, D7). The following instructions are intended for vacuum robots D3, D4, D5, D6, D7 from the manufacturer Neato. Note in advance: The procedure is performed at your own risk without any guarantee\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/www.rothamel.com\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":228,"url":"https:\/\/www.rothamel.com\/index.php\/2020\/07\/17\/container-teil-2-arbeiten-mit-docker-containern\/","url_meta":{"origin":517,"position":5},"title":"Container \u2013 Part 2: Working with Docker-Containers","author":"Rolf Rothamel","date":"2020-07-17","format":false,"excerpt":"After showing the creation of an exemplary Docker container in the first part, this part of the article series now focuses on working with containers.","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/www.rothamel.com\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"jetpack_likes_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/www.rothamel.com\/index.php\/wp-json\/wp\/v2\/posts\/517","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.rothamel.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.rothamel.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.rothamel.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.rothamel.com\/index.php\/wp-json\/wp\/v2\/comments?post=517"}],"version-history":[{"count":1,"href":"https:\/\/www.rothamel.com\/index.php\/wp-json\/wp\/v2\/posts\/517\/revisions"}],"predecessor-version":[{"id":518,"href":"https:\/\/www.rothamel.com\/index.php\/wp-json\/wp\/v2\/posts\/517\/revisions\/518"}],"wp:attachment":[{"href":"https:\/\/www.rothamel.com\/index.php\/wp-json\/wp\/v2\/media?parent=517"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.rothamel.com\/index.php\/wp-json\/wp\/v2\/categories?post=517"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.rothamel.com\/index.php\/wp-json\/wp\/v2\/tags?post=517"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}