| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- #!/usr/bin/env python3
- import argparse
- import json
- from datetime import datetime
- from pathlib import Path
- DEFAULT_OFFICIAL_MODEL_YAML = Path(
- r"G:\LMStudio\.lmstudio\hub\models\qwen\qwen3.6-35b-a3b\model.yaml"
- )
- DEFAULT_OFFICIAL_MANIFEST = Path(
- r"G:\LMStudio\.lmstudio\hub\models\qwen\qwen3.6-35b-a3b\manifest.json"
- )
- DEFAULT_OUTPUT_ROOT = Path("artifacts/lmstudio-model-wrappers")
- DEFAULT_TARGET_MODEL_KEY = "qwen3.6-35b-a3b-uncensored-hauhaucs-aggressive"
- DEFAULT_WRAPPER_OWNER = "local"
- DEFAULT_WRAPPER_NAME = "qwen3.6-35b-a3b-hauhaucs-aggressive"
- def build_wrapper_model_yaml(
- official_model_yaml_text: str,
- target_model_key: str,
- wrapper_owner: str,
- wrapper_name: str,
- ) -> str:
- lines = official_model_yaml_text.splitlines()
- output = []
- replaced_model = False
- inserted_base = False
- skipped_original_base = False
- for line in lines:
- stripped = line.strip()
- if not replaced_model and stripped.startswith("model: "):
- output.append(f"model: {wrapper_owner}/{wrapper_name}")
- replaced_model = True
- continue
- if not inserted_base and stripped.startswith("base:"):
- output.append(f'base: "{target_model_key}"')
- inserted_base = True
- skipped_original_base = True
- continue
- if skipped_original_base:
- if line.startswith("metadataOverrides:"):
- skipped_original_base = False
- output.append(line)
- continue
- output.append(line)
- if not replaced_model:
- raise ValueError("Official model.yaml is missing a model declaration")
- if not inserted_base:
- raise ValueError("Official model.yaml is missing a base declaration")
- return "\n".join(output).rstrip() + "\n"
- def build_wrapper_manifest(
- official_manifest: dict,
- target_model_key: str,
- wrapper_owner: str,
- wrapper_name: str,
- ) -> dict:
- manifest = {
- "type": "model",
- "owner": wrapper_owner,
- "name": wrapper_name,
- "dependencies": [
- {
- "type": "model",
- "purpose": "baseModel",
- "modelKeys": [target_model_key],
- }
- ],
- "revision": int(official_manifest.get("revision", 1)),
- }
- source_dep = (official_manifest.get("dependencies") or [{}])[0]
- if source_dep.get("sources"):
- manifest["dependencies"][0]["sources"] = source_dep["sources"]
- return manifest
- def build_readme(
- target_model_key: str,
- wrapper_owner: str,
- wrapper_name: str,
- ) -> str:
- return (
- f"# {wrapper_owner}/{wrapper_name}\n\n"
- "This wrapper exposes LM Studio reasoning metadata for a local HauhauCS GGUF.\n\n"
- "## Source Model\n\n"
- f"- Local model key: `{target_model_key}`\n\n"
- "## Intent\n\n"
- "- Preserve the original HauhauCS weights unchanged\n"
- "- Reuse the official Qwen3.6 reasoning-capable model wrapper behavior\n"
- "- Keep the wrapper portable and separate from LM Studio `.internal` state\n\n"
- "## Next Steps\n\n"
- "- Place or link this wrapper into an LM Studio-recognized local model package location\n"
- "- Reload LM Studio model metadata\n"
- "- Validate the wrapper via UI reasoning toggle and `/api/v1/chat`\n"
- )
- def build_wrapper_files(
- target_model_key: str,
- wrapper_owner: str,
- wrapper_name: str,
- official_model_yaml_text: str,
- official_manifest: dict,
- ) -> dict:
- model_yaml = build_wrapper_model_yaml(
- official_model_yaml_text=official_model_yaml_text,
- target_model_key=target_model_key,
- wrapper_owner=wrapper_owner,
- wrapper_name=wrapper_name,
- )
- manifest = build_wrapper_manifest(
- official_manifest=official_manifest,
- target_model_key=target_model_key,
- wrapper_owner=wrapper_owner,
- wrapper_name=wrapper_name,
- )
- readme = build_readme(
- target_model_key=target_model_key,
- wrapper_owner=wrapper_owner,
- wrapper_name=wrapper_name,
- )
- return {
- "model.yaml": model_yaml,
- "manifest.json": json.dumps(manifest, ensure_ascii=False, indent=2) + "\n",
- "README.md": readme,
- }
- def generate_wrapper(
- output_dir: Path,
- target_model_key: str,
- wrapper_owner: str,
- wrapper_name: str,
- official_model_yaml_text: str,
- official_manifest: dict,
- dry_run: bool,
- ) -> dict:
- files = build_wrapper_files(
- target_model_key=target_model_key,
- wrapper_owner=wrapper_owner,
- wrapper_name=wrapper_name,
- official_model_yaml_text=official_model_yaml_text,
- official_manifest=official_manifest,
- )
- summary = {
- "generated_at": datetime.now().isoformat(),
- "dry_run": dry_run,
- "output_dir": str(output_dir),
- "wrapper_model": f"{wrapper_owner}/{wrapper_name}",
- "target_model_key": target_model_key,
- "files": sorted(files.keys()),
- }
- if dry_run:
- return summary
- output_dir.mkdir(parents=True, exist_ok=True)
- for name, content in files.items():
- (output_dir / name).write_text(content, encoding="utf-8")
- (output_dir / "summary.json").write_text(
- json.dumps(summary, ensure_ascii=False, indent=2) + "\n",
- encoding="utf-8",
- )
- return summary
- def load_inputs(model_yaml_path: Path, manifest_path: Path) -> tuple[str, dict]:
- return (
- model_yaml_path.read_text(encoding="utf-8"),
- json.loads(manifest_path.read_text(encoding="utf-8")),
- )
- def main() -> int:
- parser = argparse.ArgumentParser()
- parser.add_argument("--target-model-key", default=DEFAULT_TARGET_MODEL_KEY)
- parser.add_argument("--wrapper-owner", default=DEFAULT_WRAPPER_OWNER)
- parser.add_argument("--wrapper-name", default=DEFAULT_WRAPPER_NAME)
- parser.add_argument("--official-model-yaml", default=str(DEFAULT_OFFICIAL_MODEL_YAML))
- parser.add_argument("--official-manifest", default=str(DEFAULT_OFFICIAL_MANIFEST))
- parser.add_argument("--output-dir", default="")
- parser.add_argument("--output-root", default=str(DEFAULT_OUTPUT_ROOT))
- parser.add_argument("--dry-run", action="store_true")
- args = parser.parse_args()
- official_model_yaml_text, official_manifest = load_inputs(
- Path(args.official_model_yaml),
- Path(args.official_manifest),
- )
- if args.output_dir:
- output_dir = Path(args.output_dir)
- else:
- output_dir = Path(args.output_root) / args.wrapper_owner / args.wrapper_name
- summary = generate_wrapper(
- output_dir=output_dir,
- target_model_key=args.target_model_key,
- wrapper_owner=args.wrapper_owner,
- wrapper_name=args.wrapper_name,
- official_model_yaml_text=official_model_yaml_text,
- official_manifest=official_manifest,
- dry_run=args.dry_run,
- )
- print(json.dumps(summary, ensure_ascii=False, indent=2))
- return 0
- if __name__ == "__main__":
- raise SystemExit(main())
|