pmrinal2005 commited on
Commit
0cebb30
Β·
verified Β·
1 Parent(s): a521353

Upload folder using huggingface_hub

Browse files
Files changed (4) hide show
  1. README.md +52 -39
  2. backend/config.py +49 -30
  3. backend/model_loader.py +76 -40
  4. backend/prompt_builder.py +66 -44
README.md CHANGED
@@ -1,39 +1,52 @@
1
- ---
2
- title: Elysium
3
- emoji: 🌿
4
- colorFrom: green
5
- colorTo: indigo
6
- sdk: gradio
7
- sdk_version: 6.17.3
8
- app_file: app.py
9
- python_version: "3.12"
10
- pinned: true
11
- hardware: zero-a10g
12
- short_description: Agentic civilization with bioluminescent hypergraph
13
- tags:
14
- - minicpm-v
15
- - voxcpm2
16
- - llama.cpp
17
- - agentic
18
- - track:wood
19
- - sponsor:modal
20
- - achievement:offbrand
21
- - achievement:llama
22
- - achievement:offgrid
23
- - achievement:welltuned
24
- - badge-tiny-titan
25
- ---
26
-
27
- # 🌿 Elysium β€” Persistent Agentic Civilization
28
-
29
- Elysium is a self-evolving civilization of agents living inside a fine-tuned MiniCPM-V 4.6 (β‰ˆ4B).
30
- Every interaction grows a Living Mycelial Hypergraph rendered as a bioluminescent, Google-Maps-style infinite canvas.
31
-
32
- - **Brain:** fine-tuned MiniCPM-V 4.6 via `llama-cpp-python` on ZeroGPU
33
- - **Voice:** VoxCPM2 (agent debate audio drama)
34
- - **Memory:** `rustworkx` hypergraph persisted to `/data`
35
- - **JSON:** GBNF grammar enforces a strict ElysiumResponse schema on every forward pass
36
- - **Tools:** offline-first DuckDuckGo, email, reminders, calendar, weather, calculator, fetch_url, transit_lookup, files
37
- - **Frontend:** 100% custom HTML/Canvas2D on `gradio.Server` (no default Gradio UI)
38
-
39
- Runs identically online (HF Spaces) and offline (`git clone … && python app.py`).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Elysium
3
+ emoji: 🌿
4
+ colorFrom: green
5
+ colorTo: indigo
6
+ sdk: gradio
7
+ sdk_version: 6.17.3
8
+ app_file: app.py
9
+ python_version: "3.12"
10
+ pinned: true
11
+ hardware: zero-a10g
12
+ short_description: Agentic Civilization with Bioluminescent Hypergraph
13
+ tags:
14
+ - minicpm-v
15
+ - voxcpm2
16
+ - llama.cpp
17
+ - agentic
18
+ - track:wood
19
+ - sponsor:modal
20
+ - achievement:offbrand
21
+ - achievement:llama
22
+ - achievement:offgrid
23
+ - achievement:welltuned
24
+ - badge-tiny-titan
25
+ ---
26
+
27
+ # 🌿 Elysium β€” Persistent Agentic Civilization
28
+
29
+ Elysium is a self-evolving civilization of agents living inside a fine-tuned MiniCPM-V 4.6 (β‰ˆ4B).
30
+ Every interaction grows a Living Mycelial Hypergraph rendered as a bioluminescent, Google-Maps-style infinite canvas.
31
+
32
+ - **Brain:** fine-tuned MiniCPM-V 4.6 via `llama-cpp-python` on ZeroGPU
33
+ - Repo: [`build-small-hackathon/elysium-MiniCPM-V-4.6-F16-GGUF`](https://huggingface.co/build-small-hackathon/elysium-MiniCPM-V-4.6-F16-GGUF)
34
+ - File: `elysium-f16.gguf`
35
+ - **Voice:** VoxCPM2 (agent debate audio drama)
36
+ - **Memory:** `rustworkx` hypergraph persisted to `/data`
37
+ - **JSON:** GBNF grammar enforces a strict ElysiumResponse schema on every forward pass
38
+ - **Tools:** offline-first DuckDuckGo, email, reminders, calendar, weather, calculator, fetch_url, transit_lookup, files
39
+ - **Frontend:** 100% custom HTML/Canvas2D on `gradio.Server` (no default Gradio UI)
40
+
41
+ Runs identically online (HF Spaces) and offline (`git clone … && python app.py`).
42
+
43
+ ## Required environment variables (Settings β†’ Variables and secrets)
44
+
45
+ | Variable | Value |
46
+ |---|---|
47
+ | `ELYSIUM_MODEL_REPO` | `build-small-hackathon/elysium-MiniCPM-V-4.6-F16-GGUF` |
48
+ | `ELYSIUM_GGUF_FILE` | `elysium-f16.gguf` |
49
+ | `ELYSIUM_MMPROJ_FILE` | *(leave empty β€” no mmproj published yet, vision auto-disables)* |
50
+ | `HF_TOKEN` | your read token (optional for public repos but recommended) |
51
+
52
+ > The defaults already point at the real published repo, so you don't strictly need to set these β€” but doing so makes the Space's config explicit.
backend/config.py CHANGED
@@ -1,30 +1,49 @@
1
- """Path detection β€” HF Spaces /data bucket vs local ./local_data clone."""
2
- import os
3
- from pathlib import Path
4
-
5
-
6
- def _detect() -> Path:
7
- p = Path("/data")
8
- if p.exists() and os.access(p, os.W_OK):
9
- return p
10
- local = Path(__file__).resolve().parent.parent / "local_data"
11
- local.mkdir(exist_ok=True)
12
- return local
13
-
14
-
15
- DATA_PATH = _detect()
16
- HYPERGRAPH_DB = DATA_PATH / "hypergraph" / "civilization.db"
17
- FOSSILS_DIR = DATA_PATH / "fossils"
18
- AUDIO_CACHE = DATA_PATH / "audio_cache"
19
- NODE_POSITIONS = DATA_PATH / "node_positions"
20
- REMINDERS_DB = DATA_PATH / "reminders.db"
21
- CALENDAR_ICS = DATA_PATH / "calendar.ics"
22
- OUTBOX = DATA_PATH / "outbox"
23
-
24
- for d in (HYPERGRAPH_DB.parent, FOSSILS_DIR, AUDIO_CACHE, NODE_POSITIONS, OUTBOX):
25
- d.mkdir(parents=True, exist_ok=True)
26
-
27
- MODEL_REPO = os.environ.get("ELYSIUM_MODEL_REPO", "build-small-hackathon/elysium-GGUF")
28
- GGUF_FILE = os.environ.get("ELYSIUM_GGUF_FILE", "elysium-Q6_K.gguf")
29
- MMPROJ_FILE = os.environ.get("ELYSIUM_MMPROJ_FILE", "mmproj-model-f16.gguf")
30
- OFFLINE_MODE = os.environ.get("ELYSIUM_OFFLINE", "0") == "1"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Path detection β€” HF Spaces /data bucket vs local ./local_data clone.
2
+
3
+ Environment variables (defaults match the real published repo):
4
+ ELYSIUM_MODEL_REPO default: build-small-hackathon/elysium-MiniCPM-V-4.6-F16-GGUF
5
+ ELYSIUM_GGUF_FILE default: elysium-f16.gguf
6
+ ELYSIUM_MMPROJ_FILE default: "" (no mmproj file is published yet β€” vision auto-disables)
7
+ HF_TOKEN optional read token for private/gated repos
8
+ ELYSIUM_OFFLINE "1" to force offline mode
9
+ """
10
+ import os
11
+ from pathlib import Path
12
+
13
+
14
+ def _detect() -> Path:
15
+ p = Path("/data")
16
+ if p.exists() and os.access(p, os.W_OK):
17
+ return p
18
+ local = Path(__file__).resolve().parent.parent / "local_data"
19
+ local.mkdir(exist_ok=True)
20
+ return local
21
+
22
+
23
+ DATA_PATH = _detect()
24
+ HYPERGRAPH_DB = DATA_PATH / "hypergraph" / "civilization.db"
25
+ FOSSILS_DIR = DATA_PATH / "fossils"
26
+ AUDIO_CACHE = DATA_PATH / "audio_cache"
27
+ NODE_POSITIONS = DATA_PATH / "node_positions"
28
+ REMINDERS_DB = DATA_PATH / "reminders.db"
29
+ CALENDAR_ICS = DATA_PATH / "calendar.ics"
30
+ OUTBOX = DATA_PATH / "outbox"
31
+
32
+ for d in (HYPERGRAPH_DB.parent, FOSSILS_DIR, AUDIO_CACHE, NODE_POSITIONS, OUTBOX):
33
+ d.mkdir(parents=True, exist_ok=True)
34
+
35
+ # ─── Model configuration ─────────────────────────────────────────────────────
36
+ # These defaults point at the REAL published repository:
37
+ # https://huggingface.co/build-small-hackathon/elysium-MiniCPM-V-4.6-F16-GGUF
38
+ # which currently contains:
39
+ # - elysium-f16.gguf (the main MiniCPM-V 4.6 fine-tuned weights)
40
+ # No mmproj file is published yet, so MMPROJ_FILE defaults to "" and vision
41
+ # is gracefully disabled at load time.
42
+ MODEL_REPO = os.environ.get("ELYSIUM_MODEL_REPO", "build-small-hackathon/elysium-MiniCPM-V-4.6-F16-GGUF")
43
+ GGUF_FILE = os.environ.get("ELYSIUM_GGUF_FILE", "elysium-f16.gguf")
44
+ MMPROJ_FILE = os.environ.get("ELYSIUM_MMPROJ_FILE", "") # empty β†’ no vision
45
+
46
+ # Optional auth token (read token works for public repos too and avoids rate limits)
47
+ HF_TOKEN = os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN") or None
48
+
49
+ OFFLINE_MODE = os.environ.get("ELYSIUM_OFFLINE", "0") == "1"
backend/model_loader.py CHANGED
@@ -1,40 +1,76 @@
1
- """Loads the fine-tuned GGUF via llama-cpp-python.
2
- Pattern follows the HF ZeroGPU + small-talk reference:
3
- - hf_hub_download files at module import (warm cache)
4
- - instantiate Llama inside @spaces.GPU function on each request
5
- """
6
- from huggingface_hub import hf_hub_download
7
- from .config import MODEL_REPO, GGUF_FILE, MMPROJ_FILE
8
-
9
- print(f"[model_loader] downloading {MODEL_REPO}/{GGUF_FILE} …")
10
- MODEL_PATH = hf_hub_download(repo_id=MODEL_REPO, filename=GGUF_FILE)
11
-
12
- try:
13
- MMPROJ_PATH = hf_hub_download(repo_id=MODEL_REPO, filename=MMPROJ_FILE)
14
- print(f"[model_loader] mmproj ready: {MMPROJ_PATH}")
15
- except Exception as e:
16
- print(f"[model_loader] mmproj unavailable ({e}) β€” vision disabled")
17
- MMPROJ_PATH = None
18
-
19
-
20
- def make_llm():
21
- """Create a fresh Llama inside a GPU context.
22
- The .gguf file is filesystem-cached, so this is fast after the first call."""
23
- from llama_cpp import Llama
24
-
25
- chat_handler = None
26
- if MMPROJ_PATH:
27
- try:
28
- from llama_cpp.llama_chat_format import MiniCPMv26ChatHandler
29
- chat_handler = MiniCPMv26ChatHandler(clip_model_path=MMPROJ_PATH, verbose=False)
30
- except Exception as e:
31
- print(f"[model_loader] vision chat handler failed: {e}")
32
-
33
- return Llama(
34
- model_path=MODEL_PATH,
35
- chat_handler=chat_handler,
36
- n_gpu_layers=-1,
37
- n_ctx=8192,
38
- flash_attn=True,
39
- verbose=False,
40
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Loads the fine-tuned GGUF via llama-cpp-python.
2
+
3
+ Pattern follows the HF ZeroGPU + small-talk reference:
4
+ - hf_hub_download files at module import (warm cache)
5
+ - instantiate Llama inside @spaces.GPU function on each request
6
+
7
+ Robustness:
8
+ - Uses HF_TOKEN if available (avoids 401 on rate-limited / gated lookups)
9
+ - Treats MMPROJ_FILE as optional. If unset OR download fails, vision is
10
+ disabled gracefully (text-only chat still works).
11
+ - Fails loudly with a helpful message if the main GGUF cannot be fetched.
12
+ """
13
+ import os
14
+ import traceback
15
+ from huggingface_hub import hf_hub_download
16
+ from .config import MODEL_REPO, GGUF_FILE, MMPROJ_FILE, HF_TOKEN
17
+
18
+
19
+ def _download(repo_id: str, filename: str):
20
+ """hf_hub_download with optional token, returns path or raises."""
21
+ kwargs = {"repo_id": repo_id, "filename": filename}
22
+ if HF_TOKEN:
23
+ kwargs["token"] = HF_TOKEN
24
+ return hf_hub_download(**kwargs)
25
+
26
+
27
+ print(f"[model_loader] downloading {MODEL_REPO}/{GGUF_FILE} …")
28
+ try:
29
+ MODEL_PATH = _download(MODEL_REPO, GGUF_FILE)
30
+ print(f"[model_loader] main model ready: {MODEL_PATH}")
31
+ except Exception as e:
32
+ # We re-raise so the Space fails fast with a clear message rather than
33
+ # silently running with no brain. The traceback is already useful.
34
+ print(f"[model_loader] FAILED to download {MODEL_REPO}/{GGUF_FILE}: {e}")
35
+ print("[model_loader] Check that ELYSIUM_MODEL_REPO and ELYSIUM_GGUF_FILE "
36
+ "point at a real public file, and (for private repos) that HF_TOKEN is set.")
37
+ raise
38
+
39
+ # ─── mmproj (vision projector) is OPTIONAL ──────────────────────────────────
40
+ MMPROJ_PATH = None
41
+ if MMPROJ_FILE:
42
+ print(f"[model_loader] attempting mmproj {MODEL_REPO}/{MMPROJ_FILE} …")
43
+ try:
44
+ MMPROJ_PATH = _download(MODEL_REPO, MMPROJ_FILE)
45
+ print(f"[model_loader] mmproj ready: {MMPROJ_PATH}")
46
+ except Exception as e:
47
+ print(f"[model_loader] mmproj unavailable ({e}) β€” vision disabled")
48
+ MMPROJ_PATH = None
49
+ else:
50
+ print("[model_loader] ELYSIUM_MMPROJ_FILE not set β€” vision disabled (text-only mode)")
51
+
52
+
53
+ def make_llm():
54
+ """Create a fresh Llama inside a GPU context.
55
+ The .gguf file is filesystem-cached, so this is fast after the first call.
56
+ """
57
+ from llama_cpp import Llama
58
+
59
+ chat_handler = None
60
+ if MMPROJ_PATH:
61
+ try:
62
+ from llama_cpp.llama_chat_format import MiniCPMv26ChatHandler
63
+ chat_handler = MiniCPMv26ChatHandler(clip_model_path=MMPROJ_PATH, verbose=False)
64
+ except Exception as e:
65
+ print(f"[model_loader] vision chat handler failed: {e}")
66
+ traceback.print_exc()
67
+ chat_handler = None
68
+
69
+ return Llama(
70
+ model_path=MODEL_PATH,
71
+ chat_handler=chat_handler,
72
+ n_gpu_layers=-1,
73
+ n_ctx=8192,
74
+ flash_attn=True,
75
+ verbose=False,
76
+ )
backend/prompt_builder.py CHANGED
@@ -1,44 +1,66 @@
1
- """Build multimodal messages for llama-cpp-python chat completion."""
2
- import base64, io, uuid, datetime
3
- from typing import Optional
4
- from PIL import Image
5
-
6
- SYSTEM_PROMPT = """You are Elysium β€” a persistent agentic civilization.
7
- You ALWAYS respond with a single valid JSON object exactly matching the
8
- ElysiumResponse schema v1.0.0. No preamble. No markdown fences. JSON only.
9
-
10
- Decide complexity dynamically:
11
- - SIMPLE_REPLY: trivial Q β€” no agents (council_deliberation.agent_outputs = [])
12
- - QUERY / MORNING_BRIEFING / EVENING_REPORT: spawn 1–5 agents in agent_outputs,
13
- each with thinking + stance + tts_speech_text + tts_voice_design{voice_id,pace,tone}
14
- - TOOL_REQUIRED: populate tool_calls when external data is needed
15
- - SPECIATION_EVENT: only on unresolved cross-domain tension
16
- - Always populate ui_directives (camera_focus_node_id, pulses, threads)
17
- - All node_id and edge_id values must be unique strings
18
- """
19
-
20
-
21
- def _img_to_data_uri(img: Image.Image) -> str:
22
- buf = io.BytesIO()
23
- img.convert("RGB").save(buf, format="JPEG", quality=88)
24
- return "data:image/jpeg;base64," + base64.b64encode(buf.getvalue()).decode()
25
-
26
-
27
- def build_messages(user_text: str, image: Optional[Image.Image], hg_context: str = ""):
28
- user_content = []
29
- if image is not None:
30
- user_content.append({"type": "image_url", "image_url": {"url": _img_to_data_uri(image)}})
31
- ctx = f"\n\n[Hypergraph context]\n{hg_context}" if hg_context else ""
32
- user_content.append({"type": "text", "text": user_text + ctx})
33
-
34
- return [
35
- {"role": "system", "content": SYSTEM_PROMPT},
36
- {"role": "user", "content": user_content},
37
- ]
38
-
39
-
40
- def new_session_meta():
41
- return {
42
- "session_id": str(uuid.uuid4()),
43
- "timestamp_utc": datetime.datetime.utcnow().isoformat() + "Z",
44
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Build messages for llama-cpp-python chat completion.
2
+
3
+ If vision is available (mmproj loaded), images are embedded as data URIs in
4
+ a multimodal `content` list. Otherwise, we fall back to a text-only message
5
+ so the model never errors out on image input.
6
+ """
7
+ import base64
8
+ import io
9
+ import uuid
10
+ import datetime
11
+ from typing import Optional
12
+ from PIL import Image
13
+
14
+ from .model_loader import MMPROJ_PATH
15
+
16
+ SYSTEM_PROMPT = """You are Elysium β€” a persistent agentic civilization.
17
+ You ALWAYS respond with a single valid JSON object exactly matching the
18
+ ElysiumResponse schema v1.0.0. No preamble. No markdown fences. JSON only.
19
+
20
+ Decide complexity dynamically:
21
+ - SIMPLE_REPLY: trivial Q β€” no agents (council_deliberation.agent_outputs = [])
22
+ - QUERY / MORNING_BRIEFING / EVENING_REPORT: spawn 1–5 agents in agent_outputs,
23
+ each with thinking + stance + tts_speech_text + tts_voice_design{voice_id,pace,tone}
24
+ - TOOL_REQUIRED: populate tool_calls when external data is needed
25
+ - SPECIATION_EVENT: only on unresolved cross-domain tension
26
+ - Always populate ui_directives (camera_focus_node_id, pulses, threads)
27
+ - All node_id and edge_id values must be unique strings
28
+ """
29
+
30
+
31
+ def _img_to_data_uri(img: Image.Image) -> str:
32
+ buf = io.BytesIO()
33
+ img.convert("RGB").save(buf, format="JPEG", quality=88)
34
+ return "data:image/jpeg;base64," + base64.b64encode(buf.getvalue()).decode()
35
+
36
+
37
+ def build_messages(user_text: str, image: Optional[Image.Image], hg_context: str = ""):
38
+ ctx = f"\n\n[Hypergraph context]\n{hg_context}" if hg_context else ""
39
+
40
+ # Vision available β†’ multimodal content list
41
+ if image is not None and MMPROJ_PATH:
42
+ user_content = [
43
+ {"type": "image_url", "image_url": {"url": _img_to_data_uri(image)}},
44
+ {"type": "text", "text": user_text + ctx},
45
+ ]
46
+ return [
47
+ {"role": "system", "content": SYSTEM_PROMPT},
48
+ {"role": "user", "content": user_content},
49
+ ]
50
+
51
+ # No vision β†’ plain text. If user attached an image, note it textually.
52
+ note = ""
53
+ if image is not None and not MMPROJ_PATH:
54
+ note = "\n\n[Note: user attached an image but the vision projector is not loaded; respond based on text only.]"
55
+
56
+ return [
57
+ {"role": "system", "content": SYSTEM_PROMPT},
58
+ {"role": "user", "content": user_text + ctx + note},
59
+ ]
60
+
61
+
62
+ def new_session_meta():
63
+ return {
64
+ "session_id": str(uuid.uuid4()),
65
+ "timestamp_utc": datetime.datetime.utcnow().isoformat() + "Z",
66
+ }