SarahXia0405 commited on
Commit
0c571ff
·
verified ·
1 Parent(s): adf4467

Update api/server.py

Browse files
Files changed (1) hide show
  1. api/server.py +80 -51
api/server.py CHANGED
@@ -1,8 +1,7 @@
1
  # api/server.py
2
  import os
3
  import time
4
- import json
5
- from typing import Dict, List, Optional
6
 
7
  from fastapi import FastAPI, UploadFile, File, Form, Request
8
  from fastapi.responses import FileResponse, JSONResponse
@@ -23,12 +22,15 @@ from api.clare_core import (
23
  summarize_conversation,
24
  )
25
 
 
26
  try:
27
  from langsmith import Client
28
  except Exception:
29
  Client = None
30
 
31
-
 
 
32
  API_DIR = os.path.dirname(__file__)
33
 
34
  MODULE10_PATH = os.path.join(API_DIR, "module10_responsible_ai.pdf")
@@ -39,8 +41,13 @@ WEB_INDEX = os.path.join(WEB_DIST, "index.html")
39
  WEB_ASSETS = os.path.join(WEB_DIST, "assets")
40
 
41
  LS_DATASET_NAME = os.getenv("LS_DATASET_NAME", "clare_user_events").strip()
42
- LS_PROJECT = os.getenv("LANGSMITH_PROJECT", os.getenv("LANGCHAIN_PROJECT", "")).strip()
 
 
43
 
 
 
 
44
  app = FastAPI(title="Clare API")
45
 
46
  app.add_middleware(
@@ -51,6 +58,9 @@ app.add_middleware(
51
  allow_headers=["*"],
52
  )
53
 
 
 
 
54
  if os.path.isdir(WEB_ASSETS):
55
  app.mount("/assets", StaticFiles(directory=WEB_ASSETS), name="assets")
56
 
@@ -68,10 +78,13 @@ def index():
68
  )
69
 
70
 
71
- SESSIONS: Dict[str, Dict] = {}
 
 
 
72
 
73
 
74
- def _preload_module10_chunks():
75
  if os.path.exists(MODULE10_PATH):
76
  try:
77
  return build_rag_chunks_from_file(MODULE10_PATH, MODULE10_DOC_TYPE) or []
@@ -84,12 +97,12 @@ def _preload_module10_chunks():
84
  MODULE10_CHUNKS_CACHE = _preload_module10_chunks()
85
 
86
 
87
- def _get_session(user_id: str) -> Dict:
88
  if user_id not in SESSIONS:
89
  SESSIONS[user_id] = {
90
  "user_id": user_id,
91
  "name": "",
92
- "history": [],
93
  "weaknesses": [],
94
  "cognitive_state": {"confusion": 0, "mastery": 0},
95
  "course_outline": DEFAULT_COURSE_TOPICS,
@@ -99,6 +112,9 @@ def _get_session(user_id: str) -> Dict:
99
  return SESSIONS[user_id]
100
 
101
 
 
 
 
102
  _ls_client = None
103
  if Client is not None:
104
  try:
@@ -108,10 +124,12 @@ if Client is not None:
108
  _ls_client = None
109
 
110
 
111
- def _log_event_to_langsmith(data: Dict):
 
 
 
112
  if _ls_client is None:
113
  return
114
-
115
  try:
116
  inputs = {
117
  "question": data.get("question", ""),
@@ -134,6 +152,9 @@ def _log_event_to_langsmith(data: Dict):
134
  print("[langsmith] log failed:", repr(e))
135
 
136
 
 
 
 
137
  class LoginReq(BaseModel):
138
  name: str
139
  user_id: str
@@ -160,17 +181,23 @@ class SummaryReq(BaseModel):
160
 
161
  class FeedbackReq(BaseModel):
162
  user_id: str
163
- rating: str
164
  assistant_message_id: Optional[str] = None
 
165
  assistant_text: str
166
  user_text: Optional[str] = ""
 
167
  comment: Optional[str] = ""
 
168
  refs: Optional[List[str]] = []
169
  learning_mode: Optional[str] = None
170
  doc_type: Optional[str] = None
171
  timestamp_ms: Optional[int] = None
172
 
173
 
 
 
 
174
  @app.post("/api/login")
175
  def login(req: LoginReq):
176
  user_id = (req.user_id or "").strip()
@@ -185,9 +212,6 @@ def login(req: LoginReq):
185
 
186
  @app.post("/api/chat")
187
  def chat(req: ChatReq):
188
- t0 = time.time()
189
- marks = {"start": 0.0}
190
-
191
  user_id = (req.user_id or "").strip()
192
  msg = (req.message or "").strip()
193
  if not user_id:
@@ -203,25 +227,31 @@ def chat(req: ChatReq):
203
  ),
204
  "refs": [],
205
  "latency_ms": 0.0,
206
- "latency_breakdown": {"total_ms": 0.0, "marks_ms": {}, "segments_ms": {}},
207
  }
