Problem
Original text(使用者原話,2026-08-07):
「應該要分開,當作重要的模型參數來考慮」
PromptRenderer.defaultTokenBudget = 200 是全域寫死的單一常數,套用到所有 backend。但 prompt 能力是逐模型的性質,差異極大:多數 backend 根本不吃 prompt,而吃的那些上限也不同。目前的設計把「Whisper 的限制」當成「所有引擎的限制」。
Type
feature
Priority
P1。這不只是設計潔癖 —— 它讓 context biasing 在預設路由下靜默失效,而 CLI 還印出成功訊息(見下方 Impact)。
Actual(現況,逐項在原始碼查證)
一、預算是單一全域常數,無任何 per-model 概念
// Sources/BestASRKit/Context/PromptRenderer.swift
public static let defaultTokenBudget = 200
public static func render(_ context: LoadedContext,
tokenBudget: Int = defaultTokenBudget) -> Rendered
參數位存在,但全 codebase 無任何呼叫點傳非預設值 —— 只有一個 call site:
Sources/BestASRKit/CommandCore.swift:103
return ContextBundle(loaded: loaded, rendered: PromptRenderer.render(loaded))
二、7 個引擎中只有 2 個會消費 prompt
| 引擎 |
原始碼中 prompt 出現次數 |
WhisperKitEngine |
12 |
WhisperCppEngine |
5 |
ParakeetEngine |
0 |
AppleSpeechEngine |
0 |
ChineseFamilyEngine(paraformer/sensevoice) |
0 |
ExternalProcessEngine |
0 |
Engine(protocol) |
0 |
對那 5 個引擎,整套 render 照跑、截斷照發生、injected (N) 照印 —— 然後產物被丟棄。
三、連「吃 prompt 的那兩個」限制也不同,且無一由 renderer 感知
WhisperKitEngine.clampedPromptTokens(_:limit:224) —— tokenizer 實測的 224,取 suffix(224)
WhisperCppEngine —— 無自訂 clamp,直接 --prompt <字串> 交給 whisper-cli,理論上可超過 200
註解本身寫明 200 是為 Whisper 調的:
/// ~200-token budget, below Whisper's ~224 practical prompt limit; the
/// WhisperKit engine additionally clamps encoded tokens as a safety net.
設計本身很嚴謹 —— 問題是它被無條件套用到不是 Whisper 的東西。
四、suffix(224) 的方向與 renderer 的優先序相反
PromptRenderer 依 names → terms → phrases 排序,最重要的排最前。clampedPromptTokens 溢出時取 suffix(224),砍掉的正是最前面的人名。目前 200 < 224 撞不到,但兩個機制的優先方向不一致,是潛在的坑。
Impact
這不是理論問題 —— 實測已經咬到。 2026-08-07 轉錄 IMPS 2026 會議錄音(10 檔約 7.3 小時)時:
recommend 在 high profile 選出 fluid-parakeet 0.6b-v3(「ranked #1 of 20 benchmarked candidates」,WER 6.1%)—— 也就是唯一完全不吃 prompt 的那一類。CLI 照樣印:
Context: <…>/.bestasr/context
injected (49): Che Cheng, Che, Hau-Hung Yang, …, Joint Thurstonian, …
truncated (53): criterion-related validity, …, Q-matrix, …
看起來 context biasing 生效了。實際上完全沒有:
| 目標詞 |
無 context 探針 |
parakeet + context |
whisper 無 context |
whisper + context |
| Joint Thurstonian |
「during the thumbia」 |
「during Sonya」 |
「joint adornment models」 |
「Joint Thornia Models」 |
| Likert |
「the request scale」 |
「the scale or L F」 |
— |
— |
Joint Thurstonian 與 Likert scale 都在已注入的 49 個裡,parakeet 命中數 0。同一段音訊換 whisper.cpp large-v3-turbo,context 讓輸出從「joint adornment」修到「Joint Thornia」並辨識為專有名詞 —— 證明 context 本身有效,失效的是 backend。
三個後果:
- 使用者被誤導。
injected (N) 讀起來是「已生效」,實際上對 5/7 引擎是 no-op。使用者會據此去精簡詞表 —— 白工。
- benchmark 路由與 context 目標衝突。routing 只看 WER,於是在有
context_dir 的情況下仍可能選中不吃 prompt 的引擎。而該 benchmark 語料顯然不像會議廳+專業術語音訊 —— 排名第一的模型在這批素材上可讀性最差。
- 截斷是真的發生的。實測 102 個項目只注入 49、截斷 53:42 個人名吃掉 171 tokens(85.5%),52 個術語只擠進 7 個,8 個 phrases 因
exhausted flag 被整類丟棄。對 parakeet 而言這整段計算與取捨都毫無意義。
Expected
把 prompt 預算視為引擎能力宣告,而非全域常數:
Residue
「哪個模型在會議廳音訊上最好」不是本 issue 能解的。本 issue 只處理能力宣告的正確性 —— 讓系統不再宣稱做了它沒做的事。benchmark 語料是否該擴充到會議/演講類音訊,是另一條線(現行語料顯然讓 parakeet 在此類素材上被高估)。
相關
Clarity Surface(idd-clarify run 2026-08-06T18:55:06Z)
| Type |
Source |
Suggested canonical |
Status |
| ambiguity |
「應該要分開,當作重要的模型參數來考慮」 |
「分開」有兩種讀法:(a) budget 數值依模型不同(本 issue 採此解);(b) 連 render 是否執行、能力宣告本身都依模型分流(Expected 第 3 點涵蓋)。兩者相容但工作量差一級,實作前宜確認範圍 |
resolved @ 2026-08-06T20:04Z(reason: scope = (a)+(b) 兩者皆含。依據為本 issue Expected 自身已同時列出兩層 —— 第 2 項「render 由所選引擎取得 budget」屬 (a),第 1 項「Engine protocol 增加 prompt 能力宣告」與第 3 項「不支援時整段跳過 render」屬 (b)。Complexity 即按此範圍評定。⚠ 此為 diagnose 階段的明示假設:若原意僅為 (a),請據此收窄 Expected 並重評 —— 範圍收窄會使 #129 硬閘的觸發理由改變) |
| missing-context |
「支援但上限未知(交由後端自行截斷)」 |
已解:whisper-cli --help 明文 --prompt PROMPT initial prompt (max n_text_ctx/2 tokens);Whisper 模型 n_text_ctx = 448 → 224,與 WhisperKitEngine.clampedPromptTokens 的 224 相同。兩個 whisper backend 上限一致,第三態(上限未知)可能不需要 |
resolved @ 2026-08-06T19:00Z(reason: whisper-cli 說明文件實測確認上限為 n_text_ctx/2 = 224) |
| terminology |
「prompt」 |
本 issue 全文指 ASR decoder 的 conditioning text(Whisper 的 initial_prompt / promptTokens),非 LLM 意義的 prompt。混淆會誤導實作者往錯的抽象層找 |
dismissed @ 2026-08-06T20:04Z(reason: 該列本身即為正確的 canonical 定義,且本 issue 全文用法與之一致(WhisperKitEngine.clampedPromptTokens / whisper-cli --prompt 皆為 decoder conditioning text)。無待決事項,保留作為術語註記) |
Current Status
Phase: proposed(Spectra tier)
| 欄位 |
值 |
| Diagnosis |
#164 comment(2026-08-07) |
| Conflict Class |
C_shared_module_coord — Engines/Engine.swift public protocol + 6 conformer |
| Complexity |
Spectra(Layer 2 + Layer 3 皆命中:public protocol +4th requirement × 6 conformer;跨 ≥4 子系統且設計空間互斥) |
| 排序 |
必須排在 #165 之後(先收斂 spawn seam,再擴 protocol) |
| 明示假設 |
scope = (a)+(b)(依 Expected 自身涵蓋範圍)。若原意僅 (a),Layer 2 不命中、複雜度降為 Plan/Simple —— 收窄 Expected 後重跑即可 |
| 設計建議 |
能力宣告採二態(第三態目前無 instance);不給預設實作(給預設等於允許沉默,與本 issue 初衷相反) |
| 提案 |
✅ per-engine-prompt-capability 已建立、validated、parked(摘要) |
| ⚠️ 存放位置 |
parked change 在 .git/spectra-app/changes/,不受 git 追蹤、不會 push —— 關鍵決策已鏡射於上述 comment |
| 下一步 |
/spectra-apply per-engine-prompt-capability(自動 unpark 並開始實作) |
Problem
PromptRenderer.defaultTokenBudget = 200是全域寫死的單一常數,套用到所有 backend。但 prompt 能力是逐模型的性質,差異極大:多數 backend 根本不吃 prompt,而吃的那些上限也不同。目前的設計把「Whisper 的限制」當成「所有引擎的限制」。Type
feature
Priority
P1。這不只是設計潔癖 —— 它讓 context biasing 在預設路由下靜默失效,而 CLI 還印出成功訊息(見下方 Impact)。
Actual(現況,逐項在原始碼查證)
一、預算是單一全域常數,無任何 per-model 概念
參數位存在,但全 codebase 無任何呼叫點傳非預設值 —— 只有一個 call site:
二、7 個引擎中只有 2 個會消費 prompt
prompt出現次數WhisperKitEngineWhisperCppEngineParakeetEngineAppleSpeechEngineChineseFamilyEngine(paraformer/sensevoice)ExternalProcessEngineEngine(protocol)對那 5 個引擎,整套 render 照跑、截斷照發生、
injected (N)照印 —— 然後產物被丟棄。三、連「吃 prompt 的那兩個」限制也不同,且無一由 renderer 感知
WhisperKitEngine.clampedPromptTokens(_:limit:224)—— tokenizer 實測的 224,取suffix(224)WhisperCppEngine—— 無自訂 clamp,直接--prompt <字串>交給whisper-cli,理論上可超過 200註解本身寫明 200 是為 Whisper 調的:
設計本身很嚴謹 —— 問題是它被無條件套用到不是 Whisper 的東西。
四、
suffix(224)的方向與 renderer 的優先序相反PromptRenderer依 names → terms → phrases 排序,最重要的排最前。clampedPromptTokens溢出時取suffix(224),砍掉的正是最前面的人名。目前 200 < 224 撞不到,但兩個機制的優先方向不一致,是潛在的坑。Impact
這不是理論問題 —— 實測已經咬到。 2026-08-07 轉錄 IMPS 2026 會議錄音(10 檔約 7.3 小時)時:
recommend在highprofile 選出 fluid-parakeet 0.6b-v3(「ranked #1 of 20 benchmarked candidates」,WER 6.1%)—— 也就是唯一完全不吃 prompt 的那一類。CLI 照樣印:看起來 context biasing 生效了。實際上完全沒有:
Joint Thurstonian與Likert scale都在已注入的 49 個裡,parakeet 命中數 0。同一段音訊換 whisper.cpp large-v3-turbo,context 讓輸出從「joint adornment」修到「Joint Thornia」並辨識為專有名詞 —— 證明 context 本身有效,失效的是 backend。三個後果:
injected (N)讀起來是「已生效」,實際上對 5/7 引擎是 no-op。使用者會據此去精簡詞表 —— 白工。context_dir的情況下仍可能選中不吃 prompt 的引擎。而該 benchmark 語料顯然不像會議廳+專業術語音訊 —— 排名第一的模型在這批素材上可讀性最差。exhaustedflag 被整類丟棄。對 parakeet 而言這整段計算與取捨都毫無意義。Expected
把 prompt 預算視為引擎能力宣告,而非全域常數:
Engineprotocol 增加 prompt 能力宣告,兩態即可:不支援 / 支援且上限 N(兩個 whisper backend 實測上限皆為 224;若日後接入上限未知的後端再擴為三態)PromptRenderer.render由所選引擎取得 budget,而非defaultTokenBudgetinjected (N)WhisperCppEngine的上限明確設為 224(whisper-cli文件的n_text_ctx/2;並非「無上限」——先前以為它沒有 clamp 所以可高於 200,實測其說明明文標示上限,與 WhisperKit 一致)clampedPromptTokens的suffix方向是否應改為prefix,以與 renderer 的優先序一致recommend/ routing 在偵測到 context 存在時,把「是否支援 prompt」納入選型考量;至少在選中不支援的引擎時發出警告Residue
「哪個模型在會議廳音訊上最好」不是本 issue 能解的。本 issue 只處理能力宣告的正確性 —— 讓系統不再宣稱做了它沒做的事。benchmark 語料是否該擴充到會議/演講類音訊,是另一條線(現行語料顯然讓 parakeet 在此類素材上被高估)。
相關
#163—— 同批診斷發現的安裝問題(release 不含 resource bundle)。兩者獨立:transcribe 在乾淨安裝上必 crash:release 未含 resource bundle、fallback 路徑寫死、sidecar 版本說謊導致 wrapper 永不自癒 #163 是裝不起來,本 issue 是裝起來了但 context 悄悄失效。kiki830621/chchen-lab#12(IMPS 2026 會議錄音轉錄,private repo)。Clarity Surface(idd-clarify run 2026-08-06T18:55:06Z)
whisper-cli --help明文--prompt PROMPT initial prompt (max n_text_ctx/2 tokens);Whisper 模型n_text_ctx = 448→ 224,與WhisperKitEngine.clampedPromptTokens的 224 相同。兩個 whisper backend 上限一致,第三態(上限未知)可能不需要initial_prompt/promptTokens),非 LLM 意義的 prompt。混淆會誤導實作者往錯的抽象層找WhisperKitEngine.clampedPromptTokens/whisper-cli --prompt皆為 decoder conditioning text)。無待決事項,保留作為術語註記)Current Status
Phase:
proposed(Spectra tier)C_shared_module_coord—Engines/Engine.swiftpublic protocol + 6 conformer#165之後(先收斂 spawn seam,再擴 protocol)per-engine-prompt-capability已建立、validated、parked(摘要).git/spectra-app/changes/,不受 git 追蹤、不會 push —— 關鍵決策已鏡射於上述 comment/spectra-apply per-engine-prompt-capability(自動 unpark 並開始實作)