1919from .services .embedding .schemas import EmbedRequest , EmbedResponse , QueryRequest , QueryResponse , DocumentResult
2020from .services .embedding .weaviate_service import get_weaviate_client , ensure_schema_exists , DOCUMENT_CLASS_NAME
2121from .services .llm import llm_service
22- from .services .llm .schemas import GenerateRequest , GenerateResponse
23- from .utils .error_schema import ErrorResponse
22+ from .services .llm .schemas import GenerateRequest , GenerateResponse
23+ from .services .rag .schemas import CourseGenerationRequest , Course
24+ from .services .rag import course_generator
25+ from .utils .error_schema import ErrorResponse
2426from .utils .handle_httpx_exception import handle_httpx_exception
2527
26-
2728# --- Configuration ---
2829load_dotenv ()
2930logger = logging .getLogger ("skillforge.genai" )
3031
3132APP_PORT = int (os .getenv ("GENAI_PORT" , "8082" ))
3233APP_TITLE = os .getenv ("GENAI_APP_NAME" , "SkillForge GenAI Service" )
3334APP_VERSION = os .getenv ("GENAI_APP_VERSION" , "0.0.1" )
34- APP_DESCRIPTION = (
35- "SkillForge GenAI Service provides endpoints for web crawling, "
36- "chunking, embedding, semantic querying, and text generation using LLMs. "
37- "Ideal for integrating vector search and AI-driven workflows."
38- )
35+ APP_DESCRIPTION = (
36+ "SkillForge GenAI Service provides endpoints for web crawling, "
37+ "chunking, embedding, semantic querying, and text generation using LLMs. "
38+ "Ideal for integrating vector search and AI-driven workflows."
39+ )
40+ API_PREFIX = "/api/v1"
3941TAGS_METADATA = [
4042 {"name" : "System" , "description" : "Health checks and system status." },
4143 {"name" : "Crawler" , "description" : "Crawl and clean website content." },
@@ -110,7 +112,7 @@ async def unhandled_exception_handler(request: Request, exc: Exception):
110112
111113# ---- System Endpoints --------
112114# -------------------------------
113- @app .get (" /health" , tags = ["System" ])
115+ @app .get (f" { API_PREFIX } /health" , tags = ["System" ])
114116async def health ():
115117 """
116118 Deep health check. Verifies the application and its core dependencies (e.g., DB, vector store).
@@ -126,7 +128,7 @@ async def health():
126128 content = {"status" : "error" , "message" : "Dependency failure. See logs for details." }
127129 )
128130
129- @app .get (" /ping" , tags = ["System" ])
131+ @app .get (f" { API_PREFIX } /ping" , tags = ["System" ])
130132async def ping ():
131133 """
132134 Lightweight liveness check. Confirms the API process is running, but does not check dependencies.
@@ -139,7 +141,7 @@ async def ping():
139141# -------------------------------
140142# ----- Crawler endpoints -----
141143# -------------------------------
142- @app .post (" /crawl" , response_model = CrawlResponse , responses = {400 : {"model" : ErrorResponse }, 500 : {"model" : ErrorResponse }}, tags = ["Crawler" ])
144+ @app .post (f" { API_PREFIX } /crawl" , response_model = CrawlResponse , responses = {400 : {"model" : ErrorResponse }, 500 : {"model" : ErrorResponse }}, tags = ["Crawler" ])
143145async def crawl (request : CrawlRequest ):
144146 url = str (request .url )
145147 try :
@@ -174,7 +176,7 @@ async def crawl(request: CrawlRequest):
174176# -------------------------------
175177# ----- Vector DB endpoints -----
176178# -------------------------------
177- @app .post (" /embed" , response_model = EmbedResponse , tags = ["Embedder" ])
179+ @app .post (f" { API_PREFIX } /embed" , response_model = EmbedResponse , tags = ["Embedder" ])
178180async def embed_url (request : EmbedRequest ):
179181 """Orchestrates the full workflow: Crawl -> Chunk -> Embed -> Store."""
180182 url_str = str (request .url )
@@ -209,7 +211,7 @@ async def embed_url(request: EmbedRequest):
209211
210212
211213
212- @app .post (" /query" , response_model = QueryResponse )
214+ @app .post (f" { API_PREFIX } /query" , response_model = QueryResponse )
213215async def query_vector_db (request : QueryRequest ):
214216 """Queries the vector database for text chunks semantically similar to the query."""
215217 client = get_weaviate_client ()
@@ -231,7 +233,7 @@ async def query_vector_db(request: QueryRequest):
231233# -------------------------------
232234# --- LLM Endpoints -------------
233235# -------------------------------
234- @app .post (" /generate" , response_model = GenerateResponse , tags = ["LLM" ])
236+ @app .post (f" { API_PREFIX } /generate" , response_model = GenerateResponse , tags = ["LLM" ])
235237async def generate_completion (request : GenerateRequest ):
236238 """Generates a text completion using the configured LLM abstraction layer."""
237239 try :
@@ -245,7 +247,19 @@ async def generate_completion(request: GenerateRequest):
245247 logging .error (f"ERROR during text generation: { e } " )
246248 raise HTTPException (status_code = 500 , detail = f"Failed to generate text: { str (e )} " )
247249
248-
250+ # ──────────────────────────────────────────────────────────────────────────
251+ # NEW – main RAG endpoint
252+ # ──────────────────────────────────────────────────────────────────────────
253+ @app .post (f"{ API_PREFIX } /rag/generate-course" , response_model = Course , tags = ["rag" ])
254+ async def generate_course (req : CourseGenerationRequest ):
255+ """
256+ • POST because generation is a side-effectful operation (non-idempotent).
257+ • Returns a fully-validated Course JSON ready for the course-service.
258+ """
259+ try :
260+ return course_generator .generate_course (req )
261+ except Exception as e :
262+ raise HTTPException (500 , str (e )) from e
249263
250264# -------------------------------
251265# --------- MAIN ----------------
0 commit comments