← Back to DevBytes

Building a Invoice Processing Agent with LangChain: Complete Guide

What is an Invoice Processing Agent?

An Invoice Processing Agent is an AI-powered system that automates the extraction, validation, and organization of data from invoice documents. Built using LangChain, it combines large language models (LLMs) with specialized tools to read PDFs or images, extract fields like invoice numbers, dates, line items, and totals, cross-reference them against business rules, and output structured JSON or database records. Unlike a simple parser, an agent can reason about ambiguous layouts, handle missing data gracefully, and even flag discrepancies for human review.

Think of it as a digital accounts payable clerk that works 24/7, never makes typo errors, and scales effortlessly from 10 invoices a day to 10,000.

Why Automate Invoice Processing with LangChain?

Manual invoice processing is slow, expensive, and error-prone. Companies spend an average of $15–$30 per invoice when handled manually, and mistakes lead to payment delays, duplicate payments, and strained vendor relationships. Automating this workflow matters because:

LangChain is particularly well-suited because it provides a unified framework to chain document loaders, LLM calls, structured output parsers, and custom validation tools — all while maintaining context across steps. You get a composable, testable pipeline rather than a monolithic black box.

Architecture Overview

A well-designed invoice agent typically follows a multi-stage pipeline:

The LangChain agent orchestrates these stages, deciding when to call which tool based on the current state of processing. This allows dynamic handling of edge cases — for example, if extraction confidence is low, the agent may re-read a specific chunk or escalate for human review.

Setting Up the Environment

Installing Dependencies

Start by installing the required packages. You'll need LangChain core, OpenAI integration, PDF loaders, and structured output utilities:

# Create a new project directory
mkdir invoice-agent && cd invoice-agent

# Create a virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install core dependencies
pip install langchain langchain-openai langchain-community
pip install pypdf pdfplumber  # PDF parsing
pip install pillow pytesseract  # OCR for image-based invoices
pip install pandas  # For data manipulation
pip install python-dotenv  # Environment variable management

Setting Up API Keys

Create a .env file to store your API keys securely. The agent will use OpenAI for LLM calls, but you can swap in other providers:

# .env file
OPENAI_API_KEY=sk-your-openai-api-key-here
# Optional: For using alternative models
# ANTHROPIC_API_KEY=sk-ant-your-key

Load these in your application entry point:

# config.py
import os
from dotenv import load_dotenv

load_dotenv()

OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
if not OPENAI_API_KEY:
    raise ValueError("OPENAI_API_KEY not found in environment variables")

Building the Core Components

Document Loading and Intelligent Splitting

Invoices can be multi-page PDFs with tables that span page breaks. A naive text split would destroy table context, making extraction unreliable. Use a PDF loader that preserves layout, then split with awareness of structural boundaries:

# document_loader.py
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from typing import List
from langchain.schema import Document

def load_invoice_pdf(file_path: str) -> List[Document]:
    """
    Load a PDF invoice and split it into semantically meaningful chunks.
    Uses PyPDFLoader for initial loading and a custom splitter
    that respects page boundaries and table structures.
    """
    loader = PyPDFLoader(file_path)
    raw_documents = loader.load()
    
    # Use a splitter with larger chunk size to keep tables intact
    # and significant overlap to preserve context across boundaries
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=2000,
        chunk_overlap=400,
        separators=["\n\n\n", "\n\n", "\n", " ", ""],
        length_function=len,
    )
    
    split_docs = text_splitter.split_documents(raw_documents)
    
    print(f"Loaded {len(raw_documents)} pages, split into {len(split_docs)} chunks")
    return split_docs

# Example usage:
# docs = load_invoice_pdf("invoices/sample_invoice.pdf")

For image-based invoices (scanned documents), add an OCR preprocessing step:

# ocr_loader.py
from PIL import Image
import pytesseract
from langchain.schema import Document

def ocr_invoice_image(image_path: str) -> Document:
    """
    Extract text from a scanned invoice image using Tesseract OCR.
    Returns a LangChain Document ready for the extraction pipeline.
    """
    image = Image.open(image_path)
    
    # Configure Tesseract for better extraction
    # --psm 6: Assume a uniform block of text
    # --oem 3: Use LSTM OCR engine
    custom_config = r'--oem 3 --psm 6 -c tessedit_char_whitelist=0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.,$€£%()-/@\n '
    
    extracted_text = pytesseract.image_to_string(
        image, 
        config=custom_config
    )
    
    return Document(
        page_content=extracted_text,
        metadata={"source": image_path, "type": "ocr_invoice"}
    )

