SarahXia0405 commited on
Commit
941d25c
·
verified ·
1 Parent(s): 4ddc6e7

Update api/server.py

Browse files
Files changed (1) hide show
  1. api/server.py +90 -7
api/server.py CHANGED
@@ -58,9 +58,13 @@ WARMUP_STARTED = False
58
  CLARE_ENABLE_WARMUP = os.getenv("CLARE_ENABLE_WARMUP", "1").strip() == "1"
59
  CLARE_WARMUP_BLOCK_READY = os.getenv("CLARE_WARMUP_BLOCK_READY", "0").strip() == "1"
60
 
 
61
  CLARE_ENABLE_LANGSMITH_LOG = os.getenv("CLARE_ENABLE_LANGSMITH_LOG", "0").strip() == "1"
62
  CLARE_LANGSMITH_ASYNC = os.getenv("CLARE_LANGSMITH_ASYNC", "1").strip() == "1"
63
 
 
 
 
64
  # ----------------------------
65
  # App
66
  # ----------------------------
@@ -172,6 +176,9 @@ if (Client is not None) and CLARE_ENABLE_LANGSMITH_LOG:
172
 
173
 
174
  def _log_event_to_langsmith(data: Dict[str, Any]):
 
 
 
175
  if _ls_client is None:
176
  return
177
 
@@ -205,6 +212,50 @@ def _log_event_to_langsmith(data: Dict[str, Any]):
205
  _do()
206
 
207
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  # ----------------------------
209
  # Health endpoints
210
  # ----------------------------
@@ -220,6 +271,7 @@ def health():
220
  "ready": bool(WARMUP_DONE) if CLARE_WARMUP_BLOCK_READY else True,
221
  "langsmith_enabled": bool(CLARE_ENABLE_LANGSMITH_LOG),
222
  "langsmith_async": bool(CLARE_LANGSMITH_ASYNC),
 
223
  "ts": int(time.time()),
224
  }
225
 
@@ -264,9 +316,6 @@ MICRO_QUIZ_INSTRUCTION = (
264
  )
265
 
266
 
267
-
268
-
269
-
270
  # ----------------------------
271
  # Schemas
272
  # ----------------------------
@@ -282,6 +331,7 @@ class ChatReq(BaseModel):
282
  language_preference: str = "Auto"
283
  doc_type: str = "Syllabus"
284
 
 
285
  class QuizStartReq(BaseModel):
286
  user_id: str
287
  language_preference: str = "Auto"
@@ -307,6 +357,10 @@ class FeedbackReq(BaseModel):
307
 
308
  user_id: str
309
  rating: str # "helpful" | "not_helpful"
 
 
 
 
310
  assistant_message_id: Optional[str] = None
311
 
312
  assistant_text: str
@@ -355,6 +409,7 @@ def chat(req: ChatReq):
355
  ),
356
  "refs": [],
357
  "latency_ms": 0.0,
 
358
  }
359
 
360
  t0 = time.time()
@@ -376,7 +431,7 @@ def chat(req: ChatReq):
376
  marks_ms["rag_retrieve_done"] = (time.time() - t0) * 1000.0
377
 
378
  try:
379
- answer, new_history = chat_with_clare(
380
  message=msg,
381
  history=sess["history"],
382
  model_name=sess["model_name"],
@@ -441,6 +496,7 @@ def chat(req: ChatReq):
441
  "learning_mode": req.learning_mode,
442
  "doc_type": req.doc_type,
443
  "refs": refs,
 
444
  }
445
  )
446
 
@@ -451,8 +507,10 @@ def chat(req: ChatReq):
451
  ),
452
  "refs": refs,
453
  "latency_ms": total_ms,
 
454
  }
455
 
 
456
  @app.post("/api/quiz/start")
457
  def quiz_start(req: QuizStartReq):
458
  user_id = (req.user_id or "").strip()
@@ -475,7 +533,7 @@ def quiz_start(req: QuizStartReq):
475
  )
476
 
477
  try:
478
- answer, new_history = chat_with_clare(
479
  message=quiz_instruction,
480
  history=sess["history"], # 直接接在当前会话 history 后面
481
  model_name=sess["model_name"],
@@ -518,6 +576,7 @@ def quiz_start(req: QuizStartReq):
518
  "refs": refs,
519
  "rag_used_chunks_count": len(rag_used_chunks or []),
520
  "history_len": len(sess["history"]),
 
521
  }
522
  )
523
 
@@ -528,6 +587,7 @@ def quiz_start(req: QuizStartReq):
528
  ),
529
  "refs": refs,
530
  "latency_ms": total_ms,
 
531
  }
532
 
533
 
@@ -612,6 +672,7 @@ def api_feedback(req: FeedbackReq):
612
  tags = req.tags or []
613
  timestamp_ms = int(req.timestamp_ms or int(time.time() * 1000))
614
 
 
615
  _log_event_to_langsmith(
616
  {
617
  "experiment_id": EXPERIMENT_ID,
@@ -622,6 +683,7 @@ def api_feedback(req: FeedbackReq):
622
  "timestamp_ms": timestamp_ms,
623
  "rating": rating,
624
  "assistant_message_id": req.assistant_message_id,
 
625
 
626
  # Keep the Example readable:
627
  "question": user_text, # what user asked (optional)
@@ -636,7 +698,28 @@ def api_feedback(req: FeedbackReq):
636
  }
637
  )
638
 
639
- return {"ok": True}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
640
 
641
 
642
  @app.post("/api/export")
@@ -698,4 +781,4 @@ def spa_fallback(full_path: str, request: Request):
698
  return JSONResponse(
699
  {"detail": "web/build not found. Build frontend first (web/build/index.html)."},
700
  status_code=500,
701
- )
 
58
  CLARE_ENABLE_WARMUP = os.getenv("CLARE_ENABLE_WARMUP", "1").strip() == "1"
59
  CLARE_WARMUP_BLOCK_READY = os.getenv("CLARE_WARMUP_BLOCK_READY", "0").strip() == "1"
60
 
61
+ # Dataset logging (create_example)
62
  CLARE_ENABLE_LANGSMITH_LOG = os.getenv("CLARE_ENABLE_LANGSMITH_LOG", "0").strip() == "1"
63
  CLARE_LANGSMITH_ASYNC = os.getenv("CLARE_LANGSMITH_ASYNC", "1").strip() == "1"
64
 
65
+ # Feedback logging (create_feedback -> attach to run_id)
66
+ CLARE_ENABLE_LANGSMITH_FEEDBACK = os.getenv("CLARE_ENABLE_LANGSMITH_FEEDBACK", "1").strip() == "1"
67
+
68
  # ----------------------------
69
  # App
70
  # ----------------------------
 
176
 
177
 
178
  def _log_event_to_langsmith(data: Dict[str, Any]):
179
+ """
180
+ Dataset logging: create_example into LS_DATASET_NAME
181
+ """
182
  if _ls_client is None:
183
  return
184
 
 
212
  _do()
213
 
214
 
215
+ def _write_feedback_to_langsmith_run(
216
+ run_id: str,
217
+ rating: str,
218
+ comment: str = "",
219
+ tags: Optional[List[str]] = None,
220
+ metadata: Optional[Dict[str, Any]] = None,
221
+ ) -> bool:
222
+ """
223
+ Run-level feedback: create_feedback attached to a specific run_id.
224
+ This is separate from dataset create_example logging.
225
+ """
226
+ if not CLARE_ENABLE_LANGSMITH_FEEDBACK:
227
+ return False
228
+ if Client is None:
229
+ return False
230
+
231
+ rid = (run_id or "").strip()
232
+ if not rid:
233
+ return False
234
+
235
+ try:
236
+ ls = Client()
237
+ score = 1 if rating == "helpful" else 0
238
+
239
+ meta = metadata or {}
240
+ if tags is not None:
241
+ meta["tags"] = tags
242
+
243
+ if LS_PROJECT:
244
+ meta.setdefault("langsmith_project", LS_PROJECT)
245
+
246
+ ls.create_feedback(
247
+ run_id=rid,
248
+ key="ui_rating",
249
+ score=score,
250
+ comment=comment or "",
251
+ metadata=meta,
252
+ )
253
+ return True
254
+ except Exception as e:
255
+ print("[langsmith] create_feedback failed:", repr(e))
256
+ return False
257
+
258
+
259
  # ----------------------------
260
  # Health endpoints
261
  # ----------------------------
 
271
  "ready": bool(WARMUP_DONE) if CLARE_WARMUP_BLOCK_READY else True,
272
  "langsmith_enabled": bool(CLARE_ENABLE_LANGSMITH_LOG),