208
 
 
 
 
 
 
 
 
209
  resolved_lang = detect_language(msg, req.language_preference)
210
- marks["language_detect_done"] = (time.time() - t0) * 1000.0
211
 
 
212
  sess["weaknesses"] = update_weaknesses_from_message(msg, sess["weaknesses"])
213
- marks["weakness_update_done"] = (time.time() - t0) * 1000.0
214
 
 
215
  sess["cognitive_state"] = update_cognitive_state_from_message(msg, sess["cognitive_state"])
216
- marks["cognitive_update_done"] = (time.time() - t0) * 1000.0
217
 
 
218
  rag_context_text, rag_used_chunks = retrieve_relevant_chunks(msg, sess["rag_chunks"])
219
- marks["rag_retrieve_done"] = (time.time() - t0) * 1000.0
220
-
221
- rag_context_chars = len(rag_context_text or "")
222
- used_chunks_count = len(rag_used_chunks or [])
223
- history_len = len(sess.get("history") or [])
224
 
 
225
  try:
226
  answer, new_history = chat_with_clare(
227
  message=msg,
@@ -239,9 +269,19 @@ def chat(req: ChatReq):
239
  print(f"[chat] error: {repr(e)}")
240
  return JSONResponse({"error": f"chat failed: {repr(e)}"}, status_code=500)
241
 
242
- marks["llm_done"] = (time.time() - t0) * 1000.0
 
 
 
 
 
 
 
 
 
 
 
243
 
244
- total_ms = (time.time() - t0) * 1000.0
245
  sess["history"] = new_history
246
 
247
  refs = [
@@ -249,31 +289,24 @@ def chat(req: ChatReq):
249
  for c in (rag_used_chunks or [])
250
  ]
251
 
252
- # segments_ms:给你一眼看“每段耗时”
253
- marks_ms = dict(marks)
254
- segments_ms = {}
255
- order = ["start", "language_detect_done", "weakness_update_done", "cognitive_update_done", "rag_retrieve_done", "llm_done"]
256
- prev = 0.0
257
- for k in order[1:]:
258
- cur = marks_ms.get(k, prev)
259
- segments_ms[k] = max(0.0, cur - prev)
260
- prev = cur
261
-
262
- latency_breakdown = {
263
- "marks_ms": marks_ms,
264
- "segments_ms": segments_ms,
265
- "total_ms": total_ms,
266
- }
267
 
 
268
  _log_event_to_langsmith(
269
  {
270
- "experiment_id": "RESP_AI_W10",
271
  "student_id": user_id,
272
  "student_name": sess.get("name", ""),
273
  "event_type": "chat_turn",
274
  "timestamp": time.time(),
275
  "latency_ms": total_ms,
276
- "latency_breakdown": json.dumps(latency_breakdown, ensure_ascii=False),
 
 
 
277
  "question": msg,
278
  "answer": answer,
279
  "model_name": sess["model_name"],
@@ -281,9 +314,6 @@ def chat(req: ChatReq):
281
  "learning_mode": req.learning_mode,
282
  "doc_type": req.doc_type,
283
  "refs": refs,
284
- "history_len": history_len,
285
- "rag_context_chars": rag_context_chars,
286
- "rag_used_chunks_count": used_chunks_count,
287
  }
288
  )
289
 
@@ -294,10 +324,6 @@ def chat(req: ChatReq):
294
  ),
295
  "refs": refs,
296
  "latency_ms": total_ms,
297
- "latency_breakdown": latency_breakdown,
298
- "rag_context_chars": rag_context_chars,
299
- "rag_used_chunks_count": used_chunks_count,
300
- "history_len": history_len,
301
  }
302
 
303
 
