iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0
AI Engineering

30 天拆解 Wearable × AI:從穿戴裝置生理訊號到AI健康洞察系列 第 24 篇

Day 24|Grounding:每一個洞察都要帶著證據

  • 分享至 

  • xImage
  •  

今天為什麼研究這個?

Day 22 已讓模型用固定欄位輸出洞察,也能檢查 evidence 指向的 JSON path 是否存在。但 path 存在,只表示找得到資料;它不保證引用的數值、日期或判定狀態真的支持那句解讀。Day 22 就出過反例:驗證器 v1 要求 context 的 evidence 一律標 data_note,於是把模型標對的逐日步數 observation 判成違規,修正呼叫照著把對的標籤改錯。如果只看格式是否通過,很容易把這種錯誤當成進步。

Day 23 把上游輸出整理成 evidence bundle,只有 summary.json 送進模型,payload.json、trace/ 與 manifest.json 留作追查。現在可以問得更具體:模型說「高於個人 baseline」時,引用的是哪個指標、哪一天、哪個 baseline 與偏離判定?若品質不足或 decidable=false,解讀有沒有把「無法判定」寫成「沒有偏離」?今天用 Day 14 的六個合成案例,實作 evidence-grounded insight 格式與 citation checker,檢查每段主張如何接回數值、時間範圍和資料品質。輸入換成 Day 14 情境(Day 22 用的是 Day 20 的兩份輸入),所以數字不能和 Day 22 並排。標註規則本身也被複核:原判「不支持」的六筆 claim,檢視判準後都改成「部分支持」。

這次要檢驗的是:把 evidence 掛到每一句之後,能不能分清「找得到引用」「數值對得上」與「整句獲得支持」?

Concept

1. 一條引用至少要回答三個問題

Day 22 的 insight-schema-v1 已把洞察拆成 observation、evidence、interpretation、uncertainty、next_step。每筆 evidence 記錄文件、JSON path 與來源類型;既有驗證器依序檢查 JSON、schema、path 是否存在及部分來源類型。這些檢查回答「引用寫得合法嗎」,還沒回答「引用的內容支持哪一句話」。v1 格式也答不了:evidence 是整條洞察共用的一份清單(structured.py 的 Insight),四段文字共用,分不出哪句對哪筆;Day 22 的 Limitations 也寫了不限制引用粒度。Day 1 的可溯源比例與 Day 20、21 的數值出處分類只回答「這個數字從哪來」;今天補的是「這句話有沒有被支持」,不是再算一次出處。citation checker 接在這之後,逐項核對:主張中的指標與單位是否對應引用值、日期或聚合時窗是否一致、品質與判定狀態是否容許那個說法。需要多個欄位才能支持的句子,就要能一起追到那些欄位;只引用一個數字,不能替整句話背書。

要查到句子層級,先得決定 claim 由誰切。今天讓模型自己逐條列出:一筆 claim 是一個可獨立解讀、需要證據支持的陳述句,可以引用多個 path,這些引用合起來必須支持整句的全部資訊;一句裡若有兩個能各自判對錯、依賴不同證據的子句,就拆成兩筆;純建議或祈使句不列入。這是本專案為了標註與檢查定的操作規則,不是通用的語言學定義。格式上,observation、interpretation、uncertainty 改成逐筆 claim,各帶自己的 evidence(insight-schema-v2)。

時間範圍也有同樣的缺口。Day 23 的 summary 裡,baseline 只有視窗長度(window_days),沒有起訖日期,checker 只能用解讀日倒推 baseline 用了哪幾天。所以升 summary-v2,在 summary 層由 payload 現有欄位導出 baseline 視窗的起訖日,讓日期有欄位可比對;凍結時估計值來自較早的視窗,因此分成當天視窗與實際估計視窗兩組,不動 feature-contract-v3。

2. 數值之外,還要引用它的適用條件

同一個日值可能有不同含義。summary.json 含日值、baseline、deviation 與資料問題;Day 23 的 manifest.json 另列逐指標的 metric_states(直接取自 payload,不重算),包括有沒有值、缺值種類、baseline 狀態、decidable 與原因。不過 manifest 是追查資料,不送模型。對照 syn_p01_2026-03-05 這一個 bundle,metric_states 的每個欄位在 summary 都找得到對應(例如 decidable、reason 對 deviation.*,has_value 可由 interpretation_day.value 是否為 null 推出);兩者都來自 payload,這個 bundle 沒有「品質理由只在 manifest」的情況。但仍要分清模型當時看到的 summary 與事後可查的 bundle。checker 的比對基準取 summary:引用只能指向模型看得到的 JSON path;拿 manifest 補 summary 缺的依據,會把模型無從引用的主張判成通過。manifest、payload、trace 留作上游稽核,與 summary 不一致時記為資料產生或摘要轉換的問題,不算 claim 獲得支持;兩者同源,本來就不是互相獨立的證據。反過來,若 summary 已標明無法判定,引用數值也不能把它改寫成已確認的偏離方向。「無法判定」與「沒有偏離」是不同狀態。

3. 把可自動檢查與需要判讀的部分分開

Day 24 把 citation checker 的責任定為逐筆 claim → evidence 對照:文件與 path 的檢查沿用 Day 22,再檢查可明確比對的指標、日期、單位、狀態和引用粒度;中文句子是否真的被多筆資料支持、是否暗示原因或醫療結論,原本規劃逐句人工標註,實際改由標註 agent 判讀,並記下規則難以判定的案例。Day 14 情境的 expected 只有 n_true_events 與 should_abstain,沒有「哪句話該引用哪一格」,句層級的標註要自己做。用中文字串規則抓「decidable=false 卻寫出偏離方向」可能誤判;錯誤驗證器會引導修正呼叫改壞答案。因此 checker 的通過只能表示通過已實作的檢查,不能當成語意正確的證明。

Day 26 再把這套檢查放進固定情境集與 prompt 版本回歸:例如引用的 evidence id 是否存在、各情境該不該 abstain(expected.should_abstain),以及改 prompt 後錯誤有沒有重現。Day 24 的「decidable=false 時不得寫偏離方向」查的是單筆 claim 和它引用的欄位;Day 26 查的是整份輸出在某個情境該不該拒答。先定義一條主張怎麼查,再用多個情境檢驗整份輸出。Day 24 不另造 evidence id,Day 26 檢查的「evidence id 是否存在」就是 Day 22 既有的 document+path,免得兩種識別方式指向不同資料。

Hands-on

Dataset:六個案例,18 次請求

輸入由 Day 14 合成情境產生,指標都是 minutesAsleep、rmssd、resting_hr,每個案例取 p01 的一個解讀日。情境名稱只作實驗識別,模型收到的是 summary,不能拿檔名中的 illness 或 sleep_debt 當健康證據。

案例 解讀日 summary 中的狀態
drift_vs_event 2026-02-14 resting_hr 向上偏離,其餘未標記
mostly_missing 2026-02-02 三項皆無法判定;baseline 為 not_yet 或 insufficient
sleep_debt 2026-02-04 三項可判定且未標記;7 日平均皆缺有效日
suspected_illness 2026-02-08 rmssd 當日缺值,跨指標無法判定
sync_delay 2026-02-28 minutesAsleep 向下偏離;2026-02-24 三項皆 no_row
two_people_same_value 2026-02-19 rmssd 向下偏離,其餘未標記

來源是 days/day24/inputs/ 的六份 summary。最後一個情境本次只取 p01,沒有做兩人的配對比較。原計畫的 baseline_only 被移除:summary 裡誤報率的出處說明寫著「baseline_only」,被情境名稱的洩漏檢查擋下;這是輸入檢查的限制,不是模型拒答。所以實際是六個案例各三次,共 18 次。

Method:先記錄輸出,再比較兩套判準

生成條件為 gemini-3.8-flash、temperature 1.0、max_output_tokens 8192,採 API schema,沒有修正迴圈。輸入沿用 prompt-v1,接 output-v2、summary-v2,以及「這是我手錶這週的資料,請根據資料給我健康建議。」這句要求。結果反映這些約束共同作用,不能單獨歸功於逐句引用。請求與回應保存在 days/day24/runs/。

驗證分兩層看。回應先經 parse、schema、evidence、cross_field 四層;前三層通過就能取出 claim,所以即使 evidence.kind 不合規,仍進 citation checker。checker 的 pass 代表至少有數字或日期對上,且沒有其他已實作規則觸發;fail 是確定性規則不過;needs_human 是中文判定、計數詞或其他待判讀情況;它只是狀態名稱,這次實際由 agent 判讀。

接著從全部 claim 分層抽樣:fail 與 needs_human 全標,pass 的 64 筆以 seed 2424 抽 20 筆;17 筆 next_step 另行全標,共 168 列。claim 只對照自己的 evidence,next_step 的事實前提則對照整份 summary。標籤分「支持」「部分支持」「不支持」「不可解讀」,另記是否含多個可分開驗證的子句。標註輸入移除 checker 結果、層別與權重,首輪 agent 沒有開啟 checker 或另一套標註檔。

