From 5548760b49173cf4e586d6c056160b1c6195056f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 13 Nov 2026 18:11:55 +0000 Subject: [PATCH] feat: add thinking parameter to document processing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pass thinking as light or deep on the receipt and invoice process helpers. Omit it to keep the standard pipeline. Co-authored-by: Alejandro Uribe Sánchez --- README.md | 10 ++++ docs/api-coverage-analysis.md | 2 +- lib/documents/applyDocumentProcessOptions.js | 20 +++++++ lib/documents/processDocument.js | 1 + lib/documents/processDocumentBase64.js | 4 +- lib/documents/processDocumentStream.js | 6 ++- lib/documents/processDocumentUrl.js | 6 ++- lib/types/Client.ts | 20 +++---- lib/types/VeryfiExtraArgs.ts | 19 +++++++ test/api-coverage.test.js | 56 ++++++++++++++++++++ 10 files changed, 129 insertions(+), 15 deletions(-) create mode 100644 lib/documents/applyDocumentProcessOptions.js diff --git a/README.md b/README.md index 7c8494d..331b78c 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,16 @@ let response = veryfi_client.process_document_from_url(url, null, categories, fa }); ``` +Thinking Mode reviews receipt and invoice extraction before the response is returned. Pass `light` or `deep`, and leave it out for the standard pipeline. It has to be enabled on the account. See [Process a Document](https://docs.veryfi.com/api/receipts-invoices/process-a-document/). + +```js +let response = veryfi_client.process_document(file_path, categories, false, { + thinking: 'light' +}).then(response => { + console.log(response) +}); +``` + ### Line items, tags, and other products ```js diff --git a/docs/api-coverage-analysis.md b/docs/api-coverage-analysis.md index 12796d8..581adeb 100644 --- a/docs/api-coverage-analysis.md +++ b/docs/api-coverage-analysis.md @@ -290,7 +290,7 @@ Kwargs that merge into the **body** are marked **kwargs (body)**. Kwargs passed | all variants | `compute` | `boolean \| null` | no (default `true`) | kwargs (body) | JSDoc | [docs](https://docs.veryfi.com/api/receipts-invoices/process-a-document/) | | all variants | `country` | `string \| null` | no | kwargs (body) | JSDoc | [docs](https://docs.veryfi.com/api/receipts-invoices/process-a-document/) | | all variants | `document_type` | `string \| null` | no | kwargs (body) | JSDoc | [docs](https://docs.veryfi.com/api/receipts-invoices/process-a-document/) | -| all variants | `thinking`, `thinking_llm_version` | `string \| null` | no | kwargs (body) | JSDoc | [docs](https://docs.veryfi.com/api/receipts-invoices/process-a-document/) | +| all variants | `thinking` | `"light" \| "deep" \| null` | no | kwargs (body); omitted when null | None | [docs](https://docs.veryfi.com/api/receipts-invoices/process-a-document/) | | all variants | `allowed_async_enrichments` | `DocumentEnrichment[]` | no | kwargs (body); TS type too narrow | Widen `VeryfiExtraArgs` | [docs](https://docs.veryfi.com/api/receipts-invoices/process-a-document/) | | all variants | `device_data` | object | no | kwargs (body); TS type too narrow | Widen `VeryfiExtraArgs` | [docs](https://docs.veryfi.com/api/receipts-invoices/process-a-document/) | | all variants | `emailed_receipt_id`, `receipt_id` | `integer \| null` | no | kwargs (body) | JSDoc | [docs](https://docs.veryfi.com/api/receipts-invoices/process-a-document/) | diff --git a/lib/documents/applyDocumentProcessOptions.js b/lib/documents/applyDocumentProcessOptions.js new file mode 100644 index 0000000..fb74c44 --- /dev/null +++ b/lib/documents/applyDocumentProcessOptions.js @@ -0,0 +1,20 @@ +/** + * Merge extra Process a Document options into the request body. + * `thinking` is included only when the caller sets it. Omit it, or pass null, + * to use the standard pipeline. Accepted values are `light` and `deep`. + * https://docs.veryfi.com/api/receipts-invoices/process-a-document/ + * https://faq.veryfi.com/en/articles/18356750-what-is-thinking-mode-and-how-do-i-use-it + * + * @param {Object} requestArguments Body fields already set by the caller + * @param {Object} [kwargs] Additional request parameters + * @returns {Object} Request body + */ +function applyDocumentProcessOptions(requestArguments, kwargs = {}) { + const options = Object.assign({}, kwargs); + if (options.thinking == null) { + delete options.thinking; + } + return Object.assign(requestArguments, options); +} + +module.exports = applyDocumentProcessOptions; diff --git a/lib/documents/processDocument.js b/lib/documents/processDocument.js index 09242e0..bce55fa 100644 --- a/lib/documents/processDocument.js +++ b/lib/documents/processDocument.js @@ -14,6 +14,7 @@ const fs = require('fs'); * @param {Array} categories List of categories Veryfi can use to categorize the document * @param {Boolean} auto_delete Delete this document from Veryfi after data has been extracted * @param {Object} kwargs Additional request parameters + * @param {("light"|"deep")|null} [kwargs.thinking] Thinking Mode for receipts and invoices. `light` runs a smaller agent review, `deep` runs the most thorough review. Omit it to use the standard pipeline. https://docs.veryfi.com/api/receipts-invoices/process-a-document/ * @returns {JSON} Data extracted from the document */ Client.prototype.process_document = async function ( diff --git a/lib/documents/processDocumentBase64.js b/lib/documents/processDocumentBase64.js index 086f463..e437495 100644 --- a/lib/documents/processDocumentBase64.js +++ b/lib/documents/processDocumentBase64.js @@ -1,4 +1,5 @@ const Client = require('../client/constructor'); +const applyDocumentProcessOptions = require('./applyDocumentProcessOptions'); /** * Process a document and extract all the fields from it. https://docs.veryfi.com/api/receipts-invoices/process-a-document/ * @example @@ -14,6 +15,7 @@ const Client = require('../client/constructor'); * @param {Array} categories List of categories Veryfi can use to categorize the document * @param {Boolean} auto_delete Delete this document from Veryfi after data has been extracted * @param {Object} kwargs Additional request parameters + * @param {("light"|"deep")|null} [kwargs.thinking] Thinking Mode for receipts and invoices. `light` runs a smaller agent review, `deep` runs the most thorough review. Omit it to use the standard pipeline. https://docs.veryfi.com/api/receipts-invoices/process-a-document/ * @returns {JSON} Data extracted from the document */ Client.prototype.process_document_from_base64 = async function ( @@ -31,7 +33,7 @@ Client.prototype.process_document_from_base64 = async function ( "categories": categories, "auto_delete": auto_delete, }; - request_arguments = Object.assign(request_arguments, kwargs); + request_arguments = applyDocumentProcessOptions(request_arguments, kwargs); let document = await this._request("POST", endpoint_name, request_arguments, null, false); return document['data']; } diff --git a/lib/documents/processDocumentStream.js b/lib/documents/processDocumentStream.js index d02219d..61a5a93 100644 --- a/lib/documents/processDocumentStream.js +++ b/lib/documents/processDocumentStream.js @@ -1,4 +1,5 @@ const Client = require('../client/constructor'); +const applyDocumentProcessOptions = require('./applyDocumentProcessOptions'); /** * Process a document and extract all the fields from it. https://docs.veryfi.com/api/receipts-invoices/process-a-document/ * @example @@ -12,7 +13,8 @@ const Client = require('../client/constructor'); * @param {stream.Readable} file ReadStream of a file to submit for data extraction * @param {String} file_name The file name including the extension * @param {Boolean} auto_delete Delete this document from Veryfi after data has been extracted - * @param {Object} kwargs Additional request parameters (categories, tags, async, parse_address, crop_document, compute, country, document_type, device_data, package_path, bucket, bounding_boxes, confidence_details) + * @param {Object} kwargs Additional request parameters (categories, tags, async, parse_address, crop_document, compute, country, document_type, device_data, package_path, bucket, bounding_boxes, confidence_details, thinking) + * @param {("light"|"deep")|null} [kwargs.thinking] Thinking Mode for receipts and invoices. `light` runs a smaller agent review, `deep` runs the most thorough review. Omit it to use the standard pipeline. https://docs.veryfi.com/api/receipts-invoices/process-a-document/ * @returns {JSON} Data extracted from the document */ Client.prototype.process_document_from_stream = async function ( @@ -27,7 +29,7 @@ Client.prototype.process_document_from_stream = async function ( "file_name": file_name, "auto_delete": auto_delete }; - request_arguments = Object.assign(request_arguments, kwargs); + request_arguments = applyDocumentProcessOptions(request_arguments, kwargs); let document = await this._request("POST", endpoint_name, request_arguments, null, true); return document['data']; } diff --git a/lib/documents/processDocumentUrl.js b/lib/documents/processDocumentUrl.js index a96b5e5..e62305a 100644 --- a/lib/documents/processDocumentUrl.js +++ b/lib/documents/processDocumentUrl.js @@ -1,4 +1,5 @@ const Client = require('../client/constructor'); +const applyDocumentProcessOptions = require('./applyDocumentProcessOptions'); /** * Process document from url and extract all the fields from it. https://docs.veryfi.com/api/receipts-invoices/process-a-document/ * @example @@ -12,7 +13,8 @@ const Client = require('../client/constructor'); * @param {boolean} boost_mode Flag that tells Veryfi whether boost mode should be enabled. When set to 1, Veryfi will skip data enrichment steps, but will process the document faster. Default value for this flag is 0 * @param {string|null} external_id Optional custom document identifier. Use this if you would like to assign your own ID to documents * @param {int} max_pages_to_process When sending a long document to Veryfi for processing, this parameter controls how many pages of the document will be read and processed, starting from page 1. - * @param {Object} kwargs Additional request parameters (async, tags, parse_address, crop_document, compute, country, document_type, device_data, package_path, bucket, bounding_boxes, confidence_details, thinking, thinking_llm_version, allowed_async_enrichments, emailed_receipt_id, receipt_id) + * @param {Object} kwargs Additional request parameters (async, tags, parse_address, crop_document, compute, country, document_type, device_data, package_path, bucket, bounding_boxes, confidence_details, thinking, allowed_async_enrichments, emailed_receipt_id, receipt_id) + * @param {("light"|"deep")|null} [kwargs.thinking] Thinking Mode for receipts and invoices. `light` runs a smaller agent review, `deep` runs the most thorough review. Omit it to use the standard pipeline. https://docs.veryfi.com/api/receipts-invoices/process-a-document/ * @return {JSON} Data extracted from the document. */ Client.prototype.process_document_from_url = async function ( @@ -35,7 +37,7 @@ Client.prototype.process_document_from_url = async function ( "file_urls": file_urls, "max_pages_to_process": max_pages_to_process, }; - request_arguments = Object.assign(request_arguments, kwargs); + request_arguments = applyDocumentProcessOptions(request_arguments, kwargs); let response = await this._request("POST", endpoint_name, request_arguments, null, false); return response['data']; } diff --git a/lib/types/Client.ts b/lib/types/Client.ts index 53ad36a..cdbd8fa 100644 --- a/lib/types/Client.ts +++ b/lib/types/Client.ts @@ -1,5 +1,7 @@ import {VeryfiDocument} from "./VeryfiDocument"; -import {JsonObject, VeryfiExtraArgs} from "./VeryfiExtraArgs"; +import {DocumentProcessArgs, JsonObject, VeryfiExtraArgs} from "./VeryfiExtraArgs"; + +export type {DocumentProcessArgs, ThinkingMode} from "./VeryfiExtraArgs"; import {Tag} from "./Tag"; import * as stream from "node:stream"; @@ -187,14 +189,14 @@ export declare class Client { * @param {string} file_path Path on disk to a file to submit for data extraction * @param {string[]} categories List of categories Veryfi can use to categorize the document * @param {boolean} auto_delete Delete this document from Veryfi after data has been extracted - * @param {VeryfiExtraArgs} kwargs Additional request parameters + * @param {DocumentProcessArgs} kwargs Additional request parameters. `thinking` is `light` or `deep` for Thinking Mode on receipts and invoices. Omit it for the standard pipeline. https://docs.veryfi.com/api/receipts-invoices/process-a-document/ * @returns {Promise} Object of data extracted from the document */ public process_document( file_path: string, categories?: string[], auto_delete?: boolean, - {...kwargs}?: VeryfiExtraArgs + {...kwargs}?: DocumentProcessArgs ): Promise; /** @@ -211,7 +213,7 @@ export declare class Client { * @param {String} file_name The file name including the extension * @param {Array} categories List of categories Veryfi can use to categorize the document * @param {Boolean} auto_delete Delete this document from Veryfi after data has been extracted - * @param {Object} kwargs Additional request parameters + * @param {DocumentProcessArgs} kwargs Additional request parameters. `thinking` is `light` or `deep` for Thinking Mode on receipts and invoices. Omit it for the standard pipeline. https://docs.veryfi.com/api/receipts-invoices/process-a-document/ * @returns {JSON} Data extracted from the document */ public process_document_from_base64( @@ -219,7 +221,7 @@ export declare class Client { file_name: string, categories?: string[], auto_delete?: boolean, - {...kwargs}?: VeryfiExtraArgs + {...kwargs}?: DocumentProcessArgs ): Promise; /** @@ -229,14 +231,14 @@ export declare class Client { * @param {stream.Readable} file ReadStream of a file to submit for data extraction * @param {String} file_name The file name including the extension * @param {Boolean} auto_delete Delete this document from Veryfi after data has been extracted - * @param {Object} kwargs Additional request parameters + * @param {DocumentProcessArgs} kwargs Additional request parameters. `thinking` is `light` or `deep` for Thinking Mode on receipts and invoices. Omit it for the standard pipeline. https://docs.veryfi.com/api/receipts-invoices/process-a-document/ * @returns {JSON} Data extracted from the document */ public process_document_from_stream( file: stream.Readable, file_name: string, auto_delete?: boolean, - {...kwargs}?: VeryfiExtraArgs + {...kwargs}?: DocumentProcessArgs ): Promise; /** @@ -249,7 +251,7 @@ export declare class Client { * @param {boolean} boost_mode Flag that tells Veryfi whether boost mode should be enabled. When set to 1, Veryfi will skip data enrichment steps, but will process the document faster. Default value for this flag is 0 * @param {string} external_id Optional custom document identifier. Use this if you would like to assign your own ID to documents * @param {number} max_pages_to_process When sending a long document to Veryfi for processing, this parameter controls how many pages of the document will be read and processed, starting from page 1. - * @param {VeryfiExtraArgs} kwargs Additional request parameters + * @param {DocumentProcessArgs} kwargs Additional request parameters. `thinking` is `light` or `deep` for Thinking Mode on receipts and invoices. Omit it for the standard pipeline. https://docs.veryfi.com/api/receipts-invoices/process-a-document/ * @return {Promise} Object of data extracted from the document */ public process_document_from_url( @@ -260,7 +262,7 @@ export declare class Client { boost_mode?: boolean, external_id?: string, max_pages_to_process?: number, - {...kwargs}?: VeryfiExtraArgs + {...kwargs}?: DocumentProcessArgs ): Promise; /** diff --git a/lib/types/VeryfiExtraArgs.ts b/lib/types/VeryfiExtraArgs.ts index 6f5be71..a1b286b 100644 --- a/lib/types/VeryfiExtraArgs.ts +++ b/lib/types/VeryfiExtraArgs.ts @@ -5,4 +5,23 @@ */ export declare type VeryfiExtraArgs = Record; +/** + * Thinking Mode for receipt and invoice extraction on + * POST /api/v8/partner/documents. + * `light` runs a smaller agent review. `deep` runs the most thorough review. + * Omit the field to use the standard real-time pipeline. + * https://docs.veryfi.com/api/receipts-invoices/process-a-document/ + * https://faq.veryfi.com/en/articles/18356750-what-is-thinking-mode-and-how-do-i-use-it + */ +export type ThinkingMode = "light" | "deep"; + +/** + * Extra body fields for Process a Document. + * https://docs.veryfi.com/api/receipts-invoices/process-a-document/ + */ +export interface DocumentProcessArgs { + thinking?: ThinkingMode | null; + [key: string]: any; +} + export type JsonObject = Record; diff --git a/test/api-coverage.test.js b/test/api-coverage.test.js index 5187794..a5951b6 100644 --- a/test/api-coverage.test.js +++ b/test/api-coverage.test.js @@ -137,6 +137,62 @@ describe('API coverage call contracts', () => { expect(mock.mock.calls[0][2]).toEqual(expect.objectContaining({"categories":["Food"]})); }); + test("process_document forwards thinking light", async () => { + const mock = mockRequest(); + await client.process_document('resources/receipt.png', ['Food'], false, {thinking: 'light'}); + expect(mock.mock.calls[0][0]).toBe("POST"); + expect(mock.mock.calls[0][1]).toBe("/documents/"); + expect(mock.mock.calls[0][4]).toBe(true); + expect(mock.mock.calls[0][2]).toEqual(expect.objectContaining({ + "categories": ["Food"], + "thinking": "light" + })); + }); + + test("process_document_from_stream forwards thinking deep", async () => { + const mock = mockRequest(); + const {Readable} = require('stream'); + await client.process_document_from_stream(Readable.from(Buffer.from('pdf')), 'invoice.pdf', false, {thinking: 'deep'}); + expect(mock.mock.calls[0][1]).toBe("/documents/"); + expect(mock.mock.calls[0][4]).toBe(true); + expect(mock.mock.calls[0][2]).toEqual(expect.objectContaining({"thinking": "deep", "file_name": "invoice.pdf"})); + }); + + test("process_document_from_base64 forwards thinking", async () => { + const mock = mockRequest(); + await client.process_document_from_base64('abc123', 'invoice.pdf', null, false, {thinking: 'light'}); + expect(mock.mock.calls[0][1]).toBe("/documents/"); + expect(mock.mock.calls[0][4]).toBe(false); + expect(mock.mock.calls[0][2]).toEqual(expect.objectContaining({ + "file_data": "abc123", + "thinking": "light" + })); + }); + + test("process_document_from_url forwards thinking and omits null", async () => { + const mock = mockRequest(); + await client.process_document_from_url( + 'https://example.com/invoice.pdf', + null, + null, + false, + false, + null, + 1, + {thinking: 'deep', tags: ['reviewed']} + ); + expect(mock.mock.calls[0][1]).toBe("/documents/"); + expect(mock.mock.calls[0][4]).toBe(false); + expect(mock.mock.calls[0][2]).toEqual(expect.objectContaining({ + "file_url": "https://example.com/invoice.pdf", + "thinking": "deep", + "tags": ["reviewed"] + })); + + await client.process_document_from_url('https://example.com/invoice.pdf', null, null, false, false, null, 1, {thinking: null}); + expect(mock.mock.calls[1][2]).not.toHaveProperty('thinking'); + }); + test("add_any_document_tag PUTs {name}", async () => { const mock = mockRequest(); await client.add_any_document_tag('11', 'urgent');