RAG: Vektor-Suche statt All-Items-Prompt, Kandidaten-System entfernt, Bot speichert direkt

This commit is contained in:
root 2026-03-15 16:16:24 +07:00
parent 659685228b
commit a2c0279f99
2 changed files with 29 additions and 136 deletions

View file

@ -48,7 +48,7 @@ PERMANENT (memory_type="permanent") — bleibt dauerhaft:
- Wiederkehrende Regeln ("API-Kosten monatlich beobachten")
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.
SESSION-RUECKBLICK:

View file

@ -37,9 +37,9 @@ def _release_lock():
except OSError:
pass
from telegram import BotCommand, Update, ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton
from telegram import BotCommand, Update, ReplyKeyboardMarkup, KeyboardButton
from telegram.ext import (
Application, CommandHandler, MessageHandler, CallbackQueryHandler, filters, ContextTypes,
Application, CommandHandler, MessageHandler, filters, ContextTypes,
)
BOT_COMMANDS = [
@ -52,7 +52,7 @@ BOT_COMMANDS = [
BotCommand("report", "Tagesbericht"),
BotCommand("check", "Monitoring-Check"),
BotCommand("feeds", "Feed-Status & Artikel heute"),
BotCommand("memory", "Offene Memory-Kandidaten"),
BotCommand("memory", "Gedaechtnis anzeigen"),
BotCommand("start", "Hilfe anzeigen"),
]
@ -122,7 +122,7 @@ async def cmd_start(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
"/report — Tagesbericht\n"
"/check — Monitoring-Check\n"
"/feeds — Feed-Status & Artikel\n"
"/memory — Offene Memory-Kandidaten\n\n"
"/memory — Gedaechtnis anzeigen\n\n"
"Oder einfach eine Frage stellen!",
reply_markup=KEYBOARD,
)
@ -313,75 +313,35 @@ def _find_matching_item(user_text: str, items: list[dict]) -> dict | None:
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):
if not _authorized(update):
return
candidates = memory_client.get_candidates()
if not candidates:
await update.message.reply_text("Keine offenen Kandidaten.")
items = memory_client.get_active_memory()
if not items:
await update.message.reply_text("Kein Gedaechtnis vorhanden.")
return
for item in candidates:
text = _format_candidate(item)
await update.message.reply_text(text, reply_markup=_memory_buttons(item["id"]))
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:
permanent = [i for i in items if i.get("memory_type") != "temporary"]
temporary = [i for i in items if i.get("memory_type") == "temporary"]
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 = 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.")
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)
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):
@ -455,8 +415,7 @@ async def handle_message(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("🤔 Denke nach...")
try:
context.last_suggest_result = {"type": None, "candidate_id": None}
candidates_before = {c["id"] for c in memory_client.get_candidates()}
context.last_suggest_result = {"type": None}
handlers = context.get_tool_handlers(session_id=session_id)
answer = llm.ask_with_tools(text, handlers, session_id=session_id)
if session_id:
@ -464,73 +423,8 @@ async def handle_message(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
memory_client.log_message(session_id, "assistant", answer)
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
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:
log.exception("Fehler bei Freitext")
@ -561,7 +455,6 @@ def main():
app.add_handler(CommandHandler("check", cmd_check))
app.add_handler(CommandHandler("feeds", cmd_feeds))
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.TEXT & ~filters.COMMAND, handle_message))