martinbrahm commited on
Commit
6e26e1d
·
verified ·
1 Parent(s): 3dcbfed

Upload 2 files

Browse files
Files changed (2) hide show
  1. main.py +162 -0
  2. requirements.txt +21 -2
main.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException, Request
2
+ from pydantic import BaseModel
3
+ import firebase_admin
4
+ from firebase_admin import credentials, firestore
5
+ import google.generativeai as genai
6
+ import os
7
+ import json
8
+ import datetime
9
+
10
+ # --- 1. SETUP & AUTHENTIFIZIERUNG ---
11
+ # Wir lesen die Keys aus den Hugging Face Secrets (Environment Variables)
12
+ firebase_key_json = os.getenv("FIREBASE_KEY")
13
+ gemini_key = os.getenv("GEMINI_API_KEY")
14
+
15
+ if not firebase_key_json or not gemini_key:
16
+ # Fallback für lokales Testen (falls du es lokal ausführst)
17
+ # Ersetze das nur lokal, NICHT im Repo hochladen!
18
+ print("⚠️ Warnung: Keine ENV-Variables gefunden. Prüfe lokale Config.")
19
+ else:
20
+ # Firebase initialisieren
21
+ if not firebase_admin._apps:
22
+ cred = credentials.Certificate(json.loads(firebase_key_json))
23
+ firebase_admin.initialize_app(cred)
24
+
25
+ db = firestore.client()
26
+
27
+ # Gemini initialisieren (nutzt dein funktionierendes Modell)
28
+ genai.configure(api_key=gemini_key)
29
+ model = genai.GenerativeModel('gemini-2.0-flash')
30
+
31
+ app = FastAPI()
32
+
33
+ # --- DATENMODELLE ---
34
+ class SearchRequest(BaseModel):
35
+ query: str # Das, was der User gefragt hat
36
+
37
+ # ======================================================
38
+ # ENDPOINT 1: Vapi fragt Wissen ab (LIVE im Anruf)
39
+ # ======================================================
40
+ @app.post("/search")
41
+ async def search_knowledge(request: SearchRequest):
42
+ print(f"🔎 Suche nach: {request.query}")
43
+
44
+ # A. Wissen aus Firebase holen (Nur genehmigtes!)
45
+ docs = db.collection("knowledge_base").where("status", "==", "approved").stream()
46
+
47
+ # Wir bauen einen Text-Kontext für Gemini
48
+ knowledge_list = []
49
+ for doc in docs:
50
+ d = doc.to_dict()
51
+ # Wir geben Gemini Frage + Antwort als Kontext
52
+ knowledge_list.append(f"Frage: {d.get('question')}\nAntwort: {d.get('answer')}")
53
+
54
+ context_text = "\n---\n".join(knowledge_list)
55
+
56
+ if not context_text:
57
+ return {"result": "Leider habe ich dazu keine Informationen in meiner Datenbank."}
58
+
59
+ # B. Gemini entscheidet, welche Antwort passt
60
+ prompt = f"""
61
+ Du bist der intelligente Assistent "Alex".
62
+ Ein User fragt: "{request.query}"
63
+
64
+ Hier ist dein geprüftes Wissen:
65
+ {context_text}
66
+
67
+ Aufgabe:
68
+ 1. Suche die passendste Antwort im Wissen.
69
+ 2. Formuliere sie freundlich und kurz (gesprochene Sprache).
70
+ 3. Wenn die Antwort NICHT im Wissen steht, antworte exakt: "Dazu habe ich leider keine Informationen vorliegen." (Erfinde nichts!)
71
+ """
72
+
73
+ try:
74
+ response = model.generate_content(prompt)
75
+ answer = response.text.strip()
76
+ print(f"🤖 Antwort: {answer}")
77
+ return {"result": answer}
78
+ except Exception as e:
79
+ print(f"❌ Fehler: {e}")
80
+ return {"result": "Es gab einen technischen Fehler bei der Abfrage."}
81
+
82
+ # ======================================================
83
+ # ENDPOINT 2: Lernen (NACH dem Anruf)
84
+ # ======================================================
85
+ @app.post("/learn")
86
+ async def learn_from_call(request: Request):
87
+ print("🎓 Analysiere Call für Lerneffekt...")
88
+
89
+ try:
90
+ payload = await request.json()
91
+
92
+ # Vapi sendet das Transkript oft tief verschachtelt
93
+ # Struktur kann variieren, wir suchen das Feld "transcript" oder "messages"
94
+ # Hier ein generischer Ansatz für Vapi Webhooks:
95
+ transcript = ""
96
+
97
+ if "message" in payload and "transcript" in payload["message"]:
98
+ transcript = payload["message"]["transcript"]
99
+ elif "transcript" in payload:
100
+ transcript = payload["transcript"]
101
+ else:
102
+ # Fallback: Wir nehmen den ganzen Body als Text, falls Vapi anders sendet
103
+ transcript = str(payload)
104
+
105
+ if not transcript or len(transcript) < 50:
106
+ return {"status": "skipped", "reason": "Transkript zu kurz oder leer"}
107
+
108
+ # --- DER GEMINI LERN-CODE ---
109
+ prompt = f"""
110
+ Analysiere dieses Telefonat-Transkript.
111
+ Identifiziere Fragen, die der Bot NICHT oder nur schlecht beantworten konnte.
112
+
113
+ Erstelle für jede Wissenslücke einen neuen Datenbank-Eintrag.
114
+
115
+ Format (JSON Array):
116
+ [
117
+ {{
118
+ "question": "Die Frage des Kunden",
119
+ "answer": "Die ideale Antwort (formuliere sie professionell)",
120
+ "keywords": ["keyword1", "keyword2"],
121
+ "category": "Call-Analyse"
122
+ }}
123
+ ]
124
+
125
+ Wenn alles gut lief und keine Lücken da sind, antworte mit einem leeren Array [].
126
+
127
+ Transkript:
128
+ {transcript[:15000]}
129
+ """
130
+
131
+ response = model.generate_content(prompt)
132
+ clean_json = response.text.replace("```json", "").replace("```", "").strip()
133
+
134
+ if not clean_json:
135
+ return {"status": "no_data_generated"}
136
+
137
+ new_knowledge = json.loads(clean_json)
138
+
139
+ # Speichern in Firebase (als PENDING zur Prüfung)
140
+ batch = db.batch()
141
+ count = 0
142
+ for entry in new_knowledge:
143
+ doc_ref = db.collection("knowledge_base").document()
144
+ doc_data = {
145
+ "question": entry.get("question"),
146
+ "answer": entry.get("answer"),
147
+ "keywords": entry.get("keywords", []),
148
+ "category": "Auto-Learning",
149
+ "status": "pending", # WICHTIG: Muss erst von dir genehmigt werden!
150
+ "source": "Call Analysis",
151
+ "created_at": firestore.SERVER_TIMESTAMP
152
+ }
153
+ batch.set(doc_ref, doc_data)
154
+ count += 1
155
+
156
+ batch.commit()
157
+ print(f"✅ {count} neue Einträge zur Prüfung angelegt.")
158
+ return {"status": "success", "entries_created": count}
159
+
160
+ except Exception as e:
161
+ print(f"❌ Fehler beim Lernen: {e}")
162
+ return {"status": "error", "details": str(e)}
requirements.txt CHANGED
@@ -1,3 +1,22 @@
1
- altair
2
  pandas
3
- streamlit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ streamlit>=1.33
2
  pandas
3
+ openai
4
+ google-generativeai
5
+ firebase-admin
6
+ google-cloud-firestore
7
+ duckduckgo-search
8
+ tiktoken
9
+ requests
10
+ beautifulsoup4
11
+ python-dotenv
12
+ lxml
13
+ apify-client
14
+ google-api-python-client
15
+ google-auth
16
+ google-auth-httplib2
17
+ google-auth-oauthlib
18
+ openpyxl
19
+ xlsxwriter
20
+ fastapi
21
+ uvicorn
22
+ pydantic