filter_transcripts.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import json, sqlite3, time
  2. from pathlib import Path
  3. from concurrent.futures import ThreadPoolExecutor, as_completed
  4. import requests
  5. BASE = Path("/opt/3cx-middleware/3cx-telefonie-middleware")
  6. TRANSCRIPTS = BASE / "data/transcripts"
  7. OUT = BASE / "data/transcript_relevance.jsonl"
  8. OLLAMA = "http://192.168.1.101:11434/api/chat"
  9. MODEL = "qwen3:4b"
  10. WORKERS = 2
  11. SYSTEM = """Du entscheidest ausschließlich, ob ein Telefontranskript für
  12. einen Gartenpflanzen-/Onlineshop-Kundenservice relevant ist.
  13. RELEVANT:
  14. Ein echtes Kunden-, Lieferanten- oder Geschäftsgespräch mit verwertbarem
  15. Inhalt.
  16. IRRELEVANT:
  17. Mailbox, reine Ansage, verpasster Anruf ohne Gespräch, Werbung, technische
  18. Ansage oder praktisch kein Gespräch.
  19. UNCERTAIN:
  20. Nicht eindeutig entscheidbar.
  21. Antworte ausschließlich als JSON:
  22. {"relevance":"RELEVANT|IRRELEVANT|UNCERTAIN","confidence":0.0,"reason":"kurze Begründung"}
  23. Keine Intent-Klassifizierung durchführen.
  24. """
  25. def classify(item):
  26. path = item
  27. text = path.read_text(encoding="utf-8", errors="replace").strip()
  28. payload = {
  29. "model": MODEL,
  30. "messages": [
  31. {"role": "system", "content": SYSTEM},
  32. {"role": "user", "content": text[:12000]}
  33. ],
  34. "stream": False,
  35. "think": False,
  36. "format": "json",
  37. "options": {"temperature": 0}
  38. }
  39. start = time.time()
  40. r = requests.post(OLLAMA, json=payload, timeout=180)
  41. r.raise_for_status()
  42. result = json.loads(r.json()["message"]["content"])
  43. return {
  44. "file": path.name,
  45. "relevance": result.get("relevance", "UNCERTAIN"),
  46. "confidence": result.get("confidence", 0),
  47. "reason": result.get("reason", ""),
  48. "seconds": round(time.time() - start, 1)
  49. }
  50. files = sorted(TRANSCRIPTS.glob("*.txt"))
  51. # offensichtliche Kurz-/Leer-/reine Ansagetexte nicht erneut an Qwen schicken
  52. candidates = []
  53. for p in files:
  54. text = p.read_text(encoding="utf-8", errors="replace").strip()
  55. words = len(text.split())
  56. if not text or words < 12:
  57. continue
  58. candidates.append(p)
  59. print(f"Transkripte gesamt: {len(files)}")
  60. print(f"Qwen-Kandidaten: {len(candidates)}")
  61. OUT.parent.mkdir(parents=True, exist_ok=True)
  62. with OUT.open("w", encoding="utf-8") as f:
  63. with ThreadPoolExecutor(max_workers=WORKERS) as pool:
  64. futures = {pool.submit(classify, p): p for p in candidates}
  65. done = 0
  66. for future in as_completed(futures):
  67. p = futures[future]
  68. done += 1
  69. try:
  70. result = future.result()
  71. f.write(json.dumps(result, ensure_ascii=False) + "\n")
  72. f.flush()
  73. print(
  74. f"[{done}/{len(candidates)}] "
  75. f"{p.name} {result['seconds']}s → "
  76. f"{result['relevance']} "
  77. f"{result['confidence']}",
  78. flush=True
  79. )
  80. except Exception as e:
  81. print(f"ERROR {p.name}: {e}", flush=True)
  82. print(f"\nErgebnis: {OUT}")