Devisri515 commited on
Commit
8a27bb1
Β·
1 Parent(s): 084052d

initial code

Browse files
Files changed (8) hide show
  1. .DS_Store +0 -0
  2. Dockerfile +23 -0
  3. README.md +113 -50
  4. requirements.txt +14 -0
  5. src/agent.py +73 -0
  6. src/main.py +77 -0
  7. src/rag_engine.py +56 -0
  8. tests/evaluate.py +72 -0
.DS_Store ADDED
Binary file (6.15 kB). View file
 
Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.10-slim
3
+
4
+ # Set the working directory in the container
5
+ WORKDIR /app
6
+
7
+ # Copy the requirements file into the container
8
+ COPY requirements.txt .
9
+
10
+ # Install any needed packages specified in requirements.txt
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # Copy the current directory contents into the container at /app
14
+ COPY . .
15
+
16
+ # Expose the port FastAPI runs on
17
+ EXPOSE 8000
18
+
19
+ # Define environment variable for unbuffered logs (good for Docker)
20
+ ENV PYTHONUNBUFFERED=1
21
+
22
+ # Run the application
23
+ CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
README.md CHANGED
@@ -1,64 +1,127 @@
1
- # Retrieval-Augmented Generation (RAG) Knowledge Search Engine
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- This project implements a robust Retrieval-Augmented Generation (RAG) pipeline designed to enable reliable, context-aware question answering over custom, private documents (PDFs, DOCX, CSV, TXT). It minimizes Large Language Model (LLM) "hallucinations" by grounding all answers strictly within the retrieved source material, providing full traceability and high domain-specific accuracy.
4
 
5
- ## Features
6
 
7
- * **Multi-Document Support:** Easily ingest and index documents of various formats (`.pdf`, `.txt`, `.docx`, `.csv`).
8
- * **Context Grounding:** Utilizes a custom prompt that forces the LLM to use **only** the provided context, ensuring factual and traceable answers .
9
- * **Vectorization & Retrieval:** Leverages **Google Generative AI Embeddings** and the high-performance **FAISS** vector store for sub-second similarity search .
10
- * **End-to-End Pipeline:** Wires the components into a `RetrievalQA` chain for seamless question processing .
11
- * **Experiment Tracking:** Integrates **MLflow** to track key parameters (`chunk_size`, `chunk_overlap`, `session_id`) and log question/answer pairs along with source documents for model evaluation and future feedback .
12
 
13
- ## Technology Stack
 
 
 
 
 
 
 
 
 
 
 
14
 
15
- | Component | Technology | Purpose |
16
- | :--- | :--- | :--- |
17
- | **Orchestration** | `LangChain` | Wires together the document processing, embedding, and LLM query steps . |
18
- | **LLM/Embeddings** | `Gemini 1.5 Pro` / `GoogleGenerativeAIEmbeddings` | Generative AI model and state-of-the-art embedding generation. |
19
- | **Vector Database** | `FAISS (CPU)` | Efficient, in-memory index for fast similarity search during retrieval . |
20
- | **Experimentation** | `MLflow` | Tracks document processing parameters and conversation history/metrics . |
21
- | **Document Loaders**| `PyPDFLoader`, `UnstructuredWordDocumentLoader`, etc. | Handles ingestion of various file types . |
22
 
23
- ## RAG Pipeline Overview
24
 
25
- The system utilizes a standard RAG pattern to answer questions:
 
 
 
 
 
 
26
 
27
- 1. **Ingestion & Chunking:** Documents are loaded and split into small, overlapping chunks (default 1,000 tokens, 200 overlap) using `RecursiveCharacterTextSplitter`.
28
- 2. **Embedding:** Each chunk is converted into a high-dimensional vector using **Google Generative AI Embeddings**.
29
- 3. **Vector Store:** The vectors are indexed in the **FAISS** library for efficient storage and retrieval.
30
- 4. **Retrieval:** At query time, the user's question is embedded and used to query FAISS for the Top-K (default $k=4$) most relevant document chunks.
31
- 5. **Generation:** The retrieved chunks are passed to the **Gemini 1.5 Pro** LLM along with a strict prompt instructing it to answer **only** based on the provided context, producing the final, grounded response.
32
 
 
 
 
33
 
34
- ### Prerequisites
 
 
35
 
36
- 1. **Python 3.8+**
37
- 2. A **Google API Key** for the Gemini models (set as an environment variable or passed to the `DocumentQASystem` class).
38
 
