GGUF
English
llama.cpp
decision-model
system-one
typed-decisions
calibrated-probabilities
ainode
conversational
Instructions to use frontier-infra/jebadiah-4b-v2-GGUF with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use frontier-infra/jebadiah-4b-v2-GGUF with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M # Run inference directly in the terminal: llama cli -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M # Run inference directly in the terminal: llama cli -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M # Run inference directly in the terminal: ./llama-cli -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M # Run inference directly in the terminal: ./build/bin/llama-cli -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
Use Docker
docker model run hf.co/frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
- LM Studio
- Jan
- Ollama
How to use frontier-infra/jebadiah-4b-v2-GGUF with Ollama:
ollama run hf.co/frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
- Unsloth Desktop
- Pi
How to use frontier-infra/jebadiah-4b-v2-GGUF with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
Configure the model in Pi
# Install Pi: npm install -g @earendil-works/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "llama-cpp": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Docker Model Runner
How to use frontier-infra/jebadiah-4b-v2-GGUF with Docker Model Runner:
docker model run hf.co/frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
- Lemonade
How to use frontier-infra/jebadiah-4b-v2-GGUF with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
Run and chat with the model
lemonade run user.jebadiah-4b-v2-GGUF-Q4_K_M
List all available models
lemonade list
- Hermes Agent
How to use frontier-infra/jebadiah-4b-v2-GGUF with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use frontier-infra/jebadiah-4b-v2-GGUF with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "frontier-infra/jebadiah-4b-v2-GGUF:Q4_K_M" \ --custom-provider-id llama-cpp \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
Download scripts/jebadiah_prompt.py from frontier-infra/jebadiah-4b-v2-GGUF: direct link, hf CLI and curl.
- Browser
- Download file 10.6 kB
-
https://huggingface.co/frontier-infra/jebadiah-4b-v2-GGUF/resolve/main/scripts/jebadiah_prompt.py
- Command line
-
hf download hf://frontier-infra/jebadiah-4b-v2-GGUF/scripts/jebadiah_prompt.py
-
curl -L -o jebadiah_prompt.py https://huggingface.co/frontier-infra/jebadiah-4b-v2-GGUF/resolve/main/scripts/jebadiah_prompt.py
10.6 kB
| """The Jebadiah prompt: AINode's own /v1/systemone -> /v1/decide rendering, then the base model's | |
| chat template, then ONE label token. Train, eval and the local server all go through here, and | |
| the served route (AINode) renders the same bytes because the renderer is AINode's, copied | |
| verbatim (ainode_prompt_verbatim.py, hashed into the training config). | |
| A question is the Jev wire shape {type, instructions, criteria}. AINode translates it to a list of | |
| option lines with letter labels (A..Z, AA..) and the wire names each label answers to: | |
| choice -> the criteria keys, in criteria order (or the order the caller passes) | |
| noul -> ["true", "false"] (always this order, AINode's rule) | |
| score -> the level texts in rubric order; we report them as level indices "0".."k-1" | |
| The answer position is the token right after the generation prompt, and the candidate tokens are | |
| the label strings themselves ("A", "B", ...), each one ordinary token at that boundary (checked | |
| against the tokenizer at load time). | |
| """ | |
| from __future__ import annotations | |
| import json | |
| from dataclasses import dataclass | |
| from ainode_prompt_verbatim import ( | |
| MAX_CRITERIA, | |
| PROMPT_SOURCE_COMMIT, | |
| PROMPT_SOURCE_SHA256, | |
| Translated, | |
| build_messages, | |
| criteria_pairs, | |
| option_label, | |
| option_text, | |
| serialize_state, | |
| translate_one, | |
| ) | |
| # Keys that must never appear inside a question object sent to the model. The linter rejects | |
| # them; the server refuses them. | |
| LABEL_KEYS = {"label", "labels", "expected", "passingAnswer", "passing_answer", "answer", "answers", | |
| "target", "targets", "gold", "reference", "truth", "correct"} | |
| CHAT_TEMPLATE_KWARGS = {"add_generation_prompt": True, "enable_thinking": False, "thinking": False} | |
| def flatten_text(v) -> str: | |
| """Instructions and descriptions must be strings on AINode's wire. Sources that ship an object | |
| or a list (Kev's {question, focus}) are flattened once, at conversion time, by joining the | |
| string values with a space. Never called on a state.""" | |
| if v is None: | |
| return "" | |
| if isinstance(v, str): | |
| return v.strip() | |
| if isinstance(v, dict): | |
| return " ".join(s for s in (flatten_text(x) for x in v.values()) if s) | |
| if isinstance(v, list): | |
| return " ".join(s for s in (flatten_text(x) for x in v) if s) | |
| return json.dumps(v, ensure_ascii=False) | |
| def wire_keys(q: dict, order: list | None = None) -> list[str]: | |
| """The answer keys of a question in the order the options are shown.""" | |
| t = q["type"] | |
| crit = q.get("criteria") | |
| if t == "choice": | |
| keys = list(crit) if isinstance(crit, dict) else [str(c) for c in crit] | |
| if order is not None: | |
| assert sorted(order) == sorted(keys), "order must be a permutation of the option keys" | |
| keys = list(order) | |
| return keys | |
| if t == "noul": | |
| return ["true", "false"] | |
| if t == "score": | |
| return [str(i) for i in range(len(crit))] | |
| raise ValueError(f"unknown question type {t!r}") | |
| def reorder_choice(q: dict, order: list) -> dict: | |
| """The same choice question with its criteria in `order` (AINode shows criteria order).""" | |
| crit = q["criteria"] | |
| if not isinstance(crit, dict): | |
| crit = {str(c): None for c in crit} | |
| return {**q, "criteria": {k: crit[k] for k in order}} | |
| class Rendered: | |
| prompt: str # the chat-templated text ending in the generation prompt | |
| keys: list[str] # wire keys in display order | |
| letters: list[str] # the label shown for each key, same order | |
| cand_ids: list[int] # token id of each label at the answer boundary, same order | |
| truncated: bool # the state was cut to fit the token budget | |
| label_scheme: str = "ainode" # "ainode" (option_label, what /v1/decide emits) or "extended" | |
| class Renderer: | |
| """Renders (state, question) exactly as AINode's /v1/systemone would, with this tokenizer's | |
| chat template, and knows the label token ids.""" | |
| def __init__(self, tokenizer, max_tokens: int = 2048): | |
| self.tok = tokenizer | |
| self.max_tokens = max_tokens | |
| self._label_ids: dict[str, int] = {} | |
| # every label AINode can emit up to 255 options must be one ordinary token at the boundary | |
| probe = self.render_messages("x", "q?", ["o1", "o2"]) | |
| base = tokenizer.encode(probe, add_special_tokens=False) | |
| specials = set(tokenizer.all_special_ids) | |
| for i in range(255): | |
| lab = option_label(i) | |
| comb = tokenizer.encode(probe + lab, add_special_tokens=False) | |
| suffix = comb[len(base):] | |
| if comb[:len(base)] != base or len(suffix) != 1 or suffix[0] in specials: | |
| break | |
| self._label_ids[lab] = suffix[0] | |
| self.max_options = len(self._label_ids) | |
| if self.max_options < 26: | |
| raise ValueError(f"only {self.max_options} labels are single tokens in this tokenizer") | |
| # Past AINode's single-token range (68 labels on the Qwen3.5 tokenizer: A..Z, AA..AZ, | |
| # BA..BP) the served route cannot answer anyway (its cap is 20). For the LOCAL read of a | |
| # wider question (Jevals Banking77, 77 options) we fall back to an extended alphabet: the | |
| # same A..Z, then every two-letter uppercase string that is one ordinary token at the | |
| # boundary, in alphabetical order. Rendered.label_scheme says which alphabet was used. | |
| import string | |
| self._extended: list[tuple[str, int]] = [(L, i) for L, i in self._label_ids.items() if len(L) == 1] | |
| for a in string.ascii_uppercase: | |
| for b in string.ascii_uppercase: | |
| lab = a + b | |
| comb = tokenizer.encode(probe + lab, add_special_tokens=False) | |
| suffix = comb[len(base):] | |
| if comb[:len(base)] == base and len(suffix) == 1 and suffix[0] not in specials: | |
| self._extended.append((lab, suffix[0])) | |
| self.max_options_extended = len(self._extended) | |
| def render_messages(self, state_text: str, question: str, options: list[str], | |
| letters: list[str] | None = None) -> str: | |
| messages = build_messages(state_text, None, question, options) | |
| if letters is not None: | |
| # the extended alphabet: swap AINode's letters for ours in the option lines, nothing else | |
| user = messages[1]["content"] | |
| head, _, rest = user.partition("\nOPTIONS:\n") | |
| lines = rest.split("\n") | |
| for i in range(len(options)): | |
| assert lines[i].startswith(f"{option_label(i)}. ") | |
| lines[i] = f"{letters[i]}. " + lines[i][len(option_label(i)) + 2:] | |
| messages[1]["content"] = head + "\nOPTIONS:\n" + "\n".join(lines) | |
| return self.tok.apply_chat_template(messages, tokenize=False, **CHAT_TEMPLATE_KWARGS) | |
| def render(self, state, q: dict, order: list | None = None) -> Rendered: | |
| if q["type"] == "choice" and order is not None: | |
| q = reorder_choice(q, order) | |
| crit = q.get("criteria") | |
| if q["type"] == "choice" and isinstance(crit, dict) and len(crit) > MAX_CRITERIA: | |
| # AINode's route refuses this many options (its top-20 logprob read); the local read | |
| # renders them the same way and scores every label directly off the logits | |
| pairs = criteria_pairs("q", crit, "choice") | |
| t = Translated("choice", str(q.get("instructions", "")).strip(), | |
| [option_text(n, d) for n, d in pairs], [n for n, _ in pairs]) | |
| else: | |
| t = translate_one("q", q) | |
| keys = wire_keys(q, order) | |
| n_opt = len(t.names) | |
| scheme = "ainode" | |
| if n_opt <= self.max_options: | |
| letters = [option_label(i) for i in range(n_opt)] | |
| cand_ids = [self._label_ids[L] for L in letters] | |
| shown = None | |
| elif n_opt <= self.max_options_extended: | |
| letters = [L for L, _ in self._extended[:n_opt]] | |
| cand_ids = [i for _, i in self._extended[:n_opt]] | |
| shown = letters | |
| scheme = "extended" | |
| else: | |
| raise ValueError(f"{n_opt} options exceed the {self.max_options_extended} single-token labels") | |
| state_text = serialize_state(state) | |
| prompt = self.render_messages(state_text, t.question, t.options, shown) | |
| truncated = False | |
| n = len(self.tok.encode(prompt, add_special_tokens=False)) | |
| if n > self.max_tokens: | |
| # cut the state from its end so the question, options and template survive intact | |
| overhead = n - len(self.tok.encode(state_text, add_special_tokens=False)) | |
| keep = max(self.max_tokens - overhead - 4, 16) | |
| ids = self.tok.encode(state_text, add_special_tokens=False)[:keep] | |
| state_text = self.tok.decode(ids) + " [truncated]" | |
| prompt = self.render_messages(state_text, t.question, t.options, shown) | |
| truncated = True | |
| return Rendered(prompt, keys, letters, cand_ids, truncated, scheme) | |
| def answer_from_probs(q: dict, keys: list[str], probs: list[float]) -> dict: | |
| """The Jev wire answer for one question from its probabilities over `keys` (display order). | |
| choice: {type, choice, confidence, probabilities}; noul: {type, noul}; score: {type, score, | |
| confidence, legend, probabilities}. Ties: choice -> the option listed first; score -> the | |
| lower level (the Jevals rule). Confidence is (p_max - 1/n) / (1 - 1/n), the chance-corrected | |
| top probability AINode's route also reports.""" | |
| t = q["type"] | |
| if t == "noul": | |
| return {"type": "noul", "noul": round(float(probs[keys.index("true")]), 6)} | |
| best = 0 | |
| for i, p in enumerate(probs): | |
| if p > probs[best]: | |
| best = i | |
| n = len(keys) | |
| p_max = float(probs[best]) | |
| confidence = 1.0 if n == 1 else (p_max - 1.0 / n) / (1.0 - 1.0 / n) | |
| if t == "choice": | |
| return {"type": "choice", "choice": keys[best], "confidence": round(confidence, 6), | |
| "probabilities": {k: round(float(p), 6) for k, p in zip(keys, probs)}} | |
| expected = sum(int(k) * float(p) for k, p in zip(keys, probs)) | |
| crit = q.get("criteria") or [] | |
| legend = {str(i): (c if isinstance(c, str) else str(c)) for i, c in enumerate(crit)} if isinstance(crit, list) \ | |
| else {str(i): k for i, k in enumerate(crit)} | |
| return {"type": "score", "score": round(expected, 6), "confidence": round(confidence, 6), | |
| "legend": legend, "probabilities": {k: round(float(p), 6) for k, p in zip(keys, probs)}} | |