stcc-checker-0.6b-GGUF — 电话分诊判据核对器

给一段问诊记录一条判据,回答 yes / no / unknown只输出一个词。

它不选协议、不定档位、不生成处置措辞、不做 tool call。分级由配套的确定性规则引擎 (stcc-mcp,225 份 STCC 协议 / 849 分支 / 4,168 条判据)算出。 本仓发布核对器权重,引擎在 GitHub。 注意链路里还缺一环:「该用哪份协议」需要编排层自己解决(见下方边界 1)。

先花 20 秒判断它适不适合你(详见文末「已知边界」):

你的输入是 结果
已按协议问过一轮的问诊记录 ✅ 这是它的设计场景,档位会分散到 L1–L5
用户的一句话自述("我肚子疼") ❌ 输出 97% 挤在 L1+L2,等于对谁都说"马上就医"——没有区分度
你还需要它替你选协议 ❌ 本仓不提供这一步,须编排层自己解决

🔴 读这一段,否则模型会"返回空字符串"

通过 ollama pull hf.co/... 使用时,请求必须带顶层 "think": false

  {"model": "...", "prompt": "...", "stream": false,
+  "think": false,
   "options": {"temperature": 0, "num_predict": 8}}

原因ollama pull hf.co/<repo> 不会使用本仓的 Modelfile —— Ollama 从 GGUF 元数据 自动生成模板,而 Qwen3 的默认模板带 thinking。不传这个参数,模型返回空字符串, 且不报任何错done_reason 仍是 stop,HTTP 200),看起来完全像模型坏了。 加大 num_predict 无效 —— 那不是截断。

