
手动录入发票数据是一项缓慢且容易出错的任务,企业几十年来一直难以应对。最近,Uber 工程部门公布了他们如何通过“TextSense”平台应对这一挑战,这是一个用于 GenAI 发票处理的复杂系统。该系统展示了智能文档处理的强大功能,将光学字符识别 (OCR) 与大型语言模型 (LLM) 相结合,实现了高度准确的自动化数据提取。这种先进的方法对于小型项目来说似乎遥不可及。然而,其核心原理现在已面向大众。本指南将向您展示如何复制 Uber 系统的基本工作流程。我们将使用简单而强大的工具来创建一个自动化发票数据提取的系统。
了解Uber的“TextSense”系统
在构建我们的版本之前,了解其灵感来源将大有裨益。Uber 的目标是自动化处理数百万份文档,从发票到收据。他们的“TextSense”平台(在其工程博客中详细介绍)是一个专为此目的而设计的强大多阶段流程。
图中显示了完整的文档处理流程。对于处理任何文档,在调用 LLM 之前通常进行预处理。

Source: Uber
该系统的核心工作分为三个主要阶段:
- 数字化(通过 OCR):首先,系统获取文档,例如 PDF 或发票图片。它使用先进的 OCR 引擎“读取”文档,并将所有可视文本转换为机器可读的文本。原始文本是下一步的基础。
- 智能提取(通过 LLM):OCR 流程生成的原始文本通常杂乱无章且缺乏结构化。GenAI 的魔力就在于此。Uber 将这些文本输入到一个大型语言模型中。LLM 就像位了解发票上下文的专家。它可以识别并提取特定的信息,例如“发票号码”、“总金额”和“供应商名称”,并将它们组织成结构化格式,例如 JSON。
- 验证(人机交互):没有完美的 AI。为了确保 100% 的准确性,Uber 实施了人机交互 AI 系统。此验证步骤将原始文档与 AI 提取的数据一起呈现给人工操作员。操作员可以快速确认数据正确无误,或根据需要进行微调。这种反馈循环也有助于模型的持续改进。
OCR 与 AI 和人工监督的结合,使其系统高效可靠。下图详细解释了 TextSense 的工作流程,正如上文所述。

Source: Uber
我们的计划:复制核心工作流程
我们的目标并非重建 Uber 的整个生产级平台。相反,我们将以一种简化、易于访问的方式复制其核心智能。我们将在一个 Google Colab 笔记本中构建 GenAI 发票处理 POC。
我们的计划遵循相同的逻辑步骤:
- 提取文档:我们将创建一种简单的方法,将 PDF 发票直接上传到我们的笔记本。
- 执行 OCR:我们将使用强大的开源 OCR 引擎 Tesseract 从上传的发票中提取所有文本。
- 使用 AI 提取实体:我们将使用 Google Gemini API 执行自动数据提取。我们将设计一个特定的提示,指示模型提取我们需要的关键字段。
- 创建验证 UI:我们将使用 ipywidgets 构建一个简单的交互式界面,作为我们的人机交互 AI 系统,以便快速验证提取的数据。

