COSCUP 2026 × Twinkle AI × ZeroTriz
讓台灣原生 LLM 長出在地語氣
用 Twinkle AI 4B 打造「星光型小零」
無獨顯 · CPU-only · 本地端落地實戰
Haley|AI 工程師 × ZeroTriz 計畫發起人
✦ Special Thanks to Twinkle AI — 台灣原生繁體中文開源模型社群
心理學啟發的應用工程 · 非臨床治療 · 危機關鍵字觸發保護模式
2
✦ DEMO · 30 秒
零樹計畫 ZeroTriz
沒有顯卡
CPU-only 推理
台灣模型
Twinkle AI 4B
3
ZeroTriz 的起點與五種陪伴風格
為什麼會有 ZeroTriz?
問題轉向
五種奠基於心理學的陪伴風格
🌳 林蔭型
自我慈悲(Neff)
不用急、慢慢來、我在這裡
🌙 月光型
自我決定論(SDT)
客觀來看、這是你的選擇
🔥 烈陽型
若則計畫(Gollwitzer)
停損點、做完就休息
🌊 晨曦型
正念 × 敘事治療
具象化隱喻、陪著感覺
✨ 星光型
遊戲化不確定性獎勵
水啦、太狠了吧、買杯手搖飲
今天的主角
手上一堆半成品 → 拖延症 → 想把任務視覺化
遊戲化技能樹 × AI 互動 × 心理學陪伴
加入 Twinkle AI 社群後看到產品可能性
從「要不要自己訓練模型」�變成�「能不能把台灣原生模型放進我的應用」��四月底開始接,兩週做出雛形——我以為就這樣了。
✦ PART 1 · 前言與架構
4
為什麼是 Twinkle AI
01
日記是最私密的東西
不想送去別人的伺服器
要能跑在自己家裡
02
家裡剛好有一台沒有顯卡的機器
i5-7400 + 8GB
模型要夠輕:4B / 2.87 GB
03
使用者是台灣人
AI 要像台灣人講話
繁中原生,不跳簡體
twinkle-ai-gemma-3-4b-t1-it-q4_k_m.gguf 成為最合適的選擇
✦ PART 1 · 前言與架構
5
✦ PART 1 · 前言與架構
為什麼用 CPU 跑?硬體決定與架構大轉彎
硬體選擇
筆電:Core Ultra 7 + 32GB�壓力測試、調 prompt
GCP e2-standard-2 + 8GB + 無獨顯�常駐服務、內容加密、機房在台灣
模型:gemma-3-4b-T1-it-Q4_K_M.gguf(2.87 GB)
一開始選老桌電不是省錢,是隱私。
直到那台的記憶體壞掉——會靜默寫壞資料的那種壞法,才被迫搬上雲。但無獨顯這個限制沒變,它逼出了後面所有的工程問題。
設計大轉彎:邏輯解耦
✖ Before 依賴 AI 輸出 JSON + 執行選擇邏輯�✔ After Python 處理所有邏輯,模型只負責純文字語氣生成
系統架構
React UI�心情輸入 / 風格選擇 / 歷史
▼
Flask /mood/stream�組 prompt · 邏輯 · fallback
▼
Ollama�本機推理引擎
▼
Twinkle AI 4B Q4_K_M�台灣原生語氣生成
SSE 串流
逐字回傳
首 token 一出現,前端立即逐字顯示
桌電:i5-7400 + 8GB + 無獨顯�常駐服務、資料不離家
開發 / 測試
正式服務 ①
正式服務 ②
6
主角登場:✦ 星光型小零
台味鄉民
開源社群隊友
壓力緩衝器
嚴禁:說教 · 催促 · 熱血喊話
v1 早期 · 回覆 + 獨立 🎯 行動
輸入 「今天好累,什麼都不想做」
這種不想動的感覺就像被關在房間裡,空氣都變稠了對吧?不如現在就找個超療癒的搞笑梗圖瞬間充飽電,滑到笑出來就算成功啦!
🎯 行動:去巷口超商吹個冷氣 5 分鐘
v2 現在 · 行動融入回覆 + 完結按鈕
輸入 「 今天好累,什麼都不想做 」
你現在一定超厭世的啦?辛苦了。別逼自己了,去煮一碗麵線好了,湯喝掉一半就好,燙口是正常的!休息一下再繼續也沒關係~
🌱 好,我知道了(完結按鈕)
演化:早期在回覆後另附獨立 🎯 行動 → 發現待辦感對拖延者是壓力 → 行動融入回覆+完結按鈕。兩則皆為 4B 真實輸出。
✦ PART 1 · 前言與架構
7
然後它開始叫我喝東西
2026 / 05 / 08 同一天下午,三則日記
18:43 [✦ 星光型]
「布萊恩好吵」
🎯 去巷口買杯大杯冰珍奶,或去逛夜市邊走邊吃
18:45 [✦ 星光型]
「今天沒跟到開會好好奇」
🎯 去巷口超商買杯大杯冰珍奶,邊喝邊想想開會的細節
18:46 [✦ 星光型]
「好不想上班」
🎯 去煮杯溫熱的茶,讓香氣溫暖一下你的身體
三則情境完全不同,三則都叫我去喝東西。
我當時的工程日誌
星光型 備註:
「飲料執念為特色」
對,我一開始還以為那是人格特色。
同一天還有:敵意幻覺
使用者:布萊恩好吵�小零:看你都說「我」好吵,是不是今天壓力特別大?
它以為使用者在說它。在地語料是雙面刃——道地的語氣,也帶著道地的火氣。
✦ PART 1 · 前言與架構
8
✦ 今天要回答的問題
做一個 AI 產品,
哪些事該交給模型,
哪些事該自己做?
我花了三個月才知道答案。
今天的故事,從一杯珍奶開始。
9
✦ PART 2 · 技術踩坑 1/5
踩坑一:JSON 幻覺與「永遠選第一個」
問題 1|格式幻覺
要求小模型「邊寫作文邊排版」,兩件事都做不好
問題 2|隨機選擇失效
# Prompt:「從以下行動中隨機選一個」�1. 去喝杯水�2. 伸懶腰�3. 找朋友說說話�→ 模型永遠選第一個,或三個全輸出
「隨機」對 LLM 是機率分布,不是擲骰子
解法|Python 預選 + 變數注入
# Python 後端�action = random.choice(TWINKLE_ACTIONS)�# 電腦做選擇,模型做語氣��# User Message�user_msg = f"""今日狀態:{content}
指定行動:{action}
請用星光型語氣說給使用者聽"""
✔ 一個改動同時解掉「格式」與「選擇」兩個問題:廢除 JSON、模型只負責純文字語氣包裝
同一個根因的變形:文字講 A,底下的行動欄寫 B
回覆文字:「先喝口溫開水、伸個懶腰,身體跟心情都輕鬆一點再繼續哦!」
🎯 行動欄:「把自己裹進毯子裡,像被好好抱住一樣窩著」
Python 從題庫挑的,跟文字無關
10
✖ 我做了最直覺的事
prompt += 「禁止推薦飲料」
→ 它推薦得更兇了
Ironic Process Theory
Wegner 1987 白熊實驗:越叫人不要想白熊,滿腦子都是白熊。
否定句在 attention 裡沒有「刪除」的效果,只有「提及」的效果——「飲料」進了 context,機率反而被拉高。
✔ 解法:正向鎖定
✖ 不准說 X → ✔ 只說 Y
✖ 禁止括號旁白 → ✔ 標點白名單
(逗號、句號、驚嘆號、問號)
同一個坑的另一個版本
prompt 裡放了 👉【】當排版 → 輸出跟著長出這些符號,語氣還變得像客服。
prompt 是模型的「模仿對象」,
不是「規則手冊」。
你給它看什麼,它就長成什麼樣子。
4B 的規則承載力
單一風格的輸出規則 ≤ 5 條最穩,超過之後遵守率快速下降。
全域安全規則另計——靠 fallback 與硬編碼補強,不依賴模型自律。
✔ 解法�Prompt 完全移除特殊符號�+ 單樣本示範(One-Shot)�一個好範例 > 五條規則
✦ PART 2 · 技術踩坑 2/5
踩坑二:白熊效應與符號模仿
11
踩坑三:記憶污染 —— 五種人格開始互相抄
6/21 同一句輸入,四分鐘內連續換人格
「今天終於讓小零的五種風格都調得比較符合期望了,非常開心」
22:27 🌳 林蔭型(溫柔)
先喝口溫開水、伸個懶腰,身體跟心情都輕鬆一點
22:23 ✦ 星光型(鄉民)
先去泡杯溫開水,然後站起來用力伸個大懶腰拉拉筋!
兩個語氣完全相反的人格,講了同一件事:溫開水 + 伸懶腰。
同一批裡還有這個
使用者寫的是「五種風格都調好了」。��小零卻回:「我注意到你今天跟朋友逛街時感到很放鬆…同時在工作上雖然有壓力…」
「跟朋友逛街」是兩天前的日記。它在回應過期的紀錄。
根因:短期記憶餵進去的是「AI 上一次的回覆」——輸出被回灌成輸入,一次一次把語氣稀釋掉。
# ❌ STM = 上一則 ai_summary(AI 的話)
# ✅ STM = 24 小時內的 content(使用者原話,最多 3 筆)
記憶是用來記住「這個人說了什麼」,不是記住「我上次說了什麼」。
✦ PART 2 · 技術踩坑 3/5
12
✦ PART 2 · 技術踩坑 4/5
踩坑四:效能榨取
CPU 每次全量重算
System Prompt 塞動態變數 → KV Cache 無法命中。分層之後呢?
# 分層之後,我以為解決了�System: [固定人設] ← Cache 命中�User: 時間/名字/近況 ← 每次變動��# 實測 · 同一段 1766 字 prompt�快取命中 0.3s → 總延遲 23.5s�快取失效 116.4s → 總延遲 146.8s�換人格 126.5s
+ SSE 串流:首 token 一出就顯示
同一段 prompt,命中與失效差 388 倍
Cache 分層只在「同一個人格連續使用」時有效。使用者一換風格,就要重算 126 秒——這個坑我沒解掉。
13
踩坑五:無中生有 —— 三個月,同一個根因
5 / 04
使用者:看到你就煩
小零:平靜的一天,沒有什麼特別的波動 🌾
情緒完全沒被接住,還長出 emoji
6 月
使用者:今天聽演講學到很多
小零:…不過嘛,股票賠錢這種事就不妙了…
這個人根本沒有講股票
上禮拜
使用者:今天有好多事
小零:這主管根本是在哈囉!下班前塞事情…
也沒有講主管,沒有講下班
同一個根因,橫跨三個月
我的五個 golden example,使用者輸入全部都有「主管」。輸入模糊時,模型就拿它唯一看過的具體場景來填空。
而 prompt 裡明明有一條「事實鎖定」,白紙黑字禁止這件事。
規則跟範例在同一份 prompt 裡互相矛盾,而具體的範例贏了抽象的禁令。
✦ PART 2 · 技術踩坑 5/5
14
每次我以為修好了,就會冒出下一個問題。
而每一次的解法,都是同一件事:
把某個工作,從模型手上拿回來。
我試著把這件事交給模型 | 結果 |
輸出結構化格式(JSON) | 格式跟語氣只能顧一邊 |
從三個行動裡隨機選一個 | 幾乎永遠選第一個 |
管理記憶 | 五種人格互相污染 |
自己想一個行動 | 文字講 A,底下的行動欄寫 B |
判斷情緒 | 無中生有 |
每一次的解法,都是同一件事:把某個工作,從模型手上拿回來。
✦ PART 2 · 技術踩坑小結
15
這是一場實驗
2026 / 05 / 08 開始調整 AI · 三個月
① 讓它「說得更好」
珍奶執念、白熊效應、JSON 幻覺、
記憶污染、無中生有的災難
✔ 做到了
② 讓它「說得更快」
KV cache 前綴共用、多開快取槽、
模型常駐記憶體
✘ 大部分沒用
✦ PART 3 · 技術踩坑小結ACT
(品質軸)
(速度軸)
✦ ACT 2 · 速度軸
真相:換一個人格,要重付 115 秒
人格 | 讀 prompt | 生成回覆 | 總延遲 |
🌳 林蔭 | 0.3s | 14.9s | 15.5s |
🌙 月光 | 111.2s | 15.9s | 127.4s |
🔥 烈陽 | 123.1s | 30.1s | 153.5s |
🌊 晨曦 | 110.7s | 22.2s | 133.3s |
✦ 星光 | 114.7s | 30.3s | 145.2s |
五個人格全部預熱過一輪,再逐一驗證——結果只有一個保住快取。
15.5s vs 139.8s
命中 vs 失效 總延遲差 9 倍
生成速度倒是很穩
3.8 – 4.1 token/s
五個人格幾乎一模一樣。
prompt 長度只影響「讀」,不影響「生成」。
「388 倍」只是讀 prompt 那一段。使用者實際感覺到的,是總延遲的 9 倍。
20 / 26
✦ ACT 2 · 速度軸
為什麼保不住?不是設定,是硬體
「它很慢」其實是兩件事
約 20 秒
生成回覆本身 3.8–4.1 token/s × 回覆長度
硬體,我改不了
110–123 秒
重讀 1859 字的 prompt
prompt 長度,我自己造成的
記憶體不夠
$ ollama ps
4.3 GB 100% CPU context 4096
$ free -h
7.8Gi 總量 / 5.9Gi 已用 / 1.8Gi 可用
剩下的空間裝不下五份 KV cache。
開幾個槽都一樣——這台機器就是沒有位子。
所以速度那三件事的結果是:
✘
共用 976 字前綴
引擎只要有差異就整段重算
✘
開五個 cache 槽
記憶體裝不下
✔
模型常駐記憶體
冷啟動 19s → 0.3s
三件事,兩件敗給硬體。成功的那件,跟 prompt 長度無關。
21 / 26
✦ ACT 2 · 速度軸
真相:換一個人格,要重付 115 秒
人格 | 讀 prompt | 生成回覆 | 總延遲 |
🌳 林蔭 | 0.3s | 14.9s | 15.5s |
🌙 月光 | 111.2s | 15.9s | 127.4s |
🔥 烈陽 | 123.1s | 30.1s | 153.5s |
🌊 晨曦 | 110.7s | 22.2s | 133.3s |
✦ 星光 | 114.7s | 30.3s | 145.2s |
五個人格全部預熱過一輪,再逐一驗證——只有一個保住快取。
15.5s vs 139.8s
命中 vs 失效 總延遲差 9 倍
為什麼保不住
$ free -h
7.8Gi 總量 / 5.9Gi 已用 / 1.8Gi 可用
模型 4.3GB+context 4096。
剩下的空間裝不下五份 KV cache。
生成速度 3.8 – 4.1 token/s,五個人格幾乎一模一樣。
prompt 寫多長,只影響「讀」,完全不影響「生成」。慢的是兩件不同的事:一件是硬體,一件是我自己造成的。
三件事,兩件敗給硬體。而成功的那件,跟 prompt 長度無關。
✦ ACT 3 · 收斂
那三個月,我其實一直在做同一件事
把每一項工作,搬到「做這件事最便宜」的地方。
這件工作 | 早期交給 | 現在交給 | 為什麼搬 |
選一個行動 | 模型 | random.choice() | 一次函式呼叫,不必跑完 4B 參數 |
輸出格式 | 模型 | 純文字+前端排版 | 4B 顧了格式就顧不了語氣 |
要記得什麼 | 模型 | 一句 SQL(24 小時窗) | 條件判斷不需要生成模型 |
情緒分流 | 模型 | 關鍵字比對 | 二分類不用 4B |
危機偵測 | 模型 | 關鍵字+硬編碼回應 | 這條線不能賭機率 |
把話說得有溫度 | 模型 | 模型 | 只有它做得到 |
模型是這台機器上最貴的資源。把它留給只有它能做的那一件事。
✦ ACT 3 · 收斂
微調,是同一個原則的下一步
知識也可以搬
前面搬的是「工作」。
這次搬的是「知識」——
把每次都要重讀的 1859 字說明書,
搬進模型的權重裡,變成 135 字。
誠實一點說
影片裡的快,是「prompt 變短」加上
「跑在有顯卡的機器上」,兩件事一起。
語氣還不夠台——那是下一版
資料層要處理的事。
✦ 最後
量能越多,問題就換了一個
量能不足的時候
你會被迫問:
「這件事,真的需要模型嗎?」
因為問錯了,使用者要等一百多秒。
量能充足之後
那個問題不會消失。
它只是變得容易忽略——
因為問錯了也不會有人抱怨。
換成 GPU 之後,這些工作還是不該回到模型身上。
省下來的量能,要留給真正複雜、真正只有模型做得到的事。
在 AI 時代,擁有的量能越多,
問題就從「怎麼完成一件事」,變成「怎麼有效地完成一件事」。
22
Prompt 演進:v1 → v5
版本 | 輸出格式 | System Prompt | 選擇邏輯 | 記憶來源 | 字數 | 主要問題 |
v1 | JSON | 全動態 | 模型自選 | 無 | 598 | 幻覺、永選第一個 |
v2 | JSON | 全動態 | 模型自選 | AI 回覆 | — | Cache 命中率極低 |
v3 | JSON | 靜態前綴 | 模型自選 | AI 回覆 | — | 格式不穩、風格互相污染 |
v4 | 純文字 | 靜態前綴 | Python 預選 | AI 回覆 | 1389 | 白熊效應殘留 |
v5 ✔ | 純文字 | 靜態+正向鎖定 | Python 預選 | 使用者原話 | 1859 | 目前穩定版 |
4B 模型黃金法則 —— 帶走這 5 條
1
邏輯解耦
選擇/計算交給 Python,模型只生成文字
2
純文字輸出
廢除 JSON 結構要求
3
正向鎖定
說「只做 X」,不說「不准做 Y」
4
符號潔淨
Prompt 不放特殊符號
5
記憶存原話
存使用者輸入,別回灌 AI 輸出
品質這條軸,到這裡我是滿意的。它會用「是在哈囉」跟你講話——那是 GPT 給不了我的東西。
✦ 最後
23
✦ 回到那個問題
不是模型越強越好,是分工越清楚越好。
1
邏輯解耦
選擇/計算交給 Python
2
純文字輸出
廢除 JSON 結構要求
3
正向鎖定
說「只做 X」
4
符號潔淨
prompt 有什麼就長什麼
5
記憶存原話
AI 輸出絕不回灌
今天一開始,你們看到的是一棵會長大的樹。
但我真正想做的,從來不是一棵樹。
而是一個在人最需要的時候,願意陪他二十秒的 AI。
它不用最聰明。
只要剛好在。
✦ PART 3 · 收斂
主軸收斂:小模型的正確用法
小模型不必是全能巨獸。�在明確的心理學邊界 + 後端邏輯解耦 + 護欄保護下,�Twinkle AI 4B 可以是很棒的台灣語境在地化生活 Agent。
✦
明確邊界
模型只負責「語氣」
邏輯全部後端處理
✦
護欄設計
fallback · SAFETY_LOCK · 危機保護
不依賴模型自律
✦
在地語境
Twinkle AI 把「繁體中文」
升級成「台灣人在說話」
可複用到:學習陪伴工具 · 個人工作流 Agent · 任何需要台灣語境的 LLM 應用
11 / 14
25
beta.zerotriz.com · Twinkle AI 4B · CPU-only
謝謝 Twinkle AI 社群,也謝謝今天在場的每一位。
beta.zerotriz.com零樹計畫測試版 – 30天刪除版
Contact me
歡迎加入~Twinkle AI
26
隱私防護與危機保護雙防線
隱私層|匿名 Key 系統
Display Key
使用者自訂名稱,容易記住
Normalized Key
後端轉換為安全唯一查詢值
無帳號
不要求 Email、手機、Google Login
指數退避鎖定
錯 3 次鎖 1 分鐘 / 4 次 5 分鐘 / 5 次 15 分鐘
日記內容加密儲存(Fernet)· 不經任何第三方 AI API · 機房在台灣
安全層|危機保護模式
CRISIS_KEYWORDS = ["不想活", "想消失",� "自殺", "撐不住了", "想死", ...] # 7 個��# 偵測到 → 跳過 AI,回傳固定內容�CRISIS_RESPONSE = {� "我看到你現在真的很痛苦。"� "這種時候,一個真人陪你會比我更重要。� 請聯絡你信任的人,或撥打安心專線 1925"�}
這條線不交給模型自己判斷。�但後來我拿 340 筆真實危機語料實測——攔截率只有 4.4%。
✦ 補充 · Q&A