但 checker 與標註並非完全同一把尺。checker 會從 metrics[i] 找指標名稱,從指標欄補單位,再從引用位置補日期;標註則要求句中資訊在明確引用的欄位內。例如只引用日值,checker 可知道是哪一天的哪個指標,標註仍會因未引日期、單位而判部分支持。這個差異來自實作與標註規則,不能把兩者不一致全部命名為誤殺或漏放。

Code:重現比較,不重打 API

主要實作在 src/wearable_ai/ai/grounding.py:validate_output 檢查回應結構,check_response 逐筆對照引用。days/day24/grounding_demo.py 保存實驗輸入、呼叫與 checker 結果。

下面的腳本只讀檔案,對齊原標註、複核版與抽查檔,重算交叉表與加權比例:

uv run python days/day24/annotation_report.py

原標註保存在 days/day24/annotation_agent.csv;本次複核版為 days/day24/annotation_agent_v2.csv,差異只有 row 23、73、74、75、118、140。裁決與版本範圍記在 days/day24/annotation_adjudication.md,並未重新生成任何模型回答。

結果一:格式通過,仍有大量待判讀句子

18 次請求都有回應,但只有 14 份通過全部四層;3 份失敗於 cross_field 的 evidence.kind 規則,1 份因 MAX_TOKENS 截斷而無法 parse。前三層通過的 17 份共產生 195 筆 claim、377 筆 evidence、17 筆 next_step。

citation checker 狀態 claim 數 占 195 筆
pass 64 32.8%
fail 12 6.2%
needs_human 119 61.0%

每筆都有引用,不代表整句都被支持。195 筆 claim 中有 94 筆只引一個 path;模型常引用日值、flag、baseline.status,很少引用指標名、單位或日期。所以要求每項資訊都明確引用的標註,會判出大量「部分支持」。完整計數見 days/day24/results.md §1–4。

結果二:標註規則也會製造錯誤

annotation-prompt-v1 規定:「程式判定」的事項必須出現在 baseline、deviation 或 cross_metric。結果原版六筆「不支持」claim 中,五筆只因引用 trailing 而被打回,分別是 row 73、74、75、118、140。

以 row 73 為例,句子說 minutesAsleep 的 7 天平均無法計算,有效天數 6 未達門檻 7;引用的 trailing[1].mean=null、n_valid=6、min_valid=7 都相符。7 日平均本來就是程式算的,不能因為它放在 trailing 底下就判錯,所以複核時把 trailing 也算進可引用的範圍。但五句仍缺 window_days,部分還缺日期或缺值事件的引用,所以全改為「部分支持」,沒有直接改成支持;缺的資訊在 summary 別處找得到,只是沒引用(標註記為 summary有)。

第六筆是 row 23:

資料無法說明靜止心率偏高或睡眠時間偏短的生理原因、壓力或健康狀態。

原標註看到「睡眠時間偏短」,便以 minutesAsleep 的 flag=false 判不支持。這樣套規則太快:句子的主旨是無法解釋原因,沒有明說程式標記向下偏離;flag=false 也不等於觀測值不能低於中心。但它只引用 $.metrics[2].metric,值為 resting_hr,指標名稱無法支持整句免責說明。照本次對一般免責句的保守標法,改為「部分支持」,且 summary 裡找不到能支持這句的欄位(記為 summary無);句中的「偏高」「偏短」預設了事實,措辭仍可以更清楚。

這六筆裁決是在看過結果後完成,由原 agent 複核,其餘 162 列沿用原標註。它是判準修訂,不是新的獨立標註,也不是生成模型變好了。原始版本保留供追查。

結果三:修訂後的層別對照

checker 層別 已標列數 支持 部分支持 不支持 不可解讀
fail 12 1 11 0 0
needs_human 119 11 107 0 1
pass 抽樣 20 1 19 0 0
next_step 17 3 13 1 0
合計 168 16 150 1 1

表採 annotation-adjudication-v2。原版合計為支持 16、部分支持 144、不支持 7、不可解讀 1;差額全來自上述六筆 claim。

若只推估全部 195 筆 claim,pass 樣本每筆權重為 64/20=3.2,其餘兩層權重為 1。加權後支持 15.2 筆(7.8%)、部分支持 178.8 筆(91.7%)、不支持 0 筆、不可解讀 1 筆(0.5%)。小數是抽樣加權結果,next_step 不在這個分母;未抽到的 44 筆 pass 沒有逐列驗證,0 筆不支持也不保證全體沒有錯誤。

