ml-visoft commited on
Commit
376953f
1 Parent(s): 3f17489

The answer and the feedback to the answer are stored in persistent storage.

Browse files
Files changed (4) hide show
  1. main.py +84 -36
  2. storage.py +16 -0
  3. test/test_eval_code.py +1 -2
  4. test/test_storage.py +29 -0
main.py CHANGED
@@ -26,9 +26,18 @@ HF_DATASET_AUTH_TOKEN = os.environ.get('HF_DATASET_AUTH_TOKEN', "none")
26
 
27
  LOCAL_STORAGE_PATH = ""
28
 
29
- FILE_EVENTS = f"events-{datetime.utcnow()}-{uuid4()}.jsonl"
 
 
 
30
 
31
  if "localhost" in SPACE_HOST:
 
 
 
 
 
 
32
  DATABASE_NAME = "data/sessions_meta.db"
33
  LOCAL_STORAGE_PATH = Path("data/persistent/")
34
  pathlib.Path(DATABASE_NAME).unlink(missing_ok=True)
@@ -39,7 +48,8 @@ else:
39
 
40
 
41
  LOCAL_STORAGE_PATH.mkdir(exist_ok=True, parents=True)
42
- EVENTS_FILE_PATH = LOCAL_STORAGE_PATH / FILE_EVENTS
 
43
 
44
 
45
  scheduler = None
@@ -74,8 +84,8 @@ session_state_table = global_database_tables.session_state
74
  if session_state_table not in global_database_tables:
75
  # session_id stored in cookies
76
  # see EVAL_STATUS_x below for state
77
- session_state_table.create(id=int, session_id=str, state=int, submitted=datetime,
78
- completed=datetime, current_qeval=int, pk='id',)
79
  # Can't really nail the fk specs
80
  # foreign_keys=[("current_qeval", question_evaluation_table, "id")])
81
  Session_State_cls = session_state_table.dataclass()
@@ -96,7 +106,7 @@ HTML_SUBMIT_FEEDBACK = "submit_feedback"
96
 
97
  hdrs = (HighlightJS(langs=['python', 'javascript', 'html', 'css']),)
98
 
99
- if "localhost" in SPACE_HOST:
100
  # Are we hacking locally?
101
  print("Localhost detected in SPACE_HOST. App started in debug+live mode!")
102
  app, rt = fast_app(debug=True, live=True, hdrs=hdrs)
@@ -108,7 +118,13 @@ else:
108
  ################# STORAGE
109
 
110
  def untyped_save_to_storage(dc, filename):
111
- if scheduler is None: return
 
 
 
 
 
 
112
  with scheduler.lock:
113
  js_str = json.dumps(dataclasses.asdict(dc)) + "\n"
114
  with open(filename, "a") as f:
@@ -120,26 +136,34 @@ def save_to_storage(nav_event:storage.NavigationEvent):
120
  untyped_save_to_storage(nav_event, EVENTS_FILE_PATH)
121
 
122
 
 
 
 
 
 
 
 
 
123
 
124
  def validate_and_get_question_evaluation_objectid(session, qe_id:int):
125
  if 'session_id' not in session:
126
  print("validate_and_get_question_evaluation_objectid bad session data")
127
- return None
128
  session_id = session["session_id"]
129
  state_rows = session_state_table(limit=1, where=f"session_id == '{session_id}'", order_by="id DESC")
130
  if len(state_rows) <= 0:
131
  print("validate_and_get_question_evaluation_objectid there is no state")
132
- return False, None
133
  answer_id = state_rows[0].current_qeval
134
  qa_obj_row = question_evaluation_table(limit=1, where=f"id == {answer_id}")
135
  if len(qa_obj_row) <= 0:
136
  print("validate_and_get_question_evaluation_objectid There is no answer recorded")
137
- return False, None
138
  qe_obj = qa_obj_row[0]
139
  if qe_id != qe_obj.id:
140
  print("validate_and_get_question_evaluation_objectid QE {qe_id} does not belong to {qe_obj.id}")
141
- return False, None
142
- return True, qe_obj
143
 
144
 
145
  def html_create_feedback_updown_button(qe_id, ans_id, selected=0, disabled=False):
@@ -191,7 +215,7 @@ def get(session, qe_id:int, ans_id:int, which:int):
191
  return None
192
  print(f"{qe_id=} {ans_id=} {which=}")
193
 
194
- is_ok, qe_obj = validate_and_get_question_evaluation_objectid(session, qe_id)
195
  if not is_ok:
196
  print("toggle_up_down made session/object error")
