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,13 @@
1
+ from .config import RAGForgeConfig
2
+ from .core import RAGForge
3
+ from .rag.engine import RAGEngine
4
+ from .cache.user_manager import UserManager
5
+ from .cache.response_cache import ResponseCache
6
+
7
+ __all__ = [
8
+ "RAGForge",
9
+ "RAGForgeConfig",
10
+ "RAGEngine",
11
+ "UserManager",
12
+ "ResponseCache",
13
+ ]
@@ -0,0 +1 @@
1
+ # Admin Package Initialization
@@ -0,0 +1,224 @@
1
+ from fastapi import APIRouter, Header, HTTPException, Request
2
+
3
+ from ..models.schemas import (
4
+ UserResponse,
5
+ StatisticsResponse,
6
+ AllUsersResponse,
7
+ TopUsersResponse,
8
+ CacheInformationResponse
9
+ )
10
+
11
+
12
+ def get_admin_router(forge) -> APIRouter:
13
+ """
14
+ Admin Router Factory.
15
+ Builds the APIRouter instance dynamically, linking state dependencies to Request.app.state.
16
+ """
17
+ router = APIRouter(
18
+ prefix="/admin",
19
+ tags=["Admin"]
20
+ )
21
+
22
+ def verify_admin(request: Request, key: str):
23
+ config = request.app.state.config
24
+ if key != config.ADMIN_KEY:
25
+ raise HTTPException(
26
+ status_code=403,
27
+ detail="Invalid Admin Key."
28
+ )
29
+
30
+ # ==========================================================
31
+ # HOME
32
+ # ==========================================================
33
+
34
+ @router.get("/")
35
+ def admin_home(
36
+ request: Request,
37
+ x_admin_key: str = Header(...)
38
+ ):
39
+ verify_admin(request, x_admin_key)
40
+ return {
41
+ "message": "Admin API Running"
42
+ }
43
+
44
+ # ==========================================================
45
+ # ALL USERS
46
+ # ==========================================================
47
+
48
+ @router.get("/users", response_model=AllUsersResponse)
49
+ def get_all_users(
50
+ request: Request,
51
+ x_admin_key: str = Header(...)
52
+ ):
53
+ verify_admin(request, x_admin_key)
54
+ user_manager = request.app.state.user_manager
55
+ users = user_manager.get_all_users()
56
+
57
+ return {
58
+ "total_users": len(users),
59
+ "users": users
60
+ }
61
+
62
+ # ==========================================================
63
+ # SINGLE USER
64
+ # ==========================================================
65
+
66
+ @router.get("/user/{user_id}", response_model=UserResponse)
67
+ def get_user(
68
+ request: Request,
69
+ user_id: str,
70
+ x_admin_key: str = Header(...)
71
+ ):
72
+ verify_admin(request, x_admin_key)
73
+ user_manager = request.app.state.user_manager
74
+ user = user_manager.search_user(user_id)
75
+
76
+ if user is None:
77
+ raise HTTPException(
78
+ status_code=404,
79
+ detail="User not found."
80
+ )
81
+ return user
82
+
83
+ # ==========================================================
84
+ # RESET USER
85
+ # ==========================================================
86
+
87
+ @router.delete("/user/{user_id}")
88
+ def reset_user(
89
+ request: Request,
90
+ user_id: str,
91
+ x_admin_key: str = Header(...)
92
+ ):
93
+ verify_admin(request, x_admin_key)
94
+ user_manager = request.app.state.user_manager
95
+ success = user_manager.reset_user(user_id)
96
+
97
+ if not success:
98
+ raise HTTPException(
99
+ status_code=404,
100
+ detail="User not found."
101
+ )
102
+ return {
103
+ "message": "User reset successfully."
104
+ }
105
+
106
+ # ==========================================================
107
+ # RESET ALL USERS
108
+ # ==========================================================
109
+
110
+ @router.delete("/users")
111
+ def reset_all_users(
112
+ request: Request,
113
+ x_admin_key: str = Header(...)
114
+ ):
115
+ verify_admin(request, x_admin_key)
116
+ user_manager = request.app.state.user_manager
117
+ user_manager.reset_all()
118
+
119
+ return {
120
+ "message": "All users reset successfully."
121
+ }
122
+
123
+ # ==========================================================
124
+ # STATISTICS
125
+ # ==========================================================
126
+
127
+ @router.get("/stats", response_model=StatisticsResponse)
128
+ def statistics(
129
+ request: Request,
130
+ x_admin_key: str = Header(...)
131
+ ):
132
+ verify_admin(request, x_admin_key)
133
+ user_manager = request.app.state.user_manager
134
+
135
+ return user_manager.get_statistics()
136
+
137
+ # ==========================================================
138
+ # TOP USERS
139
+ # ==========================================================
140
+
141
+ @router.get("/top-users", response_model=TopUsersResponse)
142
+ def top_users(
143
+ request: Request,
144
+ limit: int = 10,
145
+ x_admin_key: str = Header(...)
146
+ ):
147
+ verify_admin(request, x_admin_key)
148
+ user_manager = request.app.state.user_manager
149
+
150
+ return {
151
+ "users": user_manager.get_top_users(limit)
152
+ }
153
+
154
+ # ==========================================================
155
+ # ACTIVE USERS
156
+ # ==========================================================
157
+
158
+ @router.get("/active-users", response_model=TopUsersResponse)
159
+ def active_users(
160
+ request: Request,
161
+ x_admin_key: str = Header(...)
162
+ ):
163
+ verify_admin(request, x_admin_key)
164
+ user_manager = request.app.state.user_manager
165
+
166
+ return {
167
+ "users": user_manager.active_users()
168
+ }
169
+
170
+ # ==========================================================
171
+ # CACHE INFO
172
+ # ==========================================================
173
+
174
+ @router.get("/cache", response_model=CacheInformationResponse)
175
+ def cache_information(
176
+ request: Request,
177
+ x_admin_key: str = Header(...)
178
+ ):
179
+ verify_admin(request, x_admin_key)
180
+ response_cache = request.app.state.response_cache
181
+
182
+ return {
183
+ "cached_responses": response_cache.total_cached_responses(),
184
+ "responses": response_cache.get_all()
185
+ }
186
+
187
+ # ==========================================================
188
+ # CLEAR RESPONSE CACHE
189
+ # ==========================================================
190
+
191
+ @router.delete("/cache")
192
+ def clear_cache(
193
+ request: Request,
194
+ x_admin_key: str = Header(...)
195
+ ):
196
+ verify_admin(request, x_admin_key)
197
+ response_cache = request.app.state.response_cache
198
+ response_cache.clear()
199
+
200
+ return {
201
+ "message": "Response cache cleared."
202
+ }
203
+
204
+ # ==========================================================
205
+ # RESET EVERYTHING
206
+ # ==========================================================
207
+
208
+ @router.delete("/reset")
209
+ def reset_everything(
210
+ request: Request,
211
+ x_admin_key: str = Header(...)
212
+ ):
213
+ verify_admin(request, x_admin_key)
214
+ user_manager = request.app.state.user_manager
215
+ response_cache = request.app.state.response_cache
216
+
217
+ user_manager.reset_all()
218
+ response_cache.clear()
219
+
220
+ return {
221
+ "message": "Users and response cache reset successfully."
222
+ }
223
+
224
+ return router
@@ -0,0 +1,88 @@
1
+ from fastapi import FastAPI, Request, HTTPException
2
+ from .models.schemas import ChatRequest, ChatResponse
3
+ from .admin.routes import get_admin_router
4
+
5
+ def create_app(forge) -> FastAPI:
6
+ """
7
+ App Factory.
8
+ Builds the FastAPI application at runtime and loads configuration and services
9
+ into app.state.
10
+ """
11
+ app = FastAPI(
12
+ title="RAGForge Framework Backend",
13
+ version="1.1.0"
14
+ )
15
+
16
+ # Attach orchestrator and configuration to app state
17
+ app.state.forge = forge
18
+ app.state.config = forge.config
19
+ app.state.user_manager = forge.user_manager
20
+ app.state.response_cache = forge.response_cache
21
+ app.state.engine = forge.engine
22
+
23
+ # Mount admin route router
24
+ app.include_router(get_admin_router(forge))
25
+
26
+ @app.get("/")
27
+ def home():
28
+ return {
29
+ "message": "🚀 RAGForge Framework Running Successfully"
30
+ }
31
+
32
+ @app.post("/chat", response_model=ChatResponse)
33
+ def chat(
34
+ data: ChatRequest,
35
+ request: Request
36
+ ):
37
+ prompt = data.prompt.strip()
38
+
39
+ # Resolve request IP (check proxy headers first)
40
+ ip = request.headers.get(
41
+ "X-Forwarded-For",
42
+ request.client.host
43
+ ).split(",")[0].strip()
44
+
45
+ # Load references from app.state
46
+ user_manager = request.app.state.user_manager
47
+ response_cache = request.app.state.response_cache
48
+ engine = request.app.state.engine
49
+ config = request.app.state.config
50
+
51
+ # 1. Register User Request
52
+ user_manager.register_request(ip)
53
+
54
+ # 2. Check Cache
55
+ cached = response_cache.get(ip, prompt)
56
+ if cached:
57
+ user = user_manager.get_user(ip)
58
+ return {
59
+ "success": True,
60
+ "cached": True,
61
+ "provider": "cache",
62
+ "remaining_questions": user["remaining"],
63
+ "answer": cached["answer"]
64
+ }
65
+
66
+ # 3. Check Rate Limit
67
+ allowed, user = user_manager.allow_request(ip)
68
+ if not allowed:
69
+ raise HTTPException(
70
+ status_code=429,
71
+ detail=f"Daily limit reached. ({user['llm_requests']}/{config.DAILY_LIMIT})"
72
+ )
73
+
74
+ # 4. Invoke RAG engine
75
+ answer = engine.chat(prompt, ip=ip)
76
+
77
+ # 5. Save response to cache
78
+ response_cache.save(prompt, answer)
79
+
80
+ return {
81
+ "success": True,
82
+ "cached": False,
83
+ "provider": "rag",
84
+ "remaining_questions": user["remaining"],
85
+ "answer": answer
86
+ }
87
+
88
+ return app
@@ -0,0 +1 @@
1
+ # Cache Package Initialization
@@ -0,0 +1,155 @@
1
+ from typing import Any, Dict, List, Optional
2
+ from diskcache import Cache
3
+
4
+
5
+ class Repository:
6
+ """
7
+ DiskCache Repository layer.
8
+ Manages low-level cache read, write, update, delete operations.
9
+ """
10
+ def __init__(self, cache_dir: str):
11
+ self.cache = Cache(cache_dir)
12
+
13
+ # ==========================================================
14
+ # BASIC CRUD
15
+ # ==========================================================
16
+
17
+ def get(self, key: str) -> Optional[Dict]:
18
+ return self.cache.get(key)
19
+
20
+ def set(self, key: str, value: Dict):
21
+ self.cache[key] = value
22
+
23
+ def exists(self, key: str) -> bool:
24
+ return key in self.cache
25
+
26
+ def delete(self, key: str):
27
+ self.cache.pop(key, None)
28
+
29
+ def clear(self):
30
+ self.cache.clear()
31
+
32
+ # ==========================================================
33
+ # ITERATORS
34
+ # ==========================================================
35
+
36
+ def keys(self) -> List[str]:
37
+ return list(self.cache.iterkeys())
38
+
39
+ def values(self) -> List[Dict]:
40
+ return [self.cache[key] for key in self.cache.iterkeys()]
41
+
42
+ def items(self):
43
+ return [
44
+ (key, self.cache[key])
45
+ for key in self.cache.iterkeys()
46
+ ]
47
+
48
+ # ==========================================================
49
+ # UPDATE
50
+ # ==========================================================
51
+
52
+ def update(self, key: str, **kwargs):
53
+ data = self.get(key)
54
+ if data is None:
55
+ return None
56
+ data.update(kwargs)
57
+ self.set(key, data)
58
+ return data
59
+
60
+ # ==========================================================
61
+ # NESTED UPDATE
62
+ # ==========================================================
63
+
64
+ def update_nested(
65
+ self,
66
+ key: str,
67
+ field: str,
68
+ nested_key: str,
69
+ value: Any,
70
+ ):
71
+ data = self.get(key)
72
+ if data is None:
73
+ return None
74
+ if field not in data:
75
+ data[field] = {}
76
+ data[field][nested_key] = value
77
+ self.set(key, data)
78
+ return data
79
+
80
+ # ==========================================================
81
+ # INCREMENT
82
+ # ==========================================================
83
+
84
+ def increment(
85
+ self,
86
+ key: str,
87
+ field: str,
88
+ amount: int = 1,
89
+ ):
90
+ data = self.get(key)
91
+ if data is None:
92
+ return None
93
+ data[field] += amount
94
+ self.set(key, data)
95
+ return data
96
+
97
+ # ==========================================================
98
+ # INCREMENT NESTED
99
+ # ==========================================================
100
+
101
+ def increment_nested(
102
+ self,
103
+ key: str,
104
+ field: str,
105
+ nested_key: str,
106
+ amount: int = 1,
107
+ ):
108
+ data = self.get(key)
109
+ if data is None:
110
+ return None
111
+ if field not in data:
112
+ data[field] = {}
113
+ if nested_key not in data[field]:
114
+ data[field][nested_key] = 0
115
+ data[field][nested_key] += amount
116
+ self.set(key, data)
117
+ return data
118
+
119
+ # ==========================================================
120
+ # SEARCH
121
+ # ==========================================================
122
+
123
+ def find(
124
+ self,
125
+ field: str,
126
+ value: Any,
127
+ ) -> Optional[Dict]:
128
+ for _, data in self.items():
129
+ if data.get(field) == value:
130
+ return data
131
+ return None
132
+
133
+ def find_key(
134
+ self,
135
+ field: str,
136
+ value: Any,
137
+ ) -> Optional[str]:
138
+ for key, data in self.items():
139
+ if data.get(field) == value:
140
+ return key
141
+ return None
142
+
143
+ # ==========================================================
144
+ # COUNT
145
+ # ==========================================================
146
+
147
+ def count(self):
148
+ return len(self.cache)
149
+
150
+ # ==========================================================
151
+ # EXPORT
152
+ # ==========================================================
153
+
154
+ def export(self):
155
+ return self.values()
@@ -0,0 +1,105 @@
1
+ from hashlib import sha256
2
+
3
+ from .repository import Repository
4
+ from .user_manager import UserManager
5
+
6
+
7
+ class ResponseCache:
8
+ """
9
+ Cache layer for persisting generated responses.
10
+ """
11
+ def __init__(self, config):
12
+ self.repository = Repository(config.RESPONSE_CACHE_DIR)
13
+ self.user_manager = UserManager(config)
14
+
15
+ # =====================================================
16
+ # PRIVATE
17
+ # =====================================================
18
+
19
+ def _key(self, prompt: str):
20
+ return sha256(
21
+ prompt.strip().lower().encode()
22
+ ).hexdigest()
23
+
24
+ # =====================================================
25
+ # GET
26
+ # =====================================================
27
+
28
+ def get(self, ip: str, prompt: str):
29
+ key = self._key(prompt)
30
+ response = self.repository.get(key)
31
+ if response:
32
+ self.user_manager.cache_hit(ip)
33
+ else:
34
+ self.user_manager.cache_miss(ip)
35
+ return response
36
+
37
+ # =====================================================
38
+ # SAVE
39
+ # =====================================================
40
+
41
+ def save(
42
+ self,
43
+ prompt: str,
44
+ answer: str
45
+ ):
46
+ key = self._key(prompt)
47
+ self.repository.set(
48
+ key,
49
+ {
50
+ "answer": answer
51
+ }
52
+ )
53
+
54
+ # =====================================================
55
+ # DELETE
56
+ # =====================================================
57
+
58
+ def delete(
59
+ self,
60
+ prompt: str
61
+ ):
62
+ key = self._key(prompt)
63
+ self.repository.delete(key)
64
+
65
+ # =====================================================
66
+ # CLEAR
67
+ # =====================================================
68
+
69
+ def clear(self):
70
+ self.repository.clear()
71
+
72
+ # =====================================================
73
+ # EXISTS
74
+ # =====================================================
75
+
76
+ def exists(
77
+ self,
78
+ prompt: str
79
+ ):
80
+ key = self._key(prompt)
81
+ return self.repository.exists(key)
82
+
83
+ # =====================================================
84
+ # TOTAL CACHE
85
+ # =====================================================
86
+
87
+ def total_cached_responses(self):
88
+ return self.repository.count()
89
+
90
+ # =====================================================
91
+ # ALL CACHE
92
+ # =====================================================
93
+
94
+ def get_all(self):
95
+ return self.repository.values()
96
+
97
+ # =====================================================
98
+ # DEBUG
99
+ # =====================================================
100
+
101
+ def __len__(self):
102
+ return self.repository.count()
103
+
104
+ def __repr__(self):
105
+ return f"<ResponseCache size={len(self)}>"