Call_Agent_API / main.py
martinbrahm's picture
Upload main.py
93ddfa4 verified
raw
history blame
6.44 kB
from fastapi import FastAPI, Request
import json
import os
import firebase_admin
from firebase_admin import credentials, firestore
from datetime import datetime
app = FastAPI()
# --- SETUP ---
COLLECTION_KNOWLEDGE = "knowledge_base"
COLLECTION_RULES = "availability_rules"
KNOWLEDGE_CACHE = []
# --- FIREBASE VERBINDUNG ---
db = None
try:
key = os.environ.get("FIREBASE_KEY")
if key:
cred = credentials.Certificate(json.loads(key))
if not firebase_admin._apps:
firebase_admin.initialize_app(cred)
db = firestore.client()
print("✅ DB VERBUNDEN")
else:
print("❌ FEHLER: FIREBASE_KEY fehlt!")
except Exception as e:
print(f"❌ DB CRASH: {e}")
# --- CACHE LADEN ---
def reload_knowledge():
global KNOWLEDGE_CACHE
if not db: return
try:
docs = db.collection(COLLECTION_KNOWLEDGE).stream()
KNOWLEDGE_CACHE = [d.to_dict() for d in docs]
print(f"📚 {len(KNOWLEDGE_CACHE)} Einträge geladen.")
except Exception as e:
print(f"❌ Cache Fehler: {e}")
@app.on_event("startup")
async def startup():
reload_knowledge()
def get_stem(word):
w = word.lower().strip()
for end in ["ern", "en", "er", "es", "st", "te", "e", "s", "t"]:
if w.endswith(end) and len(w) > len(end)+2: return w[:-len(end)]
return w
# --- HELPER: VAPI REQUEST PARSER ---
# Holt sicher die ID und die Argumente aus dem Vapi-Salat
def parse_vapi_request(data):
tool_call_id = "unknown"
args = {}
try:
msg = data.get("message", {})
# Variante A: Dein Log-Format ("toolCallList")
if "toolCallList" in msg:
call = msg["toolCallList"][0]
tool_call_id = call["id"]
if "function" in call and "arguments" in call["function"]:
args = call["function"]["arguments"]
# Variante B: Standard Vapi ("toolCalls")
elif "toolCalls" in msg:
call = msg["toolCalls"][0]
tool_call_id = call["id"]
if "function" in call and "arguments" in call["function"]:
args = call["function"]["arguments"]
# Argumente sind oft ein String, müssen zu JSON werden
if isinstance(args, str):
args = json.loads(args)
except Exception as e:
print(f"⚠️ Parsing Info: {e}")
return tool_call_id, args
# ==========================================
# TOOL 1: VERFÜGBARKEIT (Strict Vapi Format)
# ==========================================
@app.post("/check_availability")
async def check_availability(request: Request):
data = await request.json()
print(f"🚦 TOOL REQUEST: {json.dumps(data)[:100]}...") # Debug Log
# 1. ID extrahieren (WICHTIG!)
tool_call_id, _ = parse_vapi_request(data)
today = datetime.now().strftime("%Y-%m-%d")
status = "available"
instruction = "Normal arbeiten"
# 2. Datenbank Check
try:
if db:
rules = db.collection(COLLECTION_RULES).where("active", "==", True).stream()
for r in rules:
rd = r.to_dict()
if rd.get('start_date') <= today <= rd.get('end_date'):
print(f"🛑 REGEL AKTIV: {rd.get('name')}")
status = "unavailable"
instruction = rd.get('instruction_text')
break
except Exception as e:
print(f"❌ ERROR CHECK: {e}")
print(f"👉 RESULTAT: {status} | {instruction}")
# 3. Antwort im strikten Vapi-Format
return {
"results": [
{
"toolCallId": tool_call_id,
"result": {
"status": status,
"instruction": instruction
}
}
]
}
# ==========================================
# TOOL 2: SUCHE (Jetzt auch mit ID Rückgabe!)
# ==========================================
@app.post("/search")
async def search(request: Request):
data = await request.json()
# 1. ID und Query extrahieren
tool_call_id, args = parse_vapi_request(data)
query = args.get("search_query") or args.get("query") or data.get("search_query")
print(f"🔎 FRAGE (ID: {tool_call_id}): '{query}'")
answer_text = "Dazu habe ich keine Informationen."
if query:
# --- SUCHE LOGIK (Mit Udo/Capaneo Boost) ---
STOP_WORDS = ["hallo", "guten", "tag", "moin", "bitte", "danke", "frage"]
q_words = [get_stem(w) for w in query.lower().split() if len(w)>2]
relevant_words = [w for w in q_words if w not in STOP_WORDS]
if relevant_words:
best_doc = None
best_score = 0
for doc in KNOWLEDGE_CACHE:
score = 0
title = doc.get("question", "").lower()
content = doc.get("answer", "").lower()
keywords = [k.lower() for k in doc.get("keywords", [])]
for word in relevant_words:
if word in title: score += 50
for k in keywords:
if get_stem(k) == get_stem(word): score += 30
if word in content: score += 5
if score > best_score:
best_score = score
best_doc = doc
if best_doc and best_score >= 20:
print(f"🏆 TREFFER ({best_score}): {best_doc.get('question')}")
answer_text = best_doc.get("answer")
else:
print(f"⚠️ Zu wenig Relevanz (Max: {best_score})")
# 3. Antwort im strikten Vapi-Format
# Bei 'search' erwartet das LLM meist einfach den Text im result
return {
"results": [
{
"toolCallId": tool_call_id,
"result": answer_text
}
]
}
# ==========================================
# 3. MÜLLSCHLUCKER
# ==========================================
@app.post("/vapi-incoming")
async def dummy_incoming(request: Request):
return {"status": "ok"}
@app.get("/")
def home():
return {"status": "Online", "docs_loaded": len(KNOWLEDGE_CACHE)}