197
  return "Error"
@@ -228,24 +252,36 @@ def html_get_textual_feedback_form(qe_obj, thank=False):
228
 
229
  @rt("/submit_feedback/{qe_id}")
230
  def post(session, qe_id:int, freeform_feedback:str):
231
- # Update the object
232
- # session_id = session.get("session_id", "Not set")
233
- # save_to_storage(
234
- # storage.NavigationEvent(event_type="/submit_feedback", event_session_id=session_id,
235
- # event_params={"qe_id":qe_id})
236
- # )
237
- is_ok, qe_obj = validate_and_get_question_evaluation_objectid(session, qe_id)
238
  if not is_ok:
239
  print("submit_feedback made session/object error")
240
  return "Error"
241
 
 
 
 
 
 
 
 
242
  answer_eval_js = json.loads(qe_obj.answer_eval_text)
243
  answer_eval_js[0]["explanation"] = freeform_feedback
244
  qe_obj.submitted = True
 
245
  question_evaluation_table.upsert(qe_obj)
 
 
 
 
 
 
 
 
 
246
  return html_get_textual_feedback_form(qe_obj, thank=True)
247
 
248
 
 
249
 
250
  def html_format_code_review_form(qe_obj, html_id=""):
251
  """
@@ -296,6 +332,12 @@ def html_error_results(message, html_id):
296
  div = Div(P("There was an error:", P(message)), id=html_id)
297
  return div
298
 
 
 
 
 
 
 
299
  def html_render_answer_from_db(session_id, html_id):
300
  eval_request_status, state_obj = get_latest_eval_request_status(session_id)
301
  # state_rows = session_state_table(limit=1, where=f"session_id == '{session_id}'", order_by="id DESC")
@@ -344,24 +386,36 @@ def call_gpt_and_store_result(session_obj_id, code_to_check):
344
  enhanced_answer = eval_code.eval_the_piece_of_c_code(openai_client=None, ccode=code_to_check)
345
 
346
  # we create a new QA entry.
347
- qa_obj = Question_Evaluation_cls(code_text=code_to_check, answer_eval_text=enhanced_answer, submitted=0)
348
- qa_obj = question_evaluation_table.insert(qa_obj)
349
- local_sess_obj.current_qeval = qa_obj.id
350
 
351
- # TODO save the outcome in a table, where it will be backed-up later. SqlLite is volatile.
 
 
 
 
 
 
 
 
 
 
352
 
353
  if "error" in enhanced_answer:
354
  local_sess_obj.state = EVAL_STATE_ERROR
355
  local_sess_obj.answer = enhanced_answer["error"]
356
- local_sess_obj.completed = datetime.utcnow()
357
  else:
358
  local_sess_obj.state = EVAL_STATE_ANSWER
359
- local_sess_obj.completed = datetime.utcnow()
360
  local_sess_state.update(local_sess_obj)
361
  except:
362
  import traceback
363
  traceback.print_exc()
364
 
 
 
365
 
366
  def html_render_inputbox(target_html_id, region_html_id):
367
  txtarea = Textarea(id="ccodetoeval", name="ccodetoeval", placeholder="Enter a piece of C code", rows=3)
@@ -372,14 +426,6 @@ def html_render_inputbox(target_html_id, region_html_id):
372
  )
373
  return Div(form, id=region_html_id, hx_swap_oob='true')
374
 
375
- def html_render_code_output(code):
376
- txtarea = Pre(Code(code))
377
- return txtarea
378
-
379
-
380
- def render_conditional_inputbox_results(session_id):
381
- eval_request_status, _ = get_latest_eval_request_status(session_id)
382
- pass
383
 
384
  ########## CLEAR FORM
385
 
@@ -411,6 +457,9 @@ def get(session):
411
  if 'session_id' not in session:
412
  session['session_id'] = str(uuid.uuid4())
413
  session_id = session["session_id"]
 
 
 
414
  save_to_storage(
415
  storage.NavigationEvent(event_type="/", event_session_id=session_id)
416
  )
@@ -438,7 +487,6 @@ def get(session):
438
  # session_id=session_id,
439
  # state=EVAL_STATE_ANSWER,
440
  # submitted=datetime.utcnow(),
441
- # completed=datetime.utcnow(),
442
  # )
443
  #
444
  # # we create a new QA entry.
@@ -472,7 +520,7 @@ def post(session, ccodetoeval:str):
472
  session_obj = Session_State_cls(
473
  session_id=session_id,
474
  state=EVAL_STATE_QUERY,
475
- submitted=datetime.utcnow(),
476
  )
477
  # we insert and we get the new primary key
478
  session_obj = session_state_table.insert(session_obj)
@@ -496,7 +544,7 @@ def get(session):
496
  session_obj = Session_State_cls(
497
  session_id=session_id,
498
  state=EVAL_STATE_NEW,
499
- submitted=datetime.utcnow(),
500
  )
501
  session_state_table.insert(session_obj)
502
  # re-issue forms
 
26
 
27
  LOCAL_STORAGE_PATH = ""
28
 
29
+ datetime_now = datetime.isoformat(datetime.utcnow())
30
+
31
+ FILE_EVENTS_NAME = f"events-{datetime_now}-{uuid4()}.jsonl"
32
+ FILE_SUBMITTED_NAME = f"submitted-{datetime_now}-{uuid4()}.jsonl"
33
 
34
  if "localhost" in SPACE_HOST:
35
+ IS_LOCALHOST = True
36
+ else:
37
+ IS_LOCALHOST = False
38
+
39
+
40
+ if IS_LOCALHOST:
41
  DATABASE_NAME = "data/sessions_meta.db"
42
  LOCAL_STORAGE_PATH = Path("data/persistent/")
43
  pathlib.Path(DATABASE_NAME).unlink(missing_ok=True)
 
48
 
49
 
50
  LOCAL_STORAGE_PATH.mkdir(exist_ok=True, parents=True)
51
+ EVENTS_FILE_PATH = LOCAL_STORAGE_PATH / FILE_EVENTS_NAME
52
+ SUBMITTED_FILE_PATH = LOCAL_STORAGE_PATH / FILE_SUBMITTED_NAME
53
 
54
 
55
  scheduler = None
 
84
  if session_state_table not in global_database_tables:
85
  # session_id stored in cookies
86
  # see EVAL_STATUS_x below for state
87
+ session_state_table.create(id=int, session_id=str, state=int, submitted_date=str, evaluated_date=str,
88
+ current_qeval=int, pk='id',)
89
  # Can't really nail the fk specs
90
  # foreign_keys=[("current_qeval", question_evaluation_table, "id")])
91
  Session_State_cls = session_state_table.dataclass()
 
106
 
107
  hdrs = (HighlightJS(langs=['python', 'javascript', 'html', 'css']),)
108
 
109
+ if IS_LOCALHOST:
110
  # Are we hacking locally?
111
  print("Localhost detected in SPACE_HOST. App started in debug+live mode!")
112
  app, rt = fast_app(debug=True, live=True, hdrs=hdrs)
 
118
  ################# STORAGE
119
 
120
  def untyped_save_to_storage(dc, filename):
121
+ if scheduler is None and IS_LOCALHOST:
122
+ # this is on local system, dump without checking for backup strategy
123
+ # TODO duplicate code, refactor!
124
+ js_str = json.dumps(dataclasses.asdict(dc)) + "\n"
125
+ with open(filename, "a") as f:
126
+ f.write(js_str)
127
+ return
128
  with scheduler.lock:
129
  js_str = json.dumps(dataclasses.asdict(dc)) + "\n"
130
  with open(filename, "a") as f:
 
136
  untyped_save_to_storage(nav_event, EVENTS_FILE_PATH)
137
 
138
 
139
+ @typedispatch
140
+ def save_to_storage(eval_event:storage.CodeSubmittedEvent):
141
+ untyped_save_to_storage(eval_event, SUBMITTED_FILE_PATH)
142
+
143
+
144
+
145
+ ########## EVALUATION FEEDBACK
146
+
147
 
148
  def validate_and_get_question_evaluation_objectid(session, qe_id:int):
149
  if 'session_id' not in session:
150
  print("validate_and_get_question_evaluation_objectid bad session data")
151
+ return False, None, None
152
  session_id = session["session_id"]
153
  state_rows = session_state_table(limit=1, where=f"session_id == '{session_id}'", order_by="id DESC")
154
  if len(state_rows) <= 0:
155
  print("validate_and_get_question_evaluation_objectid there is no state")
156
+ return False, None, None
157
  answer_id = state_rows[0].current_qeval
158
  qa_obj_row = question_evaluation_table(limit=1, where=f"id == {answer_id}")
159
  if len(qa_obj_row) <= 0:
160
  print("validate_and_get_question_evaluation_objectid There is no answer recorded")
161
+ return False, None, None
162
  qe_obj = qa_obj_row[0]
163
  if qe_id != qe_obj.id:
164
  print("validate_and_get_question_evaluation_objectid QE {qe_id} does not belong to {qe_obj.id}")
165
+ return False, None, None
166
+ return True, qe_obj, state_rows[0]
167
 
168
 
169
  def html_create_feedback_updown_button(qe_id, ans_id, selected=0, disabled=False):
 
215
  return None
216
  print(f"{qe_id=} {ans_id=} {which=}")
217
 
218
+ is_ok, qe_obj, session_obj = validate_and_get_question_evaluation_objectid(session, qe_id)
219
  if not is_ok:
220
  print("toggle_up_down made session/object error")
221
  return "Error"
 
252
 
253
  @rt("/submit_feedback/{qe_id}")
254
  def post(session, qe_id:int, freeform_feedback:str):
255
+ is_ok, qe_obj, session_obj = validate_and_get_question_evaluation_objectid(session, qe_id)
 
 
 
 
 
 
256
  if not is_ok:
257
  print("submit_feedback made session/object error")
258
  return "Error"
259
 
260
+ # Update the object
261
+ session_id = session.get("session_id", "Not set")
262
+ save_to_storage(
263
+ storage.NavigationEvent(event_type="/submit_feedback", event_session_id=session_id,
264
+ event_params={"qe_id":qe_id})
265
+ )
266
+
267
  answer_eval_js = json.loads(qe_obj.answer_eval_text)
268
  answer_eval_js[0]["explanation"] = freeform_feedback
269
  qe_obj.submitted = True
270
+ qe_obj.answer_eval_text = json.dumps(answer_eval_js)
271
  question_evaluation_table.upsert(qe_obj)
272
+
273
+ save_to_storage(
274
+ storage.CodeSubmittedEvent(event_session_id=session["session_id"], db_question_evaluation_id=qe_obj.id,
275
+ submitted_date=session_obj.submitted_date,
276
+ received_date=session_obj.evaluated_date,
277
+ code_to_eval=qe_obj.code_text, evaluation_response=qe_obj.answer_eval_text,
278
+ has_feedback=True, feedback_date=datetime.isoformat(datetime.utcnow()))
279
+ )
280
+
281
  return html_get_textual_feedback_form(qe_obj, thank=True)
282
 
283
 
284
+ ####### EVALUATE CODE
285
 
286
  def html_format_code_review_form(qe_obj, html_id=""):
287
  """
 
