Dealing with mountains of invoices is a familiar headache for many finance and HR departments. Just imagine manually classifying thousands of those invoices each month against a complex list of over 300 categories, ranging from office supplies and training to travel and specific service maintenance contracts. This was a challenge that one of our clients faced, consuming significant human resource time and effort.
At Infinite Lambda, we thrive on solving complex data challenges. Here, we saw an opportunity to leverage the power of GenAI combined with the robust capabilities of the Snowflake AI Data Cloud to build an application to automate invoice classification.
In this article, I will show you how we did it step by step.
The challenge: Drowning in invoices and categories
The core problem for our client was a combination of scale and complexity. Manually reading, understanding, and assigning one of 300+ potential categories to thousands of incoming invoices every single month is not just tedious; it is prone to inconsistencies and takes up significant employee time.
The goal was clear: automate invoice classification accurately and efficiently, freeing up the financial team for more strategic tasks.
Our solution: A tiered GenAI classifier on Snowflake
We designed a multi-stage classification pipeline directly within Snowflake, taking advantage of its native AI/ML capabilities, particularly Snowflake Cortex functions. The key idea was to handle the easiest classifications first using fast, cost-effective methods, reserving more sophisticated (and computationally intensive) AI for the trickiest cases.
Step 0: The vector store or building the foundation
Before classifying new invoices, we needed a knowledge base. We took the client's historical invoices that had already been categorised invoices and transformed their text content into numerical representations called vector embeddings.
This allowed us to mathematically compare invoices based on their semantic meaning, and not just on keywords.
We used Snowflake Cortex for this critical step:
This command utilises the multilingual-e5-large model via Snowflake Cortex to generate a 1024-dimension vector for each invoice's text. These vectors, along with their correct categories, form our training vector store, the foundation for our classification logic.
Step 1: Exact match classification or the quick wins
When a new invoice arrives, the first step is to see if we have seen something almost identical before. We compare the vector embedding of the new invoice against every vector in our historical store using Snowflake's built-in similarity function:
We look for the top match. If the similarity_score is very high (we set a threshold of > 0.95), we assume the new invoice belongs to the same category as its closest match in the vector store.
Why this works: This exact-match approach is based on the fact that many invoices, especially recurring ones, have very similar text content. Think of the electricity bill for April vs the electricity bill for May — they are hardly any differences in the text itself.
Results: This simple, fast, and computationally cheap step successfully classified over 60% of the incoming invoices with an impressive 83% accuracy.
Step 2: Contractor-based match or leveraging contractor information
We still had a good 40% of invoices that did not meet the high similarity threshold. For those, we introduced another piece of information, namely the contractor's name. The proposition here was that often invoices from the same contractor relate to similar types of goods or services.
At this stage, we relaxed the text similarity threshold slightly (to > 0.9) but added a new requirement: the contractor name associated with the new invoice must also be highly similar (using a suitable string similarity function or a separate embedding, threshold > 0.9) to the contractor name of the potential match from the vector store.
Why this works: It adds context. Even if the invoice text differs slightly, knowing it is from a familiar contractor increases confidence in the potential category match.
Results: This cleared another 15% of the invoices. As expected, the accuracy was slightly lower, 72%, but it efficiently handled invoices that were similar yet not identical.
Step 3: LLM classification or calling in the expert
We were now left with the hardest 25% of invoices, those that did not have a strong match based on text or contractor context. These are typically novel invoices, complex descriptions, or edge cases. Now, it was time to turn to a Large Language Model (LLM) for help.
We utilised Claude Sonnet 3.5 via AWS Bedrock. The Snowflake to Bedrock connection proved a bit tricky to set up because we would be calling a thousand requests at one time, and Bedrock only allows 20 invocations per minute. Naturally, we would get ThrottlingException all the time.
To bypass this, we needed to add some time.sleep() in between the requests. The sleep duration depends on how many requests you are sending to the LLM at the time.
If you sent fewer than 100 prompts to the LLM, you would not need to wait. However, for 1000 records, we had to use what we would call exponential_wait_time. That meant that the first time it failed, we would wait for 5s; the next time we would wait for 10s, then 20s, 40s, and 80s.
To learn about how to set up the connection, visit this Snowflake document. Just remember to add time.sleep() if you are feeding several hundred prompts.
In addition, we would not just send the raw invoice text and ask the LLM to guess from 300+ categories blindly. Instead, we implemented a Retrieval-Augmented Generation (RAG) pattern:
- Provide context: We sent the LLM the text of the unclassified invoice;
- Provide options: We provided the complete list of all 300+ possible categories;
- Provide examples: We performed one more vector similarity search — vector_cosine_similarity — for the problematic invoice against our vector store. We took the top 5 most similar historical invoices, regardless of score, and included their text and known correct categories in the prompt as examples.
This structured prompt would give the LLM much more context, helping it make an informed decision.
Why this works: The LLM excels at understanding nuance and context, especially when guided. Providing the category list and relevant examples significantly improves its ability to select the most appropriate category from a large, complex set.
Results: The LLM successfully classified the remaining 25% of invoices with 40% accuracy. While the accuracy was lower than in the previous steps, remember this stage would only handle the most ambiguous and difficult cases that the simpler, high-accuracy methods had not resolved successfully. In this percentile, even team members themselves could make mistakes.
Step 4: Updating the vector store
Our data is constantly evolving, adding new categories, and removing obsolete ones. Hence, the vector store will need to be updated frequently as well.
At the end of the pipeline, once all new invoices have been verified and finalised by humans, we can add them back to the vector store, effectively creating a feedback loop.
When we match a new invoice against the vector store, we can consider the recency of the training invoices. The more recently an invoice has been added to the training vector, the higher the chance for it to get matched, all else considered.
What makes this tiered approach effective
This multi-stage pipeline comes with several advantages:
- Efficiency: Simple, deterministic methods in the face of vector similarity handle the bulk of the workload quickly and within Snowflake;
- Cost optimisation: LLM calls, especially via services like AWS Bedrock, are often priced per token (input + output). By using the LLM for the most challenging 25% of invoices only, we minimise these costs, getting the best value for our money from the AI and do not waste expensive compute on invoices that can be easily matched.
- Accuracy management: We prioritise high-accuracy methods first, ensuring the majority of classifications are reliable;
- Leveraging Snowflake: The entire process, from vector embedding and similarity search to potentially orchestrating the LLM calls (e.g. via external functions), can be managed within the Snowflake ecosystem, which minimises data movement and simplifies the architecture.
Smarter classification = happier teams
We combined Snowflake's powerful data processing and Cortex AI functions with a strategic, tiered application of LLMs to build an effective system to automate invoice classification. This solution significantly reduces the manual burden on the client's finance and HR teams and improves consistency, demonstrating the practical power of GenAI for real-world business problems.
This tiered approach — handling easy tasks cheaply and reserving advanced AI for complex challenges — is a pattern we believe holds significant promise for many data automation tasks. If manual data categorisation is bogging down your teams, perhaps a similar GenAI-powered solution on Snowflake is the right approach for you.
For more tips and insights on leveraging the modern data stack and building AI solutions for enterprise, visit the Infinite Lambda Blog.
