carepath-api / scribe /training /scripts /export_serve.py
tranth3truong's picture
Deploy public Scribe-only CarePath Space
cc678b9
Raw
History Blame Contribute Delete
4.42 kB
"""CLI: bundle a gated GEC adapter into a self-describing serve package.
The serving API must not import ``gec.*`` (research/serving stay
decoupled). So this offline script freezes everything serving needs into a
``serve_manifest.json``: base model, the variant's ``use_retrieval`` flag, and
the DARAG prompt strings. The bundle also carries the adapter, its tokenizer,
and the enriched datastore, so a server only needs the bundle directory.
python scribe/training/scripts/export_serve.py \
--adapter-dir artifacts/gec_lora/qwen3/full/seed-13 \
--datastore artifacts/retrieval/term_datastore.json \
--output artifacts/gec_serve
Set ``LLM_PROVIDER=gec_local`` and ``GEC_BUNDLE_PATH=<output>`` to serve it.
"""
from __future__ import annotations
import argparse
import json
import shutil
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "scribe" / "training"))
sys.path.insert(0, str(Path(__file__).resolve().parents[3] / "scribe"))
from gec.cliutil import configure_stdout # noqa: E402
configure_stdout()
from gec.config import DEFAULT_BASE_MODEL, FALLBACK_BASE_MODEL # noqa: E402
from gec.prompts import IM_END, IM_START, SYSTEM_PROMPT # noqa: E402
def _read_variant(adapter_dir: Path) -> dict:
marker = adapter_dir / "darag_variant.json"
if marker.exists():
try:
return json.loads(marker.read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError):
pass
return {"variant": "full", "use_retrieval": True}
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--adapter-dir", required=True, help="trained LoRA adapter directory")
parser.add_argument("--datastore", required=True, help="enriched term datastore JSON")
parser.add_argument("--output", default="artifacts/gec_serve", help="bundle output dir")
parser.add_argument("--base-model", default=DEFAULT_BASE_MODEL)
parser.add_argument("--fallback-base-model", default=FALLBACK_BASE_MODEL)
parser.add_argument("--max-new-tokens", type=int, default=256)
parser.add_argument("--gate-report", default=None, help="optional WER report JSON to embed")
args = parser.parse_args()
adapter_dir = Path(args.adapter_dir)
if not adapter_dir.exists():
raise SystemExit(f"Adapter dir not found: {adapter_dir}. Train + gate one first.")
datastore = Path(args.datastore)
if not datastore.exists():
raise SystemExit(f"Datastore not found: {datastore}. Build it first.")
bundle = Path(args.output)
bundle.mkdir(parents=True, exist_ok=True)
# Copy the adapter (+ its tokenizer + darag_variant.json) and datastore in,
# so the bundle is portable from Drive to a serving box on its own.
adapter_out = bundle / "adapter"
if adapter_out.exists():
shutil.rmtree(adapter_out)
shutil.copytree(adapter_dir, adapter_out)
shutil.copy2(datastore, bundle / "term_datastore.json")
variant = _read_variant(adapter_dir)
manifest = {
"schema": "carepath.gec.serve/1",
"base_model": args.base_model,
"fallback_base_model": args.fallback_base_model,
"variant": variant.get("variant", "full"),
"use_retrieval": bool(variant.get("use_retrieval", True)),
"max_new_tokens": args.max_new_tokens,
"adapter_dir": "adapter",
"datastore": "term_datastore.json",
# Frozen DARAG prompt so serving reproduces the training format with no
# dependency on gec.prompts.
"prompt": {
"system": SYSTEM_PROMPT,
"im_start": IM_START,
"im_end": IM_END,
"best_label": "Best hypothesis: ",
"others_label": "Other hypotheses:",
"entities_label": "Named entities: ",
},
}
if args.gate_report and Path(args.gate_report).exists():
manifest["gate_report"] = json.loads(Path(args.gate_report).read_text(encoding="utf-8"))
(bundle / "serve_manifest.json").write_text(
json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8"
)
print(f"Wrote serve bundle -> {bundle}")
print(f" variant={manifest['variant']} use_retrieval={manifest['use_retrieval']} "
f"base={manifest['base_model']}")
print(f"Serve with: LLM_PROVIDER=gec_local GEC_BUNDLE_PATH={bundle}")
if __name__ == "__main__":
main()