chore: import upstream snapshot with attribution
Dashboard / frontend (push) Failing after 0s
Dashboard / api (push) Failing after 0s
Lint PowerShell / powershell-lint (ubuntu-latest) (push) Failing after 1s
Python Lint / Lint Python with Ruff (push) Failing after 1s
ShellCheck / Lint shell scripts (push) Failing after 1s
Matrix Smoke / linux-smoke (push) Failing after 1s
Matrix Smoke / distro: cachyos (push) Failing after 15s
Matrix Smoke / distro: linux-mint-21.3 (push) Failing after 15s
Matrix Smoke / distro: debian-12 (push) Failing after 5m21s
Matrix Smoke / distro: fedora-41 (push) Failing after 4m56s
Matrix Smoke / distro: ubuntu-24.04 (push) Failing after 2m13s
Matrix Smoke / distro: rocky-9 (push) Failing after 10m39s
Matrix Smoke / distro: manjaro (push) Failing after 12m11s
Matrix Smoke / distro: opensuse-tw (push) Failing after 11m53s
Matrix Smoke / distro: archlinux (push) Failing after 20m3s
Matrix Smoke / distro: ubuntu-22.04 (push) Failing after 13m49s
Validate .env Schema / tier-1-env-validation (push) Successful in 52s
Validate .env Schema / tier-2-env-validation (push) Successful in 44s
Validate .env Schema / tier-3-env-validation (push) Successful in 52s
Validate .env Schema / tier-4-env-validation (push) Successful in 51s
Validate Extensions Catalog / Check catalog is up-to-date (push) Failing after 9m47s
Secret Scan / Scan for secrets (push) Failing after 21m4s
Validate Docker Compose / Validate Docker Compose files (push) Has been cancelled
Python Type Check / Type check with mypy (push) Has been cancelled
Validate .env Schema / tier-0-env-validation (push) Has been cancelled
Test Linux / integration-smoke (push) Has been cancelled
Lint PowerShell / powershell-lint (windows-latest) (push) Has been cancelled
Matrix Smoke / macos-smoke (push) Has been cancelled
Dashboard / frontend (push) Failing after 0s
Dashboard / api (push) Failing after 0s
Lint PowerShell / powershell-lint (ubuntu-latest) (push) Failing after 1s
Python Lint / Lint Python with Ruff (push) Failing after 1s
ShellCheck / Lint shell scripts (push) Failing after 1s
Matrix Smoke / linux-smoke (push) Failing after 1s
Matrix Smoke / distro: cachyos (push) Failing after 15s
Matrix Smoke / distro: linux-mint-21.3 (push) Failing after 15s
Matrix Smoke / distro: debian-12 (push) Failing after 5m21s
Matrix Smoke / distro: fedora-41 (push) Failing after 4m56s
Matrix Smoke / distro: ubuntu-24.04 (push) Failing after 2m13s
Matrix Smoke / distro: rocky-9 (push) Failing after 10m39s
Matrix Smoke / distro: manjaro (push) Failing after 12m11s
Matrix Smoke / distro: opensuse-tw (push) Failing after 11m53s
Matrix Smoke / distro: archlinux (push) Failing after 20m3s
Matrix Smoke / distro: ubuntu-22.04 (push) Failing after 13m49s
Validate .env Schema / tier-1-env-validation (push) Successful in 52s
Validate .env Schema / tier-2-env-validation (push) Successful in 44s
Validate .env Schema / tier-3-env-validation (push) Successful in 52s
Validate .env Schema / tier-4-env-validation (push) Successful in 51s
Validate Extensions Catalog / Check catalog is up-to-date (push) Failing after 9m47s
Secret Scan / Scan for secrets (push) Failing after 21m4s
Validate Docker Compose / Validate Docker Compose files (push) Has been cancelled
Python Type Check / Type check with mypy (push) Has been cancelled
Validate .env Schema / tier-0-env-validation (push) Has been cancelled
Test Linux / integration-smoke (push) Has been cancelled
Lint PowerShell / powershell-lint (windows-latest) (push) Has been cancelled
Matrix Smoke / macos-smoke (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Render ODS runtime config surfaces deterministically.
|
||||
|
||||
The first purpose of this script is read-only comparison: installers and
|
||||
runtime mutators can ask what config should look like without writing files.
|
||||
Follow-up wiring can then replace ad-hoc heredocs one surface at a time.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_MODEL = "qwen3.5-9b"
|
||||
DEFAULT_GGUF = "Qwen3.5-9B-Q4_K_M.gguf"
|
||||
DEFAULT_CONTEXT = 131072
|
||||
DEFAULT_LITELLM_KEY = "sk-lemonade"
|
||||
NO_KEY = "no-key"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RenderInputs:
|
||||
model: str
|
||||
gguf_file: str
|
||||
lemonade_model_id: str
|
||||
lemonade_api_base: str
|
||||
gpu_backend: str
|
||||
ods_mode: str
|
||||
llm_base_url: str
|
||||
litellm_key: str
|
||||
opencode_port: int
|
||||
context_length: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RenderedFile:
|
||||
surface: str
|
||||
path: str
|
||||
content: str
|
||||
|
||||
|
||||
def ensure_trailing_newline(text: str) -> str:
|
||||
return text if text.endswith("\n") else f"{text}\n"
|
||||
|
||||
|
||||
def lemonade_model_id(inputs: RenderInputs) -> str:
|
||||
if inputs.lemonade_model_id:
|
||||
return inputs.lemonade_model_id
|
||||
return f"extra.{inputs.gguf_file}"
|
||||
|
||||
|
||||
def hermes_model_id(inputs: RenderInputs) -> str:
|
||||
if inputs.ods_mode == "lemonade" or inputs.gpu_backend == "amd":
|
||||
return lemonade_model_id(inputs)
|
||||
return inputs.gguf_file or inputs.model
|
||||
|
||||
|
||||
def opencode_key(inputs: RenderInputs) -> str:
|
||||
return inputs.litellm_key if inputs.ods_mode == "lemonade" else NO_KEY
|
||||
|
||||
|
||||
def render_litellm_lemonade(inputs: RenderInputs) -> RenderedFile:
|
||||
model = lemonade_model_id(inputs)
|
||||
api_base = inputs.lemonade_api_base.rstrip("/") or "http://llama-server:8080/api/v1"
|
||||
content = f"""model_list:
|
||||
- model_name: default
|
||||
litellm_params:
|
||||
model: openai/{model}
|
||||
api_base: {api_base}
|
||||
api_key: {inputs.litellm_key}
|
||||
extra_body:
|
||||
chat_template_kwargs:
|
||||
enable_thinking: false
|
||||
|
||||
- model_name: "*"
|
||||
litellm_params:
|
||||
model: openai/{model}
|
||||
api_base: {api_base}
|
||||
api_key: {inputs.litellm_key}
|
||||
extra_body:
|
||||
chat_template_kwargs:
|
||||
enable_thinking: false
|
||||
|
||||
litellm_settings:
|
||||
drop_params: true
|
||||
set_verbose: false
|
||||
request_timeout: 900
|
||||
stream_timeout: 900
|
||||
"""
|
||||
return RenderedFile("litellm-lemonade", "config/litellm/lemonade.yaml", content)
|
||||
|
||||
|
||||
def render_hermes(inputs: RenderInputs) -> RenderedFile:
|
||||
model = hermes_model_id(inputs)
|
||||
content = f"""model:
|
||||
default: "{model}"
|
||||
provider: "custom"
|
||||
base_url: "{inputs.llm_base_url}"
|
||||
context_length: {inputs.context_length}
|
||||
|
||||
auxiliary:
|
||||
compression:
|
||||
context_length: {inputs.context_length}
|
||||
|
||||
compression:
|
||||
enabled: true
|
||||
threshold: 0.75
|
||||
target_ratio: 0.50
|
||||
protect_last_n: 40
|
||||
"""
|
||||
return RenderedFile("hermes", "data/hermes/config.yaml", content)
|
||||
|
||||
|
||||
def render_perplexica(inputs: RenderInputs) -> RenderedFile:
|
||||
model = lemonade_model_id(inputs) if inputs.ods_mode == "lemonade" else (inputs.gguf_file or inputs.model)
|
||||
base_url = inputs.llm_base_url.rstrip("/") or "http://llama-server:8080"
|
||||
if not (base_url.endswith("/v1") or base_url.endswith("/api/v1")):
|
||||
base_url = f"{base_url}/v1"
|
||||
payload = {
|
||||
"modelProviders": [
|
||||
{
|
||||
"id": "openai",
|
||||
"type": "openai",
|
||||
"name": "ODS",
|
||||
"config": {
|
||||
"apiKey": opencode_key(inputs),
|
||||
"baseURL": base_url,
|
||||
},
|
||||
"chatModels": [{"key": model, "name": model}],
|
||||
}
|
||||
],
|
||||
"preferences": {
|
||||
"defaultChatProvider": "openai",
|
||||
"defaultChatModel": model,
|
||||
"defaultEmbeddingProvider": "transformers",
|
||||
"defaultEmbeddingModel": "Xenova/all-MiniLM-L6-v2",
|
||||
},
|
||||
"setupComplete": True,
|
||||
}
|
||||
return RenderedFile(
|
||||
"perplexica",
|
||||
"data/perplexica/settings.seed.json",
|
||||
json.dumps(payload, indent=2, sort_keys=True) + "\n",
|
||||
)
|
||||
|
||||
|
||||
def render_opencode(inputs: RenderInputs) -> RenderedFile:
|
||||
payload = {
|
||||
"provider": "openai-compatible",
|
||||
"baseURL": inputs.llm_base_url,
|
||||
"apiKey": opencode_key(inputs),
|
||||
"model": lemonade_model_id(inputs) if inputs.ods_mode == "lemonade" else inputs.model,
|
||||
"port": inputs.opencode_port,
|
||||
}
|
||||
return RenderedFile(
|
||||
"opencode",
|
||||
".opencode/auth.json",
|
||||
json.dumps(payload, indent=2, sort_keys=True) + "\n",
|
||||
)
|
||||
|
||||
|
||||
def render_env(inputs: RenderInputs) -> RenderedFile:
|
||||
lines = [
|
||||
f"ODS_MODE={inputs.ods_mode}",
|
||||
f"LLM_BACKEND={'lemonade' if inputs.ods_mode == 'lemonade' else 'llama-server'}",
|
||||
f"LLM_MODEL={inputs.model}",
|
||||
f"GGUF_FILE={inputs.gguf_file}",
|
||||
f"GPU_BACKEND={inputs.gpu_backend}",
|
||||
f"LLM_API_URL={inputs.llm_base_url}",
|
||||
f"CTX_SIZE={inputs.context_length}",
|
||||
f"MAX_CONTEXT={inputs.context_length}",
|
||||
]
|
||||
return RenderedFile("env", ".env.generated", "\n".join(lines) + "\n")
|
||||
|
||||
|
||||
RENDERERS: dict[str, Callable[[RenderInputs], RenderedFile]] = {
|
||||
"env": render_env,
|
||||
"opencode": render_opencode,
|
||||
"litellm-lemonade": render_litellm_lemonade,
|
||||
"perplexica": render_perplexica,
|
||||
"hermes": render_hermes,
|
||||
}
|
||||
|
||||
|
||||
def parse_args(argv: list[str]) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--surface", choices=["all", *sorted(RENDERERS)], default="all")
|
||||
parser.add_argument("--model", default=DEFAULT_MODEL)
|
||||
parser.add_argument("--gguf-file", default=DEFAULT_GGUF)
|
||||
parser.add_argument("--lemonade-model-id", default="")
|
||||
parser.add_argument("--lemonade-api-base", default="http://llama-server:8080/api/v1")
|
||||
parser.add_argument("--gpu-backend", choices=["amd", "apple", "cpu", "nvidia"], default="nvidia")
|
||||
parser.add_argument("--ods-mode", choices=["local", "cloud", "hybrid", "lemonade"], default="local")
|
||||
parser.add_argument("--llm-base-url", default="http://llama-server:8080/v1")
|
||||
parser.add_argument("--litellm-key", default=DEFAULT_LITELLM_KEY)
|
||||
parser.add_argument("--opencode-port", type=int, default=3003)
|
||||
parser.add_argument("--context-length", type=int, default=DEFAULT_CONTEXT)
|
||||
parser.add_argument("--format", choices=["json", "paths"], default="json")
|
||||
parser.add_argument("--output-root", default=".", help="Root directory used with --write")
|
||||
parser.add_argument("--write", action="store_true", help="Write rendered files under --output-root")
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def select_surfaces(surface: str) -> list[str]:
|
||||
if surface == "all":
|
||||
return ["env", "opencode", "litellm-lemonade", "perplexica", "hermes"]
|
||||
return [surface]
|
||||
|
||||
|
||||
def render(args: argparse.Namespace) -> dict[str, object]:
|
||||
inputs = RenderInputs(
|
||||
model=args.model,
|
||||
gguf_file=args.gguf_file,
|
||||
lemonade_model_id=args.lemonade_model_id,
|
||||
lemonade_api_base=args.lemonade_api_base,
|
||||
gpu_backend=args.gpu_backend,
|
||||
ods_mode=args.ods_mode,
|
||||
llm_base_url=args.llm_base_url,
|
||||
litellm_key=args.litellm_key,
|
||||
opencode_port=args.opencode_port,
|
||||
context_length=args.context_length,
|
||||
)
|
||||
files = [RENDERERS[name](inputs) for name in select_surfaces(args.surface)]
|
||||
written: list[str] = []
|
||||
if args.write:
|
||||
output_root = Path(args.output_root)
|
||||
for item in files:
|
||||
target = output_root / item.path
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_text(ensure_trailing_newline(item.content), encoding="utf-8")
|
||||
written.append(str(target))
|
||||
return {
|
||||
"version": "1",
|
||||
"mode": "write" if args.write else "dry-run",
|
||||
"inputs": asdict(inputs),
|
||||
"files": [asdict(RenderedFile(item.surface, item.path, ensure_trailing_newline(item.content))) for item in files],
|
||||
"written": written,
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
args = parse_args(argv)
|
||||
payload = render(args)
|
||||
if args.format == "paths":
|
||||
for item in payload["files"]:
|
||||
print(item["path"])
|
||||
else:
|
||||
print(json.dumps(payload, indent=2, sort_keys=True))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv[1:]))
|
||||
Reference in New Issue
Block a user