332
  div = Div(P("There was an error:", P(message)), id=html_id)
333
  return div
334
 
335
+
336
+ def html_render_code_output(code):
337
+ txtarea = Pre(Code(code))
338
+ return txtarea
339
+
340
+
341
  def html_render_answer_from_db(session_id, html_id):
342
  eval_request_status, state_obj = get_latest_eval_request_status(session_id)
343
  # state_rows = session_state_table(limit=1, where=f"session_id == '{session_id}'", order_by="id DESC")
 
386
  enhanced_answer = eval_code.eval_the_piece_of_c_code(openai_client=None, ccode=code_to_check)
387
 
388
  # we create a new QA entry.
389
+ qe_obj = Question_Evaluation_cls(code_text=code_to_check, answer_eval_text=enhanced_answer, submitted=0)
390
+ qe_obj = question_evaluation_table.insert(qe_obj)
391
+ local_sess_obj.current_qeval = qe_obj.id
392
 
393
+ evaluation_date = datetime.isoformat(datetime.utcnow())
394
+
395
+ # save to persistent storage
396
+ save_to_storage(
397
+ storage.CodeSubmittedEvent(event_session_id=local_sess_obj.session_id, db_question_evaluation_id=qe_obj.id,
398
+ submitted_date=local_sess_obj.submitted_date,
399
+ received_date=evaluation_date,
400
+ code_to_eval=code_to_check, evaluation_response=json.dumps(enhanced_answer))
401
+ )
402
+
403
+ # Update the session object.
404
 
