---
title: "一次调用，抽取发票和小票上的字段"
description: "从发票文本或小票照片中抽取发票号、日期、金额、税额，TypeScript SDK 直接返回类型化值。"
canonical: "https://system-one.dev/zh-CN/use-cases/invoice-receipt-extraction"
language: "zh-CN"
---

# 一次调用，抽取发票和小票上的字段

**要点**\n\n1. 定义需要的字段（发票号、到期日、金额、税额），Clairic 从发票文本或最多 4 张小票照片里逐个返回类型化的值。
2. `referenceDate` 用来补全没写年份的日期；到期日这类值，Clairic 可以根据开票日期和账期算出来。
3. 单据上没有的字段（比如根本没有折扣）返回 null，不会编一个数。

从发票或小票里取字段，用 Clairic 的 `extract()` 就够了：传入发票文本或小票照片，再加上字段定义，就能拿到发票号、日期、金额、税额等类型化的值。

![发票文本和小票照片经 Clairic 转成类型化字段](TODO-IMG: 封面。模拟发票文本 + 模拟小票照片 → 字段卡片（invoice_number、total、tax…）。设计制作。)

## 要抽取的字段

| 字段 | 类型 / 工厂函数 | 说明 |
|---|---|---|
| invoice_number / order_number | text / `textField()` | 单据号，可带 span |
| 开票日期、due_date | date / `dateField()` | 日历日期 |
| purchase_time | datetime / `dateTimeField()` | 日期 + 时间 |
| total、subtotal、tax、tip、unit_price | number / `numberField({ decimals: 2 })` | decimals 取值 0–6 |
| quantity | number / `numberField({ integer: true })` | 整数 |
| contacts / 明细名称 | list / `listField({ items: 'text' })` | items 必填：text、number 或 date |
| days_to_pay | estimate / `estimateField({ min, max })` | 返回 median、p10、p90 等 |

## 文本发票示例（INV-2291）

下面是 [Clairic SDK 文档](https://docs.system-one.dev/zh-CN/docs/sdk/clairic)里的发票示例。先装 SDK：

```bash
npm install @system-one-ai/core@0.8.0 @system-one-ai/adapter-system-one@0.7.0 @system-one-ai/transport-fetch@0.8.0
```

保存为 `extract-invoice.mts`，在 `.env` 里设置 `SYSTEM_ONE_API_KEY`：

```ts
import {
  createSystemOne, defineFields, textField, numberField, dateField, listField, estimateField,
} from '@system-one-ai/core';
import { systemOneAdapter } from '@system-one-ai/adapter-system-one';
import { createFetchTransport } from '@system-one-ai/transport-fetch';

const apiKey = process.env.SYSTEM_ONE_API_KEY;
if (!apiKey) throw new Error('Set SYSTEM_ONE_API_KEY.');

const client = createSystemOne({
  adapter: systemOneAdapter,
  transport: createFetchTransport(),
  apiKey,
  timeoutMs: 30_000,
  maxRetries: 0,
});

const fields = defineFields({
  invoice_number: textField({ instructions: 'The invoice id printed on the document.' }),
  due_date: dateField({ instructions: 'Issue date plus the net payment days.' }),
  total: numberField({ decimals: 2, instructions: 'The total amount due.' }),
  contacts: listField({ items: 'text' }),
  days_to_pay: estimateField({ min: 0, max: 120, step: 1 }),
});

const result = await client.extract({
  model: 'clairic',
  state: [
    'INVOICE INV-2291',
    'From: Northwind Supply',
    'To: Contoso Ltd',
    'Issued: 2026-09-02, net 30',
    'Total due: 1080.00 including shipping',
    'Contact: Dana Ruiz, Priya Shah',
  ].join('\n'),
  fields,
  referenceDate: '2026-10-19',
  options: { includeQuantiles: false },
});

const invoice: string | null = result.answers.invoice_number.value;
console.log({
  model: result.model,
  invoice,
  dueDate: result.answers.due_date.value,
  total: result.answers.total.value,
  contacts: result.answers.contacts.value,
  daysToPay: result.answers.days_to_pay.median,
  truncated: result.truncated,
  usage: result.usage,
});
```

运行：`node --env-file=.env extract-invoice.mts`。每运行一次就会产生一次计费请求。

`contacts` 是 list 类型，返回联系人数组；`days_to_pay` 是 estimate 类型，读它的 `median` 即可，`p10`、`p90` 也会一并返回。

![Clairic 对发票 INV-2291 的输出：发票号、到期日、金额](TODO-IMG: 真实运行 extract-invoice.mts 的终端截图，API key 打码为 sk-****。)

[试用发票抽取 demo](https://system-one.dev/zh-CN/demo/invoice-extraction) *（CTA 按钮）*

## 歪斜、模糊的小票照片

官方博客里有个例子：一张拍歪了、字迹也不清楚的小票照片，Clairic 从中取出了订单号、购买时间、数量、单价、小计、税费和小费。小票上本来就没有折扣，所以折扣一项返回 null。

照片版代码如下（保存为 `extract-receipt.mts`），每次请求最多可以传 4 张图：

```ts
import { readFile } from 'node:fs/promises';
import {
  createSystemOne, defineFields, textField, numberField, dateTimeField,
  type ImageInput,
} from '@system-one-ai/core';
import { systemOneAdapter } from '@system-one-ai/adapter-system-one';
import { createFetchTransport } from '@system-one-ai/transport-fetch';

const apiKey = process.env.SYSTEM_ONE_API_KEY;
const imagePath = process.argv[2];
if (!apiKey || !imagePath) throw new Error('Set SYSTEM_ONE_API_KEY and supply a JPEG path.');

const images: readonly ImageInput[] = [{
  mediaType: 'image/jpeg',
  base64: (await readFile(imagePath)).toString('base64'),
}];

const client = createSystemOne({
  adapter: systemOneAdapter,
  transport: createFetchTransport(),
  apiKey,
  timeoutMs: 30_000,
  maxRetries: 0,
});

const fields = defineFields({
  order_number: textField({ instructions: 'The order or receipt number.' }),
  purchase_time: dateTimeField({ instructions: 'Date and time of purchase.' }),
  quantity: numberField({ integer: true }),
  unit_price: numberField({ decimals: 2 }),
  subtotal: numberField({ decimals: 2 }),
  tax: numberField({ decimals: 2 }),
  tip: numberField({ decimals: 2 }),
  discount: numberField({ decimals: 2, instructions: 'Discount amount, only if the receipt shows one.' }),
});

const result = await client.extract({ model: 'clairic', state: '', images, fields });

for (const [name, answer] of Object.entries(result.answers)) {
  console.log(name, answer.value, answer.confidence, answer.pNull);
}
```

小票上没有折扣时，`discount` 返回 null。正式上线前，请用自己拍的照片测一遍，并给 `confidence`、`pNull` 设好阈值。

![拍歪的小票照片和 Clairic 字段，折扣返回 null](TODO-IMG: 左：模拟的拍歪、模糊小票照片（无折扣行）；右：真实运行 extract-receipt.mts 的输出，discount 为 null，key 打码。)

## referenceDate 与账期计算

`referenceDate`（HTTP 里写作 `reference_date`）用来确定没写年份的日期属于哪一年，比如只印了月和日的小票。

到期日可以直接算。发票示例里，`due_date` 字段的 instructions 写的是 "Issue date plus the net payment days."，Clairic 会根据 2026-09-02 和 net 30 推出到期日。

## 没有的字段返回 null

`pNull` 超过 null 阈值（默认 0.5）时，value 返回 null，`guess` 里可能还有值。阈值可以用 `options.nullThreshold` 统一调，也可以按字段单独设。

[发票信息提取 demo](https://system-one.dev/zh-CN/demo/invoice-extraction) 里就有这种情况：应付合计写着"待确认"时，金额返回 null，发票号照常取出。

## 计费

Clairic 按输入大小每次请求收一次费，不按字段数计，详见 [Clairic 模型页](https://system-one.dev/zh-CN/models/clairic)。新账号注册即送 100 积分。

## 试一下 demo

同样的发票任务，可以直接在[发票信息提取 demo](https://system-one.dev/zh-CN/demo/invoice-extraction) 里试（需登录）。想直接调 API 的话，`POST https://system-one.dev/v1/systemone` 的请求体如下：

```json
{
  "model": "clairic",
  "state": "INVOICE INV-2291\nIssued: 2026-09-02, net 30\nTotal due: 1080.00",
  "reference_date": "2026-10-19",
  "fields": {
    "invoice_number": { "type": "text" },
    "total": { "type": "number", "decimals": 2 }
  }
}
```

认证、幂等和请求头见 API 文档。在重放记录仍可用期间，用同一个 key 重试同一操作不会重复扣费。

![system-one.dev 上的 Clairic 发票抽取 demo](TODO-IMG: /zh-CN/demo/invoice-extraction 页面真实截图，字段与结果区。)

[获取 API Key](https://system-one.dev/zh-CN/signup) *（CTA 按钮）*

## 相关阅读

- [OCR 与 AI 字段抽取](https://system-one.dev/zh-CN/blog/clairic-vs-ocr)
- [截图转 JSON：给 Agent 用的图片字段抽取](https://system-one.dev/zh-CN/use-cases/screenshot-to-json)
- [Clairic SDK 文档](https://docs.system-one.dev/zh-CN/docs/sdk/clairic)
- [Clairic 模型页](https://system-one.dev/zh-CN/models/clairic)

[获取 API Key](https://system-one.dev/zh-CN/signup) *（CTA 按钮，文末）*

核验日期：2026-10-22

## 常见问题

### 照片拍歪了、模糊了能读吗？

可以从拍歪、字迹不清的照片里抽取字段；官方例子就从这样一张小票里取出了订单号、购买时间、数量、单价、小计、税费和小费。请用自己的照片测试，并检查 confidence 和 pNull。

### 可以传哪些输入？

文本、JSON，以及最多 4 张以 base64 内嵌的图片（PNG、JPEG 或 WebP）。

### 发票上没有某个字段怎么办？

pNull 超过 null 阈值时返回 null，所以没有的折扣会保持 null，不会被填上。

### 怎么计费？

按输入大小每次请求收一次费：每 4KB 一块，每块 2 积分，抽取多少字段都一样。详见 Clairic 模型页。

### referenceDate 有什么用？

用来确定没写年份的日期属于哪一年，比如只印了月和日的小票。
