Spaces:
Paused
Paused
| """ | |
| helpers.py - RAG Orchestration & Document Processing | |
| This module defines the utility functions for the Pokémon Emerald knowledge base. | |
| It manages the end-to-end retrieval pipeline, including: | |
| - Document Ingestion: Recursive character splitting optimized for technical manuals. | |
| - Vector Management: Integration with ChromaDB for semantic search. | |
| - Prompt Engineering: Context-aware prompt construction with strict Pydantic | |
| formatting instructions to ensure structured JSON output. | |
| """ | |
| from langchain_community.document_loaders import TextLoader | |
| from langchain_community.vectorstores import Chroma | |
| from langchain_core.output_parsers import PydanticOutputParser | |
| from langchain_huggingface import HuggingFaceEmbeddings | |
| from langchain_text_splitters import RecursiveCharacterTextSplitter | |
| from model import load_embedding_model | |
| from schema import PokemonEmeraldResponse | |
| MANUAL_PATH = "emerald_manual.txt" | |
| def load_vector_store(filename): | |
| """ | |
| Load, split, and index the game manual into a ChromaDB vector store. | |
| Args: | |
| filename: Path to the source .txt manual. | |
| Returns: | |
| Initialised vector store for similarity search. | |
| """ | |
| print("Loading vector store...") | |
| loader = TextLoader(MANUAL_PATH) | |
| data = loader.load() | |
| text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100) | |
| chunks = text_splitter.split_documents(data) | |
| embedding_model = load_embedding_model() | |
| print(f"Created {len(chunks)} chunks.") | |
| store = Chroma.from_documents(documents=chunks, embedding=embedding_model) | |
| return store | |
| def find_similar_documents(vector_store, message, k=5): | |
| """ | |
| Queries the vector store for manual snippets matching the user's message. | |
| Args: | |
| vector_store: Initialized Chroma/FAISS instance. | |
| message: The user's natural language question. | |
| k: Number of context chunks to retrieve (default=5 for high density). | |
| Returns: | |
| List of Document objects containing page_content and metadata. | |
| """ | |
| print("Retrieving relevant documents...") | |
| context = vector_store.similarity_search(message, k=k) | |
| for i, x in enumerate(context): | |
| print(f"i={i}" + "-"*20) | |
| print(x) | |
| print() | |
| return context | |
| def create_prompt(message, history, context): | |
| """ | |
| Constructs a grounded, instruction-tuned prompt for the LLM. | |
| Integrates retrieved manual context with a strict system persona to | |
| mitigate hallucinations. Injects Pydantic-generated format instructions | |
| to ensure the model's response is a valid JSON object matching the | |
| PokemonEmeraldResponse schema. | |
| """ | |
| parser = PydanticOutputParser(pydantic_object=PokemonEmeraldResponse) | |
| context_text = "\n---\n".join([d.page_content for d in context]) | |
| format_instructions = parser.get_format_instructions() | |
| prompt = f""" | |
| System: You are a precise Pokémon Emerald Research Assistant. Your goal is to answer questions using | |
| ONLY the provided context. If the answer is not contained within the context, state: No relevant | |
| sources found. Citations should be sufficient to keep context and at least a couple of sentences. | |
| Include all relevant documents in citations. | |
| Context: {context_text} | |
| Question: {message} | |
| {format_instructions} | |
| Follow the format instructions exactly. The output will be parsed as a single JSON. | |
| If the answer cannot be found in the provided context, or if you do not know the answer, | |
| state exactly: "I do not have enough information to answer this." | |
| Do not repeat previous responses, and do not attempt to guess. | |
| """ | |
| return prompt |