基于LLM与OCR的收据自动分类系统:从原理到工程实践

📅 2026/7/25 2:29:03
基于LLM与OCR的收据自动分类系统:从原理到工程实践
在实际业务中财务报销、税务申报或费用分析往往需要处理大量来自不同渠道的纸质或电子收据。传统方式要么依赖人工逐张分类要么要求用户按固定格式上传流程繁琐且容易出错。Sorted Receipts 提供了一种思路用户将所有收据文件通过一个统一链接提交后端利用大语言模型LLM自动识别并分类。这种方案的核心价值在于降低了用户的操作门槛同时通过 AI 提升了后台处理效率。但真正落地时开发者需要解决几个关键问题LLM 如何理解收据内容收据图像或 PDF 如何转换为模型可读的文本分类规则如何定义系统如何保证处理准确性和数据安全下面我们围绕一个可运行的示例项目拆解从环境准备、模型选型、代码实现到结果验证的全过程。1. 理解 LLM 处理收据的工作机制1.1 收据信息的结构化特点收据虽然版式多样但通常包含几个关键字段商户名称、交易日期、金额、商品明细、税费等。LLM 在处理这类文档时实际完成的是两项任务首先从原始文件如图像、PDF中提取文字信息然后根据预设的类别如“餐饮”“交通”“办公用品”将提取到的文本进行归类。1.2 选择 LLM 的考量点通用大语言模型如 GPT-4、Claude 3在文本理解上能力强但直接处理图像或 PDF 需要额外步骤。如果收据质量高、文字清晰可以先用 OCR光学字符识别工具提取文本再交给 LLM 分类。如果收据版式复杂或含有表格可能需要专门训练过的视觉-语言多模态模型如 GPT-4V。对于内部部署或成本敏感的场景也可以考虑开源模型如 Llama、Qwen配合微调。1.3 分类规则的制定方式分类规则不能只靠模型自由发挥否则同一类收据可能被分到多个标签下。通常需要明确分类体系并通过提示词Prompt约束模型的输出格式。例如可以要求 LLM 始终从预定义的列表中选择类别并以 JSON 格式返回商户、日期、金额和分类结果。2. 环境准备与依赖配置2.1 基础环境要求本项目需要 Python 3.8 或以上版本主要依赖库包括处理 HTTP 请求、文件解析、OCR 和 LLM 调用。以下使用requirements.txt管理依赖fastapi0.104.1 uvicorn0.24.0 python-multipart0.0.6 pydantic2.5.0 openai1.3.0 pdf2image1.16.3 pytesseract0.3.10 pillow10.1.0 python-dotenv1.0.0安装命令pip install -r requirements.txt2.2 关键服务配置如果使用 OpenAI API需要在项目根目录创建.env文件保存密钥OPENAI_API_KEYyour_openai_api_key_here如果使用开源模型可能需要部署本地推理服务例如通过 Ollama 或 vLLM 启动模型服务并配置相应的基础 URL 和模型名称。2.3 文件上传与存储设置为了接收用户通过链接提交的收据文件需要准备一个临时存储目录。在生产环境中建议使用对象存储如 AWS S3、阿里云 OSS并设置生命周期策略定期清理过期文件。创建目录结构mkdir -p uploads/processed3. 构建收据处理的核心流程3.1 设计 API 接收端点使用 FastAPI 创建一个接收文件上传的端点支持多文件同时上传from fastapi import FastAPI, File, UploadFile, HTTPException from fastapi.responses import JSONResponse import os import uuid from typing import List app FastAPI() UPLOAD_DIR uploads os.makedirs(UPLOAD_DIR, exist_okTrue) app.post(/upload-receipts/) async def upload_receipts(files: List[UploadFile] File(...)): if not files: raise HTTPException(status_code400, detailNo files uploaded) file_paths [] for file in files: # 生成唯一文件名避免冲突 file_extension os.path.splitext(file.filename)[1] unique_filename f{uuid.uuid4()}{file_extension} file_path os.path.join(UPLOAD_DIR, unique_filename) # 保存文件 with open(file_path, wb) as f: content await file.read() f.write(content) file_paths.append(file_path) return JSONResponse({ message: fSuccessfully uploaded {len(files)} files, file_paths: file_paths })3.2 实现文件内容提取根据文件类型图像或 PDF调用相应的提取方法。对于图像使用 Tesseract OCR对于 PDF先转换为图像再 OCRimport pdf2image from PIL import Image import pytesseract def extract_text_from_image(image_path: str) - str: 从图像文件提取文本 try: image Image.open(image_path) text pytesseract.image_to_string(image, langeng) return text.strip() except Exception as e: raise RuntimeError(fFailed to extract text from image: {str(e)}) def extract_text_from_pdf(pdf_path: str) - str: 从 PDF 文件提取文本 try: images pdf2image.convert_from_path(pdf_path) full_text for image in images: text pytesseract.image_to_string(image, langeng) full_text text \n return full_text.strip() except Exception as e: raise RuntimeError(fFailed to extract text from PDF: {str(e)}) def extract_text_from_file(file_path: str) - str: 根据文件扩展名选择提取方法 ext os.path.splitext(file_path)[1].lower() if ext in [.jpg, .jpeg, .png, .bmp]: return extract_text_from_image(file_path) elif ext .pdf: return extract_text_from_pdf(file_path) else: raise ValueError(fUnsupported file type: {ext})3.3 构建 LLM 分类提示词设计一个结构化的提示词让 LLM 按照固定格式返回分类结果def build_receipt_classification_prompt(extracted_text: str) - str: 构建收据分类提示词 categories [餐饮, 交通, 办公用品, 住宿, 购物, 其他] prompt f 请分析以下收据文本内容提取关键信息并进行分类。 收据文本 {extracted_text} 请按照以下 JSON 格式返回结果 {{ merchant: 商户名称, date: 交易日期YYYY-MM-DD格式, amount: 金额数字, category: 分类标签从预定义列表中选择, confidence: 分类置信度0-1之间的小数 }} 预定义分类列表{, .join(categories)} 如果无法确定某些信息请用 null 表示。 请确保金额只返回数字不要包含货币符号。 return prompt4. 集成 LLM 并处理分类逻辑4.1 配置 LLM 客户端以 OpenAI API 为例配置客户端并实现分类函数from openai import OpenAI import os import json from dotenv import load_dotenv load_dotenv() client OpenAI(api_keyos.getenv(OPENAI_API_KEY)) def classify_receipt_with_llm(extracted_text: str) - dict: 使用 LLM 对收据文本进行分类 prompt build_receipt_classification_prompt(extracted_text) try: response client.chat.completions.create( modelgpt-3.5-turbo, messages[ {role: system, content: 你是一个专业的财务助理擅长从收据文本中提取结构化信息并进行准确分类。}, {role: user, content: prompt} ], temperature0.1 # 低温度保证输出稳定性 ) result_text response.choices[0].message.content.strip() # 解析 JSON 响应 result json.loads(result_text) return result except json.JSONDecodeError: raise RuntimeError(LLM 返回了无效的 JSON 格式) except Exception as e: raise RuntimeError(fLLM 调用失败: {str(e)})4.2 实现完整的收据处理流水线将文件提取、文本处理和分类整合为一个完整的流程def process_receipt_file(file_path: str) - dict: 处理单个收据文件的完整流程 try: # 步骤1提取文本 extracted_text extract_text_from_file(file_path) if not extracted_text.strip(): return { file_path: file_path, status: failed, error: 无法从文件中提取文本 } # 步骤2LLM 分类 classification_result classify_receipt_with_llm(extracted_text) return { file_path: file_path, status: success, extracted_text: extracted_text[:200] ... if len(extracted_text) 200 else extracted_text, classification: classification_result } except Exception as e: return { file_path: file_path, status: failed, error: str(e) } app.post(/process-receipts/) async def process_receipts(files: List[UploadFile] File(...)): 处理批量收据文件 file_paths [] for file in files: file_extension os.path.splitext(file.filename)[1] unique_filename f{uuid.uuid4()}{file_extension} file_path os.path.join(UPLOAD_DIR, unique_filename) with open(file_path, wb) as f: content await file.read() f.write(content) file_paths.append(file_path) # 批量处理文件 results [] for file_path in file_paths: result process_receipt_file(file_path) results.append(result) return JSONResponse({ processed_count: len(results), results: results })5. 运行验证与结果分析5.1 启动服务并测试使用 Uvicorn 启动 FastAPI 服务uvicorn main:app --reload --port 8000服务启动后可以通过http://localhost:8000/docs访问自动生成的 API 文档进行测试。5.2 模拟收据文件测试准备测试用的收据图像或 PDF通过 curl 或 Postman 发送 multipart/form-data 请求curl -X POST http://localhost:8000/process-receipts/ \ -F filesreceipt1.jpg \ -F filesreceipt2.pdf5.3 预期输出格式成功的响应应该包含每个文件的处理状态和分类结果{ processed_count: 2, results: [ { file_path: uploads/abc123.jpg, status: success, extracted_text: STARBUCKS Store #1234 Date: 2024-01-15 Total: $5.75..., classification: { merchant: STARBUCKS, date: 2024-01-15, amount: 5.75, category: 餐饮, confidence: 0.95 } }, { file_path: uploads/def456.pdf, status: success, extracted_text: OFFICE DEPOT Invoice Date: 2024-01-16 Total: $42.50..., classification: { merchant: OFFICE DEPOT, date: 2024-01-16, amount: 42.5, category: 办公用品, confidence: 0.92 } } ] }5.4 验证分类准确性对于关键业务场景需要建立验证机制人工抽样检查定期抽取部分结果与人工分类对比。置信度阈值对于置信度低于 0.7 的结果标记为需要人工复核。分类一致性同一商户的收据应该被分到相同类别。6. 常见问题排查与优化6.1 OCR 提取失败问题现象提取的文本为空或乱码。可能原因与解决方案问题现象常见原因检查方式处理建议文本提取为空图像质量差、文字太小检查图像分辨率、对比度预处理图像调整大小、增强对比度、转为灰度提取到乱码OCR 语言设置错误确认收据语言调整 Tesseract 语言参数中英文混合使用langengchi_simPDF 提取不全PDF 为扫描件或加密检查 PDF 属性对于扫描件按图像处理加密 PDF 需要先解密图像预处理代码示例from PIL import Image, ImageEnhance def preprocess_image(image_path: str) - Image.Image: 图像预处理提升 OCR 准确率 image Image.open(image_path) # 转为灰度 image image.convert(L) # 增强对比度 enhancer ImageEnhance.Contrast(image) image enhancer.enhance(2.0) # 调整大小保持长宽比 width, height image.size if width 2000 or height 2000: image.thumbnail((2000, 2000), Image.Resampling.LANCZOS) return image6.2 LLM 分类不准确问题现象分类结果与预期不符或置信度持续偏低。优化策略细化分类体系如果购物类别太宽泛可以细分为服装电子产品日用品等。增强提示词在提示词中加入分类规则和示例。后处理规则基于关键词匹配补充 LLM 分类结果。增强版提示词示例def build_enhanced_prompt(extracted_text: str) - str: categories { 餐饮: [餐厅, 咖啡, 快餐, 外卖, 食堂], 交通: [出租车, 地铁, 公交, 加油, 停车], 办公用品: [文具, 打印机, 纸张, 办公设备], 住宿: [酒店, 旅馆, 民宿], 购物: [超市, 商场, 网购] } examples 示例1 收据文本麦当劳 订单号123 金额$12.50 分类餐饮 示例2 收据文本中石化加油站 金额200.00 分类交通 prompt f 请分析收据文本并分类。以下是分类规则和示例 分类体系 {json.dumps(categories, indent2, ensure_asciiFalse)} {examples} 当前收据文本 {extracted_text} 请返回 JSON 格式结果。 return prompt6.3 性能与成本优化批量处理优化对于大量收据可以并行处理多个文件。设置合理的超时时间避免单个文件处理卡住整个流程。成本控制措施缓存相同商户的分类结果避免重复调用 LLM。对于简单收据可以先尝试基于规则的关键词匹配失败再调用 LLM。使用更经济的模型如 GPT-3.5-turbo进行初步分类。7. 生产环境部署建议7.1 安全考虑文件类型限制只允许上传图像和 PDF 格式检查文件魔数。文件大小限制设置最大文件大小如 10MB。敏感信息处理收据可能包含个人信息考虑数据脱敏或加密存储。API 密钥管理使用环境变量或密钥管理服务不要硬编码在代码中。7.2 可扩展性设计异步处理对于大量文件可以改为异步任务队列如 Celery Redis。微服务架构将文件上传、OCR 提取、LLM 分类拆分为独立服务。数据库集成将处理结果保存到数据库便于查询和统计分析。7.3 监控与日志关键指标监控处理成功率、平均处理时间、分类准确率。详细日志记录记录每个文件的处理流程便于排查问题。错误预警设置失败率阈值超过时发送告警。7.4 备份与容灾定期备份分类规则、模型配置等重要数据。多地域部署如果服务全球用户考虑在多地域部署以减少延迟。降级方案LLM 服务不可用时可以降级到基于规则的分类。通过以上完整的实现方案开发者可以构建一个实用的收据自动分类系统。实际项目中还需要根据具体业务需求调整分类规则、优化处理流程并建立持续改进机制来提升分类准确性。