File size: 14,138 Bytes
f7f78a2
 
 
 
 
9898985
 
 
 
 
 
 
 
 
f7f78a2
 
 
38e0e74
f7f78a2
38e0e74
 
 
 
 
f7f78a2
38e0e74
 
 
 
 
 
 
 
 
 
f7f78a2
38e0e74
 
 
 
 
 
 
 
 
 
f7f78a2
6c48c9b
 
 
 
 
 
 
 
 
 
38e0e74
 
 
2123b97
 
 
6c48c9b
 
 
38e0e74
 
 
2123b97
38e0e74
 
 
 
 
 
 
 
 
 
 
2123b97
 
 
 
6c48c9b
 
29eb3ed
7c6c740
a6d744f
7c6c740
 
 
2123b97
29eb3ed
6c48c9b
f7f78a2
aed8428
f7f78a2
38e0e74
f7f78a2
2123b97
aed8428
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f7f78a2
aed8428
 
 
 
 
 
38e0e74
f7f78a2
 
 
 
 
 
 
aed8428
 
 
 
 
 
 
 
 
f7f78a2
 
 
aed8428
 
 
 
 
 
 
 
 
 
f7f78a2
a6d744f
2123b97
f7f78a2
 
 
 
 
 
d8d0296
38e0e74
 
 
 
d8d0296
38e0e74
d8d0296
f7f78a2
 
7c6c740
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
aed8428
f7f78a2
29eb3ed
 
f7f78a2
d8d0296
38e0e74
f7f78a2
 
 
0ea4bb9
 
7c6c740
aed8428
f7f78a2
 
7c6c740
f7f78a2
 
 
7c6c740
 
f7f78a2
7c6c740
 
f7f78a2
7c6c740
 
aed8428
 
7c6c740
 
 
 
29eb3ed
 
a6d744f
29eb3ed
7c6c740
aed8428
 
 
 
 
 
 
 
7c6c740
 
 
 
 
 
 
 
29eb3ed
 
7c6c740
 
 
 
 
 
 
f7f78a2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7c6c740
f7f78a2
 
 
 
 
aeb2778
f7f78a2
 
 
 
 
 
 
 
 
 
29eb3ed
 
 
 
f7f78a2
 
ae99e22
f7f78a2
 
 
 
 
 
 
 
 
 
 
 
7c6c740
 
 
 
 
 
 
 
 
 
 
 
aed8428
7c6c740
 
 
 
 
 
 
 
 
aed8428
 
7c6c740
 
 
 
f7f78a2
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357

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(os.getcwd(), "data", "user_data", f"user_data_{user_id}.json")
    #st.write(f"Loading user data from: {file_path}")
    #st.write(f"Current working directory: {os.getcwd()}")

    #Verify if the file exists
    if not os.path.exists(file_path):
        #st.write("File does not exist.")
        return {}
    
    try:
        with open(file_path, "r") as file:
            data = json.load(file)
            #st.write(f"Loaded data: {data}")
            return data
    except json.JSONDecodeError:
        st.write("Error decoding JSON.")
        return {}
    except Exception as e:
        st.write(f"An error occurred: {e}")
        return {}

    
