UNPKG

@accounter/server

Version:

117 lines (105 loc) 3.67 kB
import { generateObject } from 'ai'; import { Injectable, Scope } from 'graphql-modules'; import stripIndent from 'strip-indent'; import { z } from 'zod'; import { anthropic } from '@ai-sdk/anthropic'; import { Currency, DocumentType } from '@shared/enums'; const documentDataSchema = z.object({ type: z.nativeEnum(DocumentType).nullable().describe('The type of financial document'), issuer: z.string().nullable().describe('Legal name of the organization that issued the document'), recipient: z .string() .nullable() .describe('Legal name and details of the entity to whom the document is addressed'), fullAmount: z .number() .nullable() .describe('Total monetary amount including taxes and all charges'), currency: z.nativeEnum(Currency).nullable().describe('ISO 4217 currency code'), vatAmount: z .number() .nullable() .describe('Value Added Tax amount if separately specified on the document'), date: z .string() .regex(/^\d{4}-\d{2}-\d{2}$/) .nullable() .describe('Document issue date in ISO 8601 format (YYYY-MM-DD)'), referenceCode: z .string() .nullable() .describe('Complete document identifier including any separators (e.g., dashes, slashes)'), }); type DocumentData = z.infer<typeof documentDataSchema>; const SUPPORTED_FILE_TYPES = [ 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'application/pdf', ] as const; type SupportedFileType = (typeof SUPPORTED_FILE_TYPES)[number]; function isSupportedFileType(value: string): value is SupportedFileType { return SUPPORTED_FILE_TYPES.includes(value as SupportedFileType); } @Injectable({ scope: Scope.Singleton, global: true, }) export class AnthropicProvider { /** * Convert File or Blob to Base64 * @param fileOrBlob File or Blob to convert * @returns Base64 encoded string */ private async fileToBase64(fileOrBlob: File | Blob): Promise<string> { const buffer = await fileOrBlob.arrayBuffer(); const base64string = Buffer.from(buffer).toString('base64'); return base64string; } /** * Extract invoice details using Anthropic API * @param fileOrBlob File or Blob to process * @returns Parsed invoice data */ async extractInvoiceDetails(fileOrBlob: File | Blob): Promise<DocumentData> { try { const fileType = fileOrBlob.type.toLowerCase(); if (!isSupportedFileType(fileType)) { throw new Error('Unsupported file type. Please provide an image or PDF.'); } const fileData = await this.fileToBase64(fileOrBlob); const { object, usage } = await generateObject({ model: anthropic('claude-3-5-sonnet-20241022'), schema: documentDataSchema, messages: [ { role: 'user', content: [ { type: 'text', text: stripIndent(`Please analyze the provided document and extract: - Document type - Issuer and recipient details - Monetary amounts (total and VAT) - Date and reference numbers Return only a JSON object without any explanation. Use NULL for missing values.`), }, { type: 'file', data: fileData, mimeType: fileType, }, ], }, ], }); // Shows token usage to calculate the cost of the request console.log('Usage:', usage); return object; } catch (error) { console.error('Error in document extraction:', error); throw error; } } }