feat: Add RAG query rewriting for contextual follow-up questions

When a user asks "Wer ist Mieter in diesem Haus?" after discussing
a specific house, the raw message lacks context for RAG search.
Now uses a quick LLM call to resolve pronouns/references before
searching WildFiles (e.g. "diesem Haus" -> "Mieter Haus Coburg").

Also moved history fetch before RAG search so context is available.

Refs: WF-90

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Christian Gick
2026-02-16 13:06:26 +02:00
parent e1438c9abf
commit a0367f32e3

71
bot.py
View File

@@ -440,19 +440,8 @@ class Bot:
async def _respond_with_ai(self, room, user_message: str): async def _respond_with_ai(self, room, user_message: str):
model = self.room_models.get(room.room_id, DEFAULT_MODEL) model = self.room_models.get(room.room_id, DEFAULT_MODEL)
# Build conversation context from room timeline # Fetch conversation history FIRST (needed for query rewriting)
messages = [{"role": "system", "content": SYSTEM_PROMPT}] history = []
# WildFiles document context
doc_results = await self.rag.search(user_message)
doc_context = self.rag.format_context(doc_results)
if doc_context:
logger.info("RAG found %d docs for: %s", len(doc_results), user_message[:50])
messages.append({"role": "system", "content": doc_context})
else:
logger.info("RAG found 0 docs for: %s", user_message[:50])
# Fetch last N messages from room via API
try: try:
resp = await self.client.room_messages( resp = await self.client.room_messages(
room.room_id, start=self.client.next_batch or "", limit=10 room.room_id, start=self.client.next_batch or "", limit=10
@@ -462,10 +451,27 @@ class Bot:
if not hasattr(evt, "body"): if not hasattr(evt, "body"):
continue continue
role = "assistant" if evt.sender == BOT_USER else "user" role = "assistant" if evt.sender == BOT_USER else "user"
messages.append({"role": role, "content": evt.body}) history.append({"role": role, "content": evt.body})
except Exception: except Exception:
logger.debug("Could not fetch room history, proceeding without context") logger.debug("Could not fetch room history, proceeding without context")
# Rewrite query using conversation context for better RAG search
search_query = await self._rewrite_query(user_message, history, model)
# WildFiles document context
doc_results = await self.rag.search(search_query)
doc_context = self.rag.format_context(doc_results)
if doc_context:
logger.info("RAG found %d docs for: %s (original: %s)", len(doc_results), search_query[:50], user_message[:50])
else:
logger.info("RAG found 0 docs for: %s (original: %s)", search_query[:50], user_message[:50])
# Build conversation context
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
if doc_context:
messages.append({"role": "system", "content": doc_context})
messages.extend(history)
# Add current user message # Add current user message
messages.append({"role": "user", "content": user_message}) messages.append({"role": "user", "content": user_message})
@@ -485,6 +491,43 @@ class Bot:
logger.exception("LLM call failed") logger.exception("LLM call failed")
await self._send_text(room.room_id, "Sorry, I couldn't generate a response.") await self._send_text(room.room_id, "Sorry, I couldn't generate a response.")
async def _rewrite_query(self, user_message: str, history: list[dict], model: str) -> str:
"""Rewrite user message into a standalone search query using conversation context."""
if not history or not self.llm:
return user_message
# Build a compact history summary (last 4 messages max)
recent = history[-4:]
context_lines = []
for msg in recent:
prefix = "User" if msg["role"] == "user" else "Assistant"
context_lines.append(f"{prefix}: {msg['content'][:200]}")
context_text = "\n".join(context_lines)
try:
resp = await self.llm.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": (
"You are a search query rewriter. Given conversation history and a new user message, "
"produce a single standalone search query that resolves all pronouns and references "
"(like 'this house', 'that document', 'it') using context from the conversation. "
"Reply with ONLY the rewritten search query in the same language as the user message. "
"No explanation, no quotes. If the message is already self-contained, return it as-is."
)},
{"role": "user", "content": f"Conversation:\n{context_text}\n\nNew message: {user_message}"},
],
max_tokens=100,
)
rewritten = resp.choices[0].message.content.strip().strip('"\'')
if rewritten and len(rewritten) < 500:
logger.info("Query rewritten: '%s' -> '%s'", user_message[:50], rewritten[:50])
return rewritten
except Exception:
logger.debug("Query rewrite failed, using original", exc_info=True)
return user_message
async def _auto_rename_room(self, room, user_message: str, ai_reply: str): async def _auto_rename_room(self, room, user_message: str, ai_reply: str):
"""Generate a short topic title and set it as the room name.""" """Generate a short topic title and set it as the room name."""
try: try: