Spaces:
Running
Running
| """Local GEC serving provider (LLM_PROVIDER=gec_local), GPU-free. | |
| Uses an injected ``generate_fn`` so the model never loads — we test the parts | |
| that bite in production: DARAG prompt assembly from the bundle manifest, the | |
| clinical safety gate, fallback-to-offline on bad output, SOAP delegation, and | |
| the build_llm wiring. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import os | |
| import sys | |
| import tempfile | |
| import unittest | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).resolve().parents[1])) | |
| from carepath.config import Settings | |
| from carepath.services.gec_local import LocalGecLLM | |
| from carepath.services.llm import FallbackClinicalLLM, LLMError, OfflineClinicalLLM, build_llm | |
| from carepath.services.retrieval import RetrievedTerm | |
| IM_END = "<|im_end|>" | |
| def _bundle(tmp: Path) -> Path: | |
| (tmp / "adapter").mkdir(parents=True) | |
| manifest = { | |
| "schema": "carepath.gec.serve/1", | |
| "base_model": "Qwen/Qwen3-4B-Instruct-2507", | |
| "variant": "full", | |
| "use_retrieval": True, | |
| "max_new_tokens": 64, | |
| "adapter_dir": "adapter", | |
| "datastore": "term_datastore.json", | |
| "prompt": { | |
| "system": "You correct Vietnamese medical ASR transcripts.", | |
| "im_start": "<|im_start|>", | |
| "im_end": IM_END, | |
| "best_label": "Best hypothesis: ", | |
| "others_label": "Other hypotheses:", | |
| "entities_label": "Named entities: ", | |
| }, | |
| } | |
| (tmp / "serve_manifest.json").write_text(json.dumps(manifest), encoding="utf-8") | |
| return tmp | |
| def _terms() -> list[RetrievedTerm]: | |
| return [RetrievedTerm(term="testosterone", score=0.91, category="biomarker", source="lexicon")] | |
| class GecLocalTests(unittest.TestCase): | |
| def test_prompt_assembly_and_strip(self) -> None: | |
| with tempfile.TemporaryDirectory() as d: | |
| seen = {} | |
| def fake_gen(prompt: str) -> str: | |
| seen["prompt"] = prompt | |
| return f"lượng testosterone cao {IM_END} <trailing junk>" | |
| llm = LocalGecLLM(_bundle(Path(d)), OfflineClinicalLLM(), generate_fn=fake_gen) | |
| result = llm.correct_transcript("luong testo cao", _terms()) | |
| self.assertEqual(result.provider, "gec_local") | |
| self.assertEqual(result.corrected_text, "lượng testosterone cao") # stripped at im_end | |
| # prompt carries system, best hypothesis, and the retrieved NE | |
| self.assertIn("You correct Vietnamese", seen["prompt"]) | |
| self.assertIn("Best hypothesis: luong testo cao", seen["prompt"]) | |
| self.assertIn("Named entities: testosterone", seen["prompt"]) | |
| self.assertTrue(seen["prompt"].endswith("<|im_start|>assistant\n")) | |
| def test_safety_gate_rejects_empty_and_runaway(self) -> None: | |
| with tempfile.TemporaryDirectory() as d: | |
| bundle = _bundle(Path(d)) | |
| empty = LocalGecLLM(bundle, OfflineClinicalLLM(), generate_fn=lambda p: IM_END) | |
| with self.assertRaises(LLMError): | |
| empty.correct_transcript("benh nhan do spo2", _terms()) | |
| runaway = LocalGecLLM( | |
| bundle, OfflineClinicalLLM(), generate_fn=lambda p: ("x " * 50) + IM_END | |
| ) | |
| with self.assertRaises(LLMError): | |
| runaway.correct_transcript("benh nhan", _terms()) | |
| def test_fallback_to_offline_on_bad_output(self) -> None: | |
| with tempfile.TemporaryDirectory() as d: | |
| primary = LocalGecLLM(_bundle(Path(d)), OfflineClinicalLLM(), generate_fn=lambda p: IM_END) | |
| wrapped = FallbackClinicalLLM(primary, OfflineClinicalLLM()) | |
| result = wrapped.correct_transcript("benh nhan do spo2", _terms()) | |
| self.assertEqual(result.provider, "offline_fallback") # degraded, not crashed | |
| def test_soap_is_delegated(self) -> None: | |
| with tempfile.TemporaryDirectory() as d: | |
| llm = LocalGecLLM(_bundle(Path(d)), OfflineClinicalLLM(), generate_fn=lambda p: "x" + IM_END) | |
| soap = llm.generate_soap("bệnh nhân đau ngực", _terms()) | |
| self.assertEqual(soap.provider, "offline") # came from the delegate | |
| self.assertTrue(soap.soap.review_required) | |
| def test_build_llm_wires_gec_local_with_fallback(self) -> None: | |
| with tempfile.TemporaryDirectory() as d: | |
| _bundle(Path(d)) | |
| env = { | |
| "LLM_PROVIDER": "gec_local", | |
| "GEC_BUNDLE_PATH": d, | |
| "GEC_SOAP_DELEGATE": "offline", | |
| } | |
| old = {k: os.environ.get(k) for k in env} | |
| os.environ.update(env) | |
| try: | |
| llm = build_llm(Settings.from_env()) | |
| finally: | |
| for k, v in old.items(): | |
| os.environ[k] = v if v is not None else "" | |
| if v is None: | |
| os.environ.pop(k, None) | |
| self.assertIsInstance(llm, FallbackClinicalLLM) | |
| ready, details = llm.readiness() | |
| self.assertEqual(details["provider"], "gec_local") | |
| if __name__ == "__main__": | |
| unittest.main() | |