RAG: Vektor-Suche statt All-Items-Prompt, Kandidaten-System entfernt, Bot speichert direkt
This commit is contained in:
parent
659685228b
commit
a2c0279f99
2 changed files with 29 additions and 136 deletions
|
|
@ -48,7 +48,7 @@ PERMANENT (memory_type="permanent") — bleibt dauerhaft:
|
||||||
- Wiederkehrende Regeln ("API-Kosten monatlich beobachten")
|
- Wiederkehrende Regeln ("API-Kosten monatlich beobachten")
|
||||||
|
|
||||||
Im Zweifel: lieber temporary als permanent.
|
Im Zweifel: lieber temporary als permanent.
|
||||||
IMMER memory_suggest aufrufen, auch wenn der User denselben Fakt wiederholt. Das System erkennt Duplikate automatisch und gibt den korrekten Status zurueck. Du sagst dann genau das was das Tool zurueckgibt.
|
memory_suggest speichert DIREKT — kein Bestaetigen noetig. Das System erkennt Duplikate automatisch. Bei Duplikaten sag kurz "Weiss ich schon." und beantworte die Frage.
|
||||||
NICHT speichern: Passwoerter, Tokens, Smalltalk, Hoeflichkeiten, reine Fragen.
|
NICHT speichern: Passwoerter, Tokens, Smalltalk, Hoeflichkeiten, reine Fragen.
|
||||||
|
|
||||||
SESSION-RUECKBLICK:
|
SESSION-RUECKBLICK:
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,9 @@ def _release_lock():
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
from telegram import BotCommand, Update, ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton
|
from telegram import BotCommand, Update, ReplyKeyboardMarkup, KeyboardButton
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
Application, CommandHandler, MessageHandler, CallbackQueryHandler, filters, ContextTypes,
|
Application, CommandHandler, MessageHandler, filters, ContextTypes,
|
||||||
)
|
)
|
||||||
|
|
||||||
BOT_COMMANDS = [
|
BOT_COMMANDS = [
|
||||||
|
|
@ -52,7 +52,7 @@ BOT_COMMANDS = [
|
||||||
BotCommand("report", "Tagesbericht"),
|
BotCommand("report", "Tagesbericht"),
|
||||||
BotCommand("check", "Monitoring-Check"),
|
BotCommand("check", "Monitoring-Check"),
|
||||||
BotCommand("feeds", "Feed-Status & Artikel heute"),
|
BotCommand("feeds", "Feed-Status & Artikel heute"),
|
||||||
BotCommand("memory", "Offene Memory-Kandidaten"),
|
BotCommand("memory", "Gedaechtnis anzeigen"),
|
||||||
BotCommand("start", "Hilfe anzeigen"),
|
BotCommand("start", "Hilfe anzeigen"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -122,7 +122,7 @@ async def cmd_start(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
||||||
"/report — Tagesbericht\n"
|
"/report — Tagesbericht\n"
|
||||||
"/check — Monitoring-Check\n"
|
"/check — Monitoring-Check\n"
|
||||||
"/feeds — Feed-Status & Artikel\n"
|
"/feeds — Feed-Status & Artikel\n"
|
||||||
"/memory — Offene Memory-Kandidaten\n\n"
|
"/memory — Gedaechtnis anzeigen\n\n"
|
||||||
"Oder einfach eine Frage stellen!",
|
"Oder einfach eine Frage stellen!",
|
||||||
reply_markup=KEYBOARD,
|
reply_markup=KEYBOARD,
|
||||||
)
|
)
|
||||||
|
|
@ -313,75 +313,35 @@ def _find_matching_item(user_text: str, items: list[dict]) -> dict | None:
|
||||||
return best
|
return best
|
||||||
|
|
||||||
|
|
||||||
def _memory_buttons(item_id: int) -> InlineKeyboardMarkup:
|
|
||||||
return InlineKeyboardMarkup([[
|
|
||||||
InlineKeyboardButton("🕒 Temporär", callback_data=f"mem_temp:{item_id}"),
|
|
||||||
InlineKeyboardButton("📌 Dauerhaft", callback_data=f"mem_perm:{item_id}"),
|
|
||||||
InlineKeyboardButton("❌ Verwerfen", callback_data=f"mem_delete:{item_id}"),
|
|
||||||
]])
|
|
||||||
|
|
||||||
|
|
||||||
def _format_candidate(item: dict) -> str:
|
|
||||||
mtype = item.get("memory_type") or "?"
|
|
||||||
type_icon = "🕒" if mtype == "temporary" else "📌" if mtype == "permanent" else "❓"
|
|
||||||
line = f"{type_icon} [{item['scope']}/{item['kind']}] {mtype}\n{item['content']}"
|
|
||||||
exp = item.get("expires_at")
|
|
||||||
if exp:
|
|
||||||
from datetime import datetime
|
|
||||||
line += f"\n⏰ Ablauf: {datetime.fromtimestamp(exp).strftime('%d.%m.%Y %H:%M')}"
|
|
||||||
return line
|
|
||||||
|
|
||||||
|
|
||||||
async def cmd_memory(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
async def cmd_memory(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
||||||
if not _authorized(update):
|
if not _authorized(update):
|
||||||
return
|
return
|
||||||
candidates = memory_client.get_candidates()
|
items = memory_client.get_active_memory()
|
||||||
if not candidates:
|
if not items:
|
||||||
await update.message.reply_text("Keine offenen Kandidaten.")
|
await update.message.reply_text("Kein Gedaechtnis vorhanden.")
|
||||||
return
|
return
|
||||||
for item in candidates:
|
permanent = [i for i in items if i.get("memory_type") != "temporary"]
|
||||||
text = _format_candidate(item)
|
temporary = [i for i in items if i.get("memory_type") == "temporary"]
|
||||||
await update.message.reply_text(text, reply_markup=_memory_buttons(item["id"]))
|
lines = [f"🧠 Gedaechtnis: {len(items)} Eintraege\n"]
|
||||||
|
if permanent:
|
||||||
|
lines.append(f"📌 Dauerhaft ({len(permanent)}):")
|
||||||
|
for i in permanent:
|
||||||
|
lines.append(f" • {i['content'][:100]}")
|
||||||
|
if temporary:
|
||||||
|
lines.append(f"\n🕒 Temporaer ({len(temporary)}):")
|
||||||
|
for i in temporary:
|
||||||
|
exp = i.get("expires_at")
|
||||||
|
exp_str = ""
|
||||||
|
if exp:
|
||||||
|
from datetime import datetime
|
||||||
|
exp_str = f" (bis {datetime.fromtimestamp(exp).strftime('%d.%m.%Y')})"
|
||||||
|
lines.append(f" • {i['content'][:100]}{exp_str}")
|
||||||
|
text = "\n".join(lines)
|
||||||
|
await update.message.reply_text(text[:4000], reply_markup=KEYBOARD)
|
||||||
|
|
||||||
|
|
||||||
async def handle_memory_callback(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
||||||
query = update.callback_query
|
|
||||||
await query.answer()
|
|
||||||
data = query.data or ""
|
|
||||||
content_preview = query.message.text.split("\n", 2)[-1][:120] if query.message.text else ""
|
|
||||||
|
|
||||||
if data.startswith("mem_temp:"):
|
|
||||||
item_id = int(data.split(":")[1])
|
|
||||||
candidates = memory_client.get_candidates()
|
|
||||||
item = next((c for c in candidates if c["id"] == item_id), None)
|
|
||||||
exp = None
|
|
||||||
if item:
|
|
||||||
exp = item.get("expires_at") or memory_client.parse_expires_from_text(item.get("content", ""))
|
|
||||||
if not exp:
|
|
||||||
exp = memory_client.default_expires()
|
|
||||||
ok = memory_client.activate_candidate(item_id, memory_type="temporary", expires_at=exp)
|
|
||||||
if ok:
|
|
||||||
from datetime import datetime
|
|
||||||
exp_str = datetime.fromtimestamp(exp).strftime("%d.%m.%Y")
|
|
||||||
await query.edit_message_text(f"🕒 Temporär gespeichert (bis {exp_str}): {content_preview}")
|
|
||||||
else:
|
|
||||||
await query.edit_message_text("Fehler beim Speichern.")
|
|
||||||
|
|
||||||
elif data.startswith("mem_perm:"):
|
|
||||||
item_id = int(data.split(":")[1])
|
|
||||||
ok = memory_client.activate_candidate(item_id, memory_type="permanent")
|
|
||||||
if ok:
|
|
||||||
await query.edit_message_text(f"📌 Dauerhaft gespeichert: {content_preview}")
|
|
||||||
else:
|
|
||||||
await query.edit_message_text("Fehler beim Speichern.")
|
|
||||||
|
|
||||||
elif data.startswith("mem_delete:"):
|
|
||||||
item_id = int(data.split(":")[1])
|
|
||||||
ok = memory_client.delete_candidate(item_id)
|
|
||||||
if ok:
|
|
||||||
await query.edit_message_text("🗑 Verworfen.")
|
|
||||||
else:
|
|
||||||
await query.edit_message_text("Fehler beim Loeschen.")
|
|
||||||
|
|
||||||
|
|
||||||
async def handle_voice(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
async def handle_voice(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
||||||
|
|
@ -455,8 +415,7 @@ async def handle_message(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
||||||
|
|
||||||
await update.message.reply_text("🤔 Denke nach...")
|
await update.message.reply_text("🤔 Denke nach...")
|
||||||
try:
|
try:
|
||||||
context.last_suggest_result = {"type": None, "candidate_id": None}
|
context.last_suggest_result = {"type": None}
|
||||||
candidates_before = {c["id"] for c in memory_client.get_candidates()}
|
|
||||||
handlers = context.get_tool_handlers(session_id=session_id)
|
handlers = context.get_tool_handlers(session_id=session_id)
|
||||||
answer = llm.ask_with_tools(text, handlers, session_id=session_id)
|
answer = llm.ask_with_tools(text, handlers, session_id=session_id)
|
||||||
if session_id:
|
if session_id:
|
||||||
|
|
@ -464,74 +423,9 @@ async def handle_message(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
||||||
memory_client.log_message(session_id, "assistant", answer)
|
memory_client.log_message(session_id, "assistant", answer)
|
||||||
|
|
||||||
suggest = context.last_suggest_result
|
suggest = context.last_suggest_result
|
||||||
log.info("suggest_result: type=%s candidate_id=%s", suggest.get("type"), suggest.get("candidate_id"))
|
log.info("suggest_result: type=%s", suggest.get("type"))
|
||||||
|
|
||||||
sent = False
|
await update.message.reply_text(answer[:4000], reply_markup=KEYBOARD)
|
||||||
|
|
||||||
if suggest["type"] == "existing_candidate" and suggest["candidate_id"]:
|
|
||||||
candidates = memory_client.get_candidates()
|
|
||||||
item = next((c for c in candidates if c["id"] == suggest["candidate_id"]), None)
|
|
||||||
if item:
|
|
||||||
await update.message.reply_text(
|
|
||||||
answer[:4000] + "\n\n" + _format_candidate(item),
|
|
||||||
reply_markup=_memory_buttons(item["id"]),
|
|
||||||
)
|
|
||||||
sent = True
|
|
||||||
|
|
||||||
elif suggest["type"] in ("active_temporary", "active_permanent", "active_other"):
|
|
||||||
from datetime import datetime as _dt
|
|
||||||
active_items = memory_client.get_active_memory()
|
|
||||||
matched = _find_matching_item(text, active_items)
|
|
||||||
if matched:
|
|
||||||
mtype = matched.get("memory_type", "")
|
|
||||||
exp = matched.get("expires_at")
|
|
||||||
if mtype == "temporary" and exp:
|
|
||||||
status_msg = f"\n\n🕒 Schon temporär gespeichert bis {_dt.fromtimestamp(exp).strftime('%d.%m.%Y')}."
|
|
||||||
elif mtype == "permanent":
|
|
||||||
status_msg = "\n\n📌 Schon dauerhaft gespeichert."
|
|
||||||
else:
|
|
||||||
status_msg = "\n\n✅ Bereits gespeichert."
|
|
||||||
await update.message.reply_text(answer[:3900] + status_msg, reply_markup=KEYBOARD)
|
|
||||||
sent = True
|
|
||||||
|
|
||||||
elif suggest["type"] == "new_candidate":
|
|
||||||
candidates_after = memory_client.get_candidates()
|
|
||||||
new_candidates = [c for c in candidates_after if c["id"] not in candidates_before]
|
|
||||||
if new_candidates:
|
|
||||||
c = new_candidates[0]
|
|
||||||
type_icon = "🕒" if c.get("memory_type") == "temporary" else "📌" if c.get("memory_type") == "permanent" else "📝"
|
|
||||||
await update.message.reply_text(
|
|
||||||
answer[:4000] + "\n\n" + type_icon + " " + c["content"],
|
|
||||||
reply_markup=_memory_buttons(c["id"]),
|
|
||||||
)
|
|
||||||
sent = True
|
|
||||||
|
|
||||||
if not sent and suggest["type"] is None:
|
|
||||||
from datetime import datetime as _dt
|
|
||||||
matched_active = _find_matching_item(text, memory_client.get_active_memory())
|
|
||||||
if matched_active:
|
|
||||||
mtype = matched_active.get("memory_type", "")
|
|
||||||
exp = matched_active.get("expires_at")
|
|
||||||
if mtype == "temporary" and exp:
|
|
||||||
status_msg = f"\n\n🕒 Schon temporär gespeichert bis {_dt.fromtimestamp(exp).strftime('%d.%m.%Y')}."
|
|
||||||
elif mtype == "permanent":
|
|
||||||
status_msg = "\n\n📌 Schon dauerhaft gespeichert."
|
|
||||||
else:
|
|
||||||
status_msg = "\n\n✅ Bereits gespeichert."
|
|
||||||
await update.message.reply_text(answer[:3900] + status_msg, reply_markup=KEYBOARD)
|
|
||||||
sent = True
|
|
||||||
else:
|
|
||||||
matched_cand = _find_matching_item(text, memory_client.get_candidates())
|
|
||||||
if matched_cand:
|
|
||||||
log.info("Fallback-Match: Kandidat ID=%s", matched_cand["id"])
|
|
||||||
await update.message.reply_text(
|
|
||||||
answer[:4000] + "\n\n" + _format_candidate(matched_cand),
|
|
||||||
reply_markup=_memory_buttons(matched_cand["id"]),
|
|
||||||
)
|
|
||||||
sent = True
|
|
||||||
|
|
||||||
if not sent:
|
|
||||||
await update.message.reply_text(answer[:4000], reply_markup=KEYBOARD)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception("Fehler bei Freitext")
|
log.exception("Fehler bei Freitext")
|
||||||
await update.message.reply_text(f"Fehler: {e}")
|
await update.message.reply_text(f"Fehler: {e}")
|
||||||
|
|
@ -561,7 +455,6 @@ def main():
|
||||||
app.add_handler(CommandHandler("check", cmd_check))
|
app.add_handler(CommandHandler("check", cmd_check))
|
||||||
app.add_handler(CommandHandler("feeds", cmd_feeds))
|
app.add_handler(CommandHandler("feeds", cmd_feeds))
|
||||||
app.add_handler(CommandHandler("memory", cmd_memory))
|
app.add_handler(CommandHandler("memory", cmd_memory))
|
||||||
app.add_handler(CallbackQueryHandler(handle_memory_callback, pattern=r"^mem_"))
|
|
||||||
app.add_handler(MessageHandler(filters.VOICE, handle_voice))
|
app.add_handler(MessageHandler(filters.VOICE, handle_voice))
|
||||||
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
|
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue