66- /rag/* - RAG retrieval and ingestion
77- /chatkit/* - ChatKit server (agent works within ChatKit)
88- /core/* - Shared utilities and config
9-
10- To integrate your ChatKit code:
11- 1. Copy your chatkit/ folder here
12- 2. Register router in this app
13- 3. Add search_tool from rag.tools to your agent (within ChatKit)
149"""
1510
11+ import json
1612import logging
17- from contextlib import asynccontextmanager
18- from fastapi import FastAPI
13+ from fastapi import FastAPI , Request , HTTPException , Header
1914from fastapi .middleware .cors import CORSMiddleware
15+ from fastapi .responses import Response , StreamingResponse
16+ from chatkit .server import StreamingResult
2017
2118# Import API routes
2219from api import search , health
20+ from lifespan import lifespan
21+ from chatkit_store import RequestContext
2322
2423# Simple logging
2524logging .basicConfig (level = logging .INFO )
2625logger = logging .getLogger (__name__ )
2726
2827
29- @asynccontextmanager
30- async def lifespan (app : FastAPI ):
31- """Lifespan manager - resilient to Qdrant failures."""
32- logger .info ("Starting RoboLearn Backend" )
33-
34- # Initialize RAG search client (non-blocking, fails gracefully)
35- try :
36- from rag .tools import get_search_client
37- client = get_search_client ()
38- if client is not None :
39- logger .info ("RAG search client initialized" )
40- else :
41- logger .warning ("RAG search client unavailable (Qdrant connection failed)" )
42- except Exception as e :
43- logger .warning (f"RAG initialization failed (non-critical): { e } " )
44-
45- yield
46-
47- logger .info ("Shutting down RoboLearn Backend" )
48-
49-
5028app = FastAPI (
5129 title = "RoboLearn Backend" ,
5230 description = "RAG search, ChatKit, and Agent integration for educational content" ,
@@ -67,23 +45,93 @@ async def lifespan(app: FastAPI):
6745app .include_router (search .router )
6846app .include_router (health .router )
6947
70- # Register ChatKit router (agent works within ChatKit)
71- _chatkit_registered = False
72- try :
73- from chatkit import router as chatkit_router
74- app .include_router (chatkit_router )
75- _chatkit_registered = True
76- logger .info ("ChatKit router registered" )
77- except ImportError :
78- logger .info ("ChatKit router not found (expected if not yet integrated)" )
79- except Exception as e :
80- logger .warning (f"Failed to register ChatKit router: { e } " )
48+
49+ def get_request_context (
50+ user_id : str = Header (..., alias = "X-User-ID" ),
51+ request_id : str | None = Header (None , alias = "X-Request-ID" ),
52+ ) -> RequestContext :
53+ """
54+ Extract request context from headers.
55+
56+ In production, you should validate authentication here.
57+ """
58+ return RequestContext (
59+ user_id = user_id ,
60+ request_id = request_id ,
61+ )
62+
63+
64+ @app .post ("/chatkit" )
65+ async def chatkit_endpoint (request : Request ):
66+ """
67+ Main ChatKit endpoint for conversational interaction.
68+
69+ Requires X-User-ID header for user identification.
70+ """
71+ payload = await request .body ()
72+ logger .debug (f"chatkit_endpoint received payload: { len (payload )} bytes" )
73+
74+ # Get server from app state
75+ chatkit_server = getattr (request .app .state , "chatkit_server" , None )
76+ if not chatkit_server :
77+ raise HTTPException (
78+ status_code = 503 ,
79+ detail = "ChatKit server not initialized. Check database configuration." ,
80+ )
81+
82+ # Extract user ID from header
83+ user_id = request .headers .get ("X-User-ID" )
84+ if not user_id :
85+ raise HTTPException (status_code = 401 , detail = "Missing X-User-ID header" )
86+
87+ try :
88+ # Process ChatKit request
89+ payload = await request .body ()
90+ # decode payload to dict from bytes and add in metadata
91+ payload_dict = json .loads (payload )
92+
93+ # Extract metadata from the correct location in ChatKit request
94+ metadata = {}
95+ if "params" in payload_dict and "input" in payload_dict ["params" ]:
96+ metadata = payload_dict ["params" ]["input" ].get ("metadata" , {})
97+
98+ logger .debug (f"Extracted metadata: { metadata } " )
99+
100+ # Create request context
101+ context = RequestContext (
102+ user_id = user_id ,
103+ request_id = request .headers .get ("X-Request-ID" ),
104+ metadata = metadata ,
105+ )
106+ result = await chatkit_server .process (payload , context )
107+
108+ # Return appropriate response type
109+ if isinstance (result , StreamingResult ):
110+ return StreamingResponse (
111+ result ,
112+ media_type = "text/event-stream" ,
113+ headers = {
114+ "Cache-Control" : "no-cache" ,
115+ "Connection" : "keep-alive" ,
116+ "X-Accel-Buffering" : "no" ,
117+ },
118+ )
119+ else :
120+ return Response (
121+ content = result .json ,
122+ media_type = "application/json" ,
123+ )
124+ except Exception as e :
125+ logger .exception (f"Error processing ChatKit request: { e } " )
126+ raise HTTPException (
127+ status_code = 500 , detail = f"Error processing request: { str (e )} "
128+ )
81129
82130
83131@app .get ("/" )
84132async def root ():
85133 """Root endpoint."""
86- chatkit_status = "✅ Active" if _chatkit_registered else "⏳ Not integrated "
134+ chatkit_status = "✅ Active" if hasattr ( app . state , "chatkit_server" ) and app . state . chatkit_server else "⏳ Not initialized "
87135 return {
88136 "service" : "RoboLearn Backend" ,
89137 "version" : "1.0.0" ,
@@ -94,11 +142,12 @@ async def root():
94142 "endpoints" : {
95143 "search" : "POST /search" ,
96144 "health" : "GET /health" ,
145+ "chatkit" : "POST /chatkit" ,
97146 },
98147 "tool" : {
99148 "name" : "search_robolearn_content" ,
100149 "import" : "from rag.tools import search_tool, SEARCH_TOOL_SCHEMA" ,
101- "note" : "Add to your agent within ChatKit " ,
150+ "note" : "Integrated with ChatKit agent" ,
102151 },
103152 }
104153
0 commit comments