@@ -345,7 +371,7 @@ async def upload(
345
 
346
  _log_event_to_langsmith(
347
  {
348
- "experiment_id": "RESP_AI_W10",
349
  "student_id": user_id,
350
  "student_name": sess.get("name", ""),
351
  "event_type": "upload",
@@ -376,7 +402,7 @@ def api_feedback(req: FeedbackReq):
376
 
377
  _log_event_to_langsmith(
378
  {
379
- "experiment_id": "RESP_AI_W10",
380
  "student_id": user_id,
381
  "student_name": student_name,
382
  "event_type": "feedback",
@@ -437,6 +463,9 @@ def memoryline(user_id: str):
437
  return {"next_review_label": "T+7", "progress_pct": 0.4}
438
 
439
 
 
 
 
440
  @app.get("/{full_path:path}")
441
  def spa_fallback(full_path: str, request: Request):
442
  if (
 
1
  # api/server.py
2
  import os
3
  import time
4
+ from typing import Dict, List, Optional, Any, Tuple
 
5
 
6
  from fastapi import FastAPI, UploadFile, File, Form, Request
7
  from fastapi.responses import FileResponse, JSONResponse
 
22
  summarize_conversation,
23
  )
24
 
25
+ # ✅ LangSmith
26
  try:
27
  from langsmith import Client
28
  except Exception:
29
  Client = None
30
 
31
+ # ----------------------------
32
+ # Paths / Constants
33
+ # ----------------------------
34
  API_DIR = os.path.dirname(__file__)
35
 
36
  MODULE10_PATH = os.path.join(API_DIR, "module10_responsible_ai.pdf")
 
41
  WEB_ASSETS = os.path.join(WEB_DIST, "assets")
42
 
43
  LS_DATASET_NAME = os.getenv("LS_DATASET_NAME", "clare_user_events").strip()
44
+ LS_PROJECT = os.getenv("LANGSMITH_PROJECT", os.getenv("LANGCHAIN_PROJECT", "")).strip() # optional
45
+
46
+ EXPERIMENT_ID = os.getenv("CLARE_EXPERIMENT_ID", "RESP_AI_W10").strip()
47
 
48
+ # ----------------------------
49
+ # App
50
+ # ----------------------------
51
  app = FastAPI(title="Clare API")
52
 
53
  app.add_middleware(
 
58
  allow_headers=["*"],
59
  )
60
 
61
+ # ----------------------------
62
+ # Static hosting (Vite build)
63
+ # ----------------------------
64
  if os.path.isdir(WEB_ASSETS):
65
  app.mount("/assets", StaticFiles(directory=WEB_ASSETS), name="assets")
66
 
 
78
  )
79
 
80
 
81
+ # ----------------------------
82
+ # In-memory session store (MVP)
83
+ # ----------------------------
84
+ SESSIONS: Dict[str, Dict[str, Any]] = {}
85
 
86
 
87
+ def _preload_module10_chunks() -> List[Dict[str, Any]]:
88
  if os.path.exists(MODULE10_PATH):
89
  try:
90
  return build_rag_chunks_from_file(MODULE10_PATH, MODULE10_DOC_TYPE) or []
 
97
  MODULE10_CHUNKS_CACHE = _preload_module10_chunks()
98
 
99
 
100
+ def _get_session(user_id: str) -> Dict[str, Any]:
101
  if user_id not in SESSIONS:
102
  SESSIONS[user_id] = {
103
  "user_id": user_id,
104
  "name": "",
105
+ "history": [], # List[Tuple[str, str]]
106
  "weaknesses": [],
107
  "cognitive_state": {"confusion": 0, "mastery": 0},
108
  "course_outline": DEFAULT_COURSE_TOPICS,
 
112
  return SESSIONS[user_id]
113
 
114
 
115
+ # ----------------------------
116
+ # LangSmith helpers
117
+ # ----------------------------
118
  _ls_client = None
119
  if Client is not None:
120
  try:
 
124
  _ls_client = None
125
 
126
 
127
+ def _log_event_to_langsmith(data: Dict[str, Any]):
128
+ """
129
+ Create an Example in LangSmith Dataset.
130
+ """
131
  if _ls_client is None:
132
  return
 
133
  try:
134
  inputs = {
135
  "question": data.get("question", ""),
 
152
  print("[langsmith] log failed:", repr(e))
153
 
154
 
155
+ # ----------------------------
156
+ # Schemas
157
+ # ----------------------------
158
  class LoginReq(BaseModel):
159
  name: str
160
  user_id: str
 
181
 
182
  class FeedbackReq(BaseModel):
183
  user_id: str
184
+ rating: str # "helpful" | "not_helpful"
185
  assistant_message_id: Optional[str] = None
186
+
187
  assistant_text: str
188
  user_text: Optional[str] = ""
189
+
190
  comment: Optional[str] = ""
191
+
192
  refs: Optional[List[str]] = []
193
  learning_mode: Optional[str] = None
194
  doc_type: Optional[str] = None
195
  timestamp_ms: Optional[int] = None
196
 
197
 
198
+ # ----------------------------
199
+ # API Routes
200
+ # ----------------------------
201
  @app.post("/api/login")
202
  def login(req: LoginReq):
203
  user_id = (req.user_id or "").strip()
 
212
 
213
  @app.post("/api/chat")
214
  def chat(req: ChatReq):
 
 
 
215
  user_id = (req.user_id or "").strip()
216
  msg = (req.message or "").strip()
217
  if not user_id:
 
227
  ),
228
  "refs": [],
229
  "latency_ms": 0.0,
 
230
  }
231
 
232
+ # ----------------------------
233
+ # Latency breakdown marks (ms)
234
+ # ----------------------------
235
+ t0 = time.time()
236
+ marks_ms: Dict[str, float] = {"start": 0.0}
237
+
238
+ # language detect
239
  resolved_lang = detect_language(msg, req.language_preference)
240
+ marks_ms["language_detect_done"] = (time.time() - t0) * 1000.0
241
 
242
+ # weakness update
243
  sess["weaknesses"] = update_weaknesses_from_message(msg, sess["weaknesses"])
244
+ marks_ms["weakness_update_done"] = (time.time() - t0) * 1000.0
245
 
246
+ # cognitive update
247
  sess["cognitive_state"] = update_cognitive_state_from_message(msg, sess["cognitive_state"])
248
+ marks_ms["cognitive_update_done"] = (time.time() - t0) * 1000.0
249
 
250
+ # rag retrieve
251
  rag_context_text, rag_used_chunks = retrieve_relevant_chunks(msg, sess["rag_chunks"])
252
+ marks_ms["rag_retrieve_done"] = (time.time() - t0) * 1000.0
 
 
 
 
253
 
254
+ # llm
255
  try:
256
  answer, new_history = chat_with_clare(
257
  message=msg,
 
269
  print(f"[chat] error: {repr(e)}")
270
  return JSONResponse({"error": f"chat failed: {repr(e)}"}, status_code=500)
271
 
272
+ marks_ms["llm_done"] = (time.time() - t0) * 1000.0
273
+ total_ms = marks_ms["llm_done"]
274
+
275
+ # segments (delta)
276
+ ordered = ["start", "language_detect_done", "weakness_update_done", "cognitive_update_done", "rag_retrieve_done", "llm_done"]
277
+ segments_ms: Dict[str, float] = {}
278
+ for i in range(1, len(ordered)):
279
+ a = ordered[i - 1]
280
+ b = ordered[i]
281
+ segments_ms[b] = max(0.0, marks_ms.get(b, 0.0) - marks_ms.get(a, 0.0))
282
+
283
+ latency_breakdown = {"marks_ms": marks_ms, "segments_ms": segments_ms, "total_ms": total_ms}
284
 
 
285
  sess["history"] = new_history
286
 
287
  refs = [
 
289
  for c in (rag_used_chunks or [])
290
  ]
291
 
292
+ # extra metadata fields
293
+ rag_context_chars = len(rag_context_text or "")
294
+ rag_used_chunks_count = len(rag_used_chunks or [])
295
+ history_len = len(sess["history"])
 
 
 
 
 
 
 
 
 
 
 
296
 
297
+ # ✅ log chat_turn to LangSmith
298
  _log_event_to_langsmith(
299
  {
300
+ "experiment_id": EXPERIMENT_ID,
301
  "student_id": user_id,
302
  "student_name": sess.get("name", ""),
303
  "event_type": "chat_turn",
304
  "timestamp": time.time(),
305
  "latency_ms": total_ms,
306
+ "latency_breakdown": latency_breakdown,
307
+ "rag_context_chars": rag_context_chars,
308
+ "rag_used_chunks_count": rag_used_chunks_count,
309
+ "history_len": history_len,
310
  "question": msg,
311
  "answer": answer,
312
  "model_name": sess["model_name"],
 
314
  "learning_mode": req.learning_mode,
315
  "doc_type": req.doc_type,
316
  "refs": refs,
 
 
 
317
  }
318
  )
319
 
 
324
  ),
325
  "refs": refs,
326
  "latency_ms": total_ms,
 
 
 
 
327
  }
328
 
329
 
 
371
 
372
  _log_event_to_langsmith(
373
  {
374
+ "experiment_id": EXPERIMENT_ID,
375
  "student_id": user_id,
376
  "student_name": sess.get("name", ""),
377
  "event_type": "upload",
 
402
 
403
  _log_event_to_langsmith(
404
  {
405
+ "experiment_id": EXPERIMENT_ID,
406
  "student_id": user_id,
407
  "student_name": student_name,
408
  "event_type": "feedback",
 
463
  return {"next_review_label": "T+7", "progress_pct": 0.4}
464
 
465
 
466
+ # ----------------------------
467
+ # SPA Fallback
468
+ # ----------------------------
469
  @app.get("/{full_path:path}")
470
  def spa_fallback(full_path: str, request: Request):
471
  if (