# Example usage:
# doc = ocr_invoice_image("invoices/scanned_invoice.jpg")
# print(doc.page_content[:500])

Creating the Extraction Chain with Structured Output

This is the heart of the agent. Define a Pydantic schema for invoice data, then create a chain that prompts the LLM to extract structured information. Using LangChain's StructuredOutputParser ensures the output is always a valid object you can programmatically use:

# extraction_schema.py
from pydantic import BaseModel, Field
from typing import List, Optional
from datetime import date
from decimal import Decimal

class LineItem(BaseModel):
    """A single line item from an invoice."""
    description: str = Field(description="Product or service description")
    quantity: int = Field(description="Quantity of items", ge=0)
    unit_price: Decimal = Field(description="Price per unit in the invoice currency")
    line_total: Decimal = Field(description="Total for this line (quantity * unit_price)")
    tax_rate: Optional[Decimal] = Field(default=None, description="Tax rate applied to this line")
    
class InvoiceData(BaseModel):
    """Complete structured data extracted from an invoice."""
    invoice_number: str = Field(description="Unique invoice identifier, e.g., 'INV-2024-001'")
    vendor_name: str = Field(description="Name of the company or person issuing the invoice")
    vendor_address: Optional[str] = Field(default=None, description="Vendor's physical or billing address")
    vendor_tax_id: Optional[str] = Field(default=None, description="VAT/Tax identification number of vendor")
    customer_name: Optional[str] = Field(default=None, description="Name of the customer being billed")
    invoice_date: date = Field(description="Date the invoice was issued, in YYYY-MM-DD format")
    due_date: Optional[date] = Field(default=None, description="Payment due date, if specified")
    currency: str = Field(default="USD", description="ISO 4217 currency code, e.g., USD, EUR, GBP")
    line_items: List[LineItem] = Field(description="All line items on the invoice", min_items=1)
    subtotal: Decimal = Field(description="Sum of all line totals before tax")
    tax_total: Decimal = Field(description="Total tax amount")
    shipping_handling: Optional[Decimal] = Field(default=None, description="Shipping or handling charges")
    discount: Optional[Decimal] = Field(default=None, description="Any discounts applied")
    grand_total: Decimal = Field(description="Final total to be paid (subtotal + tax + shipping - discount)")
    payment_terms: Optional[str] = Field(default=None, description="Payment terms, e.g., 'Net 30'")
    notes: Optional[str] = Field(default=None, description="Any additional notes or remarks on the invoice")

Now build the extraction chain that takes raw document text and returns a populated InvoiceData object:

# extraction_chain.py
from langchain_openai import ChatOpenAI
from langchain.output_parsers import PydanticOutputParser
from langchain.prompts import ChatPromptTemplate, HumanMessagePromptTemplate
from langchain.schema import SystemMessage
from extraction_schema import InvoiceData

class InvoiceExtractor:
    """Extracts structured invoice data from document text using an LLM."""
    
    def __init__(self, model_name: str = "gpt-4o-mini", temperature: float = 0):
        self.llm = ChatOpenAI(
            model=model_name,
            temperature=temperature,  # Zero temperature for consistent extraction
            max_tokens=2000
        )
        self.parser = PydanticOutputParser(pydantic_object=InvoiceData)
        
        # Build the prompt with format instructions baked in
        self.system_prompt = SystemMessage(content=(
            "You are an expert invoice data extraction assistant. "
            "Your task is to read the provided invoice text and extract "
            "all relevant fields into a structured JSON format.\n\n"
            "CRITICAL INSTRUCTIONS:\n"
            "- Extract ALL line items with their descriptions, quantities, prices, and totals.\n"
            "- For monetary values, extract them as decimal numbers WITHOUT currency symbols.\n"
            "- If a field is not present, leave it as null — do NOT hallucinate values.\n"
            "- Verify that line totals sum to the subtotal, and subtotal + tax + shipping - discount equals grand_total.\n"
            "- Dates should be in YYYY-MM-DD format.\n"
            "- If you see handwritten or OCR text with possible errors, note it in the 'notes' field.\n"
            "- Be precise about tax identification numbers and invoice numbers."
        ))
        
        self.human_template = HumanMessagePromptTemplate.from_template(
            "INVOICE DOCUMENT TEXT:\n{invoice_text}\n\n"
            "{format_instructions}\n\n"
            "Please extract the invoice data now."
        )
        
        self.prompt = ChatPromptTemplate.from_messages([
            self.system_prompt,
            self.human_template
        ])
        
        # Wire up the chain: prompt -> LLM -> parser
        self.chain = self.prompt | self.llm | self.parser
    
    def extract(self, document_text: str) -> InvoiceData:
        """
        Extract structured invoice data from raw text.
        Returns a validated InvoiceData Pydantic object.
        """
        format_instructions = self.parser.get_format_instructions()
        
        result: InvoiceData = self.chain.invoke({
            "invoice_text": document_text,
            "format_instructions": format_instructions
        })
        
        return result

