Skip to content

Commit 1839df4

Browse files
authored
Update to SmartInvoiceExtraction.py
Changed the code as the prompts here were incorrect and needed to be fixed
1 parent 8a617c1 commit 1839df4

File tree

1 file changed

+128
-87
lines changed

1 file changed

+128
-87
lines changed
Lines changed: 128 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,185 +1,226 @@
1-
import pandas as pd
1+
"""
2+
Module for extracting and analyzing data from invoices using AI models.
3+
4+
Author: Ali Ottoman
5+
"""
6+
27
import json
3-
from langchain.chains.llm import LLMChain
4-
from langchain_core.prompts import PromptTemplate
8+
import base64
9+
import io
10+
import pandas as pd
511
import streamlit as st
612
from langchain_community.chat_models.oci_generative_ai import ChatOCIGenAI
713
from langchain_core.messages import HumanMessage, SystemMessage
8-
import base64
914
from pdf2image import convert_from_bytes
10-
import io
1115

12-
# Helper function to convert a list of images into byte arrays for further processing
16+
# Function to save images in a specified format (default is JPEG)
1317
def save_images(images, output_format="JPEG"):
1418
image_list = []
15-
for image in images:
19+
for i, image in enumerate(images):
1620
img_byte_arr = io.BytesIO()
1721
image.save(img_byte_arr, format=output_format)
1822
img_byte_arr.seek(0)
1923
image_list.append(img_byte_arr)
2024
return image_list
2125

22-
# Helper function to encode an image to base64 for sending to LLM
26+
# Function to encode an image file to base64 format
2327
def encode_image(image_path):
28+
"""Encodes an image to base64 format."""
2429
with open(image_path, "rb") as image_file:
2530
return base64.b64encode(image_file.read()).decode("utf-8")
2631

27-
# Save extracted data to a CSV file and show success message in Streamlit
32+
# Function to save extracted data to a CSV file
2833
def save_to_csv(data, file_name="extracted_data.csv"):
34+
"""Saves extracted data to a CSV file."""
2935
df = pd.DataFrame(data)
3036
df.to_csv(file_name, index=False)
3137
st.success(f"Data saved to {file_name}")
3238

33-
# Extract key headers from the first image of a PDF invoice
39+
# Function to extract key elements from an invoice image using AI
3440
def extractor(image_list):
35-
# Replace this with your own compartment ID
36-
compID = "<YOUR_COMPARTMENT_OCID_HERE>"
37-
38-
# Load a multimodal LLM for invoice header analysis
41+
# TO-DO: Add your compartment ID
42+
compID = ""
43+
3944
llm = ChatOCIGenAI(
40-
model_id="meta.llama-3.2-90b-vision-instruct", # Replace with your model ID
45+
model_id="meta.llama-3.2-90b-vision-instruct",
4146
compartment_id=compID,
4247
model_kwargs={"max_tokens": 2000, "temperature": 0}
4348
)
4449

45-
# Encode the first page as base64
50+
# Extracting all key elements and providing them in a list to be selected from
4651
encoded_frame = base64.b64encode(image_list[0].getvalue()).decode("utf-8")
47-
4852
with st.spinner("Extracting the key elements"):
49-
# Provide system instruction to extract headers from invoice
5053
system_message = SystemMessage(
51-
content="""Given this invoice, extract in list format, all the headers that can be needed for analysis
52-
For example: [\"REF. NO.\", \"INSURED\", \"REINSURED\", \"POLICY NO.\", \"TYPE\", \"UNDERWRITER REF. NO.\", \"PERIOD\", \"PARTICULARS\", \"PPW DUE DATE\"]
53-
Return the answer in a list format, and include nothing else at all in the response.
54-
"""
54+
content="""Given this invoice, extract in list format, all they headers that can be needed for analysis
55+
For example: ["REF. NO.", "INSURED", "REINSURED", "POLICY NO.", "TYPE", "UNDERWRITER REF. NO.", "PERIOD", "PARTICULARS", "PPW DUE DATE"]
56+
Return the answer in a list format, and include nothing else at all in the response, not even a greeting or closing.
57+
"""
5558
)
56-
57-
# Human message includes the image
5859
human_message = HumanMessage(
5960
content=[
60-
{"type": "text", "text": "This is my invoice"},
61-
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{encoded_frame}"}},
62-
]
61+
{"type": "text", "text": "This is my invoice"},
62+
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{encoded_frame}"}},
63+
]
6364
)
64-
65-
# Invoke the LLM and extract elements
66-
ai_response = llm.invoke(input=[human_message, system_message])
65+
ai_response_for_elements = llm.invoke(input=[human_message, system_message])
66+
print(ai_response_for_elements.content)
6767
st.caption("Here are some key elements you may want to extract")
68-
return eval(ai_response.content)
68+
extracted_elements = eval(ai_response_for_elements.content)
69+
return extracted_elements
6970

