ragforge-framework 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ from langchain_classic.chains import create_retrieval_chain
2
+ from langchain_classic.chains.combine_documents import (
3
+ create_stuff_documents_chain,
4
+ )
5
+
6
+ from .prompt import get_prompt
7
+ from .retriever import get_retriever
8
+ from ..llm.service import LLMService
9
+
10
+
11
+ def get_chain(config):
12
+ """
13
+ Constructs the full LangChain document retrieval and query chain.
14
+ """
15
+ service = LLMService(config)
16
+ llm = service.get_langchain_model()
17
+
18
+ document_chain = create_stuff_documents_chain(
19
+ llm,
20
+ get_prompt()
21
+ )
22
+
23
+ rag_chain = create_retrieval_chain(
24
+ get_retriever(config),
25
+ document_chain
26
+ )
27
+
28
+ return rag_chain
@@ -0,0 +1,10 @@
1
+ from langchain_huggingface import HuggingFaceEmbeddings
2
+
3
+
4
+ def get_embeddings(config):
5
+ """
6
+ Instantiates HuggingFace Embeddings based on the configuration.
7
+ """
8
+ return HuggingFaceEmbeddings(
9
+ model_name=config.EMBEDDING_MODEL
10
+ )
@@ -0,0 +1,31 @@
1
+ from .chain import get_chain
2
+ from ..llm.gateway import current_ip_var
3
+
4
+
5
+ class RAGEngine:
6
+ """
7
+ RAG Execution Engine.
8
+ Executes LangChain query calls while logging execution state via contextvars.
9
+ """
10
+ def __init__(self, config):
11
+ self.config = config
12
+ self.chain = None
13
+
14
+ def chat(self, prompt: str, ip: str = None) -> str:
15
+ if self.chain is None:
16
+ self.chain = get_chain(self.config)
17
+
18
+ token = None
19
+ if ip:
20
+ token = current_ip_var.set(ip)
21
+
22
+ try:
23
+ response = self.chain.invoke(
24
+ {
25
+ "input": prompt
26
+ }
27
+ )
28
+ return response["answer"]
29
+ finally:
30
+ if token:
31
+ current_ip_var.reset(token)
@@ -0,0 +1,81 @@
1
+ import os
2
+ from langchain_community.document_loaders import TextLoader, PyPDFLoader
3
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
4
+
5
+ from .embeddings import get_embeddings
6
+ from .vectorstore import (
7
+ create_vectorstore,
8
+ save_vectorstore,
9
+ )
10
+
11
+
12
+ def _load_single_file(file_path: str):
13
+ """
14
+ Loads a single document file using the appropriate loader based on file extension.
15
+ """
16
+ ext = os.path.splitext(file_path)[1].lower()
17
+ try:
18
+ if ext == ".txt":
19
+ # Using TextLoader with UTF-8 encoding
20
+ return TextLoader(file_path, encoding="utf-8").load()
21
+ elif ext == ".pdf":
22
+ # Using PyPDFLoader for PDFs
23
+ return PyPDFLoader(file_path).load()
24
+ else:
25
+ print(f" [!] Skipping unsupported file extension: {file_path}")
26
+ return []
27
+ except Exception as e:
28
+ print(f" [!] Error loading file {file_path}: {e}")
29
+ return []
30
+
31
+
32
+ def _load_documents(path: str):
33
+ """
34
+ Scans and loads files from a directory recursively, or loads a single file directly.
35
+ """
36
+ if os.path.isdir(path):
37
+ documents = []
38
+ for root, _, files in os.walk(path):
39
+ for file in files:
40
+ full_path = os.path.join(root, file)
41
+ documents.extend(_load_single_file(full_path))
42
+ return documents
43
+ elif os.path.isfile(path):
44
+ return _load_single_file(path)
45
+ else:
46
+ raise FileNotFoundError(f"Path not found: {path}")
47
+
48
+
49
+ def ingest(config):
50
+ """
51
+ Ingests source text and PDF documents into the FAISS vector database.
52
+ """
53
+ print(f"\nLoading documents from {config.DATA_PATH}...")
54
+ documents = _load_documents(config.DATA_PATH)
55
+ print(f"Loaded {len(documents)} document page(s)/file(s).")
56
+
57
+ if not documents:
58
+ print(" [!] No valid text content was loaded. Ingestion skipped.")
59
+ return
60
+
61
+ print("\nSplitting documents...")
62
+ splitter = RecursiveCharacterTextSplitter(
63
+ chunk_size=config.CHUNK_SIZE,
64
+ chunk_overlap=config.CHUNK_OVERLAP
65
+ )
66
+ chunks = splitter.split_documents(documents)
67
+ print(f"Created {len(chunks)} chunks.")
68
+
69
+ print("\nLoading embedding model...")
70
+ embeddings = get_embeddings(config)
71
+
72
+ print("Creating FAISS index...")
73
+ vectorstore = create_vectorstore(
74
+ chunks,
75
+ embeddings
76
+ )
77
+
78
+ print("Saving FAISS index...")
79
+ save_vectorstore(vectorstore, config)
80
+
81
+ print("\nRAGForge ingestion completed successfully!")
@@ -0,0 +1,61 @@
1
+ from langchain_core.prompts import ChatPromptTemplate
2
+
3
+
4
+ def get_prompt():
5
+ """
6
+ Creates and returns the chat prompt template for document retrieval.
7
+ """
8
+ return ChatPromptTemplate.from_template(
9
+ """
10
+ You are the official AI assistant for Yashodeep Hundiwale.
11
+
12
+ Your purpose is to help visitors learn about Yashodeep by answering questions ONLY using the provided context.
13
+
14
+ You can answer questions about:
15
+
16
+ - Education
17
+ - Skills
18
+ - Technical expertise
19
+ - AI/ML projects
20
+ - Work experience
21
+ - Internships
22
+ - Certifications
23
+ - Achievements
24
+ - Portfolio
25
+ - Resume
26
+ - Contact information
27
+ - Career goals
28
+
29
+ Guidelines:
30
+
31
+ 1. Answer ONLY using the provided context.
32
+
33
+ 2. Never invent, guess or assume information.
34
+
35
+ 3. If the answer is unavailable in the context reply exactly:
36
+
37
+ "Sorry, I don't have that information about Yashodeep."
38
+
39
+ 4. Keep answers concise.
40
+
41
+ 5. Be professional.
42
+
43
+ 6. If the user greets you, greet them back naturally.
44
+
45
+ 7. If the question is unrelated to Yashodeep, reply:
46
+
47
+ "I'm designed to answer questions about Yashodeep Hundiwale and his portfolio."
48
+
49
+ <context>
50
+
51
+ {context}
52
+
53
+ </context>
54
+
55
+ Question:
56
+
57
+ {input}
58
+
59
+ Answer:
60
+ """
61
+ )
@@ -0,0 +1,19 @@
1
+ from .embeddings import get_embeddings
2
+ from .vectorstore import load_vectorstore
3
+
4
+
5
+ def get_retriever(config):
6
+ """
7
+ Builds the document retriever from the configured vector database.
8
+ """
9
+ embeddings = get_embeddings(config)
10
+ vectorstore = load_vectorstore(embeddings, config)
11
+
12
+ return vectorstore.as_retriever(
13
+ search_type=config.SEARCH_TYPE,
14
+ search_kwargs={
15
+ "k": config.TOP_K,
16
+ "fetch_k": config.FETCH_K,
17
+ "lambda_mult": config.LAMBDA_MULT
18
+ }
19
+ )
@@ -0,0 +1,31 @@
1
+ from langchain_community.vectorstores import FAISS
2
+
3
+
4
+ def create_vectorstore(documents, embeddings):
5
+ """
6
+ Creates a new FAISS vector database from document text chunks.
7
+ """
8
+ return FAISS.from_documents(
9
+ documents,
10
+ embeddings
11
+ )
12
+
13
+
14
+ def save_vectorstore(vectorstore, config):
15
+ """
16
+ Persists the vector index to local storage.
17
+ """
18
+ vectorstore.save_local(
19
+ config.FAISS_PATH
20
+ )
21
+
22
+
23
+ def load_vectorstore(embeddings, config):
24
+ """
25
+ Loads a FAISS vector index from local storage.
26
+ """
27
+ return FAISS.load_local(
28
+ config.FAISS_PATH,
29
+ embeddings,
30
+ allow_dangerous_deserialization=True
31
+ )
@@ -0,0 +1,148 @@
1
+ Metadata-Version: 2.4
2
+ Name: ragforge-framework
3
+ Version: 0.1.0
4
+ Summary: Modular AI Backend Framework built with FastAPI, LiteLLM and LangChain
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: diskcache>=5.6.3
7
+ Requires-Dist: faiss-cpu>=1.14.3
8
+ Requires-Dist: fastapi>=0.139.0
9
+ Requires-Dist: langchain-community>=0.4.2
10
+ Requires-Dist: langchain-google-genai>=4.2.7
11
+ Requires-Dist: langchain-groq>=1.1.3
12
+ Requires-Dist: langchain-huggingface>=1.2.2
13
+ Requires-Dist: langchain-litellm>=0.7.0
14
+ Requires-Dist: langchain-mistralai>=1.1.6
15
+ Requires-Dist: langchain-ollama>=1.1.0
16
+ Requires-Dist: langchain-openai>=1.3.5
17
+ Requires-Dist: langchain-redis>=0.2.5
18
+ Requires-Dist: langchain-text-splitters>=1.1.2
19
+ Requires-Dist: langchain>=1.3.12
20
+ Requires-Dist: langgraph>=1.2.9
21
+ Requires-Dist: litellm>=1.91.1
22
+ Requires-Dist: pandas>=3.0.3
23
+ Requires-Dist: pypdf>=6.14.2
24
+ Requires-Dist: python-dotenv>=1.2.2
25
+ Requires-Dist: sentence-transformers>=5.6.0
26
+ Requires-Dist: uvicorn>=0.51.0
27
+ Description-Content-Type: text/markdown
28
+
29
+ # 🚀 RAGForge Framework
30
+
31
+ ### Production-Ready AI Backend Framework built with FastAPI, LiteLLM and LangChain
32
+
33
+ RAGForge is a modular Python RAG (Retrieval-Augmented Generation) framework that helps developers build and deploy production-ready AI backends with built-in semantic caching, IP-based rate limiting, automatic LLM fallbacks, and comprehensive administrative APIs.
34
+
35
+ ---
36
+
37
+ ## 📖 Features
38
+
39
+ - **Retrieval-Augmented Generation (RAG):** Powered by LangChain, FAISS Vector Store, and HuggingFace Embeddings.
40
+ - **LiteLLM Gateway:** Connect to Ollama, Groq, Mistral, OpenAI, Gemini, etc. with automatic LLM fallback switching.
41
+ - **Semantic caching:** Persistent cache layers using `diskcache` to reduce latency and API costs.
42
+ - **IP-Based Rate Limiting:** Daily limit controls to prevent abuse.
43
+ - **Admin APIs:** Built-in endpoints for viewing user statistics, resetting cache, tracking LLM provider metrics, and clearing cache.
44
+ - **Unified CLI:** Simple CLI tool to initialize, build, and run applications.
45
+
46
+ ---
47
+
48
+ ## ⚙️ Installation
49
+
50
+ To install RAGForge locally from source:
51
+
52
+ ```bash
53
+ git clone https://github.com/yourusername/RAGForge.git
54
+ cd RAGForge
55
+ pip install -e .
56
+ ```
57
+
58
+ ---
59
+
60
+ ## 🚀 Quick Start (CLI Mode)
61
+
62
+ RAGForge includes a CLI executable to quickly manage projects.
63
+
64
+ ### 1. Initialize a new project
65
+
66
+ ```bash
67
+ ragforge init
68
+ ```
69
+
70
+ This generates a starter project structure:
71
+
72
+ - `data/data.txt`: Your source document repository.
73
+ - `app.py`: The entry point script configuring your framework.
74
+
75
+ ### 2. Ingest documents
76
+
77
+ Add your knowledge text database to `data/data.txt` and generate the vector index:
78
+
79
+ ```bash
80
+ ragforge ingest
81
+ ```
82
+
83
+ ### 3. Start the API server
84
+
85
+ ```bash
86
+ ragforge run --port 8000
87
+ ```
88
+
89
+ This launches a FastAPI server. Open [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs) in your browser to inspect the interactive Swagger API documentation.
90
+
91
+ ---
92
+
93
+ ## 🐍 Programmatic Usage
94
+
95
+ You can import and configure `RAGForge` inside any Python application:
96
+
97
+ ```python
98
+ from ragforge_framework import RAGForge
99
+
100
+ # 1. Initialize with custom configurations
101
+ forge = RAGForge(
102
+ data_path="data/data.txt",
103
+ faiss_path="faiss_index",
104
+ primary_model="ollama/llama3.2:latest",
105
+ fallback_models=["groq/llama-3.1-8b-instant"],
106
+ daily_limit=15,
107
+ admin_key="mysecretkey"
108
+ )
109
+
110
+ # Expose FastAPI application instance for uvicorn/gunicorn
111
+ app = forge.app
112
+
113
+ if __name__ == "__main__":
114
+ # 2. Run the application
115
+ forge.run(host="127.0.0.1", port=8000)
116
+ ```
117
+
118
+ ---
119
+
120
+ ## 🔧 Configuration Options
121
+
122
+ The `RAGForge` orchestrator accepts the following keyword arguments during instantiation:
123
+
124
+ | Parameter | Type | Default | Description |
125
+ | :------------------- | :----- | :----------------------------------- | :--------------------------------------------- |
126
+ | `primary_model` | `str` | `"ollama/llama3.2:latest"` | The primary LLM to query. |
127
+ | `fallback_models` | `list` | `["groq/llama-3.1-8b-instant", ...]` | Backup LLMs to trigger if primary fails. |
128
+ | `daily_limit` | `int` | `10` | Daily LLM request quota per IP. |
129
+ | `admin_key` | `str` | `"yashodeep"` | The secret key to secure `/admin` endpoints. |
130
+ | `data_path` | `str` | `"data/data.txt"` | Path to the source text file to index. |
131
+ | `faiss_path` | `str` | `"faiss_index"` | Path to save the FAISS vector database. |
132
+ | `embedding_model` | `str` | `"sentence-transformers/..."` | HuggingFace embedding model name. |
133
+ | `response_cache_dir` | `str` | `"./response_cache"` | Directory to store query cache. |
134
+ | `user_cache_dir` | `str` | `"./user_cache"` | Directory to store user rate limit statistics. |
135
+ | `chunk_size` | `int` | `800` | Size of split text chunks. |
136
+ | `chunk_overlap` | `int` | `100` | Text chunk overlap tokens. |
137
+
138
+ ---
139
+
140
+ ## 📊 Administration Endpoints
141
+
142
+ Secure administrative APIs are exposed under `/admin/` (requires `x-admin-key` header matching your `admin_key`):
143
+
144
+ - `GET /admin/users`: Lists stats for all registered users.
145
+ - `GET /admin/stats`: Exposes global metrics (total queries, cache hits, provider splits).
146
+ - `GET /admin/cache`: Inspects all currently cached queries and responses.
147
+ - `DELETE /admin/cache`: Clears the response cache.
148
+ - `DELETE /admin/reset`: Clears all caches and user request quotas.
@@ -0,0 +1,28 @@
1
+ ragforge_framework/__init__.py,sha256=7McZG9pq7Uf50gHLxI3pp7cdmFHcjizaG8QX-juHaNw,298
2
+ ragforge_framework/app.py,sha256=ByIxn9C7ML0wMg8avu9eH8iWRSlNZgMR2Z4zMkWBccU,2581
3
+ ragforge_framework/cli.py,sha256=1ViW9U3D9FyonDAdQiKLB9P9tenkw6xZZqJfbmMsX2s,3432
4
+ ragforge_framework/config.py,sha256=pgPA30F2RjLQ0_YdFNpgFEhqVt99paG_5LPM0Y_5Gfk,1766
5
+ ragforge_framework/core.py,sha256=pIbJz0hqmOMUsGvl4onlTjMv7ZfzWuz5vpj6OWJL078,1420
6
+ ragforge_framework/admin/__init__.py,sha256=ueJuO7uDQLSqgycAHg7vI27Hwuzv9r_e4XNwFVjanSo,31
7
+ ragforge_framework/admin/routes.py,sha256=J5jl5pEiQHq2qX-INpLdLkaubfYASqKSFDymTvdZrkY,6548
8
+ ragforge_framework/cache/__init__.py,sha256=phh4bDz18CO_ee3CPSaxl_2g-486SL8E7zM_Uq2oWEk,31
9
+ ragforge_framework/cache/repository.py,sha256=GHuo9XaYB3cLsgEDyD8f3Nfa6doBJ5GBP7mWgrSj6cQ,4070
10
+ ragforge_framework/cache/response_cache.py,sha256=XTSSgNSfTnR_Lalf7_pWesXdYn9ZJFsy0KnwnNf6ks4,2753
11
+ ragforge_framework/cache/user_manager.py,sha256=KYfqGSNNhZIJ6BQ18i8GvQYE4q7BkWesOaFLcfY0b5A,8499
12
+ ragforge_framework/llm/__init__.py,sha256=shJneb6VyTWc0xYtd5bQHY1qwkwsd9m0EEPR88Bwero,29
13
+ ragforge_framework/llm/gateway.py,sha256=n7yASnt7gf_YbfzuxteS4GIg46kC4Q92v7HtF9a_R3I,2675
14
+ ragforge_framework/llm/service.py,sha256=bKvRFnHKEN6M-UAcYRTsSh99-TyWMQ4WgKE8RtnuBuo,1497
15
+ ragforge_framework/models/__init__.py,sha256=lrhqx1ts0qG0CSFDGmndU3gM7IHg53VtO4J-ly2lMas,32
16
+ ragforge_framework/models/schemas.py,sha256=DViLeWGhv8N61yAMzKn0nOuh7RrK8iNvBiw9rrUjHJA,1211
17
+ ragforge_framework/rag/__init__.py,sha256=R6sTrirMw8Ic81opuSWxQS0xA_m_E85TEG6b2WtiVUU,29
18
+ ragforge_framework/rag/chain.py,sha256=1DNuEElD8nJ5CQp_Zrv3ZgSKxuHpFK4_kjEah4xOB4E,658
19
+ ragforge_framework/rag/embeddings.py,sha256=-qXGsX7mOiJYpfycezg75DWuS7x6cdtvQGvASEeNjcY,252
20
+ ragforge_framework/rag/engine.py,sha256=wOet9Vo7U9iCjVmJMk1yqU91KD3lKmExwZOuYRB_Gq8,779
21
+ ragforge_framework/rag/injest.py,sha256=K6SVqSPBr5AFk3HHd04SqyY_45mzSJ9H-g5gYW9nthk,2497
22
+ ragforge_framework/rag/prompt.py,sha256=NbHTSCnwSsHocNBOrz_eOdfd80M7gdTEhsLLAvRtZNA,1141
23
+ ragforge_framework/rag/retriever.py,sha256=3JZPG_SxNubWi37e-JrIRvtF4_nwsRIvUEQn6fI-GOw,526
24
+ ragforge_framework/rag/vectorstore.py,sha256=26K_e678eRTf40TPMcYouKLhMVKnmjyzLVgECnQfuTg,667
25
+ ragforge_framework-0.1.0.dist-info/METADATA,sha256=S9pI_3uR7t9QL9vZ66HO4mipuwPQpqbOAzFMqoiFV5s,5673
26
+ ragforge_framework-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
27
+ ragforge_framework-0.1.0.dist-info/entry_points.txt,sha256=AUkoOCwgNB81F0hpo3urqKxde8sDkO73S-ilhe7NVnI,57
28
+ ragforge_framework-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ragforge = ragforge_framework.cli:main