# Example usage:
# extractor = InvoiceExtractor()
# sample_text = open("sample_invoice_text.txt").read()
# invoice_data = extractor.extract(sample_text)
# print(invoice_data.model_dump_json(indent=2))

Building the Verification Agent Tool

Extraction alone isn't enough. A verification step catches errors, validates business logic, and flags suspicious data. This tool becomes one of the agent's callable functions:

# verification_tool.py
from langchain.tools import tool
from extraction_schema import InvoiceData
from decimal import Decimal
from datetime import date, timedelta
from typing import List, Dict

@tool
def verify_invoice_data(invoice_json: str) -> str:
    """
    Verify extracted invoice data for consistency and validity.
    
    Input: A JSON string representation of InvoiceData.
    Returns: A verification report with status, flags, and recommendations.
    
    Checks performed:
    - Mathematical consistency (line totals vs subtotal vs grand total)
    - Date validity (invoice date not in the future, due date after invoice date)
    - Required fields present
    - Suspicious patterns (round numbers, missing tax, unusual discounts)
    """
    import json
    
    try:
        data_dict = json.loads(invoice_json)
    except json.JSONDecodeError:
        return "ERROR: Invalid JSON input. Cannot verify."
    
    flags: List[Dict[str, str]] = []
    all_ok = True
    
    # --- Check 1: Mathematical consistency ---
    line_items = data_dict.get("line_items", [])
    calculated_subtotal = Decimal("0")
    for item in line_items:
        qty = Decimal(str(item.get("quantity", 0)))
        price = Decimal(str(item.get("unit_price", 0)))
        line_total_from_item = Decimal(str(item.get("line_total", 0)))
        expected_line_total = qty * price
        
        if abs(line_total_from_item - expected_line_total) > Decimal("0.01"):
            flags.append({
                "type": "math_error",
                "severity": "HIGH",
                "detail": f"Line item '{item.get('description', 'Unknown')[:50]}' has total {line_total_from_item} "
                          f"but quantity * price = {expected_line_total}"
            })
            all_ok = False
        
        calculated_subtotal += line_total_from_item
    
    stated_subtotal = Decimal(str(data_dict.get("subtotal", 0)))
    if abs(calculated_subtotal - stated_subtotal) > Decimal("0.02"):
        flags.append({
            "type": "subtotal_mismatch",
            "severity": "HIGH",
            "detail": f"Sum of line totals ({calculated_subtotal}) does not match stated subtotal ({stated_subtotal})"
        })
        all_ok = False
    
    # Grand total reconciliation
    tax = Decimal(str(data_dict.get("tax_total", 0)))
    shipping = Decimal(str(data_dict.get("shipping_handling", 0)))
    discount = Decimal(str(data_dict.get("discount", 0)))
    expected_grand = stated_subtotal + tax + shipping - discount
    stated_grand = Decimal(str(data_dict.get("grand_total", 0)))
    
    if abs(expected_grand - stated_grand) > Decimal("0.05"):
        flags.append({
            "type": "grand_total_mismatch",
            "severity": "CRITICAL",
            "detail": f"Expected grand total {expected_grand} but stated as {stated_grand}. "
                      f"Difference: {expected_grand - stated_grand}"
        })
        all_ok = False
    
    # --- Check 2: Date validity ---
    today = date.today()
    inv_date_str = data_dict.get("invoice_date")
    if inv_date_str:
        try:
            inv_date = date.fromisoformat(inv_date_str)
            if inv_date > today:
                flags.append({
                    "type": "future_date",
                    "severity": "MEDIUM",
                    "detail": f"Invoice date {inv_date_str} is in the future"
                })
                all_ok = False
        except ValueError:
            flags.append({
                "type": "invalid_date",
                "severity": "HIGH",
                "detail": f"Cannot parse invoice_date: {inv_date_str}"
            })
            all_ok = False
    
    due_date_str = data_dict.get("due_date")
    if inv_date_str and due_date_str:
        try:
            inv_date = date.fromisoformat(inv_date_str)
            due_date = date.fromisoformat(due_date_str)
            if due_date < inv_date:
                flags.append({
                    "type": "due_before_invoice",
                    "severity": "HIGH",
                    "detail": f"Due date {due_date_str} is before invoice date {inv_date_str}"
                })
                all_ok = False
        except ValueError:
            pass  # Already flagged above
    
    # --- Check 3: Required fields ---
    required_fields = ["invoice_number", "vendor_name", "grand_total"]
    for field in required_fields:
        val = data_dict.get(field)
        if val is None or (isinstance(val, str) and val.strip() == ""):
            flags.append({
                "type": "missing_field",
                "severity": "HIGH",
                "detail": f"Required field '{field}' is missing or empty"
            })
            all_ok = False
    
    # --- Check 4: Suspicious patterns ---
    if data_dict.get("discount") and Decimal(str(data_dict["discount"])) > stated_subtotal * Decimal("0.3"):
        flags.append({
            "type": "large_discount",
            "severity": "LOW",
            "detail": "Discount exceeds 30% of subtotal — verify manually"
        })
    
    if len(line_items) > 20:
        flags.append({
            "type": "many_line_items",
            "severity": "LOW",
            "detail": f"Invoice has {len(line_items)} line items — review for duplicates"
        })
    
    # --- Build report ---
    if all_ok and not flags:
        return json.dumps({
            "status": "PASSED",
            "message": "All verification checks passed. Invoice data is consistent.",
            "flags": []
        }, indent=2)
    else:
        return json.dumps({
            "status": "FAILED" if any(f["severity"] in ("HIGH", "CRITICAL") for f in flags) else "WARNING",
            "message": f"Found {len(flags)} issue(s). Review required.",
            "flags": flags
        }, indent=2)

