#!/usr/bin/env -S poetry run python import os import json import streamlit as st from openai import OpenAI from dotenv import load_dotenv # Load environment variables from .env file load_dotenv() # Get the OpenAI API key from environment variables api_key = os.getenv("OPENAI_API_KEY") if not api_key: raise ValueError("The OPENAI_API_KEY environment variable is not set.") client = OpenAI() def load_user_data(user_id): file_path = os.path.join("data", "user_data", f"user_data_{user_id}.json") if not os.path.exists(file_path): return {} with open(file_path, "r") as file: return json.load(file) def parseBill(data): billDate = data.get("billDate") billNo = data.get("billNo") amountDue = data.get("amountDue") extraCharge = data.get("extraCharge") taxItems = data.get("taxItem", []) subscribers = data.get("subscribers", []) totalBillCosts = [{"categorie": t.get("cat"), "amount": t.get("amt")} for t in taxItems] subscriberCosts = [] for sub in subscribers: logicalResource = sub.get("logicalResource") billSummaryItems = sub.get("billSummaryItem", []) subscriberCosts.append({ "logicalResource": logicalResource, "billSummaryItems": [ {"categorie": bsi.get("cat"), "amount": bsi.get("amt"), "name": bsi.get("name")} for bsi in billSummaryItems ], }) return { "billDate": billDate, "billNo": billNo, "amountDue": amountDue, "extraCharge": extraCharge, "totalBillCosts": totalBillCosts, "subscriberCosts": subscriberCosts } def check_related_keys(question, user_id): user_data = load_user_data(user_id) bill_keys = set() for bill in user_data.get("bills", []): bill_keys.update(bill.keys()) return [key for key in bill_keys if key.lower() in question.lower()] def process_query(query, user_id): user_data = load_user_data(user_id) bill_info = user_data.get("bills", []) related_keys = check_related_keys(query, user_id) related_keys_str = ", ".join(related_keys) if related_keys else "N/A" if related_keys_str != "N/A": context = ( f"Citeste informatiile despre costrurile in lei facturate din dictionar: {bill_info} " f"si raspunde la intrebarea: '{query}' dar numai cu info legate de: {related_keys_str}" ) else: context = ( f"Citeste informatiile despre costrurile in lei facturate din dictionar: {bill_info} " f"si raspunde la intrebarea: '{query}' dar numai cu info legate de factura" ) max_input_length = 550 st.write(f"Context:\n{context}") st.write(f"Context size: {len(context)} characters") if len(context) > max_input_length: st.warning("Prea multe caractere în context, solicitarea nu va fi trimisă.") return None return context def main(): st.title("Telecom Bill Chat with LLM Agent") if "user_id" not in st.session_state: st.session_state.user_id = None user_id = st.sidebar.text_input("Introdu numărul de telefon:") if user_id and user_id != st.session_state.user_id: data = load_user_data(user_id) if data: st.session_state.user_id = user_id st.success("Utilizator găsit!") else: st.warning("Nu am găsit date pentru acest ID. Încărcați o factură PDF la nevoie.") st.session_state.user_id = user_id uploaded_file = st.file_uploader("Încarcă factura JSON", type="json") if uploaded_file and st.session_state.user_id: bill_data = json.load(uploaded_file) parsed_bill = parseBill(bill_data) existing_data = load_user_data(st.session_state.user_id) if "bills" not in existing_data: existing_data["bills"] = [] existing_data["bills"].append(parsed_bill) file_path = os.path.join("data", "user_data", f"user_data_{st.session_state['user_id']}.json") os.makedirs(os.path.dirname(file_path), exist_ok=True) with open(file_path, "w") as file: json.dump(existing_data, file) st.success("Factura a fost încărcată și salvată cu succes!") if st.session_state.user_id: data = load_user_data(st.session_state.user_id) st.write(f"Phone Number: {st.session_state.user_id}") st.write("Facturi existente:") for bill in data.get("bills", []): st.write(bill) else: st.info("Introduceți un ID și/sau încărcați o factură JSON pentru a continua.") # Initialize conversation in the session state # "context_prompt_added" indicates whether we've added the specialized "bill info" context yet. if "messages" not in st.session_state: st.session_state["messages"] = [ {"role": "assistant", "content": "Cu ce te pot ajuta?"} ] if "context_prompt_added" not in st.session_state: st.session_state.context_prompt_added = False st.write("---") st.subheader("Chat") for msg in st.session_state["messages"]: st.chat_message(msg["role"]).write(msg["content"]) if prompt := st.chat_input("Introduceți întrebarea aici:"): if not st.session_state.user_id: st.error("Trebuie să introduceți un număr de telefon valid sau să încărcați date.") return # If the context prompt hasn't been added yet, build & inject it once; # otherwise, just add the user's raw question. if not st.session_state.context_prompt_added: final_prompt = process_query(prompt, st.session_state["user_id"]) if final_prompt is None: st.stop() st.session_state["messages"].append({"role": "user", "content": final_prompt}) st.session_state.context_prompt_added = True else: st.session_state["messages"].append({"role": "user", "content": prompt}) # Display the latest user message in the chat st.chat_message("user").write(st.session_state["messages"][-1]["content"]) # Now call GPT-4 with the entire conversation completion = client.chat.completions.create( model="gpt-4", messages=st.session_state["messages"] ) response_text = completion.choices[0].message.content.strip() st.session_state["messages"].append({"role": "assistant", "content": response_text}) st.chat_message("assistant").write(response_text) if hasattr(completion, "usage"): st.write("Prompt tokens:", completion.usage.prompt_tokens) st.write("Completion tokens:", completion.usage.completion_tokens) st.write("Total tokens:", completion.usage.total_tokens) if __name__ == "__main__": main()