SarahXia0405 commited on
Commit
4b372df
·
verified ·
1 Parent(s): 0a5b9a8

Update api/server.py

Browse files
Files changed (1) hide show
  1. api/server.py +124 -107
api/server.py CHANGED
@@ -1,7 +1,7 @@
1
  # api/server.py
2
  import os
3
  import time
4
- from typing import Dict, Any, Optional, List
5
 
6
  from fastapi import FastAPI, UploadFile, File, Form, Request
7
  from fastapi.responses import FileResponse, JSONResponse
@@ -22,57 +22,11 @@ from api.clare_core import (
22
  summarize_conversation,
23
  )
24
 
25
- # ----------------------------
26
- # LangSmith (Dataset logging)
27
- # ----------------------------
28
- # 你在 HF Space 里需要配置:
29
- # LANGSMITH_API_KEY=...
30
- # 可选:
31
- # LANGSMITH_DATASET_NAME=clare_user_events
32
- # LANGSMITH_PROJECT=...
33
  try:
34
- from langsmith import Client as LangSmithClient # type: ignore
35
  except Exception:
36
- LangSmithClient = None # type: ignore
37
-
38
- LS_DATASET_NAME = os.getenv("LANGSMITH_DATASET_NAME", "clare_user_events").strip()
39
- LS_PROJECT = os.getenv("LANGSMITH_PROJECT", "").strip()
40
-
41
- _ls_client = None
42
- if LangSmithClient is not None and os.getenv("LANGSMITH_API_KEY"):
43
- try:
44
- _ls_client = LangSmithClient()
45
- except Exception as e:
46
- print(f"[langsmith] init failed: {repr(e)}")
47
- _ls_client = None
48
-
49
-
50
- def log_event_to_langsmith(
51
- *,
52
- inputs: Dict[str, Any],
53
- outputs: Dict[str, Any],
54
- metadata: Dict[str, Any],
55
- ) -> None:
56
- """
57
- Write a single event as an Example row into LangSmith Dataset.
58
- This mirrors your old Gradio pattern (dataset作为事件日志).
59
- """
60
- if _ls_client is None:
61
- return
62
- try:
63
- # project 不是必须;dataset 足够你做过滤与分析
64
- if LS_PROJECT:
65
- metadata = {**metadata, "langsmith_project": LS_PROJECT}
66
-
67
- _ls_client.create_example(
68
- inputs=inputs,
69
- outputs=outputs,
70
- metadata=metadata,
71
- dataset_name=LS_DATASET_NAME,
72
- )
73
- except Exception as e:
74
- print(f"[langsmith] create_example failed: {repr(e)}")
75
-
76
 
77
  # ----------------------------
78
  # Paths / Constants
@@ -82,11 +36,14 @@ API_DIR = os.path.dirname(__file__)
82
  MODULE10_PATH = os.path.join(API_DIR, "module10_responsible_ai.pdf")
83
  MODULE10_DOC_TYPE = "Literature Review / Paper"
84
 
85
- # Vite build output in your repo is "web/build"
86
  WEB_DIST = os.path.abspath(os.path.join(API_DIR, "..", "web", "build"))
87
  WEB_INDEX = os.path.join(WEB_DIST, "index.html")
88
  WEB_ASSETS = os.path.join(WEB_DIST, "assets")
89
 
 
 
 
 
90
  # ----------------------------
91
  # App
92
  # ----------------------------
@@ -154,6 +111,52 @@ def _get_session(user_id: str) -> Dict:
154
  return SESSIONS[user_id]
155
 
156
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  # ----------------------------
158
  # Schemas
159
  # ----------------------------
@@ -182,19 +185,22 @@ class SummaryReq(BaseModel):
182
 
183
 
184
  class FeedbackReq(BaseModel):
185
- # FE 会发的最小字段
186
  user_id: str
187
  rating: str # "helpful" | "not_helpful"
188
- assistant_message_id: str
 
 
189
  assistant_text: str
 
 
 
 
190
 
191
- # 可选:用于更好的分析
192
- user_text: Optional[str] = None
193
- comment: Optional[str] = None
194
- refs: Optional[List[str]] = None
195
  learning_mode: Optional[str] = None
196
  doc_type: Optional[str] = None
197
- timestamp_ms: Optional[float] = None
198
 
199
 
200
  # ----------------------------
@@ -216,7 +222,6 @@ def login(req: LoginReq):
216
  def chat(req: ChatReq):
217
  user_id = (req.user_id or "").strip()
218
  msg = (req.message or "").strip()
219
-
220
  if not user_id:
221
  return JSONResponse({"error": "Missing user_id"}, status_code=400)
222
 
@@ -265,27 +270,24 @@ def chat(req: ChatReq):
265
  for c in (rag_used_chunks or [])
266
  ]
267
 