不想每次都带这个参数,就用本仓的 Modelfile 自己建一次(模板与训练逐字节一致): (Ollama 在别的主机/端口时用 stcc-mcp triage --host http://<ip>:11434,需 stcc-mcp>=0.1.4。)

hf download chenhaodev/stcc-checker-0.6b-GGUF \
    Qwen3-0.6B.Q8_0.gguf Modelfile.Q8_0 --local-dir stcc
cd stcc && ollama create stcc-checker -f Modelfile.Q8_0

之后 ollama run stcc-checker / API 调用都无需再传 think

⚠️ 不是急诊分诊系统,不做诊断,不能替代医生。L1–L5 是处置阶梯 (叫救护车 / 立即急诊 / 今天就医 / 近两日门诊 / 居家观察)。紧急情况直接拨 120。

用法

ollama pull hf.co/chenhaodev/stcc-checker-0.6b-GGUF:Q8_0

输入格式(逐字节按这个拼,模板见随仓的 Modelfile.Q8_0):

叙述:{一段问诊记录}
判据:{一条判据}

→ 输出 yes / no / unknown 之一。

import json, urllib.request
MODEL = "hf.co/chenhaodev/stcc-checker-0.6b-GGUF:Q8_0"

def check(text, criterion, model=MODEL):
    body = json.dumps({"model": model, "stream": False,
                       "prompt": f"叙述:{text}\n判据:{criterion}",
                       "think": False,          # ← 必须。见下方警告
                       "options": {"temperature": 0, "num_predict": 8}}).encode()
    req = urllib.request.Request("http://127.0.0.1:11434/api/generate", data=body,
                                 headers={"Content-Type": "application/json"})
    return json.loads(urllib.request.urlopen(req).read())["response"].strip()

check("我发烧到39度,浑身酸痛", "发热")   # → 'yes'
check("我不发烧,就是有点咳嗽",   "发热")   # → 'no'
check("我肚子疼了两天",         "发热")   # → 'unknown'

🔴 "think": False 那一行不能删 —— 删了会静默返回空字符串。原因见页首红框。

配套引擎(完整链路)

pip install 'stcc-mcp>=0.1.4'        # 或从源码:github.com/devhc123/stcc-mcp
stcc-mcp doctor             # 自检:索引 / Ollama / 模型
stcc-mcp triage --protocol Fever_Adult.md "$(cat 记录.txt)"

triage 返回的 JSON 字段:

字段 含义
tier L1L5安全上界——永不比真实答案更轻
disposition 该分支的处置措辞,来自规则表逐字取出,不是模型生成
branch_id 命中的分支字母(A/B/C…,按 STCC 顺序走首个成立分支)
certain true=证据已足以定档;false=这是安全上界,还缺证据
citations 判定依据的判据 + 行号,可审计
unresolved 还缺哪几条判据——补问它们能收紧档位(但档位只在某支被完全排除时才降)
status decided/pending/referral/indeterminate/exhausted/rejected
n_asked · latency_s · no_threshold 问了几条判据、耗时、当前阈值

档位到行动的映射由你的编排层定;本包只给档位与处置措辞。

档位/分支/处置由纯标准库的确定性引擎算出(P(no)≥τ 阈值逻辑也在那一层,见 --no-threshold)。 仓库:https://github.com/devhc123/stcc-mcp (Apache-2.0) 本仓权重也可独立使用——就是上面那个三分类函数。

unknown 为什么是第一等状态

STCC 的分支语义是「本支任一条件为 ⇒ 命中;全部为 ⇒ 转下一支」。 "没提到" ≠ "说了没有":前者必须触发追问,后者才能转分支。 把 unknown 压成 no 等于凭空伪造阴性,会让引擎走到错误的分支。

实测

split n acc false_no🔴 no_recall
test_unseen(协议完全没见过) 3,224 0.9529 0.0024 0.9784
test_seen · new_record(协议见过、记录没见过) 1,251 0.9528 0.0000 0.9286
test_seen · seen_record(记录也见过) 487 0.9692 0.0000 0.9861
Q4_K_M · test_unseen 3,224 0.9498 0.0035 0.9770
  • false_no(该成立却判成 no)是硬门,因为假 no 会把真值分支从引擎的安全收敛里排除掉。 少判 no 只造成过分诊,是成本旋钮。
  • test_seen 故意拆两轨报:28% 的记录文本在训练集出现过(配的是别的判据)。 合并会被记忆抬高——实测记忆增益仅 +1.64pt。可泛化读数是 new_record 那行。
  • 解析失败 0、截断 0。

性能(Mac / Ollama / Q8_0,全新 venv 从 PyPI 装完实测):

冷启动(首次调用,含模型载入) 1.8s(一次性)
判据级单条延迟 中位 138ms · p90 140ms
吞吐 并发 1 / 4 / 8 7.2 / 24.2 / 34.5 条/s
端到端一段记录 → 档位 1.3s(含 9 次判据核对)

引擎本身是纯标准库、无第三方依赖,pip install 只要 ~13s; 上手时间几乎全在 ollama pull 那 640MB 上(5MB/s 宽带约 2 分钟)。

  • 量化:Q8→Q4 掉 0.3pt;硬门优先推荐 Q8

端到端(合成分诊问询记录池):分支忠实度 0.951.25s/条¥0; 对照 deepseek-v4-flash 当 checker 0.97 / 32.0s / ~¥0.03。

no 的门槛是一个旋钮,不是模型的固有属性

argmax 在改写版判据上不在 Pareto 前沿:三分类里 no 会在 P(no)<0.5 时靠三方竞争胜出, 而那些正是最不确定的样本。用 P(no) ≥ τ 代替 argmax(否则在 yes/unknown 里取大者):

τ acc false_no🔴 no_recall
0.63(默认,dev 上按 false_no≤0.0036 选) 0.9426 0.0000 0.9027
0.50 0.9457 0.0000 0.9189
0.40 0.9495 0.0024 0.9378
0.30 0.9498 0.0084 0.9432
0.10 0.9516 0.0120 0.9635

⚠️ 这一档只在改写版判据(发布分布)上成立——原文判据上 argmax 已在前沿。 它是分布偏移带来的校准偏移,不是通用调参。 ⚠️ 默认 τ=0.63 是经验工作点,不是统计上"控制住了"的阈值。 按 Neyman-Pearson 范式(Tong et al., Sci Adv 2018, arXiv:1608.03109), 要以 95% 置信把 false_no 控制在 0.0036 以下,校准集需要 ≥831 条正例; 我们只有 495 —— 即便取最保守的阈值,真实 false_no 超标的概率仍有 16.8%。 495 条只能可靠控制到 α≈0.006。文献同时点名了我们用的那种选法 ("直接把经验 type I error 限制在 α 以下")不构成控制。 ⇒ 当工作点用,别当保证用。 没有硬钉"最优值",而是把整条曲线一起发布, 按自己的代价矩阵挑点;有更大标注集就自行重定。

已知边界(先读这段再决定用不用)

  1. 🔴 「该用哪份协议」这一步本仓不提供,也没有配套模型。 核对器只回答"这条判据成立吗",它不选协议;引擎也需要你先给 protocol_id。 完整链路里这一环是空的:

    一段问诊记录 ──[?? 谁来选协议 ??]──► protocol_id ──► 引擎 + 本模型 ──► L1–L5
                         ↑ 缺这一环
    

    推荐形态:编排层用一个大模型看中文标题菜单来选stcc-mcp protocols 可打印,225 份)。 上一轮(run1,221 份协议的菜单,34 条真实自述)实测:大模型菜单选择 @1 = 0.85(原文直选)/ 0.94(先抽一句主诉再选)· @2 = 1.00; 而用判据文本做向量检索去定协议只有 @1 = 0.18 —— 同一句判据(如"恶心或呕吐")跨几十份协议出现,判据级索引天然定不了协议, 这条路已经证伪,别再走。 选错协议时引擎的 referral / indeterminate 状态是自纠网,但那是网,不是替代

  2. 输入必须是「已按协议问过一轮」的记录,不是原始自述。 🔴 不这么做的后果已经量过:在真实短自述上,整套系统的输出 97% 挤在 L1+L2(叫救护车 / 立即急诊)——区分度接近零,等于对谁都说"马上就医"。 信息充分的问诊记录上,档位才会分散到 L1–L5(实测 L1:11 L2:19 L3:10 L4:10 L5:8)。 短主诉 unknown 率 94%;真实富对话(IMCS-21,748 字 / 40 轮)仍有 88%。 原因是 STCC 前置分支筛的是急症红旗(噎着、发紫、无反应), 而自然产生的语料按定义不含这些情形、医生也不会去问——这是选择效应,换更富的语料无效。

  3. 一次真实问诊不会把一支的判据全问完,于是档位停在该支上界。 这是安全收敛在正确工作,但上界松紧完全由问诊完备性决定

  4. 分级本身是 silver:分支级参照由强模型定稿 + 人工复核,终局结论仍缺一个真人护士 gold

  5. 不要对模型做输入路由:判别类模型应对所有输入都调用;能力边界由编排层判断。 模型没有"我不判"的出口——那是一条合法沉默路径,而沉默型漏报在评测中不可观测。

