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:
71
bot.py
71
bot.py
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user