martinbrahm commited on
Commit
93ddfa4
·
verified ·
1 Parent(s): 3d18df1

Upload main.py

Browse files
Files changed (1) hide show
  1. main.py +102 -75
main.py CHANGED
@@ -43,22 +43,59 @@ async def startup():
43
  reload_knowledge()
44
 
45
  def get_stem(word):
46
- # Einfaches Stemming für bessere Treffer
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
- # TOOL 1: VERFÜGBARKEIT (Bleibt wie es ist)
54
  # ==========================================
55
  @app.post("/check_availability")
56
  async def check_availability(request: Request):
57
- print("🚦 TOOL CALL: checkAvailability")
 
 
 
 
 
58
  today = datetime.now().strftime("%Y-%m-%d")
59
  status = "available"
60
- message = "Normaler Betrieb."
61
 
 
62
  try:
63
  if db:
64
  rules = db.collection(COLLECTION_RULES).where("active", "==", True).stream()
@@ -67,92 +104,82 @@ async def check_availability(request: Request):
67
  if rd.get('start_date') <= today <= rd.get('end_date'):
68
  print(f"🛑 REGEL AKTIV: {rd.get('name')}")
69
  status = "unavailable"
70
- message = rd.get('instruction_text')
71
  break
72
  except Exception as e:
73
  print(f"❌ ERROR CHECK: {e}")
74
 
 
 
 
75
  return {
76
- "result": json.dumps({
77
- "status": status,
78
- "instruction": message
79
- })
 
 
 
 
 
80
  }
81
 
82
  # ==========================================
83
- # TOOL 2: SUCHE (Jetzt mit Udo & Capaneo Priorität)
84
  # ==========================================
85
  @app.post("/search")
86
  async def search(request: Request):
87
- try:
88
- data = await request.json()
89
- query = ""
90
-
91
- # Parsing
92
- if "search_query" in data: query = data["search_query"]
93
- elif "message" in data and "toolCalls" in data["message"]:
94
- args = data["message"]["toolCalls"][0]["function"]["arguments"]
95
- if isinstance(args, str): args = json.loads(args)
96
- query = args.get("search_query") or args.get("query")
97
-
98
- print(f"🔎 FRAGE: '{query}'")
99
- if not query: return {"result": "Akustik-Fehler."}
100
-
101
- # --- UPDATE: STOP WÖRTER BEREINIGT ---
102
- # Wir entfernen "capaneo" und "udo" hier! Sie sind wichtig!
103
- # Nur Füllwörter bleiben gesperrt.
104
- STOP_WORDS = ["hallo", "guten", "tag", "moin", "bitte", "danke", "frage", "kannst", "sagen"]
105
-
106
  q_words = [get_stem(w) for w in query.lower().split() if len(w)>2]
107
  relevant_words = [w for w in q_words if w not in STOP_WORDS]
108
-
109
- if not relevant_words:
110
- return {"result": "Dazu habe ich keine Informationen."}
111
 
112
- best_doc = None
113
- best_score = 0
114
-
115
- for doc in KNOWLEDGE_CACHE:
116
- score = 0
117
- # Wir holen Titel (Frage) und Inhalt (Antwort)
118
- title = doc.get("question", "").lower()
119
- content = doc.get("answer", "").lower()
120
- keywords = [k.lower() for k in doc.get("keywords", [])]
121
-
122
- for word in relevant_words:
123
- word_stem = get_stem(word)
124
-
125
- # 1. TITEL TREFFER (Das Wichtigste!)
126
- # Wenn der Kunde "Capaneo" sagt und im Titel steht "Wer ist Capaneo" -> BINGO!
127
- if word in title:
128
- score += 50
129
-
130
- # 2. KEYWORD TREFFER
131
- # Keywords sind auch stark
132
- for k in keywords:
133
- if get_stem(k) == word_stem:
134
- score += 30
135
 
136
- # 3. TEXT TREFFER (Nur Beiwerk)
137
- # Verhindert, dass jedes Dokument mit "Capaneo" im Footer gewinnt
138
- if word in content:
139
- score += 5
140
-
141
- if score > best_score:
142
- best_score = score
143
- best_doc = doc
144
-
145
- # Score Schwelle auf 20 (damit "Hallo" keine Treffer bringt, aber "Udo" schon)
146
- if best_doc and best_score >= 20:
147
- print(f"🏆 TREFFER ({best_score} Pkt): {best_doc.get('question')}")
148
- return {"result": best_doc.get("answer")}
149
- else:
150
- print(f"⚠️ Zu wenig Relevanz (Max Score: {best_score})")
151
- return {"result": "Dazu habe ich leider keine spezifischen Informationen in meiner Datenbank."}
152
-
153
- except Exception as e:
154
- print(f"❌ SEARCH ERROR: {e}")
155
- return {"result": "Fehler."}
 
 
 
 
 
 
156
 
157
  # ==========================================
158
  # 3. MÜLLSCHLUCKER
 
43
  reload_knowledge()
44
 
45
  def get_stem(word):
 
46
  w = word.lower().strip()
47
  for end in ["ern", "en", "er", "es", "st", "te", "e", "s", "t"]:
48
  if w.endswith(end) and len(w) > len(end)+2: return w[:-len(end)]
49
  return w
50
 
51
+ # --- HELPER: VAPI REQUEST PARSER ---
52
+ # Holt sicher die ID und die Argumente aus dem Vapi-Salat
53
+ def parse_vapi_request(data):
54
+ tool_call_id = "unknown"
55
+ args = {}
56
+
57
+ try:
58
+ msg = data.get("message", {})
59
+
60
+ # Variante A: Dein Log-Format ("toolCallList")
61
+ if "toolCallList" in msg:
62
+ call = msg["toolCallList"][0]
63
+ tool_call_id = call["id"]
64
+ if "function" in call and "arguments" in call["function"]:
65
+ args = call["function"]["arguments"]
66
+
67
+ # Variante B: Standard Vapi ("toolCalls")
68
+ elif "toolCalls" in msg:
69
+ call = msg["toolCalls"][0]
70
+ tool_call_id = call["id"]
71
+ if "function" in call and "arguments" in call["function"]:
72
+ args = call["function"]["arguments"]
73
+
74
+ # Argumente sind oft ein String, müssen zu JSON werden
75
+ if isinstance(args, str):
76
+ args = json.loads(args)
77
+
78
+ except Exception as e:
79
+ print(f"⚠️ Parsing Info: {e}")
80
+
81
+ return tool_call_id, args
82
+
83
  # ==========================================
84
+ # TOOL 1: VERFÜGBARKEIT (Strict Vapi Format)
85
  # ==========================================
86
  @app.post("/check_availability")
87
  async def check_availability(request: Request):
88
+ data = await request.json()
89
+ print(f"🚦 TOOL REQUEST: {json.dumps(data)[:100]}...") # Debug Log
90
+
91
+ # 1. ID extrahieren (WICHTIG!)
92
+ tool_call_id, _ = parse_vapi_request(data)
93
+
94
  today = datetime.now().strftime("%Y-%m-%d")
95
  status = "available"
96
+ instruction = "Normal arbeiten"
97
 
98
+ # 2. Datenbank Check
99
  try:
100
  if db:
101
  rules = db.collection(COLLECTION_RULES).where("active", "==", True).stream()
 
104
  if rd.get('start_date') <= today <= rd.get('end_date'):
105
  print(f"🛑 REGEL AKTIV: {rd.get('name')}")
106
  status = "unavailable"
107
+ instruction = rd.get('instruction_text')
108
  break
109
  except Exception as e:
110
  print(f"❌ ERROR CHECK: {e}")
111
 
112
+ print(f"👉 RESULTAT: {status} | {instruction}")
113
+
114
+ # 3. Antwort im strikten Vapi-Format
115
  return {
116
+ "results": [
117
+ {
118
+ "toolCallId": tool_call_id,
119
+ "result": {
120
+ "status": status,
121
+ "instruction": instruction
122
+ }
123
+ }
124
+ ]
125
  }
126
 
127
  # ==========================================
128
+ # TOOL 2: SUCHE (Jetzt auch mit ID Rückgabe!)
129
  # ==========================================
130
  @app.post("/search")
131
  async def search(request: Request):
132
+ data = await request.json()
133
+
134
+ # 1. ID und Query extrahieren
135
+ tool_call_id, args = parse_vapi_request(data)
136
+ query = args.get("search_query") or args.get("query") or data.get("search_query")
137
+
138
+ print(f"🔎 FRAGE (ID: {tool_call_id}): '{query}'")
139
+
140
+ answer_text = "Dazu habe ich keine Informationen."
141
+
142
+ if query:
143
+ # --- SUCHE LOGIK (Mit Udo/Capaneo Boost) ---
144
+ STOP_WORDS = ["hallo", "guten", "tag", "moin", "bitte", "danke", "frage"]
 
 
 
 
 
 
145
  q_words = [get_stem(w) for w in query.lower().split() if len(w)>2]
146
  relevant_words = [w for w in q_words if w not in STOP_WORDS]
 
 
 
147
 
148
+ if relevant_words:
149
+ best_doc = None
150
+ best_score = 0
151
+ for doc in KNOWLEDGE_CACHE:
152
+ score = 0
153
+ title = doc.get("question", "").lower()
154
+ content = doc.get("answer", "").lower()
155
+ keywords = [k.lower() for k in doc.get("keywords", [])]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
+ for word in relevant_words:
158
+ if word in title: score += 50
159
+ for k in keywords:
160
+ if get_stem(k) == get_stem(word): score += 30
161
+ if word in content: score += 5
162
+
163
+ if score > best_score:
164
+ best_score = score
165
+ best_doc = doc
166
+
167
+ if best_doc and best_score >= 20:
168
+ print(f"🏆 TREFFER ({best_score}): {best_doc.get('question')}")
169
+ answer_text = best_doc.get("answer")
170
+ else:
171
+ print(f"⚠️ Zu wenig Relevanz (Max: {best_score})")
172
+
173
+ # 3. Antwort im strikten Vapi-Format
174
+ # Bei 'search' erwartet das LLM meist einfach den Text im result
175
+ return {
176
+ "results": [
177
+ {
178
+ "toolCallId": tool_call_id,
179
+ "result": answer_text
180
+ }
181
+ ]
182
+ }
183
 
184
  # ==========================================
185
  # 3. MÜLLSCHLUCKER