仓库里有什么

路径 说明
Qwen3-0.6B.Q8_0.gguf / .Q4_K_M.gguf 推理用权重。硬门优先推荐 Q8
Modelfile.Q8_0 / .Q4_K_M Ollama 模板,与训练逐字节一致(含那个空 think 块)
lora_adapter/ LoRA adapter(r=128 / α=128,7 个目标模块,纯 lora_A/B,非合并整模)。用它可自行重导任意量化档
training/history.json 训练全过程 10 个 eval 点(acc / false_no / no_recall / miss_yes),可自行核验选点
training/train.log 完整训练日志(含 chat template 掩码自检输出)

自行重导其他量化档:

from unsloth import FastLanguageModel
m, tok = FastLanguageModel.from_pretrained("lora_adapter", max_seq_length=768,
                                           load_in_4bit=False, dtype=None)
m.save_pretrained_gguf("out", tok, quantization_method="q5_k_m")   # 或别的档

training/history.json 值得看一眼:false_nono_recall 在训练中是互相对立的, 发布这一版是按硬门 false_no 选的点。想要更高 no_recall调下面那个阈值比换 checkpoint 更划算(同一模型上换工作点,不用重训)。

训练

Qwen3-0.6B + Unsloth LoRA(r=128 / α=128 / lr=2e-4 / 3 epoch / bf16,A40 ≈ $0.75)。 train_on_responses_onlymetric_for_best_model 取非对称指标(漏判方向)。 Chat template 三处(train / eval / Modelfile)逐字节一致response_part 用差值导出——Qwen3-0.6B 在 enable_thinking=False 下自带一个空 think 块, 照常规写成 <|im_start|>assistant\n 会把它算进训练目标,且不报任何错。

判据文本为逐条独立改写版(5,214/5,214),不含 STCC 原文表达; 纯阈值与单个医学术语(「咳嗽」「体温>100.4°F」)按事实保留。 改写过三道闸:数值/否定/长度/雷同的形式校验、oracle 回放与原版逐位一致、下游指标不掉。

完整复盘(含四次被自己的数据否掉的判断、一次不采纳的重训、以及那条阈值曲线的推导)见项目报告(未公开)。

Downloads last month
222
GGUF
Model size
0.6B params
Architecture
qwen3
Hardware compatibility
Log In to add your hardware

4-bit

8-bit

Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for chenhaodev/stcc-checker-0.6b-GGUF

Finetuned
Qwen/Qwen3-0.6B
Quantized
(405)
this model

Paper for chenhaodev/stcc-checker-0.6b-GGUF