# The tool is decorated with @tool, making it directly usable in a LangChain agent

Building the Summarization Tool

After extraction and verification, generate a concise human-readable summary suitable for dashboards, emails, or approval workflows:

# summarization_tool.py
from langchain.tools import tool
from langchain_openai import ChatOpenAI
from langchain.prompts import PromptTemplate

_summary_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3, max_tokens=500)

SUMMARY_PROMPT = PromptTemplate.from_template(
    """You are an accounts payable assistant. Create a concise, professional summary
    of the following verified invoice data for management review.

    INVOICE DATA (JSON):
    {invoice_json}

    VERIFICATION REPORT:
    {verification_report}

    Generate a 3-5 sentence summary covering:
    1. Who the invoice is from and for how much
    2. Key dates (issued, due)
    3. Any flags or issues from verification
    4. A recommended action (approve, reject, or review)

    SUMMARY:"""
)

@tool
def summarize_invoice(invoice_json: str, verification_report: str) -> str:
    """
    Generate a human-readable summary of an invoice with verification results.
    
    Inputs:
    - invoice_json: JSON string of the extracted InvoiceData
    - verification_report: JSON string from the verification tool
    
    Returns a plain-text summary suitable for email or dashboard display.
    """
    chain = SUMMARY_PROMPT | _summary_llm
    result = chain.invoke({
        "invoice_json": invoice_json,
        "verification_report": verification_report
    })
    return result.content

# Example:
# summary = summarize_invoice.invoke({
#     "invoice_json": invoice_data.model_dump_json(),
#     "verification_report": verification_result
# })
# print(summary)

Assembling the LangChain Agent

Defining Tools

The agent needs access to all the tools you've built. Bundle them into a list that the agent can call dynamically:

# tools_registry.py
from verification_tool import verify_invoice_data
from summarization_tool import summarize_invoice
from langchain.tools import Tool

# Additional utility: a simple calculator for the agent to verify math
@tool
def calculate_line_total(quantity: int, unit_price: float) -> str:
    """Multiply quantity by unit price and return the result as a string."""
    result = quantity * unit_price
    return f"Line total: {result:.2f}"

