Text Generation
Safetensors
GGUF
English
tenaos
gemma
llama.cpp
lora
qlora
clinical
openmrs
conversational
Instructions to use beza4588/TenaOS 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 beza4588/TenaOS 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 beza4588/TenaOS:BF16 # Run inference directly in the terminal: llama cli -hf beza4588/TenaOS:BF16
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf beza4588/TenaOS:BF16 # Run inference directly in the terminal: llama cli -hf beza4588/TenaOS:BF16
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 beza4588/TenaOS:BF16 # Run inference directly in the terminal: ./llama-cli -hf beza4588/TenaOS:BF16
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 beza4588/TenaOS:BF16 # Run inference directly in the terminal: ./build/bin/llama-cli -hf beza4588/TenaOS:BF16
Use Docker
docker model run hf.co/beza4588/TenaOS:BF16
- LM Studio
- Jan
- vLLM
How to use beza4588/TenaOS with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "beza4588/TenaOS" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "beza4588/TenaOS", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/beza4588/TenaOS:BF16
- Ollama
How to use beza4588/TenaOS with Ollama:
ollama run hf.co/beza4588/TenaOS:BF16
- Unsloth Desktop
- Pi
How to use beza4588/TenaOS with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf beza4588/TenaOS:BF16
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": "beza4588/TenaOS:BF16" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Docker Model Runner
How to use beza4588/TenaOS with Docker Model Runner:
docker model run hf.co/beza4588/TenaOS:BF16
- Lemonade
How to use beza4588/TenaOS with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull beza4588/TenaOS:BF16
Run and chat with the model
lemonade run user.TenaOS-BF16
List all available models
lemonade list
- Hermes Agent
How to use beza4588/TenaOS with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf beza4588/TenaOS:BF16
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 beza4588/TenaOS:BF16
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use beza4588/TenaOS with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf beza4588/TenaOS:BF16
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 "beza4588/TenaOS:BF16" \ --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 training_code/check_chat_template_parity.py from beza4588/TenaOS: direct link, hf CLI and curl.
- Browser
- Download file 8.78 kB
-
https://huggingface.co/beza4588/TenaOS/resolve/main/training_code/check_chat_template_parity.py
- Command line
-
hf download hf://beza4588/TenaOS/training_code/check_chat_template_parity.py
-
curl -L -o check_chat_template_parity.py https://huggingface.co/beza4588/TenaOS/resolve/main/training_code/check_chat_template_parity.py
8.78 kB
| #!/usr/bin/env python3 | |
| """Verify the training chat template renders reconstructed multi-turn examples | |
| the same way llama.cpp's --jinja rendering of the production GGUF does. | |
| This is a direct response to a real regression class found while building the | |
| retrained corpus: prepare_lora_corpus.py originally emitted tool results as | |
| `role: "tool"` messages. Empirically, hitting a live llama-server instance | |
| (--jinja, loaded with the currently-deployed merged GGUF) with that shape | |
| showed the template silently drops role="tool" content whenever the | |
| preceding assistant turn used the <tena_call> text wrapper instead of native | |
| tool_calls -- which is exactly the wire format this whole system actually | |
| uses (see llm_client.py::_messages_with_text_tool_instructions, called | |
| unconditionally whenever `tools` is passed). The fix was to reconstruct tool | |
| results as `role: "user", content: f"Result for {name}: {content}"`, matching | |
| _strip_native_tool_messages exactly. This script re-runs that same live | |
| verification in a repeatable way so future template or builder changes don't | |
| silently reintroduce the bug. | |
| Usage: | |
| python3 check_chat_template_parity.py \ | |
| --gguf /var/www/LORA_TenaOS/hf_release/tenaos-gemma4-lora/tenaos-gemma-4-E4B-it-lora-F16.gguf \ | |
| --llama-server /var/www/TenaOS/third_party/llama.cpp/sm80/bin/llama-server \ | |
| --tokenizer-dir /var/www/LORA_TenaOS/hf_release/tenaos-gemma4-lora/merged_hf \ | |
| --samples 2 | |
| Requires: a free GPU slot to load the GGUF briefly, and (optionally) a local | |
| transformers install to independently render the same conversations via | |
| AutoTokenizer.apply_chat_template for a second, HF-side comparison. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import subprocess | |
| import sys | |
| import time | |
| import urllib.request | |
| from pathlib import Path | |
| from typing import Any | |
| sys.path.insert(0, str(Path(__file__).resolve().parent)) | |
| import prepare_lora_corpus as corpus # noqa: E402 | |
| NORMALIZED_ROOT = Path(__file__).resolve().parent / "artifacts_v2" / "normalized" | |
| SAMPLE_KINDS = [ | |
| "form", | |
| "report", | |
| "cds", | |
| "patient_education", | |
| "scribe_text_english", | |
| "scribe_text_amharic", | |
| "voice_scribe_audio", | |
| ] | |
| def load_sample_conversations(samples_per_kind: int) -> list[dict[str, Any]]: | |
| out: list[dict[str, Any]] = [] | |
| for kind in SAMPLE_KINDS: | |
| path = NORMALIZED_ROOT / kind / f"{kind}_accepted.jsonl" | |
| if not path.exists(): | |
| print(f"WARNING: {path} missing, skipping {kind}") | |
| continue | |
| count = 0 | |
| with path.open() as handle: | |
| for line in handle: | |
| if count >= samples_per_kind: | |
| break | |
| record = json.loads(line) | |
| examples = corpus.multiturn_examples_from_trace(kind, record) | |
| for example in examples: | |
| out.append(example) | |
| count += 1 | |
| return out | |
| def check_role_alternation(example: dict[str, Any]) -> list[str]: | |
| problems = [] | |
| roles = [turn["role"] for turn in example["conversations"]] | |
| # Tool-calling kinds now carry an extra leading system message (the | |
| # ephemeral tool-schema instruction llm_client.py always prepends -- see | |
| # _tool_schema_system_message in prepare_lora_corpus.py), so the prefix | |
| # is [system, system, user] instead of [system, user]. Scribe kinds never | |
| # call llm.chat(..., tools=...), so they keep the single-system prefix. | |
| if roles[:1] == ["system"] and roles[1:2] == ["system"]: | |
| prefix_len = 3 | |
| if roles[:3] != ["system", "system", "user"]: | |
| problems.append(f"expected [system, system, user] prefix, got {roles[:3]}") | |
| else: | |
| prefix_len = 2 | |
| if roles[:2] != ["system", "user"]: | |
| problems.append(f"expected [system, user] prefix, got {roles[:2]}") | |
| rest = roles[prefix_len:] | |
| # Not strictly alternating any more: cds/patient_education can now emit | |
| # two consecutive "user" turns (the tool result, then a synthetic | |
| # steering nudge) -- this matches production exactly, since | |
| # tool_loop.py/material_loop.py append both as separate messages before | |
| # the next llm.chat call. Only assert the invariants that always hold: | |
| # every turn is assistant or user (no "system"/"tool" outside the | |
| # prefix), and it never starts two assistant turns back-to-back (that | |
| # would mean a turn boundary was lost during reconstruction). | |
| prev = None | |
| for i, role in enumerate(rest): | |
| if role not in ("assistant", "user"): | |
| problems.append(f"turn {i + prefix_len}: unexpected role={role!r} outside the system prefix") | |
| break | |
| if role == "assistant" and prev == "assistant": | |
| problems.append(f"turn {i + prefix_len}: two consecutive assistant turns") | |
| break | |
| prev = role | |
| if any(role == "tool" for role in roles): | |
| problems.append("contains role='tool' — production never sends this role over the wire (see module docstring)") | |
| return problems | |
| def start_llama_server(llama_server: Path, gguf: Path, port: int) -> subprocess.Popen: | |
| env_ld = str(llama_server.parent) | |
| proc = subprocess.Popen( | |
| [str(llama_server), "-m", str(gguf), "--port", str(port), "--jinja", "-ngl", "999", "-c", "32768"], | |
| cwd=str(llama_server.parent), | |
| env={"LD_LIBRARY_PATH": env_ld, "PATH": "/usr/bin:/bin"}, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.STDOUT, | |
| ) | |
| return proc | |
| def wait_for_server(port: int, *, timeout_s: float = 180.0) -> None: | |
| deadline = time.monotonic() + timeout_s | |
| url = f"http://127.0.0.1:{port}/health" | |
| while time.monotonic() < deadline: | |
| try: | |
| with urllib.request.urlopen(url, timeout=2) as resp: | |
| if resp.status == 200: | |
| return | |
| except Exception: | |
| time.sleep(2) | |
| raise TimeoutError(f"llama-server did not become healthy within {timeout_s}s") | |
| def render_via_llama_server(port: int, conversations: list[dict[str, Any]]) -> list[dict[str, Any]]: | |
| results = [] | |
| for example in conversations: | |
| body = json.dumps({"messages": example["conversations"], "max_tokens": 1}).encode("utf-8") | |
| req = urllib.request.Request( | |
| f"http://127.0.0.1:{port}/v1/chat/completions", | |
| data=body, | |
| headers={"Content-Type": "application/json"}, | |
| method="POST", | |
| ) | |
| try: | |
| with urllib.request.urlopen(req, timeout=60) as resp: | |
| payload = json.loads(resp.read()) | |
| results.append({ | |
| "id": example["id"], | |
| "kind": example["kind"], | |
| "ok": True, | |
| "prompt_tokens": payload.get("usage", {}).get("prompt_tokens"), | |
| }) | |
| except Exception as exc: | |
| results.append({"id": example["id"], "kind": example["kind"], "ok": False, "error": str(exc)}) | |
| return results | |
| def main() -> None: | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument("--gguf", type=Path, required=False) | |
| parser.add_argument("--llama-server", type=Path, required=False) | |
| parser.add_argument("--samples", type=int, default=1, help="Examples per kind to check.") | |
| parser.add_argument("--port", type=int, default=8899) | |
| parser.add_argument("--skip-server", action="store_true", help="Only run the static role-alternation check.") | |
| args = parser.parse_args() | |
| examples = load_sample_conversations(args.samples) | |
| print(f"Loaded {len(examples)} sample conversations across {len(SAMPLE_KINDS)} kinds.") | |
| static_failures = 0 | |
| for example in examples: | |
| problems = check_role_alternation(example) | |
| if problems: | |
| static_failures += 1 | |
| print(f"STATIC FAIL {example['kind']}/{example['id']}: {problems}") | |
| print(f"Static role-alternation check: {len(examples) - static_failures}/{len(examples)} passed.") | |
| if args.skip_server: | |
| raise SystemExit(1 if static_failures else 0) | |
| proc = start_llama_server(args.llama_server, args.gguf, args.port) | |
| try: | |
| wait_for_server(args.port) | |
| results = render_via_llama_server(args.port, examples) | |
| server_failures = [r for r in results if not r["ok"]] | |
| for r in server_failures: | |
| print(f"SERVER FAIL {r['kind']}/{r['id']}: {r.get('error')}") | |
| print(f"llama.cpp --jinja render check: {len(results) - len(server_failures)}/{len(results)} passed.") | |
| exit_code = 1 if (static_failures or server_failures) else 0 | |
| finally: | |
| proc.terminate() | |
| try: | |
| proc.wait(timeout=15) | |
| except subprocess.TimeoutExpired: | |
| proc.kill() | |
| raise SystemExit(exit_code) | |
| if __name__ == "__main__": | |
| main() | |