test_zammad_ollama.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. #!/usr/bin/env python3
  2. import json
  3. import sqlite3
  4. import time
  5. import urllib.request
  6. DB = "data/zammad_analysis.sqlite3"
  7. URL = "http://127.0.0.1:11434/api/chat"
  8. MODEL = "qwen3:8b"
  9. TAXONOMY = """
  10. VERSANDSTATUS
  11. LIEFERVERZUG
  12. ADRESSÄNDERUNG
  13. REKLAMATION
  14. TRANSPORTSCHADEN
  15. FEHLLIEFERUNG
  16. RECHNUNG
  17. ZAHLUNG
  18. WIDERRUF
  19. RETOURE
  20. PFLANZENBERATUNG
  21. SORTENBERATUNG
  22. PFLEGEFRAGE
  23. BESTANDSANFRAGE
  24. VORBESTELLUNG
  25. B2B
  26. GROSSHANDEL
  27. DÜNGER_FEHLT
  28. SONSTIGES
  29. """
  30. SYSTEM = f"""
  31. Du analysierst Kundenservice-Tickets von Schmid Gartenpflanzen.
  32. Klassifiziere das tatsächliche Kundenanliegen.
  33. Bestehende Primärtaxonomie:
  34. {TAXONOMY}
  35. Wichtige Regeln:
  36. - PRODUKTQUALITÄT unter REKLAMATION oder PFLANZENBERATUNG.
  37. - SCHLECHTVERPACKT unter REKLAMATION bzw. TRANSPORTSCHADEN.
  38. - FALSCHESORTEGELIEFERT unter FEHLLIEFERUNG.
  39. - DÜNGER_FEHLT ist ein eigener Intent.
  40. - Mehrere Anliegen gleichzeitig müssen als Multi-Intent erfasst werden.
  41. - Keine neuen Primärintents erfinden.
  42. Antworte ausschließlich als JSON:
  43. {{
  44. "primary_intent": "...",
  45. "secondary_intents": [],
  46. "complaint_type": null,
  47. "actions": [],
  48. "summary": "...",
  49. "taxonomy_fit": "GOOD|PARTIAL|POOR",
  50. "taxonomy_candidate": null,
  51. "confidence": 0.0
  52. }}
  53. """
  54. def ask(case):
  55. prompt = f"""
  56. Ticket #{case['ticket_number']}
  57. Betreff:
  58. {case['title'] or ''}
  59. KUNDEN:
  60. {case['customer_text'] or ''}
  61. KUNDENSERVICE:
  62. {case['agent_text'] or ''}
  63. SONSTIGE FACHLICHE ANTWORTEN:
  64. {case['unknown_text'] or ''}
  65. """
  66. payload = {
  67. "model": MODEL,
  68. "messages": [
  69. {
  70. "role": "system",
  71. "content": SYSTEM,
  72. },
  73. {
  74. "role": "user",
  75. "content": prompt,
  76. },
  77. ],
  78. "stream": False,
  79. "format": "json",
  80. "options": {
  81. "temperature": 0,
  82. },
  83. }
  84. request = urllib.request.Request(
  85. URL,
  86. data=json.dumps(payload).encode(),
  87. headers={
  88. "Content-Type": "application/json",
  89. },
  90. )
  91. started = time.time()
  92. with urllib.request.urlopen(
  93. request,
  94. timeout=300,
  95. ) as response:
  96. result = json.loads(
  97. response.read()
  98. )
  99. elapsed = time.time() - started
  100. content = result["message"]["content"]
  101. parsed = json.loads(content)
  102. return elapsed, parsed
  103. db = sqlite3.connect(DB)
  104. db.row_factory = sqlite3.Row
  105. rows = db.execute("""
  106. SELECT
  107. ticket_id,
  108. ticket_number,
  109. title,
  110. customer_text,
  111. agent_text,
  112. unknown_text
  113. FROM cases
  114. WHERE classification_status = 'NEW'
  115. ORDER BY ticket_id
  116. LIMIT 10
  117. """).fetchall()
  118. print("=" * 72)
  119. print("OLLAMA API TEST")
  120. print("=" * 72)
  121. print(f"Tickets: {len(rows)}")
  122. print(f"Modell: {MODEL}")
  123. print()
  124. for row in rows:
  125. elapsed, result = ask(row)
  126. print(
  127. f"#{row['ticket_number']} "
  128. f"{elapsed:.1f}s "
  129. f"→ {result.get('primary_intent')} "
  130. f"secondary={result.get('secondary_intents')} "
  131. f"confidence={result.get('confidence')}"
  132. )
  133. print(
  134. f" {result.get('summary', '')[:250]}"
  135. )
  136. db.close()