martinbrahm commited on
Commit
bb1daa0
·
verified ·
1 Parent(s): a3b124a

Upload main.py

Browse files
Files changed (1) hide show
  1. main.py +68 -108
main.py CHANGED
@@ -7,26 +7,25 @@ from datetime import datetime
7
 
8
  app = FastAPI()
9
 
10
- # --- KONFIGURATION ---
11
  COLLECTION_KNOWLEDGE = "knowledge_base"
12
  COLLECTION_RULES = "availability_rules"
13
- COLLECTION_INBOX = "inbox"
14
  KNOWLEDGE_CACHE = []
15
 
16
- # --- FIREBASE VERBINDUNG ---
17
  db = None
18
  try:
19
- firebase_key_json = os.environ.get("FIREBASE_KEY")
20
- if firebase_key_json:
21
- cred = credentials.Certificate(json.loads(firebase_key_json))
22
  if not firebase_admin._apps:
23
  firebase_admin.initialize_app(cred)
24
  db = firestore.client()
25
- print(f"✅ DB VERBUNDEN: {db.project}")
26
  else:
27
- print("⚠️ KEIN FIREBASE KEY GEFUNDEN!")
28
  except Exception as e:
29
- print(f"❌ DB FEHLER: {str(e)}")
30
 
31
  # --- CACHE LADEN ---
32
  def reload_knowledge():
@@ -35,7 +34,7 @@ def reload_knowledge():
35
  try:
36
  docs = db.collection(COLLECTION_KNOWLEDGE).stream()
37
  KNOWLEDGE_CACHE = [d.to_dict() for d in docs]
38
- print(f"📚 {len(KNOWLEDGE_CACHE)} Wissens-Einträge geladen.")
39
  except Exception as e:
40
  print(f"❌ Cache Fehler: {e}")
41
 
@@ -43,45 +42,43 @@ def reload_knowledge():
43
  async def startup():
44
  reload_knowledge()
45
 
46
- # --- HELPER: TEXT NORMALISIEREN (Stemmer) ---
47
  def get_stem(word):
48
  w = word.lower().strip()
49
- # Schneidet einfache deutsche Endungen ab für bessere Treffer
50
  for end in ["ern", "en", "er", "es", "st", "te", "e", "s", "t"]:
51
  if w.endswith(end) and len(w) > len(end)+2: return w[:-len(end)]
52
  return w
53
 
54
  # ==========================================
55
- # ENDPUNKT 1: VERFÜGBARKEIT (Server URL)
56
  # ==========================================
57
- # Vapi ruft das VOR dem Anruf auf, um Kontext zu laden.
58
  @app.post("/vapi-incoming")
59
- async def handle_incoming(request: Request):
60
  try:
61
  data = await request.json()
62
- msg_type = data.get("message", {}).get("type")
 
63
 
64
- print(f"📞 INCOMING REQUEST: {msg_type}")
 
65
 
 
66
  if msg_type == "assistant-request":
 
67
  today = datetime.now().strftime("%Y-%m-%d")
68
- print(f"🗓️ Prüfe Regeln für heute: {today}")
69
-
70
  context = ""
 
71
  if db:
72
  rules = db.collection(COLLECTION_RULES).where("active", "==", True).stream()
73
  for r in rules:
74
  rd = r.to_dict()
75
- start = rd.get('start_date', '0000-00-00')
76
- end = rd.get('end_date', '9999-99-99')
77
-
78
- if start <= today <= end:
79
- print(f"✅ REGEL AKTIV: {rd.get('name')}")
80
- context = f"WICHTIGE VERFÜGBARKEITS-INFO: {rd.get('instruction_text')}"
81
- break # Erste Regel gewinnt
82
 
83
- if not context:
84
- print("ℹ️ Keine Sonderregeln für heute.")
85
 
86
  return {
87
  "assistant": {
@@ -90,110 +87,73 @@ async def handle_incoming(request: Request):
90
  }
91
  }
92
  }
93
-
94
- # Für alle anderen Events (status-update etc.) einfach OK senden
95
- return {"status": "ok"}
96
-
97
  except Exception as e:
98
- print(f"❌ ERROR INCOMING: {str(e)}")
99
- return {"status": "error"}
 
100
 
101
  # ==========================================
102
- # ENDPUNKT 2: SUCHE / LOOKUP (Tool URL)
103
  # ==========================================
104
  @app.post("/search")
105
  async def search(request: Request):
106
  try:
107
  data = await request.json()
108
- # Debugging: Wir drucken das Paket, falls was schief geht
109
- # print(f"📦 RAW TOOL DATA: {json.dumps(data)[:200]}...")
110
-
111
  query = ""
112
 
113
- # --- ROBUSTE EXTRAKTION ---
114
- # 1. Fall: Vapi schickt Argumente direkt (selten)
115
- if "search_query" in data:
116
- query = data["search_query"]
117
-
118
- # 2. Fall: Vapi schickt es im "message" -> "toolCalls" Block (Standard)
119
  elif "message" in data and "toolCalls" in data["message"]:
120
- try:
121
- args = data["message"]["toolCalls"][0]["function"]["arguments"]
122
- # Vapi sendet Arguments oft als JSON-String, nicht als Objekt!
123
- if isinstance(args, str):
124
- args = json.loads(args)
125
-
126
- query = args.get("search_query") or args.get("query") or args.get("q")
127
- except Exception as parsing_err:
128
- print(f"⚠️ Argument Parsing Fehler: {parsing_err}")
129
-
130
- print(f"🔎 GEPARSTE FRAGE: '{query}'")
131
-
132
- if not query:
133
- return {"result": "Fehler: Ich habe die Frage technisch nicht verstanden (leeres Argument)."}
134
-
135
- # --- SUCHE STARTEN ---
136
- best_doc = None
137
- best_score = 0
138
 
139
- # Suchbegriffe vorbereiten
140
- q_clean = query.lower().replace("?", "").replace(".", "")
141
- q_stems = [get_stem(w) for w in q_clean.split() if len(w)>2]
142
 
143
- print(f" ⚙️ Such-Stämme: {q_stems}")
 
144
 
145
  for doc in KNOWLEDGE_CACHE:
146
  score = 0
147
- # Wir bauen einen großen Text-Blob aus Frage, Antwort und Keywords
148
- txt_content = (doc.get("question", "") + " " + doc.get("answer", "")).lower()
149
- keywords = [k.lower() for k in doc.get("keywords", [])]
150
 
151
- # A) Keywords prüfen (Hohe Punkte: 20)
152
- for k in keywords:
153
- if any(get_stem(k) in s for s in q_stems):
154
- score += 20
155
 
156
- # B) Textinhalt prüfen (Mittlere Punkte: 5 pro Treffer)
157
- for stem in q_stems:
158
- if stem in txt_content:
159
- score += 5
160
-
161
- # C) Exakte Phrase im Titel (Bonus: 10)
162
- if query.lower() in doc.get("question", "").lower():
163
- score += 10
164
 
165
  if score > best_score:
166
  best_score = score
167
  best_doc = doc
168
 
169
- # --- ERGEBNIS ---
170
- if best_doc and best_score >= 10:
171
- print(f"🏆 TREFFER ({best_score} Pkt): {best_doc.get('question')}")
172
  return {"result": best_doc.get("answer")}
173
  else:
174
- print(f"⚠️ KEIN TREFFER (Max Score: {best_score})")
175
- # In Inbox speichern (optional) zur Verbesserung
176
- if db and best_score > 0:
177
- try:
178
- db.collection(COLLECTION_INBOX).add({
179
- "question": query,
180
- "status": "review_needed",
181
- "score": best_score,
182
- "timestamp": firestore.SERVER_TIMESTAMP
183
- })
184
- except: pass
185
-
186
- return {"result": "Dazu habe ich leider keine Informationen in meiner Datenbank."}
187
 
188
  except Exception as e:
189
- print(f"❌ SEARCH ERROR: {str(e)}")
190
- return {"result": "Systemfehler bei der Suche."}
191
-
192
- @app.get("/")
193
- def home():
194
- return {"status": "Udo API Online", "docs": len(KNOWLEDGE_CACHE)}
195
-
196
- @app.get("/refresh_knowledge")
197
- def refresh_endpoint():
198
- count = reload_knowledge()
199
- return {"status": "Cache aktualisiert", "docs_loaded": count}
 
7
 
8
  app = FastAPI()
9
 
10
+ # --- SETUP ---
11
  COLLECTION_KNOWLEDGE = "knowledge_base"
12
  COLLECTION_RULES = "availability_rules"
 
13
  KNOWLEDGE_CACHE = []
14
 
15
+ # --- DB VERBINDUNG ---
16
  db = None
17
  try:
18
+ key = os.environ.get("FIREBASE_KEY")
19
+ if key:
20
+ cred = credentials.Certificate(json.loads(key))
21
  if not firebase_admin._apps:
22
  firebase_admin.initialize_app(cred)
23
  db = firestore.client()
24
+ print("✅ DB VERBUNDEN")
25
  else:
26
+ print(" FEHLER: FIREBASE_KEY fehlt!")
27
  except Exception as e:
28
+ print(f"❌ DB CRASH: {e}")
29
 
30
  # --- CACHE LADEN ---
31
  def reload_knowledge():
 
34
  try:
35
  docs = db.collection(COLLECTION_KNOWLEDGE).stream()
36
  KNOWLEDGE_CACHE = [d.to_dict() for d in docs]
37
+ print(f"📚 {len(KNOWLEDGE_CACHE)} Einträge geladen.")
38
  except Exception as e:
39
  print(f"❌ Cache Fehler: {e}")
40
 
 
42
  async def startup():
43
  reload_knowledge()
44
 
45
+ # --- HELPER ---
46
  def get_stem(word):
47
  w = word.lower().strip()
 
48
  for end in ["ern", "en", "er", "es", "st", "te", "e", "s", "t"]:
49
  if w.endswith(end) and len(w) > len(end)+2: return w[:-len(end)]
50
  return w
51
 
52
  # ==========================================
53
+ # 1. START DES ANRUFS (Verfügbarkeit)
54
  # ==========================================
 
55
  @app.post("/vapi-incoming")
56
+ async def vapi_incoming(request: Request):
57
  try:
58
  data = await request.json()
59
+ msg = data.get("message", {})
60
+ msg_type = msg.get("type")
61
 
62
+ # LOGGEN WAS PASSIERT
63
+ print(f"⚡ INCOMING EVENT: {msg_type}")
64
 
65
+ # Nur hier reagieren wir mit Regeln!
66
  if msg_type == "assistant-request":
67
+ print("📞 ANRUF STARTET - Prüfe Regeln...")
68
  today = datetime.now().strftime("%Y-%m-%d")
 
 
69
  context = ""
70
+
71
  if db:
72
  rules = db.collection(COLLECTION_RULES).where("active", "==", True).stream()
73
  for r in rules:
74
  rd = r.to_dict()
75
+ # Prüfen ob Datum passt
76
+ if rd.get('start_date') <= today <= rd.get('end_date'):
77
+ print(f"🛑 REGEL AKTIV: {rd.get('name')}")
78
+ context = f"ACHTUNG - BESONDERE SITUATION: {rd.get('instruction_text')}"
79
+ break
 
 
80
 
81
+ if not context: print("✅ Keine Regeln für heute.")
 
82
 
83
  return {
84
  "assistant": {
 
87
  }
88
  }
89
  }
90
+
 
 
 
91
  except Exception as e:
92
+ print(f"❌ ERROR START: {e}")
93
+
94
+ return {"status": "ok"}
95
 
96
  # ==========================================
97
+ # 2. WÄHREND DES ANRUFS (Suche)
98
  # ==========================================
99
  @app.post("/search")
100
  async def search(request: Request):
101
  try:
102
  data = await request.json()
 
 
 
103
  query = ""
104
 
105
+ # Parsing
106
+ if "search_query" in data: query = data["search_query"]
 
 
 
 
107
  elif "message" in data and "toolCalls" in data["message"]:
108
+ args = data["message"]["toolCalls"][0]["function"]["arguments"]
109
+ if isinstance(args, str): args = json.loads(args)
110
+ query = args.get("search_query") or args.get("query")
111
+
112
+ print(f"🔎 FRAGE: '{query}'")
113
+ if not query: return {"result": "Akustik-Fehler."}
114
+
115
+ # --- INTELLIGENTE FILTERUNG ---
116
+ # Wörter, die wir IGNORIEREN, damit er nicht bei "Capaneo" jedes Dokument findet
117
+ STOP_WORDS = ["capaneo", "udo", "schober", "firma", "gmbh", "hallo", "demo"]
118
+
119
+ q_words = [get_stem(w) for w in query.lower().split() if len(w)>2]
120
+ relevant_words = [w for w in q_words if w not in STOP_WORDS]
121
+
122
+ print(f" ⚙️ Relevante Suchwörter: {relevant_words}")
 
 
 
123
 
124
+ if not relevant_words:
125
+ print("⚠️ Keine relevanten Keywords gefunden (nur Stopwörter).")
126
+ return {"result": "Dazu habe ich keine spezifischen Informationen."}
127
 
128
+ best_doc = None
129
+ best_score = 0
130
 
131
  for doc in KNOWLEDGE_CACHE:
132
  score = 0
133
+ txt = (doc.get("question", "") + " " + doc.get("answer", "")).lower()
134
+ kws = [k.lower() for k in doc.get("keywords", [])]
 
135
 
136
+ # A) Keywords (Stark)
137
+ for k in kws:
138
+ k_stem = get_stem(k)
139
+ if k_stem in relevant_words: score += 30
140
 
141
+ # B) Text (Mittel)
142
+ for w in relevant_words:
143
+ if w in txt: score += 5
 
 
 
 
 
144
 
145
  if score > best_score:
146
  best_score = score
147
  best_doc = doc
148
 
149
+ # Schwelle auf 15 setzen
150
+ if best_doc and best_score >= 15:
151
+ print(f"🏆 TREFFER ({best_score}): {best_doc.get('question')}")
152
  return {"result": best_doc.get("answer")}
153
  else:
154
+ print(f"⚠️ KEIN GUTER TREFFER (Max: {best_score})")
155
+ return {"result": "Dazu habe ich leider keine Informationen."}
 
 
 
 
 
 
 
 
 
 
 
156
 
157
  except Exception as e:
158
+ print(f"❌ SEARCH ERROR: {e}")
159
+ return {"result": "Fehler."}