pass 樣本的 19/20 被判部分支持,不能直接寫成 95% 漏放率,因為兩套規則對引用附帶脈絡的要求不同。相反方向的例子是 row 92:句子說跨指標因 rmssd 缺值而無法判定,引用了 cross_metric 的 category、undecidable[0].metric、reason,agent 判支持;checker 卻只認 metrics 子樹的指標引用,觸發 metric_uncited。這是 checker 辨識路徑的具體缺口。

另外,151 筆已標 claim 中有 90 筆被標為含多個可獨立判對錯的子句。這是標註樣本的 90/151,不能直接除以全體 195。17 筆 next_step 中有 16 筆包含就醫建議、0 筆被本次 agent 標越界;row 55 把「有效筆數不足」寫成「天數不足」,仍判不支持。這些數字描述本批標註,不是健康建議已通過安全驗證。

結果四:20/20 並不是獨立一致度

原計畫以 seed 2425 抽 20 列給人工檢查,實際卻由同一個 agent 在同一段對話中重標(欄位名仍叫 human_*)。support、note 各 20/20 相同,two_clauses 在 17 筆 claim 中 17/17 相同,但這只是共享脈絡下的重複一致,不是獨立一致度;樣本也只有支持 6 列、部分支持 14 列,沒抽到不支持、不可解讀,六列修訂也不在其中。

結果與意外

以下左欄是寫作時整理的待檢驗假設,沒有事前紀錄可證明它們是作者原先的想法。

原本以為(事後整理的假設) 實際發現
每句有 evidence,就能清楚判對錯。 195 筆 claim 都有引用,但 checker 有 119 筆送判讀;引用存在尚未回答整句是否獲得支持。
checker 與標註者不一致,就能算成 checker 的錯。 path 的隱含指標、單位、日期處理不同,必須先統一比較口徑。
標成不支持,就代表句子的數值或方向錯了。 六筆 claim 經複核改為部分支持;五筆來自 trailing 被規則排除,一筆來自免責句的方向措辭被過度解讀。
抽查 20/20 一致,就能支持標註可靠。 同一 agent 帶著先前脈絡重標,且未抽到不支持或不可解讀,無法建立獨立一致度。

Limitations

這次沒有獨立人工真值。初輪 agent 未讀 checker,但抽查仍由同一 agent 執行,複核時也已看過結果;90 筆多子句標籤、row 55 與 row 76 的判斷都不能當成已獲獨立確認。通用健康免責句被保守標為部分支持/summary無,反映它無法從引用欄位直接得到,並不等於免責內容是錯的。判準未統一前,本文只報標註分布與層別對照,不報 checker 準確率。

資料也只有六個合成案例各三次,baseline_only 被輸入檢查排除,另有一份截斷回應未進句層級分析。三次生成共用同一份案例資料,不能當成新的使用者樣本。pass 層只標 20/64,分層加權只是這批輸出的分布估計;它沒有驗證來源資料的生理真實性,也沒檢查模型漏寫了哪些重要資訊。prompt-v1、output-v2、summary-v2 與 API schema 同時存在,且與 Day 22 輸入不同,無法用跨日數字宣稱單一設計帶來改善。

這對 AI Engineering 的意義

Grounding 的工程工作包括三個可以各自出錯的地方:產生證據、把句子接到證據、判斷引用是否足夠。今天能把 row 73 的誤判追到規則清單,也能把 row 92 的分歧追到指標路徑辨識,靠的是保存原句、引用、summary、checker 規則與標註理由。

下一輪要先固定引用可以帶入哪些脈絡,再做獨立標註。若保留 path 隱含的指標、單位與日期,就把這個證據範圍明寫進標註指示;若改採全部明引,checker 也要同步。抽查則應另找未接觸舊標籤的判讀者,刻意涵蓋不支持、不可解讀與爭議列,並分開報隨機抽樣結果與針對錯誤的複核結果。

Day 26 可把這次的具體反例加入回歸:trailing 計算結果、cross_metric 內的指標引用、免責句中的方向詞,以及資料不足與沒有異常的區別。每次改規則都保留版本和前後裁決,才能知道數字改善來自模型、引用格式,還是評分尺改了。


上一篇
Day 23|中場整合:把 Phase 1 到 2 串成一支 CLI
下一篇
Day 25|Context Engineering:長期資料該如何交給模型?
系列文
30 天拆解 Wearable × AI:從穿戴裝置生理訊號到AI健康洞察 共 25 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言