def save_user_data(user_id, data):
    file_path = os.path.join("data", "user_data", f"user_data_{user_id}.json")
    os.makedirs(os.path.dirname(file_path), exist_ok=True)
    with open(file_path, "w") as file:
        json.dump(data, 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 = []
    categories = set()
    names = set()

    for tax in taxItems:
        categories.add(tax.get("cat"))

    for sub in subscribers:
        logicalResource = sub.get("logicalResource")
        billSummaryItems = sub.get("billSummaryItem", [])
        for item in billSummaryItems:
            try:
                categories.add(item["cat"]),
                categories.add(item["name"]),
                names.add(item["name"])
            except KeyError:
                continue

            subscriberCosts.append({
                "Numar telefon": logicalResource,
                "Categorie cost": item["cat"],
                "Cost": item["name"],
                "Valoare": item["amt"]
            })  

    #st.write(f"Costuri totale factura: {totalBillCosts}")
    #st.write(f"Costuri utilizatori: {subscriberCosts}")
    #st.write(f"Categorii: {categories}")


    return {
        "Data factura": billDate,
        "Serie numar factura": billNo,
        "Total de plata": amountDue,
        "Costuri suplimentare": extraCharge,
        "Total plata factura": totalBillCosts,
        "Costuri utilizatori": subscriberCosts,
        "Entities": list(categories),
        "Costuri": list(names)
    }

def check_related_keys2(question, user_id):
    user_data = load_user_data(user_id)
    categories = set()
    for bill in user_data.get("bills", []):
        categories.update(bill.get("Entities", []))
        #st.write(f"Entities: {list(categories)}")
        #st.write(f"Question: {question}"),
    return [category for category in list(categories) if question in category]

def check_related_keys3(question, user_id):
    user_data = load_user_data(user_id)
    categories = set()
    for bill in user_data.get("bills", []):
        categories.update(bill.get("categories", []))
    
    
    related_categories = []
    for category in list(categories):
        st.write(f"Entity: {category}")

        if question in category:
            related_categories.append(category)
    
    return related_categories

def check_related_keys(question, user_id):
    user_data = load_user_data(user_id)
    entities = set()
    for bill in user_data.get("bills", []):
        entities.update(bill.get("Entities", []))
    
    #st.write(f"Entities: {list(entities)}")
    #st.write(f"Question: {question}")

    related_entities = [entity for entity in entities if entity.lower() in question.lower()]
    
    #st.write(f"Related Entities: {related_entities}")
    
    return related_entities
    
def process_query(query, user_id, model_name):
    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"- Ca asistent virtual, ai acces la informatii despre costurile facturate in lei din factura.\n"
            f"- Citeste informatiile despre costruri din json: \n"
            f" {bill_info}\n"
            f"- Raspunde la urmatoarea intrebare a clientului: :blue['{query}']\n"
            f"- In special cu info legate de: :red[{related_keys_str}].\n"
            f"- Pentru orice alt subiect raspunde ca nu poti oferi decat informatii despre facturi. Sugereaza ca intrebarea sa fie legata doar de factura si costuri.\n"
            f"- Folosesete contextul pentru a raspunde la intrebare.\n"
            f"- Daca nu ai suficiente informatii, raspunde ca nu ai suficiente informatii.\n"
            f"- Raspunde pe un ton calm, prietenos si profesionist, niciodata jignitor."
        )
    else:
        context = (
            f"- Ca asistent virtual, ai acces la informatii despre costurile facturate in lei din factura.\n"
            f"- Citeste informatiile despre costruri din json: \n"
            f" {bill_info}\n"
            f"- Raspunde la urmatoarea intrebare a clientului: :blue['{query}']\n"
            f"- In special cu info legate de :red[factura].\n"
            f"- Pentru orice alt subiect raspunde ca nu poti oferi decat informatii despre facturi. Sugereaza ca intrebarea sa fie legata doar de factura si costuri.\n"
            f"- Folosesete contextul pentru a raspunde la intrebare.\n"
            f"- Daca nu ai suficiente informatii, raspunde ca nu ai suficiente informatii.\n"
            f"- Raspunde pe un ton calm, prietenos si profesionist, niciodata jignitor."
                                    )

    max_input_length = 7550
    #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

    # Update this part to run the chosen model
    if model_name == "gpt-4o-mini":
        # Code to run model 4o mini
        st.write("Running model GPT-4o-mini")
    elif model_name == "gpt-4o":
        # Code to run model 4o
        st.write("Running model GPT-4o")

    return context

# import the datetime class from the datetime module
from datetime import datetime
def log_conversation(user_id, user_query, assistant_response, tokens, cost):
    log_entry = {
        "timestamp": datetime.now().isoformat(),
        "user_id": user_id,
        "user_query": user_query,
        "assistant_response": assistant_response,
        "tokens": tokens,
        "cost": cost
    }
    log_file_path = os.path.join("logs", "conversation_logs.json")
    os.makedirs(os.path.dirname(log_file_path), exist_ok=True)
    if os.path.exists(log_file_path):
        with open(log_file_path, "r") as log_file:
            logs = json.load(log_file)
    else:
        logs = []
    logs.append(log_entry)
    with open(log_file_path, "w") as log_file:
        json.dump(logs, log_file, indent=4)