这种方法为我们提供了一种强大且经济的智能文档处理方法,无需复杂的基础架构。
动手实践:逐步构建POC
让我们开始构建我们的系统。您可以在新的 Google Colab notebook 中按照以下步骤操作。
步骤 1:设置环境
首先,我们需要安装必要的 Python 库。此命令会安装用于处理 PDF(PyMuPDF)、运行 OCR(pytesseract)、与 Gemini API 交互以及构建 UI(ipywidgets)的软件包。它还会安装 Tesseract OCR 引擎本身。
!pip install -q -U google-generativeai PyMuPDF pytesseract pandas ipywidgets !apt-get -qq install tesseract-ocr
步骤 2:配置Google Gemini API
接下来,您需要配置您的 Gemini API 密钥。为了确保您的密钥安全,我们将使用 Colab 内置的密钥管理器。
- 从 Google AI Studio 获取您的 API 密钥。
- 在您的 Colab 笔记本中,点击左侧边栏上的密钥图标。
- 创建一个名为 GEMINI_API_KEY 的新密钥,并将您的密钥粘贴为密钥值。
以下代码将安全地访问您的密钥并配置 API。
import google.generativeai as genai
from google.colab import userdata
import fitz # PyMuPDF
import pytesseract
from PIL import Image
import pandas as pd
import ipywidgets as widgets
from ipywidgets import Layout
from IPython.display import display, clear_output
import json
import io
# Configure the Gemini API
try:
api_key = userdata.get(“GEMINI_API_KEY”)
genai.configure(api_key=api_key)
print("Gemini API configured successfully.")
except userdata.SecretNotFoundError:
print("ERROR: Secret 'GEMINI_API_KEY' not found. Please follow the instructions to set it up.")
步骤 3:上传并预处理PDF
此代码上传一个发票 PDF 文件。上传 PDF 时,它会将每一页转换为高分辨率图像,这是 OCR 的理想格式。
import fitz # PyMuPDF
from PIL import Image
import io
import os
invoice_images = []
uploaded_file_name = "/content/sample-invoice.pdf" # Replace with the actual path to your PDF file
# Ensure the file exists (optional but recommended)
if not os.path.exists(uploaded_file_name):
print(f"ERROR: File not found at '{uploaded_file_name}'. Please update the file path.")
else:
print(f"Processing '{uploaded_file_name}'...")
# Convert PDF to images
doc = fitz.open(uploaded_file_name)
for page_num in range(len(doc)):
page = doc.load_page(page_num)
pix = page.get_pixmap(dpi=300) # Higher DPI for better OCR
img = Image.open(io.BytesIO(pix.tobytes()))
invoice_images.append(img)
doc.close()
print(f"Successfully converted {len(invoice_images)} page(s) to images.")
# Display the first page as a preview
if invoice_images:
print("\n--- Invoice Preview (First Page) ---")
display(invoice_images[0].resize((600, 800)))
输出:

步骤 4:使用Tesseract OCR提取原始文本
现在,我们对刚刚创建的图像运行 OCR 处理。所有页面的文本将合并为一个字符串。这就是我们将发送给 Gemini 模型的上下文。此步骤是 AI OCR 工作流程中的关键部分。
full_invoice_text = ""
if not invoice_images:
print("Please upload a PDF invoice in the step above first.")
else:
print("Extracting text with OCR...")
for i, img in enumerate(invoice_images):
text = pytesseract.image_to_string(img)
full_invoice_text += f"\n--- Page {i+1} ---\n{text}"
print("OCR extraction complete.")
print("\n--- Extracted Text (first 500 characters) ---")
print(full_invoice_text[:500] + "...")
输出:

步骤 5:使用Gemini API进行智能提取
GenAI 发票处理就在这里。我们创建一个详细的提示,告知 Gemini 模型其角色。我们指示它提取特定字段并以清晰的 JSON 格式返回结果。请求 JSON 是一种强大的技术,可以使模型的输出结构化且易于使用。
extracted_data = {}
if not full_invoice_text.strip():
print("Cannot proceed. The extracted text is empty. Please check the PDF quality.")
else:
# Instantiate the Gemini Pro model
model = genai.GenerativeModel('gemini-2.5-pro')
# Define the fields you want to extract
fields_to_extract = "Invoice Number, Invoice Date, Due Date, Supplier Name, Supplier Address, Customer Name, Customer Address, Total Amount, Tax Amount"
# Create the detailed prompt
prompt = f"""
You are an expert in invoice data extraction.
Your task is to analyze the provided OCR text from an invoice and extract the following fields: {fields_to_extract}.
Follow these rules strictly:
1. Return the output as a single, clean JSON object.
2. The keys of the JSON object must be exactly the field names provided.
3. If a field cannot be found in the text, its value in the JSON should be `null`.
4. Do not include any explanatory text, comments, or markdown formatting (like ```json) in your response. Only the JSON object is allowed.
Here is the invoice text:
---
{full_invoice_text}
---
"""
print("Sending request to Gemini API...")
try:
# Call the API
response = model.generate_content(prompt)
# Robustly parse the JSON response
response_text = response.text.strip()
# Clean potential markdown formatting
if response_text.startswith('```json'):
response_text = response_text[7:-3].strip()
extracted_data = json.loads(response_text)
print("\n--- AI Extracted Data (JSON) ---")
print(json.dumps(extracted_data, indent=2))
except json.JSONDecodeError:
print("\n--- ERROR ---")
print("Failed to decode the model's response into JSON.")
print("Model's Raw Response:", response.text)
except Exception as e:
print(f"\nAn unexpected error occurred: {e}")
print("Model's Raw Response (if available):", getattr(response, 'text', 'N/A'))
输出:

步骤 6:构建人机交互 (HITL) 用户界面
最后,我们构建了验证界面。此代码在左侧显示发票图片,并在右侧创建一个可编辑的表单,表单中预先填充了来自 Gemini 的数据。用户可以快速查看信息、进行必要的编辑并确认。
# UI Widgets
text_widgets = {}
if not extracted_data:
print("No data was extracted by the AI. Cannot build verification UI.")
else:
form_items = []
# Create a text widget for each extracted field
for key, value in extracted_data.items():
text_widgets[key] = widgets.Text(
value=str(value) if value is not None else "",
description=key.replace('_', ' ').title() + ':',
style={'description_width': 'initial'},
layout=Layout(width='95%')
)
form_items.append(text_widgets[key])
# The form container
form = widgets.VBox(form_items, layout=Layout(padding='10px'))
# Image container
if invoice_images:
img_byte_arr = io.BytesIO()
invoice_images[0].save(img_byte_arr, format='PNG')
image_widget = widgets.Image(
value=img_byte_arr.getvalue(),
format='png',
width=500
)
image_box = widgets.HBox([image_widget], layout=Layout(justify_content='center'))
else:
image_box = widgets.HTML("No image to display.")
# Confirmation button
confirm_button = widgets.Button(description="Confirm and Save", button_style='success')
output_area = widgets.Output()
def on_confirm_button_clicked(b):
with output_area:
clear_output()
final_data = {key: widget.value for key, widget in text_widgets.items()}
# Create a pandas DataFrame
df = pd.DataFrame([final_data])
df['Source File'] = uploaded_file_name
print("--- Verified and Finalized Data ---")
display(df)
# You can now save this DataFrame to CSV, etc.
df.to_csv('verified_invoice.csv', index=False)
print("\nData saved to 'verified_invoice.csv'")
confirm_button.on_click(on_confirm_button_clicked)
# Final UI Layout
ui = widgets.HBox([
widgets.VBox([widgets.HTML("<b>Invoice Image</b>"), image_box]),
widgets.VBox([
widgets.HTML("<b>Verify Extracted Data</b>"),
form,
widgets.HBox([confirm_button], layout=Layout(justify_content='flex-end')),
output_area
], layout=Layout(flex='1'))
])
print("--- Human-in-the-Loop (HITL) Verification ---")
print("Review the data on the right. Make any corrections and click 'Confirm and Save'.")
display(ui)
输出:

修改一些值然后保存。
输出:

小结
本 POC 成功证明了像 Uber 的“TextSense”这样复杂系统背后的核心逻辑是可复制的。通过将开源 OCR 与像 Google Gemini 这样强大的 LLM 相结合,您可以构建一个高效的 GenAI 发票处理系统。这种智能文档处理方法显著减少了人工工作量并提高了准确性。添加一个简单的人机交互 AI 界面,确保最终数据的可靠性。
您可以在此基础上进行扩展,添加更多字段、改进验证机制,并将其集成到更大的工作流程中。


评论留言