HardToFindAGoodUserName's picture
Two-view app (chat + DB dashboard), web search, rename, boosted links
0a1a9f9 verified
Raw
History Blame Contribute Delete
6.1 kB
"""Long-context Q&A over the WAM knowledge base (no RAG / no embeddings).
At our scale (~hundreds of tracked papers) the whole corpus's *summaries* + scores +
benchmark leaderboard + author directions + trends fit in a long-context model's window, so
we build a compact "knowledge pack" from SQLite and let the model answer directly with
citations β€” no retrieval step to miss documents, no vector index to maintain.
"""
from __future__ import annotations
import json
import sqlite3
from wam.config import Config, load_config
from wam.llm import LLMClient
from wam.logging import get_logger
from wam.store import Database
log = get_logger("webapp.qa")
SYSTEM = (
"You answer questions about World Action Models research. You have two sources:\n"
"1) the KNOWLEDGE PACK below β€” our curated corpus (tracked papers with scores, a "
"benchmark leaderboard, author directions, trends). Use it for facts about what we track.\n"
"2) WEB SEARCH results (when provided) β€” use them for the original paper text, definitions "
"of related concepts, and anything not in the pack.\n"
"Cite tracked papers by id like (arxiv:2606.01234) and web sources by their URL. Prefer the "
"pack for our corpus facts; never invent papers, numbers, or citations. Be concise and "
"concrete, and write in English.")
def build_pack(conn: sqlite3.Connection, max_papers: int = 400,
include_dropped: bool = True) -> str:
"""Compact, citeable corpus snapshot for the long-context Q&A model."""
sections: list[str] = []
papers = conn.execute(
"SELECT id, title, track, published, scores_json, summary_json FROM papers "
"WHERE track IN ('core','adjacent') ORDER BY "
"COALESCE(json_extract(scores_json,'$.weighted_total'),0) DESC LIMIT ?", (max_papers,)
).fetchall()
lines = ["## PAPERS"]
for r in papers:
s = json.loads(r["scores_json"]) if r["scores_json"] else {}
tldr = json.loads(r["summary_json"] or "{}").get("tldr", "") if r["summary_json"] else ""
score = f" score={s['weighted_total']}" if s.get("weighted_total") is not None else ""
wam = json.dumps(s.get("wam", {})) if s else ""
lines.append(f"- ({r['id']}) [{r['track']}{score}] {r['title']} β€” {tldr}"
+ (f" wam_scores={wam}" if wam else ""))
sections.append("\n".join(lines))
# Dropped papers stay in the KB (title-only) so it still covers them, per project policy.
if include_dropped:
drops = conn.execute("SELECT id, title FROM papers WHERE track='drop' "
"ORDER BY first_seen DESC LIMIT 200").fetchall()
if drops:
sections.append("## OTHER (filtered-out) PAPERS β€” title only\n"
+ "\n".join(f"- ({d['id']}) {d['title']}" for d in drops))
bench = conn.execute(
"SELECT model_name, training_dataset, benchmark, task, metric_name, metric_value, "
"claimed_by_authors, source_paper_id FROM benchmarks WHERE metric_value IS NOT NULL "
"ORDER BY benchmark LIMIT 400").fetchall()
if bench:
bl = ["## BENCHMARK LEADERBOARD (model | training data | benchmark | task | metric=value | source)"]
for b in bench:
src = "authors" if b["claimed_by_authors"] else "3rd-party"
bl.append(f"- {b['model_name']} | {b['training_dataset'] or '?'} | {b['benchmark']} | "
f"{b['task'] or '-'} | {b['metric_name']}={b['metric_value']} | {src} "
f"({b['source_paper_id']})")
sections.append("\n".join(bl))
authors = conn.execute(
"SELECT name, affiliation, directions FROM authors ORDER BY "
"json_array_length(paper_ids_json) DESC LIMIT 60").fetchall()
if authors:
al = ["## INFLUENTIAL AUTHORS"]
for a in authors:
al.append(f"- {a['name']}{' ('+a['affiliation']+')' if a['affiliation'] else ''}: "
f"{a['directions'] or ''}")
sections.append("\n".join(al))
snap = conn.execute("SELECT max(snapshot_date) FROM fronts").fetchone()[0]
if snap:
fronts = conn.execute("SELECT name, size, momentum, summary FROM fronts WHERE "
"snapshot_date=? ORDER BY size DESC", (snap,)).fetchall()
fl = ["## TRENDS / DIRECTIONS (name | papers | momentum | summary)"]
for f in fronts:
fl.append(f"- {f['name']} | {f['size']} | {f['momentum']} | {f['summary']}")
sections.append("\n".join(fl))
return "\n\n".join(sections)
def answer(question: str, history: list[dict] | None = None, cfg: Config | None = None,
client: LLMClient | None = None) -> dict:
"""Answer a question, optionally with prior conversation turns for multi-turn chat.
``history`` is a list of {"role": "user"|"assistant", "content": ...} from earlier turns.
"""
cfg = cfg or load_config()
client = client or LLMClient(cfg)
with Database(cfg) as db:
pack = build_pack(db.conn, max_papers=int(cfg.get("qa.max_papers", 400)),
include_dropped=bool(cfg.get("qa.include_dropped", True)))
if not pack.strip():
return {"answer": "The knowledge base is empty β€” run the pipeline first.", "pack_chars": 0}
# Optionally let the model also search the web (paper original text, related concepts)
# via OpenRouter's web plugin β€” works with any model.
extra_body = None
if bool(cfg.get("qa.web_search", True)):
extra_body = {"plugins": [{"id": "web",
"max_results": int(cfg.get("qa.web_max_results", 5))}]}
messages = [{"role": "system", "content": f"{SYSTEM}\n\nKNOWLEDGE PACK:\n{pack}"}]
messages += list(history or [])
messages.append({"role": "user", "content": question})
# Generous ceiling: the qa-tier model may be a reasoning model (burns tokens first).
ans = client.chat("qa", messages, label="qa", max_tokens=6000, extra_body=extra_body)
return {"answer": ans, "pack_chars": len(pack), "web": bool(extra_body)}