Optimized Prompts: - Create utils/rag_prompt_builder.py with citation-optimized prompts - Specialized for DAO tokenomics and technical documentation - Proper citation format [1], [2] with doc_id, page, section - Memory context integration (facts, events, summaries) - Token count estimation RAG Service Metrics: - Add comprehensive logging in query_pipeline.py - Log: question, doc_ids, scores, retrieval method, timing - Track: retrieval_time, total_query_time, documents_found, citations_count - Add metrics in ingest_pipeline.py: pages_processed, blocks_processed, pipeline_time Router Improvements: - Use optimized prompt builder in _handle_rag_query() - Add graceful fallback: if RAG unavailable, use Memory only - Log prompt token count, RAG usage, Memory usage - Return detailed metadata (rag_used, memory_used, citations_count, metrics) Evaluation Tools: - Create tests/rag_eval.py for systematic quality testing - Test fixed questions with expected doc_ids - Save results to JSON and CSV - Compare RAG Service vs Router results - Track: citations, expected docs found, query times Documentation: - Create docs/RAG_METRICS_PLAN.md - Plan for Prometheus metrics collection - Grafana dashboard panels and alerts - Implementation guide for metrics
170 lines
6.0 KiB
Python
170 lines
6.0 KiB
Python
"""
|
|
RAG Prompt Builder - optimized prompts for DAO tokenomics and documents
|
|
"""
|
|
|
|
from typing import List, Dict, Any, Optional
|
|
|
|
|
|
def build_rag_prompt_with_citations(
|
|
question: str,
|
|
memory_context: Dict[str, Any],
|
|
rag_citations: List[Dict[str, Any]],
|
|
rag_documents: Optional[List[Dict[str, Any]]] = None
|
|
) -> str:
|
|
"""
|
|
Build optimized prompt for RAG queries with citations
|
|
|
|
Optimized for:
|
|
- DAO tokenomics questions
|
|
- Technical documentation
|
|
- Multi-document answers with proper citations
|
|
|
|
Args:
|
|
question: User question
|
|
memory_context: Memory context (facts, events, summaries)
|
|
rag_citations: List of citations from RAG
|
|
rag_documents: Optional full documents (for context)
|
|
|
|
Returns:
|
|
Formatted prompt for LLM
|
|
"""
|
|
# Base system prompt
|
|
system_prompt = (
|
|
"Ти — експерт-консультант з токеноміки та архітектури DAO в екосистемі DAARION.city.\n"
|
|
"Твоя задача: дати чітку, структуровану відповідь на основі наданих документів та особистої пам'яті.\n\n"
|
|
"**Правила формування відповіді:**\n"
|
|
"1. Використовуй тільки інформацію з наданих документів та пам'яті\n"
|
|
"2. Посилайся на документи через індекси [1], [2], [3] тощо\n"
|
|
"3. Для технічних термінів (стейкінг, токени, ролі) давай конкретні приклади\n"
|
|
"4. Якщо в документах немає відповіді — чесно скажи, що не знаєш\n"
|
|
"5. Відповідай українською, структуровано (списки, абзаци)\n\n"
|
|
)
|
|
|
|
# Build Memory section
|
|
memory_section = _build_memory_section(memory_context)
|
|
|
|
# Build Documents section with citations
|
|
documents_section = _build_documents_section(rag_citations, rag_documents)
|
|
|
|
# Combine into final prompt
|
|
prompt_parts = [system_prompt]
|
|
|
|
if memory_section:
|
|
prompt_parts.append("**1. Особиста пам'ять та контекст:**\n")
|
|
prompt_parts.append(memory_section)
|
|
prompt_parts.append("\n")
|
|
|
|
if documents_section:
|
|
prompt_parts.append("**2. Релевантні документи DAO:**\n")
|
|
prompt_parts.append(documents_section)
|
|
prompt_parts.append("\n")
|
|
|
|
prompt_parts.append(f"**Питання користувача:**\n{question}\n\n")
|
|
prompt_parts.append("**Твоя відповідь (з цитатами [1], [2] тощо):**")
|
|
|
|
return "\n".join(prompt_parts)
|
|
|
|
|
|
def _build_memory_section(memory_context: Dict[str, Any]) -> str:
|
|
"""Build memory context section"""
|
|
parts = []
|
|
|
|
# User facts
|
|
facts = memory_context.get("facts", [])
|
|
if facts:
|
|
facts_list = []
|
|
for fact in facts[:5]: # Top 5 facts
|
|
key = fact.get("fact_key", "")
|
|
value = fact.get("fact_value", "")
|
|
if key and value:
|
|
facts_list.append(f"- {key}: {value}")
|
|
|
|
if facts_list:
|
|
parts.append("Особисті факти користувача:")
|
|
parts.extend(facts_list)
|
|
parts.append("")
|
|
|
|
# Recent events
|
|
events = memory_context.get("recent_events", [])
|
|
if events:
|
|
events_list = []
|
|
for event in events[:3]: # Last 3 events
|
|
body = event.get("body_text", "")
|
|
if body:
|
|
events_list.append(f"- {body[:150]}...")
|
|
|
|
if events_list:
|
|
parts.append("Останні події в діалозі:")
|
|
parts.extend(events_list)
|
|
parts.append("")
|
|
|
|
# Dialog summaries
|
|
summaries = memory_context.get("dialog_summaries", [])
|
|
if summaries:
|
|
summary_text = summaries[0].get("summary_text", "")
|
|
if summary_text:
|
|
parts.append(f"Підсумок попередніх діалогів: {summary_text[:200]}...")
|
|
|
|
return "\n".join(parts) if parts else ""
|
|
|
|
|
|
def _build_documents_section(
|
|
citations: List[Dict[str, Any]],
|
|
documents: Optional[List[Dict[str, Any]]] = None
|
|
) -> str:
|
|
"""
|
|
Build documents section with proper citation format
|
|
|
|
Format:
|
|
[1] (doc_id=microdao-tokenomics, page=1, section=Токеноміка):
|
|
MicroDAO використовує токен μGOV як ключ доступу...
|
|
"""
|
|
if not citations:
|
|
return "Документи не знайдено."
|
|
|
|
parts = []
|
|
|
|
for idx, citation in enumerate(citations[:5], start=1): # Top 5 citations
|
|
doc_id = citation.get("doc_id", "unknown")
|
|
page = citation.get("page", 0)
|
|
section = citation.get("section", "")
|
|
excerpt = citation.get("excerpt", "")
|
|
|
|
# Build citation header
|
|
header_parts = [f"[{idx}]"]
|
|
if doc_id != "unknown":
|
|
header_parts.append(f"doc_id={doc_id}")
|
|
if page:
|
|
header_parts.append(f"page={page}")
|
|
if section:
|
|
header_parts.append(f"section={section}")
|
|
|
|
header = " (" + ", ".join(header_parts) + "):"
|
|
|
|
# Add excerpt
|
|
if excerpt:
|
|
# Limit excerpt length
|
|
excerpt_clean = excerpt[:300] + "..." if len(excerpt) > 300 else excerpt
|
|
parts.append(f"{header}\n{excerpt_clean}")
|
|
else:
|
|
parts.append(f"{header}\n(фрагмент недоступний)")
|
|
|
|
parts.append("") # Empty line between citations
|
|
|
|
return "\n".join(parts)
|
|
|
|
|
|
def estimate_token_count(text: str, chars_per_token: float = 4.0) -> int:
|
|
"""
|
|
Rough estimate of token count
|
|
|
|
Args:
|
|
text: Text to estimate
|
|
chars_per_token: Average characters per token (default 4.0 for most models)
|
|
|
|
Returns:
|
|
Estimated token count
|
|
"""
|
|
return int(len(text) / chars_per_token)
|
|
|