Spaces:
Running
Running
logger.py: batch-checkpoint refactor (see app.py commit for details)
Browse files- core/logger.py +37 -10
core/logger.py
CHANGED
|
@@ -228,7 +228,9 @@ class SessionLogger:
|
|
| 228 |
|
| 229 |
logger._last_confirmed_state = logger._state_hash()
|
| 230 |
logger._ensure_worker()
|
| 231 |
-
|
|
|
|
|
|
|
| 232 |
return logger
|
| 233 |
|
| 234 |
def _filename(self):
|
|
@@ -267,7 +269,6 @@ class SessionLogger:
|
|
| 267 |
"submitted_at": _now_iso(),
|
| 268 |
"char_count": len(text),
|
| 269 |
}
|
| 270 |
-
self._enqueue_upload()
|
| 271 |
|
| 272 |
def set_discussion_plan(self, text):
|
| 273 |
if not self.session_id:
|
|
@@ -276,7 +277,6 @@ class SessionLogger:
|
|
| 276 |
"text": text,
|
| 277 |
"extracted_at": _now_iso(),
|
| 278 |
}
|
| 279 |
-
self._enqueue_upload()
|
| 280 |
|
| 281 |
def set_perfect_answer(self, text):
|
| 282 |
if not self.session_id:
|
|
@@ -286,7 +286,6 @@ class SessionLogger:
|
|
| 286 |
"generated_at": _now_iso(),
|
| 287 |
"char_count": len(text),
|
| 288 |
}
|
| 289 |
-
self._enqueue_upload()
|
| 290 |
|
| 291 |
def set_dominant_framework(self, framework):
|
| 292 |
"""Record which of the 3 frameworks (consequence / duty_rights /
|
|
@@ -295,7 +294,6 @@ class SessionLogger:
|
|
| 295 |
if not self.session_id:
|
| 296 |
return
|
| 297 |
self.dominant_framework = framework
|
| 298 |
-
self._enqueue_upload()
|
| 299 |
|
| 300 |
def add_feedback(self, message_index, kind, message_excerpt=""):
|
| 301 |
"""Record a participant aha! / ugh. reaction on a bot message.
|
|
@@ -304,6 +302,10 @@ class SessionLogger:
|
|
| 304 |
"stress" (made participant uncomfortable),
|
| 305 |
"none" (cancel, second click on the already-active emoji).
|
| 306 |
Every click (including cancels) is appended with its own timestamp.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
"""
|
| 308 |
if not self.session_id:
|
| 309 |
return
|
|
@@ -314,7 +316,6 @@ class SessionLogger:
|
|
| 314 |
"timestamp": _now_iso(),
|
| 315 |
"message_excerpt": (message_excerpt or "")[:200],
|
| 316 |
})
|
| 317 |
-
self._enqueue_upload()
|
| 318 |
|
| 319 |
def get_current_kind(self, message_index):
|
| 320 |
"""Return the current ('aha' / 'stress' / None) reaction for a given
|
|
@@ -327,7 +328,11 @@ class SessionLogger:
|
|
| 327 |
|
| 328 |
def add_argument_snapshot(self, trigger, text, context=None):
|
| 329 |
"""Snapshot the participant's notepad textbox at an interaction event.
|
| 330 |
-
The bot NEVER sees this; logger-only side-channel.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 331 |
if not self.session_id:
|
| 332 |
return
|
| 333 |
text = text or ""
|
|
@@ -343,18 +348,19 @@ class SessionLogger:
|
|
| 343 |
if context:
|
| 344 |
snap["context"] = context
|
| 345 |
self.argument_snapshots.append(snap)
|
| 346 |
-
self._enqueue_upload()
|
| 347 |
|
| 348 |
def add_manager_verdict(self, verdict_dict):
|
| 349 |
"""Append a manager verdict to the session-level history list.
|
| 350 |
Called once per chat turn, right after the manager LLM returns.
|
| 351 |
|
| 352 |
`verdict_dict` should be the dict form of `ManagerVerdict.to_dict()`.
|
|
|
|
|
|
|
|
|
|
| 353 |
"""
|
| 354 |
if not self.session_id:
|
| 355 |
return
|
| 356 |
self.manager_history.append(dict(verdict_dict))
|
| 357 |
-
self._enqueue_upload()
|
| 358 |
|
| 359 |
def add_turn(self, user_message, manager_verdict, plan_control,
|
| 360 |
assistant_response, scratch=None, latency_s=None):
|
|
@@ -376,7 +382,8 @@ class SessionLogger:
|
|
| 376 |
if latency_s:
|
| 377 |
turn["latency_s"] = latency_s
|
| 378 |
self.turns.append(turn)
|
| 379 |
-
|
|
|
|
| 380 |
|
| 381 |
def _derive_feedback_current(self):
|
| 382 |
"""Collapse feedback events to {message_index: latest_kind},
|
|
@@ -468,6 +475,26 @@ class SessionLogger:
|
|
| 468 |
"session_summary": self._derive_session_summary(),
|
| 469 |
}
|
| 470 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 471 |
def _enqueue_upload(self):
|
| 472 |
"""Push the latest payload onto the upload queue. If a payload is
|
| 473 |
already queued, it gets superseded (only the newest survives).
|
|
|
|
| 228 |
|
| 229 |
logger._last_confirmed_state = logger._state_hash()
|
| 230 |
logger._ensure_worker()
|
| 231 |
+
# NOTE: caller (on_recover_session in app.py) must call
|
| 232 |
+
# checkpoint() after this returns, to persist the
|
| 233 |
+
# 'session_resumed' snapshot appended above.
|
| 234 |
return logger
|
| 235 |
|
| 236 |
def _filename(self):
|
|
|
|
| 269 |
"submitted_at": _now_iso(),
|
| 270 |
"char_count": len(text),
|
| 271 |
}
|
|
|
|
| 272 |
|
| 273 |
def set_discussion_plan(self, text):
|
| 274 |
if not self.session_id:
|
|
|
|
| 277 |
"text": text,
|
| 278 |
"extracted_at": _now_iso(),
|
| 279 |
}
|
|
|
|
| 280 |
|
| 281 |
def set_perfect_answer(self, text):
|
| 282 |
if not self.session_id:
|
|
|
|
| 286 |
"generated_at": _now_iso(),
|
| 287 |
"char_count": len(text),
|
| 288 |
}
|
|
|
|
| 289 |
|
| 290 |
def set_dominant_framework(self, framework):
|
| 291 |
"""Record which of the 3 frameworks (consequence / duty_rights /
|
|
|
|
| 294 |
if not self.session_id:
|
| 295 |
return
|
| 296 |
self.dominant_framework = framework
|
|
|
|
| 297 |
|
| 298 |
def add_feedback(self, message_index, kind, message_excerpt=""):
|
| 299 |
"""Record a participant aha! / ugh. reaction on a bot message.
|
|
|
|
| 302 |
"stress" (made participant uncomfortable),
|
| 303 |
"none" (cancel, second click on the already-active emoji).
|
| 304 |
Every click (including cancels) is appended with its own timestamp.
|
| 305 |
+
|
| 306 |
+
NOTE: in-memory only. The next checkpoint() (triggered by next chat
|
| 307 |
+
turn / end_session) will persist these events. Direct upload was
|
| 308 |
+
removed in the batch-push refactor to reduce HF commit pressure.
|
| 309 |
"""
|
| 310 |
if not self.session_id:
|
| 311 |
return
|
|
|
|
| 316 |
"timestamp": _now_iso(),
|
| 317 |
"message_excerpt": (message_excerpt or "")[:200],
|
| 318 |
})
|
|
|
|
| 319 |
|
| 320 |
def get_current_kind(self, message_index):
|
| 321 |
"""Return the current ('aha' / 'stress' / None) reaction for a given
|
|
|
|
| 328 |
|
| 329 |
def add_argument_snapshot(self, trigger, text, context=None):
|
| 330 |
"""Snapshot the participant's notepad textbox at an interaction event.
|
| 331 |
+
The bot NEVER sees this; logger-only side-channel.
|
| 332 |
+
|
| 333 |
+
NOTE: in-memory only. The next checkpoint() (triggered by next chat
|
| 334 |
+
turn / submit_initial / end_session) will persist these snapshots.
|
| 335 |
+
"""
|
| 336 |
if not self.session_id:
|
| 337 |
return
|
| 338 |
text = text or ""
|
|
|
|
| 348 |
if context:
|
| 349 |
snap["context"] = context
|
| 350 |
self.argument_snapshots.append(snap)
|
|
|
|
| 351 |
|
| 352 |
def add_manager_verdict(self, verdict_dict):
|
| 353 |
"""Append a manager verdict to the session-level history list.
|
| 354 |
Called once per chat turn, right after the manager LLM returns.
|
| 355 |
|
| 356 |
`verdict_dict` should be the dict form of `ManagerVerdict.to_dict()`.
|
| 357 |
+
|
| 358 |
+
NOTE: in-memory only. The next checkpoint() (called right after
|
| 359 |
+
the parent chat turn completes) will persist this verdict.
|
| 360 |
"""
|
| 361 |
if not self.session_id:
|
| 362 |
return
|
| 363 |
self.manager_history.append(dict(verdict_dict))
|
|
|
|
| 364 |
|
| 365 |
def add_turn(self, user_message, manager_verdict, plan_control,
|
| 366 |
assistant_response, scratch=None, latency_s=None):
|
|
|
|
| 382 |
if latency_s:
|
| 383 |
turn["latency_s"] = latency_s
|
| 384 |
self.turns.append(turn)
|
| 385 |
+
# NOTE: in-memory only. app.py's per-turn handler must call
|
| 386 |
+
# checkpoint() right after _log_turn() to persist this turn.
|
| 387 |
|
| 388 |
def _derive_feedback_current(self):
|
| 389 |
"""Collapse feedback events to {message_index: latest_kind},
|
|
|
|
| 475 |
"session_summary": self._derive_session_summary(),
|
| 476 |
}
|
| 477 |
|
| 478 |
+
def checkpoint(self):
|
| 479 |
+
"""Public entry-point: explicitly push the current session state
|
| 480 |
+
to HF. Called from app.py at well-defined trigger points:
|
| 481 |
+
|
| 482 |
+
- per chat turn (after _log_turn in on_send)
|
| 483 |
+
- submit_initial (after add_argument_snapshot in on_submit_initial_confirm)
|
| 484 |
+
- session recovery (after from_dict in on_recover_session)
|
| 485 |
+
|
| 486 |
+
end_session_confirm uses flush() instead (synchronous, retry-heavy).
|
| 487 |
+
end_session_arm is NOT a checkpoint trigger — the arm event is
|
| 488 |
+
captured in-memory and ride along the next push.
|
| 489 |
+
|
| 490 |
+
Replaces the older behaviour where every add_xxx / set_xxx method
|
| 491 |
+
triggered its own upload. That created O(N) HF commits per session
|
| 492 |
+
and caused 256-commits/hour rate-limit pressure during concurrent
|
| 493 |
+
prod runs. New behaviour: O(turns) commits per session, with the
|
| 494 |
+
per-turn push carrying all interim feedback/snapshot/manager events
|
| 495 |
+
in a single cumulative payload."""
|
| 496 |
+
self._enqueue_upload()
|
| 497 |
+
|
| 498 |
def _enqueue_upload(self):
|
| 499 |
"""Push the latest payload onto the upload queue. If a payload is
|
| 500 |
already queued, it gets superseded (only the newest survives).
|