使用免费的AI大模型处理单据

AI大语言模型

朋友新店进货有不少打印单据、手写单据需要录入记账软件,起初使用图像识别软件先将单据图片转换为Excel,人工核对数据正确性并提取所需的数据,再按照记账软件模板创建符合导入格式的Excel。这种方式虽比纯人工录入表格效率要高得多,但单据太多也很花时间。

硅基流动上线免费的PaddleOCR-VL-1.5模型后,就有了用大模型API来批量处理单据的想法。

创建 OpenAI 客户端:

from openai import OpenAI

SILICONFLOW_API_KEY = "硅基流动API Key"
SILICONFLOW_BASE_URL = "https://api.siliconflow.cn/v1"
client = OpenAI(api_key=SILICONFLOW_API_KEY, base_url=SILICONFLOW_BASE_URL)

将图片编码为Data URL:

import base64

def encode_image_to_base64_url(self, image_path):
    """
    编码图片并格式化为 Data URL
    """
    try:
        with open(image_path, "rb") as image_file:
            base64_image = base64.b64encode(image_file.read()).decode('utf-8')
            mime_type = Image.open(image_path).get_format_mimetype()
            return f"data:{mime_type};base64,{base64_image}"
    except FileNotFoundError:
        print(f"错误:图片文件未找到 '{image_path}'")
        return None

调用PaddleOCR-VL-1.5模型提取图片中的文本:

import re

def get_raw_text_from_image(self, image_url):
    """
    使用OCR模型提取文本
    """
    response = client.chat.completions.create(
        model="PaddlePaddle/PaddleOCR-VL-1.5",
        messages=[{
            "role": "user",
            "content": [
                {
                    "type": "image_url",
                    "image_url": {
                        "url": image_url,
                        "detail": "high"
                    }
                }
            ]
        }],
        max_tokens=8192,
        timeout=120
    )

    raw_text = response.choices[0].message.content
    pattern = r'<\|LOC_\d+\|>'
    cleaned_text = re.sub(pattern, ' ', raw_text)
    cleaned_text = re.sub(r'\s+', ' ', cleaned_text).strip()

    return cleaned_text

使用语言模型Qwen/Qwen3-8B将图片中提取到的原始文本转换为结构化数据:

import re
import json

def structure_text_with_llm(self, raw_text):
    """
    使用语言模型提取原始文本为JSON数据
    """
    system_prompt = "你是一个专业的单据分析师,擅长从非结构化文本中提取关键信息并将其转换为结构化数据。"

    user_prompt = f"""
    **待处理文本:**
    ---
    {raw_text}
    ---

    **JSON字段说明:**
    1. **counterparty**: 智能提取交易对方名称,优先使用页眉中的名称。
    2.  **product**: 智能提取产品名称、规格型号,将它们合并,并用空格隔开 (例如, "公牛插座 GN-901")。
    3.  **quantity**: 智能提取数量(例如, 2)。
    4. **unit**: 智能提取单位(例如, 个、件)。
    5.  **price**: 智能提取产品的单价。
    6.  **amount**: 智能提取产品的金额。
    7.  **date**: 智能提取日期,格式为 "YYYY-MM-DD"。
    
    **注意事项:**
    1. 如果某个字段无法找到,使用 `null` 作为值。
    2. 最终输出应为包含多个项目的JSON数组,每个项目对应一个产品。

    **示例输出:**
    {{
        items:[{{
            "counterparty": "张三",
            "product": "公牛插座 GN-901",
            "quantity": 10,
            "unit": "个",
            "price": 12.50,
            "amount": 125.00,
            "date": "2026-05-02"
        }}]
    }}

    """

    response = client.chat.completions.create(
        model="Qwen/Qwen3-8B",
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_prompt}
        ],
        max_tokens=8192,
        temperature=0.0,
        response_format={"type": "json_object"},
        timeout=120
    )
    
    content = response.choices[0].message.content
    content = content.strip()
    json_str = re.search(r'{.*}', content, re.DOTALL)
    
    return json.loads(json_str.group(0)) if json_str else None

PaddleOCR-VL-1.5的识别正确率还是不错的,但API返回了位置标记,这里只是简单的过滤了位置标记内容。可能是我处理不够好,最终使用语言模型结构化数据不太理想。

于是,我又尝试了硅基流动的一款免费视觉模型:THUDM/GLM-4.1V-9B-Thinking

import re
import json

def get_json_from_image(self, image_url):
    """
    使用多模态模型从图片获取结构化JSON数据
    """
    user_prompt = f"""
    从单据图片中提取关键信息,并将其转换为结构化JSON数据。

    **JSON字段说明:**
    1. **counterparty**: 智能提取交易对方名称,优先使用页眉中的名称。
    2.  **product**: 智能提取产品名称、规格型号,将它们合并,并用空格隔开 (例如, "公牛插座 GN-901")。
    3.  **quantity**: 智能提取数量(例如, 2)。
    4. **unit**: 智能提取单位(例如, 个、件)。
    5.  **price**: 智能提取产品的单价。
    6.  **amount**: 智能提取产品的金额。
    7.  **date**: 智能提取日期,格式为 "YYYY-MM-DD"。
    
    **注意事项:**
    1. 如果某个字段无法找到,使用 `null` 作为值。
    2. 如果为手写单据图片,只提取手写内容,忽略打印内容。
    3. 部分单据会有类似如下格式的内容:数量x单价=金额(例如,6个x2=12),需从中智能提取数量、单位、单价和金额。
    4. 最终输出应为包含多个项目的JSON数组,每个项目对应一个产品。

    **示例输出:**
    {{
        items:[{{
            "counterparty": "张三",
            "product": "公牛插座 GN-901",
            "quantity": 10,
            "unit": "个",
            "price": 12.5,
            "amount": 125,
            "date": "2026-05-02"
        }}]
    }}

    """

    response = client.chat.completions.create(
        model="THUDM/GLM-4.1V-9B-Thinking",
        messages=[{
            "role": "user",
            "content": [{
                "type": "image_url",
                "image_url": {
                    "url": image_url,
                    "detail": "high"
                }
            }]
        }, {
            "role": "user",
            "content": user_prompt
        }],
        max_tokens=8192,
        timeout=120
    )

    content = response.choices[0].message.content
    content = content.strip()
    json_str = re.search(r'{.*}', content, re.DOTALL)
    
    return json.loads(json_str.group(0)) if json_str else None

测试效果很不错,部分AI不认识的手写体,基本我也认不出了。相比使用OCR先提取文本再使用语言模型结构化数据,视觉模型一步到位更简单。

AI大语言模型