268
- # 可选:把 chat_turn 也写入 dataset(你以前 Gradio 有)
269
- try:
270
- log_event_to_langsmith(
271
- inputs={
272
- "question": msg,
273
- "student_id": user_id,
274
- },
275
- outputs={
276
- "answer": answer,
277
- },
278
- metadata={
279
- "event_type": "chat_turn",
280
- "timestamp": time.time(),
281
- "latency_ms": latency_ms,
282
- "learning_mode": req.learning_mode,
283
- "language": resolved_lang,
284
- "doc_type": req.doc_type,
285
- },
286
- )
287
- except Exception:
288
- pass
289
 
290
  return {
291
  "reply": answer,
@@ -323,7 +325,6 @@ async def upload(
323
  if doc_type == "Syllabus":
324
  class _F:
325
  pass
326
-
327
  fo = _F()
328
  fo.name = tmp_path
329
  try:
@@ -339,42 +340,58 @@ async def upload(
339
  new_chunks = []
340
 
341
  status_md = f"✅ Loaded base reading + uploaded {doc_type} file."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  return {"ok": True, "added_chunks": len(new_chunks), "status_md": status_md}
343
 
344
 
345
  @app.post("/api/feedback")
346
- def feedback(req: FeedbackReq):
347
  user_id = (req.user_id or "").strip()
348
  if not user_id:
349
  return JSONResponse({"ok": False, "error": "Missing user_id"}, status_code=400)
350
 
 
 
 
351
  rating = (req.rating or "").strip().lower()
352
  if rating not in ("helpful", "not_helpful"):
353
- return JSONResponse({"ok": False, "error": "rating must be helpful|not_helpful"}, status_code=400)
354
-
355
- # 写入 LangSmith dataset(与你 Gradio 时代一致)
356
- try:
357
- log_event_to_langsmith(
358
- inputs={
359
- "question": req.user_text or "", # 允许为空(只对 assistant reply 点赞)
360
- "student_id": user_id,
361
- "assistant_message_id": req.assistant_message_id,
362
- },
363
- outputs={
364
- "answer": req.assistant_text or "",
365
- },
366
- metadata={
367
- "event_type": "feedback",
368
- "rating": rating,
369
- "comment": (req.comment or "").strip(),
370
- "learning_mode": req.learning_mode or "",
371
- "doc_type": req.doc_type or "",
372
- "refs": req.refs or [],
373
- "timestamp_ms": req.timestamp_ms or (time.time() * 1000.0),
374
- },
375
- )
376
- except Exception as e:
377
- print(f"[feedback] log failed: {repr(e)}")
378
 
379
  return {"ok": True}
380
 
 
1
  # api/server.py
2
  import os
3
  import time
4
+ from typing import Dict, List, Optional
5
 
6
  from fastapi import FastAPI, UploadFile, File, Form, Request
7
  from fastapi.responses import FileResponse, JSONResponse
 
22
  summarize_conversation,
23
  )
24
 
25
+ # ✅ LangSmith (same idea as your Gradio app.py)
 
 
 
 
 
 
 
26
  try:
27
+ from langsmith import Client
28
  except Exception:
29
+ Client = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
  # ----------------------------
32
  # Paths / Constants
 
36
  MODULE10_PATH = os.path.join(API_DIR, "module10_responsible_ai.pdf")
37
  MODULE10_DOC_TYPE = "Literature Review / Paper"
38
 
 
39
  WEB_DIST = os.path.abspath(os.path.join(API_DIR, "..", "web", "build"))
40
  WEB_INDEX = os.path.join(WEB_DIST, "index.html")
41
  WEB_ASSETS = os.path.join(WEB_DIST, "assets")
42
 
43
+ # ✅ LangSmith dataset name (match what you used before)
44
+ LS_DATASET_NAME = os.getenv("LS_DATASET_NAME", "clare_user_events").strip()
45
+ LS_PROJECT = os.getenv("LANGSMITH_PROJECT", os.getenv("LANGCHAIN_PROJECT", "")).strip() # optional
46
+
47
  # ----------------------------
48
  # App
49
  # ----------------------------
 
111
  return SESSIONS[user_id]
112
 
113
 
114
+ # ----------------------------
115
+ # LangSmith helpers
116
+ # ----------------------------
117
+ _ls_client = None
118
+ if Client is not None:
119
+ try:
120
+ _ls_client = Client()
121
+ except Exception as e:
122
+ print("[langsmith] init failed:", repr(e))
123
+ _ls_client = None
124
+
125
+
126
+ def _log_event_to_langsmith(data: Dict):
127
+ """
128
+ Create an Example in LangSmith Dataset (clare_user_events).
129
+ Mimic your previous Gradio log_event behavior.
130
+
131
+ Inputs/Outputs show up as "Inputs" / "Reference Outputs".
132
+ Everything else goes into metadata columns.
133
+ """
134
+ if _ls_client is None:
135
+ return
136
+
137
+ try:
138
+ inputs = {
139
+ "question": data.get("question", ""),
140
+ "student_id": data.get("student_id", ""),
141
+ "student_name": data.get("student_name", ""),
142
+ }
143
+ outputs = {"answer": data.get("answer", "")}
144
+ metadata = {k: v for k, v in data.items() if k not in ("question", "answer")}
145
+
146
+ # helpful for filtering in UI
147
+ if LS_PROJECT:
148
+ metadata.setdefault("langsmith_project", LS_PROJECT)
149
+
150
+ _ls_client.create_example(
151
+ inputs=inputs,
152
+ outputs=outputs,
153
+ metadata=metadata,
154
+ dataset_name=LS_DATASET_NAME,
155
+ )
156
+ except Exception as e:
157
+ print("[langsmith] log failed:", repr(e))
158
+
159
+
160
  # ----------------------------
161
  # Schemas
162
  # ----------------------------
 
185
 
186
 
187
  class FeedbackReq(BaseModel):
 
188
  user_id: str
189
  rating: str # "helpful" | "not_helpful"
190
+ assistant_message_id: Optional[str] = None
191
+
192
+ # what the user is rating
193
  assistant_text: str
194
+ user_text: Optional[str] = ""
195
+
196
+ # optional free-text comment
197
+ comment: Optional[str] = ""
198
 
199
+ # context for analysis
200
+ refs: Optional[List[str]] = []
 
 
201
  learning_mode: Optional[str] = None
202
  doc_type: Optional[str] = None
203
+ timestamp_ms: Optional[int] = None
204
 
205
 
206
  # ----------------------------
 
222
  def chat(req: ChatReq):
223
  user_id = (req.user_id or "").strip()
224
  msg = (req.message or "").strip()
 
225
  if not user_id:
226
  return JSONResponse({"error": "Missing user_id"}, status_code=400)
227
 
 
270
  for c in (rag_used_chunks or [])
271
  ]
272
 
273
+ # log chat_turn to LangSmith (uses login name/id; NO hardcoding)
274
+ _log_event_to_langsmith(
275
+ {
276
+ "experiment_id": "RESP_AI_W10",
277
+ "student_id": user_id,
278
+ "student_name": sess.get("name", ""),
279
+ "event_type": "chat_turn",
280
+ "timestamp": time.time(),
281
+ "latency_ms": latency_ms,
282
+ "question": msg,
283
+ "answer": answer,
284
+ "model_name": sess["model_name"],
285
+ "language": resolved_lang,
286
+ "learning_mode": req.learning_mode,
287
+ "doc_type": req.doc_type,
288
+ "refs": refs,
289
+ }
290
+ )
 
 
 
291
 
292
  return {
293
  "reply": answer,
 
325
  if doc_type == "Syllabus":
326
  class _F:
327
  pass
 
328
  fo = _F()
329
  fo.name = tmp_path
330
  try:
 
340
  new_chunks = []
341
 
342
  status_md = f"✅ Loaded base reading + uploaded {doc_type} file."
343
+
344
+ # ✅ optional: log upload event
345
+ _log_event_to_langsmith(
346
+ {
347
+ "experiment_id": "RESP_AI_W10",
348
+ "student_id": user_id,
349
+ "student_name": sess.get("name", ""),
350
+ "event_type": "upload",
351
+ "timestamp": time.time(),
352
+ "doc_type": doc_type,
353
+ "filename": safe_name,
354
+ "added_chunks": len(new_chunks),
355
+ "question": f"[upload] {safe_name}",
356
+ "answer": status_md,
357
+ }
358
+ )
359
+
360
  return {"ok": True, "added_chunks": len(new_chunks), "status_md": status_md}
361
 
362
 
363
  @app.post("/api/feedback")
364
+ def api_feedback(req: FeedbackReq):
365
  user_id = (req.user_id or "").strip()
366
  if not user_id:
367
  return JSONResponse({"ok": False, "error": "Missing user_id"}, status_code=400)
368
 
369
+ sess = _get_session(user_id)
370
+ student_name = sess.get("name", "")
371
+
372
  rating = (req.rating or "").strip().lower()
373
  if rating not in ("helpful", "not_helpful"):
374
+ return JSONResponse({"ok": False, "error": "Invalid rating"}, status_code=400)
375
+
376
+ # record feedback as its own event row in the SAME dataset
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",
383
+ "timestamp": time.time(),
384
+ "rating": rating,
385
+ "assistant_message_id": req.assistant_message_id,
386
+ "question": (req.user_text or "").strip(),
387
+ "answer": (req.assistant_text or "").strip(),
388
+ "comment": (req.comment or "").strip(),
389
+ "refs": req.refs or [],
390
+ "learning_mode": req.learning_mode,
391
+ "doc_type": req.doc_type,
392
+ "timestamp_ms": req.timestamp_ms,
393
+ }
394
+ )
 
 
 
 
395
 
396
  return {"ok": True}
397