405
  if "error" in enhanced_answer:
406
  local_sess_obj.state = EVAL_STATE_ERROR
407
  local_sess_obj.answer = enhanced_answer["error"]
408
+ local_sess_obj.evaluated_date = evaluation_date
409
  else:
410
  local_sess_obj.state = EVAL_STATE_ANSWER
411
+ local_sess_obj.evaluated_date = evaluation_date
412
  local_sess_state.update(local_sess_obj)
413
  except:
414
  import traceback
415
  traceback.print_exc()
416
 
417
+ ######## CODE INPUT FORM
418
+
419
 
420
  def html_render_inputbox(target_html_id, region_html_id):
421
  txtarea = Textarea(id="ccodetoeval", name="ccodetoeval", placeholder="Enter a piece of C code", rows=3)
 
426
  )
427
  return Div(form, id=region_html_id, hx_swap_oob='true')
428
 
 
 
 
 
 
 
 
 
429
 
430
  ########## CLEAR FORM
431
 
 
457
  if 'session_id' not in session:
458
  session['session_id'] = str(uuid.uuid4())
459
  session_id = session["session_id"]
460
+ if len(session_state_table(where=f"session_id=='{session_id}'")) <= 0:
461
+ # old session ID, "resetting".
462
+ session['session_id'] = str(uuid.uuid4())
463
  save_to_storage(
464
  storage.NavigationEvent(event_type="/", event_session_id=session_id)
465
  )
 