39
- ### Usage
 
 
40
 
41
- The core functionality is encapsulated in the `DocumentQASystem` class, which manages the entire RAG workflow.
 
 
 
 
 
 
 
 
 
42
 
43
- 1. **Initialize the System:**
44
- ```python
45
- qa_system = DocumentQASystem(api_key='YOUR_GEMINI_API_KEY')
46
- ```
47
- 2. **Process Documents:** Place your documents (`.pdf`, `.txt`, `.docx`, `.csv`) in a directory and provide the file paths.
48
- ```python
49
- sample_files = ["./data/manual.pdf", "./data/report.docx"]
50
- chunk_count = qa_system.process_documents(
51
- file_paths=sample_files,
52
- chunk_size=1000,
53
- chunk_overlap=200
54
- )
55
- print(f"Processed {chunk_count} document chunks.")
56
- ```
57
- 3. **Ask Questions:**
58
- ```python
59
- result = qa_system.ask_question("Under what conditions can a treaty be terminated?")
60
- print("Answer:", result["answer"])
61
- print("Sources:", result["sources"])
62
- ```
63
- ### Contributing:
64
- Contributions are welcome! Please open issues or submit pull requests to suggest improvements or new features.
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Agentic RAG Knowledge Search</title>
7
+ <style>
8
+ body {
9
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
10
+ line-height: 1.6;
11
+ color: #333;
12
+ max-width: 800px;
13
+ margin: 0 auto;
14
+ padding: 20px;
15
+ }
16
+ h1, h2, h3 {
17
+ color: #2c3e50;
18
+ margin-top: 1.5em;
19
+ }
20
+ h1 {
21
+ border-bottom: 2px solid #eaeaea;
22
+ padding-bottom: 0.5em;
23
+ }
24
+ code {
25
+ background-color: #f6f8fa;
26
+ padding: 0.2em 0.4em;
27
+ border-radius: 3px;
28
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
29
+ font-size: 85%;
30
+ }
31
+ pre {
32
+ background-color: #f6f8fa;
33
+ padding: 16px;
34
+ overflow: auto;
35
+ border-radius: 6px;
36
+ line-height: 1.45;
37
+ }
38
+ pre code {
39
+ background-color: transparent;
40
+ padding: 0;
41
+ font-size: 100%;
42
+ }
43
+ ul {
44
+ padding-left: 2em;
45
+ }
46
+ li {
47
+ margin-bottom: 0.5em;
48
+ }
49
+ a {
50
+ color: #0366d6;
51
+ text-decoration: none;
52
+ }
53
+ a:hover {
54
+ text-decoration: underline;
55
+ }
56
+ </style>
57
+ </head>
58
+ <body>
59
 
60
+ <h1>Agentic RAG Knowledge Search</h1>
61
 
62
+ <p>A production-ready AI Microservice that uses an <strong>Agentic Router</strong> to intelligently decide between retrieving answers from internal private documents (RAG) or searching the live web.</p>
63
 
64
+ <p>Built with <strong>FastAPI</strong>, <strong>LangChain</strong>, <strong>Docker</strong>, and <strong>Google Gemini</strong>.</p>
 
 
 
 
65
 
66
+ <h2> Features</h2>
67
+ <ul>
68
+ <li><strong>Agentic Routing</strong>: Uses an LLM to decide <em>where</em> to get information.</li>
69
+ <li><strong>Hybrid Tools</strong>:
70
+ <ul>
71
+ <li><code>lookup_internal_policy</code>: Searches local PDFs using FAISS + Embeddings.</li>
72
+ <li><code>search_web</code>: Searches DuckDuckGo for real-time info.</li>
73
+ </ul>
74
+ </li>
75
+ <li><strong>REST API</strong>: Fully documented API using FastAPI.</li>
76
+ <li><strong>Containerized</strong>: Docker support for easy deployment.</li>
77
+ </ul>
78
 
79
+ <h2> Project Structure</h2>
80
+ <ul>
81
+ <li><code>src/rag_engine.py</code>: Handles PDF ingestion and Vector Database (FAISS).</li>
82
+ <li><code>src/agent.py</code>: Defines the Agent, Tools, and LangChain logic.</li>
83
+ <li><code>src/main.py</code>: The FastAPI server entry point.</li>
84
+ <li><code>data/</code>: Place your PDF documents here.</li>
85
+ </ul>
86
 
87
+ <h2> Setup & Installation</h2>
88
 
89
+ <h3>1. Prerequisites</h3>
90
+ <ul>
91
+ <li>Get a <a href="https://aistudio.google.com/">Free Google Gemini API Key</a>.</li>
92
+ <li>Create a <code>.env</code> file in this directory:
93
+ <pre><code>GOOGLE_API_KEY=AIzaSyD...your_key_here...</code></pre>
94
+ </li>
95
+ </ul>
96
 
97
+ <h3>2. Add Data</h3>
98
+ <p>Place your PDF file (e.g., policy documents, course materials) into the <code>data/</code> folder and rename it to <code>policy.pdf</code> (or update <code>src/agent.py</code> to match your filename).</p>
 
 
 
99
 
100
+ <h3>3. Run with Docker (Recommended)</h3>
101
+ <pre><code>docker build -t agentic-rag .
102
+ docker run -p 8000:8000 --env-file .env agentic-rag</code></pre>
103
 
104
+ <h3>4. Run Locally</h3>
105
+ <pre><code>pip install -r requirements.txt
106
+ python src/main.py</code></pre>
107
 
108
+ <h2> API Usage</h2>
109
+ <p><strong>Endpoint:</strong> <code>POST /chat</code></p>
110
 
111
+ <pre><code>{
112
+ "query": "What are the rules for termination in the policy?"
113
+ }</code></pre>
114
 
115
+ <p><strong>Response:</strong></p>
116
+ <pre><code>{
117
+ "response": "According to the policy, termination requires...",
118
+ "steps": [
119
+ {
120
+ "tool": "lookup_internal_policy",
121
+ "tool_input": "termination rules"
122
+ }
123
+ ]
124
+ }</code></pre>
125
 
