File size: 12,319 Bytes
a43dae1
 
 
 
 
 
 
ea69cd5
 
 
 
 
a43dae1
 
ea69cd5
 
a43dae1
 
 
 
 
 
 
 
 
 
 
 
 
 
ea69cd5
a43dae1
 
ea69cd5
 
a43dae1
 
 
 
 
ea69cd5
 
 
a43dae1
ea69cd5
 
a43dae1
ea69cd5
 
 
 
a43dae1
ea69cd5
 
 
a43dae1
 
 
 
ea69cd5
 
 
 
 
 
 
 
 
a43dae1
ea69cd5
 
a43dae1
ea69cd5
 
a43dae1
 
 
 
ea69cd5
a43dae1
 
 
ea69cd5
a43dae1
ea69cd5
a43dae1
 
 
ea69cd5
a43dae1
ea69cd5
 
 
 
 
a43dae1
ea69cd5
 
a43dae1
ea69cd5
 
 
a43dae1
ea69cd5
 
a43dae1
ea69cd5
a43dae1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ea69cd5
a43dae1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ea69cd5
a43dae1
 
 
 
 
 
 
 
 
 
ea69cd5
 
a43dae1
 
 
 
 
 
 
 
ea69cd5
 
a43dae1
ea69cd5
a43dae1
ea69cd5
a43dae1
ea69cd5
 
a43dae1
ea69cd5
a43dae1
ea69cd5
 
 
 
 
 
 
 
 
a43dae1
ea69cd5
 
 
a43dae1
ea69cd5
a43dae1
 
ea69cd5
a43dae1
ea69cd5
 
a43dae1
ea69cd5
a43dae1
 
 
 
 
 
 
ea69cd5
a43dae1
 
 
 
ea69cd5
 
a43dae1
 
ea69cd5
 
a43dae1
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
# =============================================================================
# DON'T STEAL THE FREE CODE OF DEVS! Use it for free an do not touch credits!
# If you steal this code, in the future you will pay for apps like this!
# A bit of respect goes a long way – all rights reserved under German law.
# Copyright Volkan Kücükbudak https://github.com/volkansah
# Repo URL: https://github.com/AiCodeCraft
# =============================================================================
import streamlit as st
import os
import json
import datetime
import openai
from datetime import timedelta
import logging
from datasets import load_dataset, Dataset, concatenate_datasets

# ------------------ Logging konfigurieren ------------------
logging.basicConfig(
    level=logging.INFO,  # Log-Level auf INFO setzen
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
logger.info("Starte App mit HF-Dataset Memory...")

# ------------------ Hugging Face Token laden ------------------
HF_TOKEN_MEMORY = os.getenv('HF_TOKEN_MEMORY', '').strip()
if HF_TOKEN_MEMORY:
    logger.info("Hugging Face Token gefunden.")
else:
    logger.warning("Kein Hugging Face Token gefunden. Falls benötigt, bitte setzen!")

# ------------------ Einstellungen für das Memory-Dataset ------------------
DATASET_REPO = "AiCodeCarft/customer_memory"

def load_memory_dataset():
    """
    Versucht, das Memory-Dataset vom HF Hub zu laden.
    Falls nicht vorhanden, wird ein leeres Dataset mit den Spalten
    'user_id', 'query' und 'response' erstellt und gepusht.
    """
    try:
        ds = load_dataset(DATASET_REPO, split="train")
        st.write("Dataset loaded from HF Hub.")
        logger.info("Dataset erfolgreich vom HF Hub geladen.")
    except Exception as e:
        st.write("Dataset not found on HF Hub. Creating a new one...")
        logger.info("Kein Dataset gefunden. Erstelle ein neues Dataset...")
        data = {"user_id": [], "query": [], "response": []}
        ds = Dataset.from_dict(data)
        ds.push_to_hub(DATASET_REPO)
        st.write("New dataset created and pushed to HF Hub.")
        logger.info("Neues Dataset erfolgreich erstellt und gepusht.")
    return ds

def add_to_memory(user_id, query, response):
    """
    Fügt einen neuen Eintrag (Query und Antwort) zum Memory-Dataset hinzu
    und pusht das aktualisierte Dataset an den HF Hub.
    """
    ds = load_memory_dataset()
    # Neuer Eintrag als kleines Dataset
    new_entry = Dataset.from_dict({
        "user_id": [user_id],
        "query": [query],
        "response": [response]
    })
    # Bestehendes Dataset mit dem neuen Eintrag zusammenführen
    updated_ds = concatenate_datasets([ds, new_entry])
    # Push updated dataset to HF Hub
    updated_ds.push_to_hub(DATASET_REPO)
    st.write("Memory updated.")
    logger.info("Memory-Dataset erfolgreich aktualisiert.")

def get_memory(user_id):
    """
    Filtert das Memory-Dataset nach der angegebenen Customer ID
    und gibt alle Einträge (Query und Antwort) zurück.
    """
    ds = load_memory_dataset()
    filtered_ds = ds.filter(lambda x: x["user_id"] == user_id)
    logger.info(f"Memory für User {user_id} abgerufen. {len(filtered_ds)} Einträge gefunden.")
    return filtered_ds

# ------------------ OpenAI GPT-4 API-Anbindung ------------------
def generate_response(prompt):
    """
    Sendet den Prompt an die OpenAI API und gibt die Antwort zurück.
    """
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "You are a customer support AI for TechGadgets.com."},
            {"role": "user", "content": prompt}
        ]
    )
    logger.info("Antwort von OpenAI erhalten.")
    return response.choices[0].message.content

# ------------------ Streamlit App UI ------------------
st.title("AI Customer Support Agent with Memory 🛒")
st.caption("Chat with a customer support assistant who remembers your past interactions.")

# Eingabe des OpenAI API Keys
openai_api_key = st.text_input("Enter OpenAI API Key", type="password")
if openai_api_key:
    os.environ['OPENAI_API_KEY'] = openai_api_key
    openai.api_key = openai_api_key
    logger.info("OpenAI API Key gesetzt.")

    # ------------------ Klasse: CustomerSupportAIAgent ------------------
    class CustomerSupportAIAgent:
        def __init__(self):
            # Wir nutzen hier die HF Dataset Funktionen als Memory-Speicher
            self.client = openai  # OpenAI Client
            self.app_id = "customer-support"

        def handle_query(self, query, user_id=None):
            try:
                # Hole relevante Erinnerungen aus dem HF Dataset
                memories = get_memory(user_id)
                context = "Relevant past information:\n"
                # Falls Einträge vorhanden sind, baue den Kontext
                if len(memories) > 0:
                    for entry in memories:
                        context += f"- Query: {entry['query']}\n  Response: {entry['response']}\n"
                logger.info("Kontext aus Memory-Dataset erstellt.")

                # Kombiniere Kontext und aktuelle Anfrage
                full_prompt = f"{context}\nCustomer: {query}\nSupport Agent:"
                logger.info("Vollständiger Prompt für OpenAI erstellt.")

                # Generiere Antwort mit OpenAI
                answer = generate_response(full_prompt)

                # Speicher die Interaktion im Memory-Dataset
                add_to_memory(user_id, query, answer)
                logger.info("Interaktion im Memory-Dataset gespeichert.")

                return answer
            except Exception as e:
                logger.error(f"Fehler bei handle_query: {e}")
                st.error(f"An error occurred while handling the query: {e}")
                return "Sorry, I encountered an error. Please try again later."

        def generate_synthetic_data(self, user_id: str) -> dict | None:
            try:
                today = datetime.datetime.now()
                order_date = (today - timedelta(days=10)).strftime("%B %d, %Y")
                expected_delivery = (today + timedelta(days=2)).strftime("%B %d, %Y")

                # Prompt zur Generierung synthetischer Kundendaten für einen Lieferservice
                prompt = f"""Generate a detailed customer profile and order history for a DeliverItExpress customer with ID {user_id}. Include:
                1. Customer name and basic info (age, gender, and contact details)
                2. A recent order of a gourmet meal (placed on {order_date} and delivered by {expected_delivery})
                3. Order details including food items, total price, and order number
                4. Customer's delivery address
                5. 2-3 previous orders from the past year with different types of cuisines
                6. 2-3 customer service interactions regarding delivery issues (e.g., late delivery, missing items)
                7. Any preferences or patterns in their ordering behavior (e.g., favorite cuisines, peak ordering times)

                Format the output as a JSON object."""
                logger.info("Prompt for generating synthetic delivery service data created.")

                response = self.client.ChatCompletion.create(
                    model="gpt-4",
                    messages=[
                        {"role": "system", "content": "You are a data generation AI that creates realistic customer profiles and order histories. Always respond with valid JSON."},
                        {"role": "user", "content": prompt}
                    ]
                )
                logger.info("Antwort für synthetische Daten von OpenAI erhalten.")
                customer_data = json.loads(response.choices[0].message.content)

                # Optional: Speichere auch diese Daten im Memory-Dataset
                for key, value in customer_data.items():
                    if isinstance(value, list):
                        for item in value:
                            add_to_memory(user_id, f"{key} item", json.dumps(item))
                    else:
                        add_to_memory(user_id, key, json.dumps(value))
                logger.info("Synthetische Daten im Memory-Dataset gespeichert.")
                return customer_data
            except Exception as e:
                logger.error(f"Fehler bei generate_synthetic_data: {e}")
                st.error(f"Failed to generate synthetic data: {e}")
                return None

    # ------------------ Initialisierung des CustomerSupportAIAgent ------------------
    support_agent = CustomerSupportAIAgent()

    # ------------------ Sidebar: Customer ID und Optionen ------------------
    st.sidebar.title("Enter your Customer ID:")
    previous_customer_id = st.session_state.get("previous_customer_id", None)
    customer_id = st.sidebar.text_input("Enter your Customer ID")

    if customer_id != previous_customer_id:
        st.session_state.messages = []
        st.session_state.previous_customer_id = customer_id
        st.session_state.customer_data = None
        logger.info("Neue Customer ID erkannt – Chatverlauf und synthetische Daten zurückgesetzt.")

    # Button: Synthetische Daten generieren
    if st.sidebar.button("Generate Synthetic Data"):
        if customer_id:
            with st.spinner("Generating customer data..."):
                st.session_state.customer_data = support_agent.generate_synthetic_data(customer_id)
            if st.session_state.customer_data:
                st.sidebar.success("Synthetic data generated successfully!")
                logger.info("Synthetische Daten erfolgreich generiert.")
            else:
                st.sidebar.error("Failed to generate synthetic data.")
                logger.error("Fehler beim Generieren synthetischer Daten.")
        else:
            st.sidebar.error("Please enter a customer ID first.")
            logger.warning("Kein Customer ID eingegeben beim Versuch, synthetische Daten zu generieren.")

    # Button: Customer Profile anzeigen
    if st.sidebar.button("View Customer Profile"):
        if st.session_state.customer_data:
            st.sidebar.json(st.session_state.customer_data)
        else:
            st.sidebar.info("No customer data generated yet. Click 'Generate Synthetic Data' first.")

    # Button: Memory-Info anzeigen
    if st.sidebar.button("View Memory Info"):
        if customer_id:
            memories = get_memory(customer_id)
            st.sidebar.write(f"Memory for customer **{customer_id}**:")
            for mem in memories:
                st.sidebar.write(f"**Query:** {mem['query']}\n**Response:** {mem['response']}\n---")
        else:
            st.sidebar.error("Please enter a customer ID.")

    # ------------------ Chatverlauf initialisieren und anzeigen ------------------
    if "messages" not in st.session_state:
        st.session_state.messages = []

    # Vorherige Nachrichten anzeigen
    for message in st.session_state.messages:
        with st.chat_message(message["role"]):
            st.markdown(message["content"])

    # ------------------ Haupt-Chat: Benutzereingabe ------------------
    query = st.chat_input("How can I assist you today?")
    if query and customer_id:
        # Benutzeranfrage zum Chatverlauf hinzufügen
        st.session_state.messages.append({"role": "user", "content": query})
        with st.chat_message("user"):
            st.markdown(query)
        logger.info("Benutzeranfrage hinzugefügt.")

        # Generiere Antwort und zeige sie an
        with st.spinner("Generating response..."):
            answer = support_agent.handle_query(query, user_id=customer_id)
        st.session_state.messages.append({"role": "assistant", "content": answer})
        with st.chat_message("assistant"):
            st.markdown(answer)
        logger.info("Antwort des Assistenten hinzugefügt.")

    elif query and not customer_id:
        st.error("Please enter a customer ID to start the chat.")
        logger.warning("Chat gestartet ohne Customer ID.")

else:
    st.warning("Please enter your OpenAI API key to use the customer support agent.")
    logger.info("Warte auf Eingabe des OpenAI API Keys.")