Download app.py from ameforge/ananke: direct link, hf CLI and curl.
- Browser
- Download file 10.5 kB
-
https://huggingface.co/spaces/ameforge/ananke/resolve/main/app.py
- Command line
-
hf download hf://spaces/ameforge/ananke/app.py
-
curl -L -o app.py https://huggingface.co/spaces/ameforge/ananke/resolve/main/app.py
10.5 kB
| """Ananke v1 — public demo (AMEFORGE). | |
| This file only contains the user interface. The Ananke engine and its memory | |
| are loaded at startup from a private AMEFORGE repository. | |
| """ | |
| import html as html_lib | |
| import importlib.util | |
| import os | |
| import gradio as gr | |
| from huggingface_hub import hf_hub_download | |
| PRIVATE_REPO = "AMFORGE/ananke-core" | |
| TOKEN = os.environ.get("HF_TOKEN") | |
| _engine_path = hf_hub_download(PRIVATE_REPO, "ananke_core.py", token=TOKEN) | |
| _memory_path = hf_hub_download(PRIVATE_REPO, "memory.pkl.gz", token=TOKEN) | |
| _spec = importlib.util.spec_from_file_location("ananke_core", _engine_path) | |
| engine = importlib.util.module_from_spec(_spec) | |
| _spec.loader.exec_module(engine) | |
| # v1 : the answer is chosen by a learned reader (loaded privately), not by hand-written rules | |
| _reader_path = hf_hub_download(PRIVATE_REPO, "reader.py", token=TOKEN) | |
| _rspec = importlib.util.spec_from_file_location("reader", _reader_path) | |
| reader_mod = importlib.util.module_from_spec(_rspec) | |
| _rspec.loader.exec_module(reader_mod) | |
| READER = reader_mod.Reader([hf_hub_download(PRIVATE_REPO, f"reader_s{i}.pt", token=TOKEN) for i in (0, 1)]) | |
| BASE = engine.Ananke.load(_memory_path) | |
| SOURCES = sorted({f[3] for f in BASE.facts if f[3]}) | |
| S = BASE.stats() | |
| EXAMPLES = [ | |
| "What is the capital of Togo?", | |
| "What is the official language of Cape Verde?", | |
| "When did Ghana become independent?", | |
| "Who was the first president of Senegal?", | |
| "What is the largest city of Nigeria?", | |
| "Who is Kwame Nkrumah?", | |
| "What is the capital of Atlantis?", | |
| ] | |
| def _answer(mem, question): | |
| """memory finds the sentences; the learned reader picks the answer in them""" | |
| a0, hits = mem.ask(question, k=5) | |
| if a0 is None and not hits: | |
| return None, [] | |
| if a0 == getattr(engine, "DEFINITION", None) or not hits: | |
| return a0, hits[:3] | |
| ent, rel = mem.find_entity(question) | |
| if ent is not None and not rel: # "who is X?": show what memory holds about X | |
| return a0, hits[:3] | |
| a, sent = READER.answer(question, hits) | |
| if a is None: | |
| return a0, hits[:3] | |
| shown = [h for h in hits if h[1] == sent] + [h for h in hits if h[1] != sent] | |
| return a, shown[:3] | |
| def esc(t): | |
| return html_lib.escape(t or "") | |
| def _card(answer, hits, where): | |
| if answer is None and not hits: | |
| return ("""<div class="card idk"><div class="idk-title">I don't know</div>""" | |
| """<p>Ananke has not read anything that answers this question. """ | |
| """It refuses rather than inventing an answer.</p>""" | |
| """<p class="hint">Teach it: paste a text or a Wikipedia title in the <b>Teach it</b> tab.</p></div>""") | |
| if answer == getattr(engine, "DEFINITION", None): | |
| head, cls = "Here is what it read about it", "defn" | |
| elif answer: | |
| head, cls = esc(answer.title()), "ans" | |
| else: | |
| head, cls = "No short answer — here is what it read", "ans" | |
| quotes = "".join( | |
| f'<div class="quote"><p>{esc(sentence)}</p>' | |
| f'<span class="src">source: {esc(source or "your text")}</span></div>' | |
| for _, sentence, _, source in hits) | |
| return (f'<div class="card {cls}"><div class="headline">{head}</div>' | |
| f'<div class="where">Found in {esc(where)}.</div>' | |
| f'<div class="quotes">{quotes}</div></div>') | |
| def ask(question, personal): | |
| question = (question or "").strip() | |
| if not question: | |
| return '<div class="card empty">Type a question above.</div>', personal | |
| if personal is not None: | |
| a, hits = _answer(personal, question) | |
| if a is not None or hits: | |
| return _card(a, hits, "what you taught it"), personal | |
| a, hits = _answer(BASE, question) | |
| return _card(a, hits, "its general memory"), personal | |
| def teach(text, title, personal): | |
| personal = personal if personal is not None else engine.Ananke() | |
| msgs = [] | |
| if title and title.strip(): | |
| try: | |
| n, r = personal.read(engine.fetch_wikipedia(title.strip()), source=f"Wikipedia: {title.strip()}") | |
| msgs.append(f"<b>{esc(title.strip())}</b> — read {n} new facts.") | |
| except Exception as e: | |
| msgs.append(f"Could not read <b>{esc(title.strip())}</b> ({esc(str(e))}).") | |
| if text and text.strip(): | |
| n, r = personal.read(text, source="your text") | |
| msgs.append(f"Your text — read {n} new facts, {r} reinforced.") | |
| if not msgs: | |
| return '<div class="card empty">Paste a text or give a Wikipedia title.</div>', personal | |
| s = personal.stats() | |
| body = "".join(f"<li>{m}</li>" for m in msgs) | |
| return (f'<div class="card teach"><div class="headline">Learned.</div><ul>{body}</ul>' | |
| f'<p class="hint">Your personal memory: {s["faits"]} facts, {s["mots_lus"]} words read. ' | |
| f'Private to your session — it disappears when you leave.</p></div>', personal) | |
| def forget(_): | |
| return '<div class="card empty">Your personal memory has been erased.</div>', None | |
| THEME = gr.themes.Soft( | |
| primary_hue=gr.themes.colors.indigo, | |
| secondary_hue=gr.themes.colors.amber, | |
| neutral_hue=gr.themes.colors.slate, | |
| ) | |
| CSS = """ | |
| .gradio-container { max-width: 940px !important; } | |
| .hero { background: linear-gradient(135deg, #1e1b4b 0%, #3730a3 55%, #92400e 130%); | |
| border-radius: 22px; padding: 38px 34px 30px; color: #fff; margin-bottom: 20px; | |
| box-shadow: 0 12px 40px rgba(30,27,75,.35); } | |
| .hero .kicker { display: inline-block; font-size: 12px; letter-spacing: 2.5px; text-transform: uppercase; | |
| color: #fcd34d; border: 1px solid rgba(252,211,77,.5); border-radius: 999px; | |
| padding: 5px 14px; margin-bottom: 14px; } | |
| .hero h1 { font-size: 44px; margin: 0 0 6px; font-weight: 800; letter-spacing: -1px; } | |
| .hero h1 .v1 { background: #fcd34d; color: #1e1b4b; font-size: 20px; border-radius: 10px; | |
| padding: 3px 10px; vertical-align: middle; margin-left: 8px; letter-spacing: 0; } | |
| .hero .tagline { font-size: 18px; opacity: .92; margin: 0 0 18px; } | |
| .hero .pills { display: flex; flex-wrap: wrap; gap: 10px; } | |
| .hero .pill { background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.22); | |
| border-radius: 999px; padding: 6px 15px; font-size: 13.5px; } | |
| .hero .pill b { color: #fcd34d; } | |
| .chips { flex-wrap: wrap !important; gap: 8px !important; margin: 10px 0 4px; } | |
| .chip { border-radius: 999px !important; font-size: 13px !important; padding: 7px 14px !important; | |
| background: #eef2ff !important; border: 1px solid #c7d2fe !important; color: #3730a3 !important; | |
| flex: 0 1 auto !important; } | |
| .chip:hover { background: #e0e7ff !important; } | |
| .ask-btn { border-radius: 14px !important; font-size: 17px !important; padding: 12px !important; } | |
| .big-q textarea { font-size: 16px !important; border-radius: 14px !important; } | |
| .card { border-radius: 18px; padding: 24px 26px; margin-top: 16px; | |
| background: #fff; border: 1px solid #e2e8f0; box-shadow: 0 6px 24px rgba(15,23,42,.07); } | |
| .card.ans { border-top: 5px solid #4f46e5; } | |
| .card.defn { border-top: 5px solid #d97706; } | |
| .card.idk { border-top: 5px solid #94a3b8; background: #f8fafc; } | |
| .card.teach { border-top: 5px solid #059669; } | |
| .card.empty { color: #64748b; } | |
| .headline { font-size: 30px; font-weight: 800; color: #1e1b4b; margin-bottom: 4px; letter-spacing: -.5px; } | |
| .idk-title { font-size: 26px; font-weight: 800; color: #475569; margin-bottom: 8px; } | |
| .where { color: #64748b; font-size: 14px; margin-bottom: 14px; } | |
| .hint { color: #64748b; font-size: 13.5px; margin-top: 10px; } | |
| .quotes { display: flex; flex-direction: column; gap: 10px; margin-top: 6px; } | |
| .quote { background: #f1f5f9; border-left: 4px solid #818cf8; border-radius: 0 12px 12px 0; | |
| padding: 12px 16px; } | |
| .quote p { margin: 0 0 6px; color: #1e293b; font-size: 15px; line-height: 1.55; } | |
| .quote .src { font-size: 12.5px; color: #64748b; } | |
| .card ul { margin: 8px 0; padding-left: 20px; color: #1e293b; } | |
| .src-pills { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; } | |
| .src-pills span { background: #eef2ff; border: 1px solid #c7d2fe; color: #3730a3; | |
| border-radius: 999px; padding: 5px 13px; font-size: 13px; } | |
| .footer { text-align: center; color: #94a3b8; font-size: 13px; margin: 26px 0 10px; } | |
| .footer a { color: #4f46e5; } | |
| """ | |
| HERO = f"""<div class="hero"> | |
| <div class="kicker">AMEFORGE · Research prototype</div> | |
| <h1>Ananke <span class="v1">v1</span></h1> | |
| <p class="tagline">A memory that learns by reading — no hand-written answer rules.</p> | |
| <div class="pills"> | |
| <span class="pill"><b>{len(SOURCES)}</b> Wikipedia articles read</span> | |
| <span class="pill"><b>{S['faits']:,}</b> facts</span> | |
| <span class="pill"><b>{S['mots_lus']:,}</b> words</span> | |
| <span class="pill">runs on CPU</span> | |
| </div></div>""" | |
| FOOTER = """<div class="footer">Research prototype by <b>AMEFORGE</b> (Windsor, Canada) · | |
| <a href="https://huggingface.co/AMFORGE/ananke-v1">model card</a> · | |
| Not a chatbot: Ananke retrieves and quotes — always read the quotes.</div>""" | |
| with gr.Blocks(title="Ananke v1 — AMEFORGE", theme=THEME, css=CSS) as demo: | |
| personal = gr.State(None) | |
| gr.HTML(HERO) | |
| with gr.Tab("Ask"): | |
| q = gr.Textbox(label="Your question", placeholder="What is the capital of Togo?", | |
| elem_classes=["big-q"]) | |
| with gr.Row(elem_classes=["chips"]): | |
| for ex in EXAMPLES: | |
| b = gr.Button(ex, size="sm", elem_classes=["chip"]) | |
| b.click(lambda x=ex: x, outputs=q) | |
| btn = gr.Button("Ask", variant="primary", elem_classes=["ask-btn"]) | |
| out = gr.HTML() | |
| btn.click(ask, [q, personal], [out, personal]) | |
| q.submit(ask, [q, personal], [out, personal]) | |
| with gr.Tab("Teach it"): | |
| gr.Markdown("Give Ananke something new to read. It learns it **in one reading**, " | |
| "then you can ask about it in the *Ask* tab.") | |
| title = gr.Textbox(label="A Wikipedia article title (English)", placeholder="Lomé") | |
| text = gr.Textbox(label="…or paste your own text", lines=8) | |
| with gr.Row(): | |
| tbtn = gr.Button("Read it", variant="primary") | |
| fbtn = gr.Button("Forget what I taught") | |
| tout = gr.HTML() | |
| tbtn.click(teach, [text, title, personal], [tout, personal]) | |
| fbtn.click(forget, [personal], [tout, personal]) | |
| with gr.Tab("What it has read"): | |
| gr.HTML('<div class="src-pills">' + "".join(f"<span>{esc(s)}</span>" for s in SOURCES) + "</div>") | |
| gr.HTML(FOOTER) | |
| if __name__ == "__main__": | |
| demo.launch() | |