126
+ </body>
127
+ </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.109.2
2
+ uvicorn==0.27.1
3
+ python-dotenv==1.0.1
4
+ langchain==0.1.9
5
+ langchain-google-genai==0.0.9
6
+ langchain-community==0.0.24
7
+ faiss-cpu==1.7.4
8
+ pypdf==4.0.2
9
+ duckduckgo-search==4.3.1
10
+ ragas==0.1.1
11
+ datasets==2.17.1
12
+ google-search-results
13
+ pydantic==2.6.1
14
+ requests
src/agent.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from langchain.agents import AgentExecutor, create_tool_calling_agent
3
+ from langchain_google_genai import ChatGoogleGenerativeAI
4
+ from langchain_core.tools import tool
5
+ from langchain_community.tools import DuckDuckGoSearchRun
6
+ from langchain import hub
7
+ from dotenv import load_dotenv
8
+ from src.rag_engine import KnowledgeBase
9
+
10
+ load_dotenv()
11
+
12
+ # --- Configuration ---
13
+ PDF_PATH = os.path.join("data", "policy.pdf")
14
+
15
+ # Initialize the RAG engine
16
+ kb = KnowledgeBase(pdf_path=PDF_PATH)
17
+ # Index documents on startup
18
+ kb.load_and_index()
19
+
20
+ # --- Define Tools ---
21
+
22
+ @tool
23
+ def lookup_internal_policy(query: str) -> str:
24
+ """
25
+ Useful for answering questions about specific internal policies,
26
+ documents, laws, or the contents of the uploaded PDF file.
27
+ ALWAYS use this tool first if the question implies looking up specific rules or documents.
28
+ """
29
+ return kb.retrieve(query)
30
+
31
+ @tool
32
+ def search_web(query: str) -> str:
33
+ """
34
+ Useful for finding current events, news, general knowledge,
35
+ or information that is NOT contained in the internal policy documents.
36
+ """
37
+ search = DuckDuckGoSearchRun()
38
+ return search.run(query)
39
+
40
+ # --- Initialize Agent ---
41
+
42
+ def get_agent_executor():
43
+ """
44
+ Constructs the Agent with the Gemini LLM and the tools.
45
+ """
46
+ # 1. Setup the Gemini LLM
47
+ if not os.getenv("GOOGLE_API_KEY"):
48
+ raise ValueError("GOOGLE_API_KEY not found in environment variables")
49
+
50
+ llm = ChatGoogleGenerativeAI(
51
+ model="gemini-1.5-flash", # Or gemini-1.5-pro
52
+ temperature=0,
53
+ convert_system_message_to_human=True
54
+ )
55
+
56
+ # 2. Define the Toolkit
57
+ tools = [lookup_internal_policy, search_web]
58
+
59
+ # 3. Pull a standard prompt for Tool Calling from LangChain Hub
60
+ # This prompt tells the LLM: "You have these tools. Use them to answer."
61
+ prompt = hub.pull("hwchase17/openai-tools-agent")
62
+
63
+ # 4. Construct the Agent
64
+ agent = create_tool_calling_agent(llm, tools, prompt)
65
+
66
+ # 5. Create the Executor (Runtime)
67
+ agent_executor = AgentExecutor(
68
+ agent=agent,
69
+ tools=tools,
70
+ verbose=True, # Logs thoughts to console
71
+ return_intermediate_steps=True # Return the thought process
72
+ )
73
+ return agent_executor
src/main.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from pydantic import BaseModel
3
+ from src.agent import get_agent_executor
4
+ import logging
5
+
6
+ # Setup logging
7
+ logging.basicConfig(level=logging.INFO)
8
+ logger = logging.getLogger(__name__)
9
+
10
+ app = FastAPI(
11
+ title="Agentic RAG Service",
12
+ description="An AI Microservice that routes between Internal Docs and Web Search.",
13
+ version="1.0"
14
+ )
15
+
16
+ # Initialize Agent Logic
17
+ try:
18
+ agent_executor = get_agent_executor()
19
+ except Exception as e:
20
+ logger.error(f"Failed to initialize agent: {e}")
21
+ agent_executor = None
22
+
23
+ # --- API Models ---
24
+ class QueryRequest(BaseModel):
25
+ query: str
26
+
27
+ class StepInfo(BaseModel):
28
+ tool: str
29
+ tool_input: str
30
+
31
+ class QueryResponse(BaseModel):
32
+ response: str
33
+ steps: list[StepInfo] = []
34
+
35
+ # --- Routes ---
36
+
37
+ @app.get("/")
38
+ async def root():
39
+ return {
40
+ "status": "active",
41
+ "service": "Agentic Knowledge Search",
42
+ "docs_url": "/docs"
43
+ }
44
+
45
+ @app.post("/chat", response_model=QueryResponse)
46
+ async def chat(request: QueryRequest):
47
+ if not agent_executor:
48
+ raise HTTPException(status_code=500, detail="Agent not initialized (Check API Keys)")
49
+
50
+ try:
51
+ logger.info(f"Received query: {request.query}")
52
+
53
+ # Invoke the agent
54
+ result = agent_executor.invoke({"input": request.query})
55
+
56
+ # Extract the 'Thought Process' (Intermediate Steps) to show the recruiter
57
+ # 'intermediate_steps' is a list of tuples: (AgentAction, Observation)
58
+ steps_data = []
59
+ if "intermediate_steps" in result:
60
+ for action, observation in result["intermediate_steps"]:
61
+ steps_data.append(StepInfo(
62
+ tool=action.tool,
63
+ tool_input=str(action.tool_input)
64
+ ))
65
+
66
+ return QueryResponse(
67
+ response=result["output"],
68
+ steps=steps_data
69
+ )
70
+
71
+ except Exception as e:
72
+ logger.error(f"Error processing query: {e}")
73
+ raise HTTPException(status_code=500, detail=str(e))
74
+
75
+ if __name__ == "__main__":
76
+ import uvicorn
77
+ uvicorn.run(app, host="0.0.0.0", port=8000)
src/rag_engine.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from langchain_community.document_loaders import PyPDFLoader
3
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
4
+ from langchain_community.vectorstores import FAISS
5
+ from langchain_google_genai import GoogleGenerativeAIEmbeddings
6
+ from dotenv import load_dotenv
7
+
8
+ # Load environment variables
9
+ load_dotenv()
10
+
11
+ class KnowledgeBase:
12
+ """
13
+ Handles document loading, chunking, and vector storage.
14
+ """
15
+ def __init__(self, pdf_path: str):
16
+ self.pdf_path = pdf_path
17
+ self.vector_store = None
18
+ # Using Gemini Embeddings
19
+ self.embeddings = GoogleGenerativeAIEmbeddings(model="models/embedding-001")
20
+
21
+ def load_and_index(self):
22
+ """
23
+ Loads the PDF, splits it into chunks, and builds the FAISS index.
24
+ """
25
+ if not os.path.exists(self.pdf_path):
26
+ print(f"⚠️ Warning: File not found at {self.pdf_path}. Internal search will be empty.")
27
+ return
28
+
29
+ print(f"πŸ“„ Loading PDF from: {self.pdf_path}...")
30
+ loader = PyPDFLoader(self.pdf_path)
31
+ docs = loader.load()
32
+
33
+ print("βœ‚οΈ Splitting text into chunks...")
34
+ text_splitter = RecursiveCharacterTextSplitter(
35
+ chunk_size=1000,
36
+ chunk_overlap=200
37
+ )
38
+ chunks = text_splitter.split_documents(docs)
39
+
40
+ print(f"πŸ’Ύ Indexing {len(chunks)} chunks into Vector Store...")
41
+ self.vector_store = FAISS.from_documents(chunks, self.embeddings)
42
+ print("βœ… Indexing Complete.")
43
+
44
+ def retrieve(self, query: str, k: int = 4) -> str:
45
+ """
46
+ Retrieves the top-k most relevant text chunks for a query.
47
+ Returns a single string of context.
48
+ """
49
+ if not self.vector_store:
50
+ return "No internal documents have been indexed."
51
+
52
+ docs = self.vector_store.similarity_search(query, k=k)
53
+
54
+ # Combine the content of the retrieved docs
55
+ context = "\n\n".join([f"[Source: Page {d.metadata.get('page', '?')}] {d.page_content}" for d in docs])
56
+ return context
tests/evaluate.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+ # Add project root to python path so we can import 'src'
4
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
5
+
6
+ from ragas import evaluate
7
+ from ragas.metrics import faithfulness, answer_relevance
8
+ from datasets import Dataset
9
+ from src.agent import get_agent_executor
10
+ from langchain_google_genai import ChatGoogleGenerativeAI, GoogleGenerativeAIEmbeddings
11
+
12
+ def run_evaluation():
13
+ print("πŸš€ Starting Evaluation Pipeline...")
14
+ agent = get_agent_executor()
15
+
16
+ # 1. Define Test Data
17
+ # Questions that cover both Internal Docs and External Search
18
+ questions = [
19
+ "What are the specific conditions for treaty termination?", # Should use Internal Tool
20
+ "What is the current Prime Minister of the UK?", # Should use Web Tool
21
+ ]
22
+
23
+ # The 'Ground Truth' answers (what we expect)
24
+ ground_truths = [
25
+ ["Treaties can be terminated by consent of all parties, material breach, or fundamental change of circumstances."],
26
+ ["Keir Starmer is the Prime Minister of the UK (as of late 2024)."]
27
+ ]
28
+
29
+ answers = []
30
+ contexts = []
31
+
32
+ # 2. Run the Agent on the questions
33
+ for q in questions:
34
+ print(f"Testing Question: {q}")
35
+ try:
36
+ result = agent.invoke({"input": q})
37
+ answers.append(result["output"])
38
+
39
+ # Extract retrieved context if available (simplified for this script)
40
+ # In a real scenario, we'd pull the actual text chunks from the tool output
41
+ contexts.append(["Context retrieved from agent tools..."])
42
+
43
+ except Exception as e:
44
+ print(f"Error on question '{q}': {e}")
45
+ answers.append("Error")
46
+ contexts.append([""])
47
+
48
+ # 3. Create Ragas Dataset
49
+ data = {
50
+ "question": questions,
51
+ "answer": answers,
52
+ "contexts": contexts,
53
+ "ground_truth": ground_truths
54
+ }
55
+ dataset = Dataset.from_dict(data)
56
+
57
+ # 4. Score it
58
+ # Note: Ragas metrics often require an LLM for judging.
59
+ # Ensure your GOOGLE_API_KEY supports the models Ragas tries to call.
60
+ print("πŸ“Š Calculating Metrics (Faithfulness, Relevance)...")
61
+
62
+ # Configure Ragas to use Gemini if OpenAI is not available
63
+ # (This part is complex in Ragas v0.1 without OpenAI,
64
+ # so we print the raw results for the MVP if metrics fail)
65
+ print("Evaluation Results:")
66
+ for i, q in enumerate(questions):
67
+ print(f"Q: {q}")
68
+ print(f"A: {answers[i]}")
69
+ print("-" * 20)
70
+
71
+ if __name__ == "__main__":
72
+ run_evaluation()