Files changed (1) hide show
  1. app.py +131 -3
app.py CHANGED
@@ -825,6 +825,101 @@ def extract_causal_signals(
825
 
826
  return signals
827
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
828
  # ============================================================
829
  # CHATBOT + RSNN DETECTION
830
  # ============================================================
@@ -922,7 +1017,8 @@ def respond(
922
 
923
  return (
924
  "The model produced "
925
- "no scoreable response."
 
926
  )
927
 
928
 
@@ -1061,6 +1157,25 @@ def respond(
1061
  )
1062
 
1063
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1064
  flagged_preview = [
1065
 
1066
  f"{i}:{token_strings[i]}"
@@ -1179,7 +1294,8 @@ def respond(
1179
  +
1180
  "\n\n"
1181
  +
1182
- report
 
1183
  )
1184
 
1185
 
@@ -1201,7 +1317,7 @@ with gr.Blocks() as demo:
1201
  """
1202
  # LatticeSpike Hallucination-Aware Chatbot
1203
 
1204
- **LLM:** Meta Llama 3.2 1B Instruct
1205
  **Detector:** LatticeSpike RSNN
1206
 
1207
  The LLM generates an answer, then LatticeSpike analyzes
@@ -1212,6 +1328,16 @@ For this first version, each message is evaluated independently.
1212
  )
1213
 
1214
 
 
 
 
 
 
 
 
 
 
 
1215
  chatbot = gr.ChatInterface(
1216
 
1217
  fn=respond,
@@ -1242,6 +1368,8 @@ For this first version, each message is evaluated independently.
1242
  label="Top-p",
1243
  ),
1244
  ],
 
 
1245
  )
1246
 
1247
 
 
825
 
826
  return signals
827
 
828
+ # ============================================================
829
+ # TOKEN-LEVEL HALLUCINATION HTML BUILDER
830
+ # ============================================================
831
+
832
+ def build_highlight_html(token_strings, flagged_set, burst_set, anomaly_scores):
833
+ """
834
+ Build an inline HTML token map.
835
+ Red = anomaly above threshold (hallucinated)
836
+ Amber = RSNN burst spike
837
+ Hover tooltip shows token index and anomaly score.
838
+ """
839
+
840
+ legend = (
841
+ '<div style="font-family:sans-serif;font-size:12px;'
842
+ 'margin-bottom:8px;display:flex;gap:14px;flex-wrap:wrap;">'
843
+ '<span>'
844
+ '<span style="background:#ffd8d7;color:#82071e;border-radius:3px;'
845
+ 'padding:1px 6px;font-weight:700;">H</span>'
846
+ '&nbsp;Hallucinated (anomaly &gt; threshold)'
847
+ '</span>'
848
+ '<span>'
849
+ '<span style="background:#fff3cd;color:#7d4e00;border-radius:3px;'
850
+ 'padding:1px 6px;font-weight:700;">B</span>'
851
+ '&nbsp;RSNN burst spike'
852
+ '</span>'
853
+ '</div>'
854
+ )
855
+
856
+ parts = []
857
+
858
+ for i, raw in enumerate(token_strings):
859
+
860
+ # Humanise the token string
861
+ display = raw
862
+ for src, dst in [
863
+ ('\u0120', ' '), # Ġ → space (GPT-2/Llama BPE)
864
+ ('\u2581', ' '), # ▁ → space (SentencePiece)
865
+ ('\u010a', '\n'), # Ċ → newline
866
+ ('<0x0A>', '\n'), # literal newline token
867
+ ]:
868
+ display = display.replace(src, dst)
869
+
870
+ # HTML-escape, then convert newlines to <br>
871
+ display = (
872
+ display
873
+ .replace('&', '&amp;')
874
+ .replace('<', '&lt;')
875
+ .replace('>', '&gt;')
876
+ .replace('\n', '<br>')
877
+ )
878
+
879
+ if display.strip() == '':
880
+ display = '&nbsp;'
881
+
882
+ score = (
883
+ float(anomaly_scores[i])
884
+ if i < len(anomaly_scores)
885
+ else 0.0
886
+ )
887
+
888
+ title = f"[{i}] {raw} | anomaly={score:.4f}"
889
+
890
+ if i in flagged_set:
891
+ style = (
892
+ "background:#ffd8d7;color:#82071e;"
893
+ "border-radius:3px;padding:1px 3px;cursor:default;"
894
+ )
895
+ elif i in burst_set:
896
+ style = (
897
+ "background:#fff3cd;color:#7d4e00;"
898
+ "border-radius:3px;padding:1px 3px;cursor:default;"
899
+ )
900
+ else:
901
+ style = "cursor:default;"
902
+
903
+ parts.append(
904
+ f'<span style="{style}" title="{title}">{display}</span>'
905
+ )
906
+
907
+ text_html = (
908
+ '<div style="font-family:sans-serif;font-size:14px;'
909
+ 'line-height:1.9;background:#fff;border:1px solid #d0d7de;'
910
+ 'border-radius:8px;padding:12px 16px;word-break:break-word;">'
911
+ + ''.join(parts)
912
+ + '</div>'
913
+ )
914
+
915
+ heading = (
916
+ '<h4 style="font-family:sans-serif;margin-bottom:6px;'
917
+ 'color:#1f2328;">Token-level Hallucination Map</h4>'
918
+ )
919
+
920
+ return heading + legend + text_html
921
+
922
+
923
  # ============================================================
924
  # CHATBOT + RSNN DETECTION
925
  # ============================================================
 
1017
 
1018
  return (
1019
  "The model produced "
1020
+ "no scoreable response.",
1021
+ "<p><em>No tokens to visualize.</em></p>",
1022
  )
1023
 
1024
 
 
1157
  )
1158
 
1159
 
1160
+ # ----------------------------------------------------
1161
+ # BUILD TOKEN-LEVEL HTML VISUALIZATION
1162
+ # ----------------------------------------------------
1163
+
1164
+ anomaly_list = (
1165
+ anomaly
1166
+ .detach()
1167
+ .cpu()
1168
+ .tolist()
1169
+ )
1170
+
1171
+ hallu_html = build_highlight_html(
1172
+ token_strings,
1173
+ flagged_set=set(flagged_indices),
1174
+ burst_set=set(burst_indices),
1175
+ anomaly_scores=anomaly_list,
1176
+ )
1177
+
1178
+
1179
  flagged_preview = [
1180
 
1181
  f"{i}:{token_strings[i]}"
 
1294
  +
1295
  "\n\n"
1296
  +
1297
+ report,
1298
+ hallu_html,
1299
  )
1300
 
1301
 
 
1317
  """
1318
  # LatticeSpike Hallucination-Aware Chatbot
1319
 
1320
+ **LLM:** Meta Llama 3.2 1B Instruct
1321
  **Detector:** LatticeSpike RSNN
1322
 
1323
  The LLM generates an answer, then LatticeSpike analyzes
 
1328
  )
1329
 
1330
 
1331
+ hallu_viz = gr.HTML(
1332
+ value=(
1333
+ "<p style='font-family:sans-serif;color:#57606a;font-size:13px;'>"
1334
+ "<em>Submit a question to see the token-level hallucination map.</em>"
1335
+ "</p>"
1336
+ ),
1337
+ label="Token-level Hallucination Visualization",
1338
+ )
1339
+
1340
+
1341
  chatbot = gr.ChatInterface(
1342
 
1343
  fn=respond,
 
1368
  label="Top-p",
1369
  ),
1370
  ],
1371
+
1372
+ additional_outputs=[hallu_viz],
1373
  )
1374
 
1375