70-
# Main Streamlit app function
71+
# Main function to handle invoice data extraction and analysis
7172
def invoiceAnalysisPlus():
7273
st.title("Invoice Data Extraction")
73-
74+
7475
with st.sidebar:
7576
st.title("Parameters")
76-
# Replace with your own compartment ID
77-
compID = "<YOUR_COMPARTMENT_OCID_HERE>"
77+
78+
# Compartment ID input
79+
compID = "" # Add your compartment ID here
80+
# User prompt input
7881
user_prompt = st.text_input("Input the elements you are looking to extract here")
7982
st.caption("Our AI assistant has extracted the following key elements from the invoice. Please select the elements you wish to extract.")
8083

84+
8185
uploaded_file = st.file_uploader("Upload your invoices here:", type=["pdf"])
82-
86+
8387
if uploaded_file is not None:
8488
with st.spinner("Processing..."):
85-
# Convert PDF to image list
8689
if uploaded_file.type == "application/pdf":
8790
images = convert_from_bytes(uploaded_file.read(), fmt="jpeg")
8891
else:
8992
images = [convert_from_bytes(uploaded_file.read(), fmt="jpeg")[0]]
90-
91-
# Save as byte streams
92-
image_list = save_images(images)
93-
94-
# Load both image-based and text-based LLMs
93+
94+
image_list = save_images(images) # Convert to byte arrays
95+
9596
llm = ChatOCIGenAI(
96-
model_id="meta.llama-3.2-90b-vision-instruct", # Replace with your model ID
97+
model_id="meta.llama-3.2-90b-vision-instruct",
9798
compartment_id=compID,
9899
model_kwargs={"max_tokens": 2000, "temperature": 0}
99100
)
100101
llm_for_prompts = ChatOCIGenAI(
101-
model_id="cohere.command-r-plus-08-2024", # Replace with your model ID
102+
model_id="cohere.command-r-plus-08-2024",
102103
compartment_id=compID,
103104
model_kwargs={"max_tokens": 2000, "temperature": 0}
104105
)
105-
106-
# Select box UI for user to pick elements and their data types
107-
data_types = ["Text", "Number", "Percentage", "Date"]
106+
107+
# Options for data types
108+
data_types = [ "Text", "Number", "Percentage", "Date"]
109+
110+
# Lists to store names and their types
108111
elements = []
109-
110112
if "availables" not in st.session_state:
111113
st.session_state.availables = extractor(image_list)
112-
113-
for i in range(3): # Max 3 fields
114-
col1, col2 = st.columns([2, 1])
115-
with col1:
114+
for i in range(3): # Adjust 'n' for the maximum number of selections
115+
col1, col2 = st.columns([2, 1]) # Adjust width ratio if needed
116+
117+
with col1:
118+
# Preserve user selection across reruns
116119
name = st.selectbox(f"Select an element {i+1}", st.session_state.availables, key=f"name_{i}", index=i)
117120
with col2:
118121
data_type = st.selectbox(f"Type {i+1}", data_types, key=f"type_{i}")
119122
elements.append((name, data_type))
120123