273
  "langsmith_async": bool(CLARE_LANGSMITH_ASYNC),
274
+ "langsmith_feedback_enabled": bool(CLARE_ENABLE_LANGSMITH_FEEDBACK),
275
  "ts": int(time.time()),
276
  }
277
 
 
316
  )
317
 
318
 
 
 
 
319
  # ----------------------------
320
  # Schemas
321
  # ----------------------------
 
331
  language_preference: str = "Auto"
332
  doc_type: str = "Syllabus"
333
 
334
+
335
  class QuizStartReq(BaseModel):
336
  user_id: str
337
  language_preference: str = "Auto"
 
357
 
358
  user_id: str
359
  rating: str # "helpful" | "not_helpful"
360
+
361
+ # NEW: attach feedback to a specific LangSmith run
362
+ run_id: Optional[str] = None
363
+
364
  assistant_message_id: Optional[str] = None
365
 
366
  assistant_text: str
 
409
  ),
410
  "refs": [],
411
  "latency_ms": 0.0,
412
+ "run_id": None,
413
  }
414
 
415
  t0 = time.time()
 
431
  marks_ms["rag_retrieve_done"] = (time.time() - t0) * 1000.0
432
 
433
  try:
434
+ answer, new_history, run_id = chat_with_clare(
435
  message=msg,
436
  history=sess["history"],
437
  model_name=sess["model_name"],
 
496
  "learning_mode": req.learning_mode,
497
  "doc_type": req.doc_type,
498
  "refs": refs,
499
+ "run_id": run_id, # NEW: keep in dataset metadata for debugging
500
  }
501
  )
502
 
 
507
  ),
508
  "refs": refs,
509
  "latency_ms": total_ms,
510
+ "run_id": run_id, # NEW: FE attaches feedback to this run
511
  }
512
 
513
+
514
  @app.post("/api/quiz/start")
515
  def quiz_start(req: QuizStartReq):
516
  user_id = (req.user_id or "").strip()
 
533
  )
534
 
535
  try:
536
+ answer, new_history, run_id = chat_with_clare(
537
  message=quiz_instruction,
538
  history=sess["history"], # 直接接在当前会话 history 后面
539
  model_name=sess["model_name"],
 
576
  "refs": refs,
577
  "rag_used_chunks_count": len(rag_used_chunks or []),
578
  "history_len": len(sess["history"]),
579
+ "run_id": run_id, # NEW
580
  }
581
  )
582
 
 
587
  ),
588
  "refs": refs,
589
  "latency_ms": total_ms,
590
+ "run_id": run_id, # NEW
591
  }
592
 
593
 
 
672
  tags = req.tags or []
673
  timestamp_ms = int(req.timestamp_ms or int(time.time() * 1000))
674
 
675
+ # 1) Dataset event log (what you already have)
676
  _log_event_to_langsmith(
677
  {
678
  "experiment_id": EXPERIMENT_ID,
 
683
  "timestamp_ms": timestamp_ms,
684
  "rating": rating,
685
  "assistant_message_id": req.assistant_message_id,
686
+ "run_id": req.run_id, # NEW
687
 
688
  # Keep the Example readable:
689
  "question": user_text, # what user asked (optional)
 
698
  }
699
  )
700
 
701
+ # 2) Run-level feedback (attach to actual LangSmith run)
702
+ # Only works when FE provides run_id and LangSmith credentials are configured.
703
+ wrote_run_feedback = False
704
+ if req.run_id:
705
+ wrote_run_feedback = _write_feedback_to_langsmith_run(
706
+ run_id=req.run_id,
707
+ rating=rating,
708
+ comment=comment,
709
+ tags=tags,
710
+ metadata={
711
+ "experiment_id": EXPERIMENT_ID,
712
+ "student_id": user_id,
713
+ "student_name": student_name,
714
+ "assistant_message_id": req.assistant_message_id,
715
+ "learning_mode": req.learning_mode,
716
+ "doc_type": req.doc_type,
717
+ "refs": refs,
718
+ "timestamp_ms": timestamp_ms,
719
+ },
720
+ )
721
+
722
+ return {"ok": True, "run_feedback_written": wrote_run_feedback}
723
 
724
 
725
  @app.post("/api/export")
 
781
  return JSONResponse(
782
  {"detail": "web/build not found. Build frontend first (web/build/index.html)."},
783
  status_code=500,
784
+ )