# Additional utility: lookup known vendor database (simulated)
@tool
def lookup_vendor(vendor_name: str) -> str:
    """
    Look up a vendor in the known vendor database.
    Returns vendor details if found, or 'NOT_FOUND' if unknown.
    """
    # Simulated database — in production, query a real DB
    known_vendors = {
        "acme corp": "TAX_ID: ACME123 | Payment Terms: Net 30 | Status: ACTIVE",
        "globex industries": "TAX_ID: GBX456 | Payment Terms: Net 15 | Status: ACTIVE",
        "initech": "TAX_ID: INT789 | Payment Terms: Net 45 | Status: ACTIVE",
    }
    
    vendor_lower = vendor_name.lower().strip()
    if vendor_lower in known_vendors:
        return known_vendors[vendor_lower]
    return "NOT_FOUND"

# Master tool list for the agent
agent_tools = [
    verify_invoice_data,
    summarize_invoice,
    calculate_line_total,
    lookup_vendor,
]

Creating the Agent Executor

Now wire everything into a LangChain agent. The agent receives a document, uses the extractor to get structured data, then iteratively calls verification, vendor lookup, and summarization tools as needed:

# invoice_agent.py
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from extraction_chain import InvoiceExtractor
from tools_registry import agent_tools
from document_loader import load_invoice_pdf
from typing import Dict, Any

class InvoiceProcessingAgent:
    """
    A complete LangChain agent that orchestrates invoice processing:
    load -> extract -> verify -> enrich -> summarize.
    """
    
    def __init__(self, model_name: str = "gpt-4o"):
        self.extractor = InvoiceExtractor()
        
        # The agent uses a more capable model for reasoning about tool usage
        self.agent_llm = ChatOpenAI(
            model=model_name, 
            temperature=0,
            max_tokens=2000
        )
        
        # Agent prompt with clear instructions on the workflow
        self.agent_prompt = ChatPromptTemplate.from_messages([
            ("system", 
             """You are an intelligent invoice processing agent. Your job is to process
             invoices through a structured pipeline:

             STEP 1: The user will provide extracted invoice data as JSON.
             STEP 2: You MUST call 'verify_invoice_data' with the JSON to check for errors.
             STEP 3: If verification flags issues, analyze them and note recommendations.
             STEP 4: Call 'lookup_vendor' to enrich vendor information.
             STEP 5: Call 'summarize_invoice' with the invoice JSON AND verification report.
             STEP 6: Present the final summary to the user along with any recommended actions.

             ALWAYS follow ALL steps in order. Do not skip verification.
             If verification shows CRITICAL issues, recommend REJECTION.
             If verification shows HIGH issues, recommend MANUAL REVIEW.
             If verification passes, recommend APPROVAL.
             
             Use the tools provided. Be thorough and professional."""),
            ("user", "{input}"),
            MessagesPlaceholder(variable_name="agent_scratchpad"),
        ])
        
        # Create the OpenAI tools agent
        self.agent = create_openai_tools_agent(
            llm=self.agent_llm,
            tools=agent_tools,
            prompt=self.agent_prompt
        )
        
        self.executor = AgentExecutor(
            agent=self.agent,
            tools=agent_tools,
            verbose=True,
            max_iterations=10,
            handle_parsing_errors=True,
            return_intermediate_steps=True
        )
    
    def process_invoice(self, file_path: str) -> Dict[str, Any]:
        """
        End-to-end processing of an invoice PDF.
        
        Returns a dictionary with:
        - extracted_data: The structured InvoiceData as a dict
        - verification_report: Results from the verification step
        - vendor_info: Enriched vendor data (if found)
        - summary: Human-readable summary
        - recommendation: APPROVE, REVIEW, or REJECT
        - intermediate_steps: Full agent trace for auditing
        """
        print(f"\n{'='*60}")
        print(f"Processing invoice: {file_path}")
        print(f"{'='*60}\n")
        
        # Step 1: Load document
        print("[Stage 1/4] Loading document...")
        documents = load_invoice_pdf(file_path)
        full_text = "\n\n".join([doc.page_content for doc in documents])
        
        # Step 2: Extract structured data
        print("[Stage 2/4] Extracting structured data...")
        invoice_data = self.extractor.extract(full_text)
        invoice_json = invoice_data.model_dump_json(indent=2)
        print(f"  Extracted invoice #{invoice_data.invoice_number} "
              f"from {invoice_data.vendor_name}")
        
        # Step 3-6: Agent handles verification, lookup, summarization
        print("[Stage 3-6/4] Agent processing (verify, lookup, summarize)...")
        
        agent_input = (
            f"Here is the extracted invoice data as JSON:\n\n"
            f"json\n{invoice_json}\n\n\n"
            f"Please follow the pipeline: verify this data, look up the vendor, "
            f"and provide a final summary with a recommendation."
        )
        
        result = self.executor.invoke({
            "input": agent_input
        })
        
        return {
            "extracted_data": invoice_data.model_dump(),
            "agent_output": result.get("output", ""),
            "intermediate_steps": [
                {"action": step[0].tool, "input": step[0].tool_input, "output": step[1]}
                for step in result.get("intermediate_steps", [])
            ],
            "status": "COMPLETE"
        }

