Update app.py
Browse files
app.py
CHANGED
|
@@ -96,7 +96,6 @@ class RFTMemoryStore:
|
|
| 96 |
)
|
| 97 |
""")
|
| 98 |
|
| 99 |
-
# FTS index
|
| 100 |
cur.execute("""
|
| 101 |
CREATE VIRTUAL TABLE IF NOT EXISTS events_fts USING fts5(
|
| 102 |
event_id,
|
|
@@ -133,6 +132,34 @@ class RFTMemoryStore:
|
|
| 133 |
os.makedirs(d, exist_ok=True)
|
| 134 |
return d
|
| 135 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
def collapse_score(self, session_id: str, role: str, text: str) -> float:
|
| 137 |
role_w = {"user": 1.0, "tool": 0.9, "assistant": 0.6}.get(role, 0.7)
|
| 138 |
tokens = set(t.lower() for t in re.findall(r"[A-Za-z0-9_]+", text or ""))
|
|
@@ -206,7 +233,7 @@ class RFTMemoryStore:
|
|
| 206 |
f.flush()
|
| 207 |
os.fsync(f.fileno())
|
| 208 |
|
| 209 |
-
#
|
| 210 |
con = sqlite3.connect(self.db_path)
|
| 211 |
cur = con.cursor()
|
| 212 |
cur.execute("""
|
|
@@ -219,34 +246,6 @@ class RFTMemoryStore:
|
|
| 219 |
|
| 220 |
return rec
|
| 221 |
|
| 222 |
-
def get_events(self, session_id: str, limit: int = 400) -> List[Dict[str, Any]]:
|
| 223 |
-
con = sqlite3.connect(self.db_path)
|
| 224 |
-
cur = con.cursor()
|
| 225 |
-
cur.execute("""
|
| 226 |
-
SELECT event_id, seq, ts_ms, role, text, digest, prev_hash, chain_hash, collapse
|
| 227 |
-
FROM events
|
| 228 |
-
WHERE session_id=?
|
| 229 |
-
ORDER BY seq ASC
|
| 230 |
-
LIMIT ?
|
| 231 |
-
""", (session_id, int(limit)))
|
| 232 |
-
rows = cur.fetchall()
|
| 233 |
-
con.close()
|
| 234 |
-
|
| 235 |
-
out = []
|
| 236 |
-
for r in rows:
|
| 237 |
-
out.append({
|
| 238 |
-
"event_id": r[0],
|
| 239 |
-
"seq": int(r[1] or 0),
|
| 240 |
-
"ts_ms": r[2],
|
| 241 |
-
"role": r[3],
|
| 242 |
-
"text": r[4],
|
| 243 |
-
"digest": r[5],
|
| 244 |
-
"prev_hash": r[6],
|
| 245 |
-
"chain_hash": r[7],
|
| 246 |
-
"collapse": float(r[8] or 0.0),
|
| 247 |
-
})
|
| 248 |
-
return out
|
| 249 |
-
|
| 250 |
def search_lexical(self, session_id: str, query: str, k: int = 8) -> List[RetrievalHit]:
|
| 251 |
match = safe_fts_match(query)
|
| 252 |
|
|
@@ -284,7 +283,7 @@ class RFTMemoryStore:
|
|
| 284 |
"event_id": h.event_id,
|
| 285 |
"seq": h.seq,
|
| 286 |
"role": h.role,
|
| 287 |
-
"
|
| 288 |
"score": h.score,
|
| 289 |
"digest": h.digest,
|
| 290 |
"chain_hash": h.chain_hash
|
|
@@ -360,8 +359,8 @@ GUIDED_DEMO_STEPS = [
|
|
| 360 |
"What city did I say?",
|
| 361 |
"My favourite drink is Coke Zero now. This overrides earlier.",
|
| 362 |
"What’s my favourite drink?",
|
| 363 |
-
"Search for
|
| 364 |
-
"Search for
|
| 365 |
]
|
| 366 |
|
| 367 |
HOW_TO_MD = """
|
|
@@ -396,6 +395,7 @@ def new_session_id() -> str:
|
|
| 396 |
|
| 397 |
|
| 398 |
def events_to_messages(events: List[Dict[str, Any]]) -> List[Dict[str, str]]:
|
|
|
|
| 399 |
msgs = []
|
| 400 |
for e in events:
|
| 401 |
if e["role"] in ("user", "assistant"):
|
|
@@ -455,20 +455,19 @@ def chat_turn(session_id: str, user_msg: str, retrieval_k: int):
|
|
| 455 |
retrieved_view = "\n".join([f"{h.score:.4f} | {h.role} | {h.text}" for h in hits]) if hits else "(none)"
|
| 456 |
messages = events_to_messages(events)
|
| 457 |
|
| 458 |
-
return
|
|
|
|
| 459 |
|
| 460 |
|
| 461 |
def run_guided_demo(session_id: str, retrieval_k: int):
|
| 462 |
if not session_id:
|
| 463 |
session_id = new_session_id()
|
| 464 |
|
| 465 |
-
|
| 466 |
-
last_retrieved = ""
|
| 467 |
-
last_ledger = ""
|
| 468 |
for step in GUIDED_DEMO_STEPS:
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
return
|
| 472 |
|
| 473 |
|
| 474 |
def manual_search(session_id: str, query: str, k: int) -> str:
|
|
@@ -501,7 +500,7 @@ def verify_uploaded_receipt(file_obj) -> str:
|
|
| 501 |
|
| 502 |
def reset_session():
|
| 503 |
sid = new_session_id()
|
| 504 |
-
return sid, [], "", "", None
|
| 505 |
|
| 506 |
|
| 507 |
def fill_example(selected: str) -> str:
|
|
@@ -519,7 +518,8 @@ with gr.Blocks(title="RFT Memory Receipt Engine") as demo:
|
|
| 519 |
|
| 520 |
with gr.Tabs():
|
| 521 |
with gr.Tab("Chat"):
|
| 522 |
-
|
|
|
|
| 523 |
|
| 524 |
with gr.Row():
|
| 525 |
example_pick = gr.Dropdown(label="Example prompts", choices=EXAMPLE_PROMPTS, value=EXAMPLE_PROMPTS[0])
|
|
@@ -539,14 +539,14 @@ with gr.Blocks(title="RFT Memory Receipt Engine") as demo:
|
|
| 539 |
send.click(
|
| 540 |
chat_turn,
|
| 541 |
inputs=[session_id, user_msg, retrieval_k],
|
| 542 |
-
outputs=[session_id, chatbot, retrieved_out, ledger_out, receipt_path],
|
| 543 |
-
)
|
| 544 |
|
| 545 |
with gr.Tab("Guided Demo"):
|
| 546 |
gr.Markdown("Runs a scripted set of messages to show storage, recall, overrides, search, and receipts.")
|
| 547 |
run_demo_btn = gr.Button("Run Guided Demo", variant="primary")
|
| 548 |
|
| 549 |
-
demo_chatbot = gr.Chatbot(label="Demo conversation", height=320
|
| 550 |
demo_retrieved = gr.Textbox(label="Last retrieved memory slices", lines=8)
|
| 551 |
demo_ledger = gr.Textbox(label="Ledger after demo", lines=14)
|
| 552 |
demo_receipt_path = gr.Textbox(label="Last demo receipt path (server)", lines=1)
|
|
@@ -555,8 +555,8 @@ with gr.Blocks(title="RFT Memory Receipt Engine") as demo:
|
|
| 555 |
run_demo_btn.click(
|
| 556 |
run_guided_demo,
|
| 557 |
inputs=[session_id, retrieval_k],
|
| 558 |
-
outputs=[session_id, demo_chatbot, demo_retrieved, demo_ledger, demo_receipt_path],
|
| 559 |
-
)
|
| 560 |
|
| 561 |
with gr.Tab("Manual Search"):
|
| 562 |
q = gr.Textbox(label="Search query", placeholder="Type keywords…")
|
|
@@ -576,7 +576,7 @@ with gr.Blocks(title="RFT Memory Receipt Engine") as demo:
|
|
| 576 |
new_sess_btn.click(
|
| 577 |
reset_session,
|
| 578 |
inputs=[],
|
| 579 |
-
outputs=[session_id, chatbot, retrieved_out, ledger_out, receipt_file],
|
| 580 |
)
|
| 581 |
|
| 582 |
demo.launch()
|
|
|
|
| 96 |
)
|
| 97 |
""")
|
| 98 |
|
|
|
|
| 99 |
cur.execute("""
|
| 100 |
CREATE VIRTUAL TABLE IF NOT EXISTS events_fts USING fts5(
|
| 101 |
event_id,
|
|
|
|
| 132 |
os.makedirs(d, exist_ok=True)
|
| 133 |
return d
|
| 134 |
|
| 135 |
+
def get_events(self, session_id: str, limit: int = 400) -> List[Dict[str, Any]]:
|
| 136 |
+
con = sqlite3.connect(self.db_path)
|
| 137 |
+
cur = con.cursor()
|
| 138 |
+
cur.execute("""
|
| 139 |
+
SELECT event_id, seq, ts_ms, role, text, digest, prev_hash, chain_hash, collapse
|
| 140 |
+
FROM events
|
| 141 |
+
WHERE session_id=?
|
| 142 |
+
ORDER BY seq ASC
|
| 143 |
+
LIMIT ?
|
| 144 |
+
""", (session_id, int(limit)))
|
| 145 |
+
rows = cur.fetchall()
|
| 146 |
+
con.close()
|
| 147 |
+
|
| 148 |
+
out = []
|
| 149 |
+
for r in rows:
|
| 150 |
+
out.append({
|
| 151 |
+
"event_id": r[0],
|
| 152 |
+
"seq": int(r[1] or 0),
|
| 153 |
+
"ts_ms": r[2],
|
| 154 |
+
"role": r[3],
|
| 155 |
+
"text": r[4],
|
| 156 |
+
"digest": r[5],
|
| 157 |
+
"prev_hash": r[6],
|
| 158 |
+
"chain_hash": r[7],
|
| 159 |
+
"collapse": float(r[8] or 0.0),
|
| 160 |
+
})
|
| 161 |
+
return out
|
| 162 |
+
|
| 163 |
def collapse_score(self, session_id: str, role: str, text: str) -> float:
|
| 164 |
role_w = {"user": 1.0, "tool": 0.9, "assistant": 0.6}.get(role, 0.7)
|
| 165 |
tokens = set(t.lower() for t in re.findall(r"[A-Za-z0-9_]+", text or ""))
|
|
|
|
| 233 |
f.flush()
|
| 234 |
os.fsync(f.fileno())
|
| 235 |
|
| 236 |
+
# Index update
|
| 237 |
con = sqlite3.connect(self.db_path)
|
| 238 |
cur = con.cursor()
|
| 239 |
cur.execute("""
|
|
|
|
| 246 |
|
| 247 |
return rec
|
| 248 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 249 |
def search_lexical(self, session_id: str, query: str, k: int = 8) -> List[RetrievalHit]:
|
| 250 |
match = safe_fts_match(query)
|
| 251 |
|
|
|
|
| 283 |
"event_id": h.event_id,
|
| 284 |
"seq": h.seq,
|
| 285 |
"role": h.role,
|
| 286 |
+
"content": h.text,
|
| 287 |
"score": h.score,
|
| 288 |
"digest": h.digest,
|
| 289 |
"chain_hash": h.chain_hash
|
|
|
|
| 359 |
"What city did I say?",
|
| 360 |
"My favourite drink is Coke Zero now. This overrides earlier.",
|
| 361 |
"What’s my favourite drink?",
|
| 362 |
+
"Search for Nova and show the matching memory line.",
|
| 363 |
+
"Search for Coke and show the matching memory line.",
|
| 364 |
]
|
| 365 |
|
| 366 |
HOW_TO_MD = """
|
|
|
|
| 395 |
|
| 396 |
|
| 397 |
def events_to_messages(events: List[Dict[str, Any]]) -> List[Dict[str, str]]:
|
| 398 |
+
# Gradio expects: [{"role": "...", "content": "..."}, ...]
|
| 399 |
msgs = []
|
| 400 |
for e in events:
|
| 401 |
if e["role"] in ("user", "assistant"):
|
|
|
|
| 455 |
retrieved_view = "\n".join([f"{h.score:.4f} | {h.role} | {h.text}" for h in hits]) if hits else "(none)"
|
| 456 |
messages = events_to_messages(events)
|
| 457 |
|
| 458 |
+
# IMPORTANT: return receipt_path twice so File can download it
|
| 459 |
+
return session_id, messages, retrieved_view, ledger, receipt_path, receipt_path
|
| 460 |
|
| 461 |
|
| 462 |
def run_guided_demo(session_id: str, retrieval_k: int):
|
| 463 |
if not session_id:
|
| 464 |
session_id = new_session_id()
|
| 465 |
|
| 466 |
+
last = (session_id, [], "", "", "", None)
|
|
|
|
|
|
|
| 467 |
for step in GUIDED_DEMO_STEPS:
|
| 468 |
+
last = chat_turn(session_id, step, retrieval_k)
|
| 469 |
+
session_id = last[0]
|
| 470 |
+
return last
|
| 471 |
|
| 472 |
|
| 473 |
def manual_search(session_id: str, query: str, k: int) -> str:
|
|
|
|
| 500 |
|
| 501 |
def reset_session():
|
| 502 |
sid = new_session_id()
|
| 503 |
+
return sid, [], "", "", "", None
|
| 504 |
|
| 505 |
|
| 506 |
def fill_example(selected: str) -> str:
|
|
|
|
| 518 |
|
| 519 |
with gr.Tabs():
|
| 520 |
with gr.Tab("Chat"):
|
| 521 |
+
# DO NOT pass type=... (your Gradio doesn't accept it)
|
| 522 |
+
chatbot = gr.Chatbot(label="Conversation", height=320)
|
| 523 |
|
| 524 |
with gr.Row():
|
| 525 |
example_pick = gr.Dropdown(label="Example prompts", choices=EXAMPLE_PROMPTS, value=EXAMPLE_PROMPTS[0])
|
|
|
|
| 539 |
send.click(
|
| 540 |
chat_turn,
|
| 541 |
inputs=[session_id, user_msg, retrieval_k],
|
| 542 |
+
outputs=[session_id, chatbot, retrieved_out, ledger_out, receipt_path, receipt_file],
|
| 543 |
+
)
|
| 544 |
|
| 545 |
with gr.Tab("Guided Demo"):
|
| 546 |
gr.Markdown("Runs a scripted set of messages to show storage, recall, overrides, search, and receipts.")
|
| 547 |
run_demo_btn = gr.Button("Run Guided Demo", variant="primary")
|
| 548 |
|
| 549 |
+
demo_chatbot = gr.Chatbot(label="Demo conversation", height=320)
|
| 550 |
demo_retrieved = gr.Textbox(label="Last retrieved memory slices", lines=8)
|
| 551 |
demo_ledger = gr.Textbox(label="Ledger after demo", lines=14)
|
| 552 |
demo_receipt_path = gr.Textbox(label="Last demo receipt path (server)", lines=1)
|
|
|
|
| 555 |
run_demo_btn.click(
|
| 556 |
run_guided_demo,
|
| 557 |
inputs=[session_id, retrieval_k],
|
| 558 |
+
outputs=[session_id, demo_chatbot, demo_retrieved, demo_ledger, demo_receipt_path, demo_receipt_file],
|
| 559 |
+
)
|
| 560 |
|
| 561 |
with gr.Tab("Manual Search"):
|
| 562 |
q = gr.Textbox(label="Search query", placeholder="Type keywords…")
|
|
|
|
| 576 |
new_sess_btn.click(
|
| 577 |
reset_session,
|
| 578 |
inputs=[],
|
| 579 |
+
outputs=[session_id, chatbot, retrieved_out, ledger_out, receipt_path, receipt_file],
|
| 580 |
)
|
| 581 |
|
| 582 |
demo.launch()
|