homelab-brain/homelab-ai-bot/tools/deep_research.py
Homelab Cursor 7a3327eb90 fix: deep_research Tool — Logging + allow_clarification=False
- Ausfuehrliches Logging fuer Debugging
- allow_clarification deaktiviert (verhindert Rueckfrage-Deadlock)
- Ergebnis-Schwelle auf 100 Zeichen gesenkt
2026-03-20 21:47:33 +01:00

153 lines
5.1 KiB
Python

"""Deep Research Tool — Open Deep Research (CT 121) via LangGraph API."""
import requests
import time
import logging
log = logging.getLogger("deep_research")
DEEP_RESEARCH_URL = "http://10.10.10.121:2024"
ASSISTANT_ID = "e9a5370f-7a53-55a8-ada8-6ab9ef15bb5b"
RESEARCH_MODEL = "openai/gpt-4o-mini"
POLL_INTERVAL = 10
MAX_WAIT = 600
SYSTEM_PROMPT_EXTRA = """DEEP RESEARCH:
Du hast Zugriff auf deep_research — eine KI-gestuetzte Tiefenrecherche die 20-30 Quellen durchsucht.
Nutze es wenn der User explizit "recherchiere", "finde heraus", "vergleiche" sagt oder eine komplexe Frage hat.
NICHT fuer einfache Fakten oder Homelab-Fragen.
WICHTIG: deep_research dauert 2-5 Minuten. Das ist normal. Warte auf das Ergebnis.
Das Ergebnis ist ein ausfuehrlicher Report. Fasse ihn fuer Telegram zusammen (max ~3000 Zeichen)."""
TOOLS = [
{
"type": "function",
"function": {
"name": "deep_research",
"description": "Startet eine tiefe Web-Recherche zu einem Thema. Durchsucht 20-30 Quellen und erstellt einen ausfuehrlichen Report. Dauert 2-5 Minuten — das ist normal, warte auf das Ergebnis.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Die Recherche-Frage, moeglichst spezifisch formuliert."
}
},
"required": ["query"]
},
},
},
]
def _create_thread():
r = requests.post(f"{DEEP_RESEARCH_URL}/threads",
json={}, timeout=10)
r.raise_for_status()
return r.json()["thread_id"]
def _start_run(thread_id, query):
payload = {
"assistant_id": ASSISTANT_ID,
"input": {
"messages": [{"role": "user", "content": query}]
},
"config": {
"configurable": {
"summarization_model": f"openai:{RESEARCH_MODEL}",
"research_model": f"openai:{RESEARCH_MODEL}",
"compression_model": f"openai:{RESEARCH_MODEL}",
"final_report_model": f"openai:{RESEARCH_MODEL}",
"allow_clarification": False,
}
}
}
r = requests.post(f"{DEEP_RESEARCH_URL}/threads/{thread_id}/runs",
json=payload, timeout=30)
r.raise_for_status()
return r.json()["run_id"]
def _poll_run(thread_id, run_id):
elapsed = 0
while elapsed < MAX_WAIT:
time.sleep(POLL_INTERVAL)
elapsed += POLL_INTERVAL
try:
r = requests.get(
f"{DEEP_RESEARCH_URL}/threads/{thread_id}/runs/{run_id}",
timeout=10)
r.raise_for_status()
data = r.json()
status = data.get("status", "unknown")
log.info("Poll %ds: status=%s", elapsed, status)
if status == "success":
return True, None
if status in ("error", "failed"):
err = data.get("error", "Unbekannter Fehler")
log.error("Run failed: %s", err)
return False, err
if status == "interrupted":
return False, "Research wurde unterbrochen"
except Exception as e:
log.warning("Poll error at %ds: %s", elapsed, e)
return False, f"Timeout nach {MAX_WAIT}s"
def _get_result(thread_id):
r = requests.get(f"{DEEP_RESEARCH_URL}/threads/{thread_id}/state",
timeout=30)
r.raise_for_status()
state = r.json()
values = state.get("values", {})
messages = values.get("messages", [])
log.info("Messages in result: %d", len(messages))
for i, msg in enumerate(messages):
content = msg.get("content", "")
clen = len(content) if isinstance(content, str) else 0
log.info(" msg[%d] type=%s len=%d", i, msg.get("type", "?"), clen)
for msg in reversed(messages):
content = msg.get("content", "")
if isinstance(content, str) and len(content) > 100:
return content
return "Kein Report generiert."
def handle_deep_research(query: str, **kw):
log.info("deep_research gestartet: %s", query[:100])
try:
thread_id = _create_thread()
log.info("Thread erstellt: %s", thread_id)
run_id = _start_run(thread_id, query)
log.info("Run gestartet: %s", run_id)
ok, error = _poll_run(thread_id, run_id)
if not ok:
log.error("Research fehlgeschlagen: %s", error)
return f"Deep Research fehlgeschlagen: {error}"
report = _get_result(thread_id)
log.info("Report erhalten: %d Zeichen", len(report))
if len(report) > 6000:
report = report[:6000] + "\n\n[... Report gekuerzt]"
return report
except requests.ConnectionError:
log.error("CT 121 nicht erreichbar")
return "Deep Research (CT 121) nicht erreichbar. Service laeuft moeglicherweise nicht."
except Exception as e:
log.exception("Deep Research Fehler")
return f"Deep Research Fehler: {e}"
HANDLERS = {
"deep_research": handle_deep_research,
}