# --- Run the agent ---
if __name__ == "__main__":
    agent = InvoiceProcessingAgent()
    result = agent.process_invoice("invoices/sample_invoice.pdf")
    
    print("\n" + "="*60)
    print("FINAL AGENT OUTPUT:")
    print("="*60)
    print(result["agent_output"])
    
    print("\n" + "="*60)
    print("EXTRACTED DATA (structured):")
    print("="*60)
    print(json.dumps(result["extracted_data"], indent=2, default=str))

Putting It All Together — Complete Pipeline Script

Here is a self-contained script that runs the entire pipeline end-to-end. Save it as run_pipeline.py and execute it against a directory of invoices:

# run_pipeline.py
"""
Complete Invoice Processing Pipeline
Usage: python run_pipeline.py --input-dir ./invoices --output-dir ./results
"""

import argparse
import json
import os
from pathlib import Path
from datetime import datetime
from invoice_agent import InvoiceProcessingAgent
import pandas as pd

def process_directory(input_dir: str, output_dir: str):
    """Process all PDF invoices in a directory and save results."""
    input_path = Path(input_dir)
    output_path = Path(output_dir)
    output_path.mkdir(parents=True, exist_ok=True)
    
    pdf_files = list(input_path.glob("*.pdf"))
    print(f"Found {len(pdf_files)} PDF files to process")
    
    agent = InvoiceProcessingAgent()
    all_results = []
    
    for i, pdf_file in enumerate(pdf_files, 1):
        print(f"\nProcessing file {i}/{len(pdf_files)}: {pdf_file.name}")
        
        try:
            result = agent.process_invoice(str(pdf_file))
            
            # Save individual result
            result_filename = pdf_file.stem + "_result.json"
            result_path = output_path / result_filename
            with open(result_path, "w") as f:
                json.dump(result, f, indent=2, default=str)
            
            # Collect summary for aggregate report
            extracted = result.get("extracted_data", {})
            all_results.append({
                "filename": pdf_file.name,
                "invoice_number": extracted.get("invoice_number", "UNKNOWN"),
                "vendor_name": extracted.get("vendor_name", "UNKNOWN"),
                "invoice_date": extracted.get("invoice_date"),
                "grand_total": float(extracted.get("grand_total", 0)),
                "currency": extracted.get("currency", "USD"),
                "agent_summary": result.get("agent_output", "")[:200],
                "status": result.get("status", "ERROR")
            })
            
            print(f"  ✓ Saved result to {result_path}")
            
        except Exception as e:
            print(f"  ✗ Error processing {pdf_file.name}: {str(e)}")
            all_results.append({
                "filename": pdf_file.name,
                "status": f"ERROR: {str(e)[:100]}"
            })
    
    # Generate aggregate report
    df = pd.DataFrame(all_results)
    report_path = output_path / f"aggregate_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
    df.to_csv(report_path, index=False)
    
    print(f"\n{'='*60}")
    print(f"Pipeline complete. Processed {len(pdf_files)} invoices.")
    print(f"Aggregate report saved to: {report_path}")
    print(f"Individual results in: {output_path}")
    
    # Quick stats
    success_count = sum(1 for r in all_results if r.get("status") == "COMPLETE")
    total_value = sum(r.get("grand_total", 0) for r in all_results if r.get("grand_total"))
    print(f"Successfully processed: {success_count}/{len(pdf_files)}")
    print(f"Total invoice value: ${total_value:,.2f}")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Process invoices with LangChain agent")
    parser.add_argument("--input-dir", required=True, help="Directory containing invoice PDFs")
    parser.add_argument("--output-dir", required=True, help="Directory for results")
    args = parser.parse_args()
    
    process_directory(args.input_dir, args.output_dir)

Best Practices for Production Invoice Agents

When moving from prototype to production, keep these practices in mind:

Ext

— Ad —

Google AdSense will appear here after approval

← Back to all articles