def main():
    st.title("Bill info LLM Agent (OpenAI)")
    st.image("https://miro.medium.com/v2/resize:fit:100/format:webp/1*NfE0G4nEj4xX7Z_8dSx83g.png")

    # Create a sidebar menu to choose between models
    model_name = st.sidebar.selectbox("Choose OpenAI Model", ["gpt-4o-mini", "gpt-4o"])
    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:", placeholder="Incearca 0724077190")

    st.session_state.user_data = None

    if user_id and user_id != st.session_state.user_id:
        data = load_user_data(user_id)
        st.session_state.user_data = data
        if data:
            st.session_state.user_id = user_id
            st.success("Utilizator găsit!")
            st.write(f"Numar telefon: {st.session_state.user_id}")
            st.session_state.user_data = data
        else:
            st.warning("Nu am găsit date pentru acest ID.")
            st.warning("Încărcați o factură json.")
            st.session_state.user_id = user_id
            st.session_state.user_data = None

    # If the user has no data yet Show the upload (st.file_uploader...) dialog,
    # If the user has stored data in data\user_data\"user_data{user_id}.json, display the existing bills data - st.write(bill) but compacted
    if st.session_state.user_data:
        st.write("Facturi existente (extras):")
        for bill in st.session_state.user_data.get("bills", []):
            st.write({
                "Data factura": bill.get("Data factura"),
                "Serie numar factura": bill.get("Serie numar factura"),
                "Total de plata": bill.get("Total de plata"),
                "Costuri suplimentare": bill.get("Costuri suplimentare")
            })
        
        # Display entities found in user data
        st.write("Entități găsite în datele utilizatorului:")
        entities = set()
        for bill in st.session_state.user_data.get("bills", []):
            entities.update(bill.get("Entities", []))
        st.write(list(entities))

    if not st.session_state.user_data:
        uploaded_file = st.file_uploader("Incarca factura", 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)
            
            # Check if the billNo already exists in the existing data
            existing_bill_nos = [bill.get("Data factura") for bill in existing_data.get("bills", [])]
            if parsed_bill.get("Data factura") in existing_bill_nos:
                st.warning("Factură existentă.")
            else:
                if "bills" not in existing_data:
                    existing_data["bills"] = []
                existing_data["bills"].append(parsed_bill)
                save_user_data(st.session_state.user_id, existing_data)
                st.success("Factura a fost încărcată și salvată cu succes!")

    # 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ă introduci un număr de telefon valid sau să încarci 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"], model_name)
            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"])

        # Display the related keys
        related_keys = check_related_keys(prompt, st.session_state["user_id"])
        st.write("Focus pe entitatile:", related_keys)

        # Now call GPT-4 with the entire conversation
        completion = client.chat.completions.create(
            model=model_name,
            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)

            # Estimate cost per conversation (find the OpenAI costs for gpt-4o and gpt-4o-mini model per token)
            prompt_tokens = completion.usage.prompt_tokens
            completion_tokens = completion.usage.completion_tokens
            total_tokens = completion.usage.total_tokens

            # Estimate cost per conversation
            if model_name == "gpt-4o":
                cost_per_token = 0.03 / 1000  # $0.03 per 1,000 tokens
            elif model_name == "gpt-4o-mini":
                cost_per_token = 0.015 / 1000  # $0.015 per 1,000 tokens

            estimated_cost = total_tokens * cost_per_token
            #st.write("Estimated cost:", estimated_cost)

            # Log the conversation
            log_conversation(
                st.session_state["user_id"],
                prompt,
                response_text,
                {
                    "prompt_tokens": prompt_tokens,
                    "completion_tokens": completion_tokens,
                    "total_tokens": total_tokens,
                    "estimated_cost": estimated_cost
                },
                estimated_cost
            )

if __name__ == "__main__":
    main()