487
  # session_id=session_id,
488
  # state=EVAL_STATE_ANSWER,
489
  # submitted=datetime.utcnow(),
 
490
  # )
491
  #
492
  # # we create a new QA entry.
 
520
  session_obj = Session_State_cls(
521
  session_id=session_id,
522
  state=EVAL_STATE_QUERY,
523
+ submitted_date=datetime.isoformat(datetime.utcnow()),
524
  )
525
  # we insert and we get the new primary key
526
  session_obj = session_state_table.insert(session_obj)
 
544
  session_obj = Session_State_cls(
545
  session_id=session_id,
546
  state=EVAL_STATE_NEW,
547
+ submitted_date=datetime.isoformat(datetime.utcnow()),
548
  )
549
  session_state_table.insert(session_obj)
550
  # re-issue forms
storage.py CHANGED
@@ -10,3 +10,19 @@ class NavigationEvent():
10
  event_session_id:str = ""
11
  event_params:dict=dataclasses.field(default_factory=dict)
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  event_session_id:str = ""
11
  event_params:dict=dataclasses.field(default_factory=dict)
12
 
13
+ @dataclasses.dataclass(slots=True)
14
+ class CodeSubmittedEvent():
15
+ version:int = 1
16
+ submitted_date: str = datetime.isoformat(datetime.utcnow())
17
+ received_date: str = datetime.isoformat(datetime.utcnow())
18
+ event_session_id:str = ""
19
+ db_question_evaluation_id:int = 0
20
+ code_to_eval:str = ""
21
+ evaluation_engine:str = "chatGPT"
22
+ prompt_version:int = 1
23
+ evaluation_response:str = ""
24
+ has_feedback:bool = False
25
+ feedback_date:str = ""
26
+
27
+
28
+
test/test_eval_code.py CHANGED
@@ -1,4 +1,5 @@
1
  import pytest
 
2
  import eval_code
3
 
4
  TYPICAL_ANSWER = """[
@@ -24,5 +25,3 @@ def test_parse_chatgpt_answer():
24
  an_eval = ans[0]
25
  assert "criteria" in an_eval.keys()
26
  assert "explanation" in an_eval.keys()
27
-
28
-
 
1
  import pytest
2
+
3
  import eval_code
4
 
5
  TYPICAL_ANSWER = """[
 
25
  an_eval = ans[0]
26
  assert "criteria" in an_eval.keys()
27
  assert "explanation" in an_eval.keys()
 
 
test/test_storage.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ import json
3
+
4
+ import storage
5
+
6
+ def test_loading_submitted():
7
+ sub_file = Path("../data/persistent/submitted-2024-08-14 07:16:27.351924-02978438-3a8a-413b-84f1-2d3198cba2f8.jsonl")
8
+ submits = []
9
+ with open(sub_file, "r") as f:
10
+ for line in f:
11
+ js_dict = json.loads(line)
12
+ piece = storage.CodeSubmittedEvent(**js_dict)
13
+ submits.append(piece)
14
+ assert len(submits) > 0
15
+ eval_obj = json.loads(submits[0].evaluation_response)
16
+ assert len(eval_obj) > 0
17
+
18
+ def test_loading_submitted2():
19
+ sub_file = Path(
20
+ "../data/persistent/submitted-2024-08-14 07:42:05.411354-9030c34a-a31a-47ad-a399-1b0b0dbd6e33.jsonl")
21
+ submits = []
22
+ with open(sub_file, "r") as f:
23
+ for line in f:
24
+ js_dict = json.loads(line)
25
+ piece = storage.CodeSubmittedEvent(**js_dict)
26
+ submits.append(piece)
27
+ assert len(submits) > 0
28
+ eval_obj = json.loads(submits[0].evaluation_response)
29
+ assert len(eval_obj) > 0