Update Llama_Utility.py
Browse files- Llama_Utility.py +294 -0
Llama_Utility.py
CHANGED
|
@@ -0,0 +1,294 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
import os
|
| 3 |
+
from datetime import datetime
|
| 4 |
+
import openai
|
| 5 |
+
from google.cloud import firestore
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
import pandas as pd
|
| 8 |
+
import ast
|
| 9 |
+
import json
|
| 10 |
+
from pandasai.responses.response_parser import ResponseParser
|
| 11 |
+
# Load environment variables and configure Firestore
|
| 12 |
+
load_dotenv()
|
| 13 |
+
db = firestore.Client.from_service_account_json("firestore-key.json")
|
| 14 |
+
from langchain_google_genai import ChatGoogleGenerativeAI
|
| 15 |
+
# Initialize the ChatSambaNovaCloud LLM client (using the latest model)
|
| 16 |
+
from langchain_community.chat_models.sambanova import ChatSambaNovaCloud
|
| 17 |
+
import google.generativeai as genai
|
| 18 |
+
|
| 19 |
+
llm = ChatSambaNovaCloud(
|
| 20 |
+
model="Meta-Llama-3.1-70B-Instruct",
|
| 21 |
+
max_tokens=1024,
|
| 22 |
+
temperature=0.7,
|
| 23 |
+
top_k=1,
|
| 24 |
+
top_p=0.01,
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# Response parser for formatting outputs from pandasai
|
| 30 |
+
class FlaskResponse(ResponseParser):
|
| 31 |
+
def __init__(self, context) -> None:
|
| 32 |
+
super().__init__(context)
|
| 33 |
+
|
| 34 |
+
def format_dataframe(self, result):
|
| 35 |
+
return result['value'].to_html()
|
| 36 |
+
|
| 37 |
+
def format_plot(self, result):
|
| 38 |
+
# Save the plot using savefig
|
| 39 |
+
try:
|
| 40 |
+
|
| 41 |
+
img_path = result['value']
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
except ValueError:
|
| 45 |
+
img_path = str(result['value'])
|
| 46 |
+
print("value error!", img_path)
|
| 47 |
+
|
| 48 |
+
print("response_class_path:", img_path)
|
| 49 |
+
return img_path
|
| 50 |
+
|
| 51 |
+
def format_other(self, result):
|
| 52 |
+
return str(result['value'])
|
| 53 |
+
|
| 54 |
+
def generateResponse(prompt, model='Meta-Llama-3.1-70B-Instruct'):
|
| 55 |
+
# Templates for extracting transaction information
|
| 56 |
+
relevant_info_template = """
|
| 57 |
+
Intent: The CRUD operation (create, read, update, delete)
|
| 58 |
+
Transaction Type: e.g. Purchase, Sale, Inventory
|
| 59 |
+
Details: A list of key detail fields extracted.
|
| 60 |
+
"""
|
| 61 |
+
sample_single_transaction_template = """
|
| 62 |
+
*Intent*: Create
|
| 63 |
+
*Transaction Type*: Purchase
|
| 64 |
+
*Details*: - Item: Car, - Quantity: 1, - Cost: 10000, etc.
|
| 65 |
+
"""
|
| 66 |
+
sample_multi_transaction_template = """
|
| 67 |
+
*Intent*: Create
|
| 68 |
+
Transaction 1:
|
| 69 |
+
*Transaction Type*: Purchase
|
| 70 |
+
*Details*: - Item: Car, - Quantity: 1, etc.
|
| 71 |
+
Transaction 2:
|
| 72 |
+
*Transaction Type*: Sale
|
| 73 |
+
*Details*: - Item: Chair, - Quantity: 2, etc.
|
| 74 |
+
"""
|
| 75 |
+
response = openai.OpenAI(
|
| 76 |
+
api_key=os.environ.get("SAMBANOVA_API_KEY"),
|
| 77 |
+
base_url="https://api.sambanova.ai/v1",
|
| 78 |
+
).chat.completions.create(
|
| 79 |
+
model=model,
|
| 80 |
+
messages=[
|
| 81 |
+
{"role": "system", "content":
|
| 82 |
+
f"You are a helpful assistant that classifies transactions. Format your output with these guidelines: {relevant_info_template} "
|
| 83 |
+
f"Sample single transaction: {sample_single_transaction_template} "
|
| 84 |
+
f"Sample multi-transaction: {sample_multi_transaction_template}"},
|
| 85 |
+
{"role": "user", "content": prompt}
|
| 86 |
+
]
|
| 87 |
+
)
|
| 88 |
+
try:
|
| 89 |
+
response_text = response.choices[0].message.content
|
| 90 |
+
except Exception as e:
|
| 91 |
+
print(f'An error occurred: {str(e)}')
|
| 92 |
+
response_text = None
|
| 93 |
+
return response_text
|
| 94 |
+
|
| 95 |
+
def parse_value(value):
|
| 96 |
+
value = value.strip()
|
| 97 |
+
try:
|
| 98 |
+
# Try to detect currency symbols or codes
|
| 99 |
+
currency_match = re.search(r"([A-Z]{3}|\$|€|£)", value)
|
| 100 |
+
currency = currency_match.group(1) if currency_match else None
|
| 101 |
+
|
| 102 |
+
# Remove currency symbols/codes for numeric conversion
|
| 103 |
+
cleaned_value = re.sub(r"([A-Z]{3}|\$|€|£)", "", value).replace(",", "").strip()
|
| 104 |
+
|
| 105 |
+
if "%" in cleaned_value:
|
| 106 |
+
return float(cleaned_value.replace("%", "")), currency
|
| 107 |
+
elif cleaned_value.replace(".", "", 1).isdigit():
|
| 108 |
+
return float(cleaned_value) if "." in cleaned_value else int(cleaned_value), currency
|
| 109 |
+
return value, currency
|
| 110 |
+
except ValueError:
|
| 111 |
+
return value, None
|
| 112 |
+
|
| 113 |
+
def extract_transaction_details(text):
|
| 114 |
+
details = {}
|
| 115 |
+
transaction_currency = None # Default currency
|
| 116 |
+
# Use regex to extract key: value pairs (handles bold formatting if present)
|
| 117 |
+
detail_matches = re.findall(
|
| 118 |
+
r"-\s*\*{0,2}([\w\s]+)\*{0,2}:\s*([\w\s,.$%-]+?)(?:\s*[\n]|$)",
|
| 119 |
+
text,
|
| 120 |
+
re.DOTALL
|
| 121 |
+
)
|
| 122 |
+
for field, value in detail_matches:
|
| 123 |
+
field_key = field.strip().lower().replace(" ", "_")
|
| 124 |
+
parsed_value, detected_currency = parse_value(value)
|
| 125 |
+
if detected_currency and not transaction_currency:
|
| 126 |
+
transaction_currency = detected_currency
|
| 127 |
+
details[field_key] = parsed_value
|
| 128 |
+
if transaction_currency:
|
| 129 |
+
details["currency"] = transaction_currency
|
| 130 |
+
return details
|
| 131 |
+
|
| 132 |
+
def parse_ai_response(response_text):
|
| 133 |
+
data = {
|
| 134 |
+
"intent": None,
|
| 135 |
+
"transaction_type": None,
|
| 136 |
+
"details": {},
|
| 137 |
+
"created_at": datetime.now().isoformat()
|
| 138 |
+
}
|
| 139 |
+
intent_match = re.search(r"\*Intent\*:\s*(\w+)", response_text)
|
| 140 |
+
if intent_match:
|
| 141 |
+
data["intent"] = intent_match.group(1)
|
| 142 |
+
transaction_type_match = re.search(r"\*Transaction Type\*:\s*(\w+)", response_text)
|
| 143 |
+
if transaction_type_match:
|
| 144 |
+
data["transaction_type"] = transaction_type_match.group(1)
|
| 145 |
+
data["details"] = extract_transaction_details(response_text)
|
| 146 |
+
return data
|
| 147 |
+
|
| 148 |
+
def parse_multiple_transactions(response_text):
|
| 149 |
+
transactions = []
|
| 150 |
+
# Split the response into sections by "Transaction <number>:"
|
| 151 |
+
transaction_sections = re.split(r"Transaction \d+:", response_text, flags=re.IGNORECASE)
|
| 152 |
+
transaction_sections = [section.strip() for section in transaction_sections if section.strip()]
|
| 153 |
+
# If the first section does not contain transaction info, remove it
|
| 154 |
+
if transaction_sections and not re.search(r"\*Transaction Type\*", transaction_sections[0], re.IGNORECASE):
|
| 155 |
+
transaction_sections.pop(0)
|
| 156 |
+
intent_match = re.search(r"\*Intent\*:\s*(\w+)", response_text)
|
| 157 |
+
intent = intent_match.group(1) if intent_match else None
|
| 158 |
+
for section in transaction_sections:
|
| 159 |
+
data = {
|
| 160 |
+
"intent": intent,
|
| 161 |
+
"transaction_type": None,
|
| 162 |
+
"details": {},
|
| 163 |
+
"created_at": datetime.now().isoformat()
|
| 164 |
+
}
|
| 165 |
+
transaction_type_match = re.search(r"\*Transaction Type\*:\s*(\w+)", section)
|
| 166 |
+
if transaction_type_match:
|
| 167 |
+
data["transaction_type"] = transaction_type_match.group(1)
|
| 168 |
+
data["details"] = extract_transaction_details(section)
|
| 169 |
+
transactions.append(data)
|
| 170 |
+
return transactions
|
| 171 |
+
|
| 172 |
+
def read_datalake(user_phone, user_question):
|
| 173 |
+
inventory_ref = db.collection("users").document(user_phone).collection("inventory")
|
| 174 |
+
sales_ref = db.collection("users").document(user_phone).collection("sales")
|
| 175 |
+
inventory_list = [doc.to_dict() for doc in inventory_ref.stream()]
|
| 176 |
+
sales_list = [doc.to_dict() for doc in sales_ref.stream()]
|
| 177 |
+
inventory_df = pd.DataFrame(inventory_list)
|
| 178 |
+
sales_df = pd.DataFrame(sales_list)
|
| 179 |
+
from pandasai import SmartDatalake
|
| 180 |
+
lake = SmartDatalake(
|
| 181 |
+
[inventory_df, sales_df],
|
| 182 |
+
config={
|
| 183 |
+
"llm": llm,
|
| 184 |
+
"custom_whitelisted_dependencies": ["ast"],
|
| 185 |
+
"response_parser": FlaskResponse,
|
| 186 |
+
"enable_cache": False,
|
| 187 |
+
"save_logs": False
|
| 188 |
+
}
|
| 189 |
+
)
|
| 190 |
+
response = lake.chat(user_question)
|
| 191 |
+
return response
|
| 192 |
+
|
| 193 |
+
def create_inventory(user_phone, transaction_data):
|
| 194 |
+
for transaction in transaction_data:
|
| 195 |
+
item_name = transaction['details'].get('item')
|
| 196 |
+
if item_name:
|
| 197 |
+
doc_ref = db.collection("users").document(user_phone).collection("inventory").document(item_name)
|
| 198 |
+
doc_ref.set(transaction)
|
| 199 |
+
return True
|
| 200 |
+
|
| 201 |
+
def create_sale(user_phone, transaction_data):
|
| 202 |
+
for transaction in transaction_data:
|
| 203 |
+
item_name = transaction['details'].get('item')
|
| 204 |
+
if not item_name:
|
| 205 |
+
continue
|
| 206 |
+
inventory = fetch_transaction(user_phone, item_name)
|
| 207 |
+
if inventory and inventory['details'].get('quantity') is not None:
|
| 208 |
+
new_stock = inventory['details'].get('quantity') - transaction['details'].get('quantity', 0)
|
| 209 |
+
inventory['details']['quantity'] = new_stock
|
| 210 |
+
inv_ref = db.collection("users").document(user_phone).collection("inventory").document(item_name)
|
| 211 |
+
inv_ref.set(inventory)
|
| 212 |
+
sale_ref = db.collection("users").document(user_phone).collection("sales").document(item_name)
|
| 213 |
+
sale_ref.set(transaction)
|
| 214 |
+
return True
|
| 215 |
+
|
| 216 |
+
def update_transaction(user_phone, transaction_id, update_data):
|
| 217 |
+
doc_ref = db.collection("users").document(user_phone).collection("transactions").document(transaction_id)
|
| 218 |
+
doc_ref.update(update_data)
|
| 219 |
+
return True
|
| 220 |
+
|
| 221 |
+
def fetch_transaction(user_phone, transaction_id=None):
|
| 222 |
+
if transaction_id:
|
| 223 |
+
doc_ref = db.collection("users").document(user_phone).collection("inventory").document(transaction_id)
|
| 224 |
+
transaction = doc_ref.get()
|
| 225 |
+
if transaction.exists:
|
| 226 |
+
return transaction.to_dict()
|
| 227 |
+
return None
|
| 228 |
+
else:
|
| 229 |
+
collection_ref = db.collection("users").document(user_phone).collection("inventory")
|
| 230 |
+
transactions = [doc.to_dict() for doc in collection_ref.stream()]
|
| 231 |
+
return transactions
|
| 232 |
+
|
| 233 |
+
def delete_transaction(user_phone, transaction_data):
|
| 234 |
+
# Assume transaction id defaults to the item name in transaction details
|
| 235 |
+
transaction_id = transaction_data[0]['details'].get('item')
|
| 236 |
+
transaction_type = transaction_data[0]['transaction_type']
|
| 237 |
+
if transaction_type:
|
| 238 |
+
transaction_type = transaction_type.lower()
|
| 239 |
+
else:
|
| 240 |
+
transaction_type = "inventory"
|
| 241 |
+
doc_ref = db.collection("users").document(user_phone).collection(transaction_type).document(transaction_id)
|
| 242 |
+
item_doc = doc_ref.get()
|
| 243 |
+
if item_doc.exists:
|
| 244 |
+
doc_ref.delete()
|
| 245 |
+
return True
|
| 246 |
+
else:
|
| 247 |
+
return False
|
| 248 |
+
|
| 249 |
+
def persist_temporary_transaction(transactions, mobile):
|
| 250 |
+
temp_ref = db.collection("users").document(mobile).collection("temp_transactions").document('pending-user-action')
|
| 251 |
+
data = {
|
| 252 |
+
"transactions": transactions,
|
| 253 |
+
"status": "pending",
|
| 254 |
+
"created_at": datetime.now().isoformat()
|
| 255 |
+
}
|
| 256 |
+
temp_ref.set(data)
|
| 257 |
+
return True
|
| 258 |
+
|
| 259 |
+
def process_intent(parsed_trans_data, mobile):
|
| 260 |
+
"""
|
| 261 |
+
Process the detected intent and handle transactions accordingly.
|
| 262 |
+
- For 'create', it differentiates between purchase/inventory and sale.
|
| 263 |
+
- For 'delete', it attempts to delete the transaction.
|
| 264 |
+
- Returns a message indicating the result.
|
| 265 |
+
"""
|
| 266 |
+
intent = parsed_trans_data[0]['intent'].lower() if parsed_trans_data and parsed_trans_data[0].get('intent') else None
|
| 267 |
+
trans_type = parsed_trans_data[0]['transaction_type'].lower() if parsed_trans_data and parsed_trans_data[0].get('transaction_type') else None
|
| 268 |
+
|
| 269 |
+
if intent == 'create':
|
| 270 |
+
if trans_type in ('purchase', 'purchases', 'inventory'):
|
| 271 |
+
if create_inventory(mobile, parsed_trans_data):
|
| 272 |
+
firestore_msg = "Transaction recorded successfully!"
|
| 273 |
+
else:
|
| 274 |
+
firestore_msg = "Sorry, could not record transaction!"
|
| 275 |
+
elif trans_type in ('sale', 'sales'):
|
| 276 |
+
if create_sale(mobile, parsed_trans_data):
|
| 277 |
+
firestore_msg = "Transaction recorded successfully!"
|
| 278 |
+
else:
|
| 279 |
+
firestore_msg = "Sorry, could not record transaction!"
|
| 280 |
+
else:
|
| 281 |
+
firestore_msg = f"Transaction type '{trans_type}' is not supported for create operations."
|
| 282 |
+
elif intent == 'update':
|
| 283 |
+
# Update logic would be implemented here.
|
| 284 |
+
firestore_msg = "Update operation not implemented yet."
|
| 285 |
+
elif intent == 'delete':
|
| 286 |
+
item = parsed_trans_data[0]['details'].get('item', 'item')
|
| 287 |
+
item_deleted = delete_transaction(mobile, parsed_trans_data)
|
| 288 |
+
if item_deleted:
|
| 289 |
+
firestore_msg = f"You successfully deleted {item} from {trans_type}!"
|
| 290 |
+
else:
|
| 291 |
+
firestore_msg = f"Sorry, could not delete {item} from {trans_type}!"
|
| 292 |
+
else:
|
| 293 |
+
firestore_msg = f"The detected intent, {intent}, is not currently supported!"
|
| 294 |
+
return firestore_msg
|