121-
# Generate appropriate prompt based on selected or input fields
122-
if elements:
124+
if elements is not None:
123125
system_message_cohere = SystemMessage(
124-
content=f"""
125-
Based on the following set of elements {elements}, with their respective types, extract their values and respond only in valid JSON format (no explanation):
126-
{', '.join([f'- {e[0]}' for e in elements])}
127-
For example:
128-
{{
129-
{elements[0][0]}: "296969",
130-
{elements[1][0]}: "296969",
131-
{elements[2][0]}: "296969"
132-
}}
133-
"""
134-
)
126+
content=f"""
127+
Based on the following set of elements {elements}, with their respective types ({elements[0][1]}, {elements[1][1]}, {elements[2][1]}), Extract the following details and provide the response only in valid JSON format (no extra explanation or text):
128+
- {elements[0][0]}
129+
- {elements[1][0]}
130+
- {elements[2][0]}
131+
Ensure the extracted data is formatted correctly as JSON and include nothing else at all in the response, not even a greeting or closing.
132+
For example:
133+
{{
134+
{elements[0][0]}: "296969",
135+
{elements[1][0]}: "296969",
136+
{elements[2][0]}: "296969",
137+
}}
138+
""")
135139
ai_response_cohere = system_message_cohere
136140
else:
141+
# Cohere section for generating the prompt
137142
system_message_cohere = SystemMessage(
138-
content=f"""
139-
Generate a system prompt to extract fields based on user-defined elements: {user_prompt}.
140-
Output should be JSON only. No other text.
141-
"""
142-
)
143+
content=f"""
144+
Based on the following system prompt, create a new prompt accordingly based on the elements specified in the user prompt here ({user_prompt}).
145+
146+
This is the system prompt template:
147+
"
148+
Extract the following details and provide the response only in valid JSON format (no extra explanation or text):
149+
- **Debit / Credit Note No.**
150+
- **Policy Period**
151+
- **Insured**
152+
- **Vessel Name**
153+
- **Details**
154+
- **Currency**
155+
- **Gross Premium 100%**
156+
- **OIMSL Share**
157+
- **Total Deductions**
158+
- **Net Premium**
159+
- **Premium Schedule**
160+
- **Installment Amount**
161+
162+
Ensure the extracted data is formatted correctly as JSON and include nothing else at all in the response, not even a greeting or closing.
163+
164+
For example:
165+
166+
"Debit / Credit Note No.": "296969",
167+
"Policy Period": "Feb 20, 2024 to Jul 15, 2025",
168+
"Insured": "Stealth Maritime Corp. S.A.",
169+
"Vessel Name": "SUPRA DUKE - HULL & MACHINERY", (Make sure this is the entire vessel name only)
170+
"Details": "SUPRA DUKE - Original Premium",
171+
"Currency": "USD",
172+
"Gross Premium 100%": 56973.63,
173+
"OIMSL Share": 4557.89,
174+
"Total Deductions": 979.92,
175+
"Net Premium": 3577.97,
176+
"Premium Schedule": ["Apr 20, 2024", "Jun 14, 2024", "Sep 13, 2024", "Dec 14, 2024", "Mar 16, 2025", "Jun 14, 2025"],
177+
"Installment Amount": [372.87, 641.02, 641.02, 641.02, 641.02, 641.02]
178+
179+
)" ensure your response is a system prompt format with an example of what the ouput should look like. Also ensure to mention in your gernerated prompt that no other content whatsover should appear except the JSON
180+
""")
143181
ai_response_cohere = llm_for_prompts.invoke(input=[system_message_cohere])
182+
print(ai_response_cohere)
144183

145-
# Extracted data list
146184
extracted_data = []
147-
185+
148186
with st.spinner("Analyzing invoice..."):
149187
for idx, img_byte_arr in enumerate(image_list):
150188
try:
189+
# Convert the image to base64 directly from memory
151190
encoded_frame = base64.b64encode(img_byte_arr.getvalue()).decode("utf-8")
152-
153-
if elements:
191+
if elements is not None:
154192
system_message = ai_response_cohere
155193
else:
156-
system_message = SystemMessage(content=ai_response_cohere.content)
157-
194+
system_message = SystemMessage(
195+
content=ai_response_cohere.content)
158196
human_message = HumanMessage(
159197
content=[
160198
{"type": "text", "text": "This is my invoice"},
161199
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{encoded_frame}"}},
162200
]
163201
)
164-
165202
ai_response = llm.invoke(input=[human_message, system_message])
166-
json_start = ai_response.content.find('{')
167-
json_end = ai_response.content.find('}', json_start)
168-
json_data = ai_response.content[json_start:json_end + 1]
169-
170-
response_dict = json.loads(json_data)
203+
print(ai_response.content)
204+
index = ai_response.content.find('{')
205+
index2 = ai_response.content.find('}')
206+
x = ai_response.content[index:]
207+
x2 = x[:index2+1]
208+
print(x2)
209+
response_dict = json.loads(x2)
210+
211+
# Add metadata for tracking
171212
response_dict["File Name"] = uploaded_file.name
172-
response_dict["Page Number"] = idx + 1
213+
response_dict["Page Number"] = idx + 1
214+
173215
extracted_data.append(response_dict)
174216

175217
except Exception as e:
176218
st.error(f"Error processing page {idx+1}: {str(e)}")
177-
178-
# Display and save results
219+
179220
if extracted_data:
180221
save_to_csv(extracted_data)
181222
st.dataframe(pd.DataFrame(extracted_data))
182223

183-
# Run the app
224+
# Run the chatbot function
184225
if __name__ == "__main__":
185-
invoiceAnalysisPlus()
226+
invoiceAnalysisPlus()

0 commit comments

Comments
 (0)