1 of 26

COSCUP 2026 × Twinkle AI × ZeroTriz

讓台灣原生 LLM 長出在地語氣

用 Twinkle AI 4B 打造「星光型小零」

無獨顯 · CPU-only · 本地端落地實戰

Haley|AI 工程師 × ZeroTriz 計畫發起人

✦ Special Thanks to Twinkle AI — 台灣原生繁體中文開源模型社群

心理學啟發的應用工程 · 非臨床治療 · 危機關鍵字觸發保護模式

2 of 26

2

✦ DEMO · 30 秒

零樹計畫 ZeroTriz

沒有顯卡

CPU-only 推理

台灣模型

Twinkle AI 4B

3 of 26

3

ZeroTriz 的起點與五種陪伴風格

為什麼會有 ZeroTriz?

問題轉向

五種奠基於心理學的陪伴風格

🌳 林蔭型

自我慈悲(Neff)

不用急、慢慢來、我在這裡

🌙 月光型

自我決定論(SDT)

客觀來看、這是你的選擇

🔥 烈陽型

若則計畫(Gollwitzer)

停損點、做完就休息

🌊 晨曦型

正念 × 敘事治療

具象化隱喻、陪著感覺

✨ 星光型

遊戲化不確定性獎勵

水啦、太狠了吧、買杯手搖飲

今天的主角

手上一堆半成品 → 拖延症 → 想把任務視覺化

遊戲化技能樹 × AI 互動 × 心理學陪伴

加入 Twinkle AI 社群後看到產品可能性

從「要不要自己訓練模型」�變成�「能不能把台灣原生模型放進我的應用」��四月底開始接,兩週做出雛形——我以為就這樣了。

✦ PART 1 · 前言與架構

4 of 26

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 of 26

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 of 26

6

主角登場:✦ 星光型小零

台味鄉民

開源社群隊友

壓力緩衝器

嚴禁:說教 · 催促 · 熱血喊話

v1 早期 · 回覆 + 獨立 🎯 行動

輸入 「今天好累,什麼都不想做」

這種不想動的感覺就像被關在房間裡,空氣都變稠了對吧?不如現在就找個超療癒的搞笑梗圖瞬間充飽電,滑到笑出來就算成功啦!

🎯 行動:去巷口超商吹個冷氣 5 分鐘

v2 現在 · 行動融入回覆 + 完結按鈕

輸入 「 今天好累,什麼都不想做 」

你現在一定超厭世的啦?辛苦了。別逼自己了,去煮一碗麵線好了,湯喝掉一半就好,燙口是正常的!休息一下再繼續也沒關係~

🌱 好,我知道了(完結按鈕)

演化:早期在回覆後另附獨立 🎯 行動 → 發現待辦感對拖延者是壓力 → 行動融入回覆+完結按鈕。兩則皆為 4B 真實輸出。

✦ PART 1 · 前言與架構

7 of 26

7

然後它開始叫我喝東西

2026 / 05 / 08 同一天下午,三則日記

18:43 [✦ 星光型]

「布萊恩好吵」

🎯 去巷口買杯大杯冰珍奶,或去逛夜市邊走邊吃

18:45 [✦ 星光型]

「今天沒跟到開會好好奇」

🎯 去巷口超商買杯大杯冰珍奶,邊喝邊想想開會的細節

18:46 [✦ 星光型]

「好不想上班」

🎯 去煮杯溫熱的茶,讓香氣溫暖一下你的身體

三則情境完全不同,三則都叫我去喝東西。

我當時的工程日誌

星光型 備註:

「飲料執念為特色」

對,我一開始還以為那是人格特色。

同一天還有:敵意幻覺

使用者:布萊恩好吵�小零:看你都說「我」好吵,是不是今天壓力特別大?

它以為使用者在說它。在地語料是雙面刃——道地的語氣,也帶著道地的火氣。

✦ PART 1 · 前言與架構

8 of 26

8

✦ 今天要回答的問題

做一個 AI 產品,

哪些事該交給模型,

哪些事該自己做?

我花了三個月才知道答案。

今天的故事,從一杯珍奶開始。

9 of 26

9

✦ PART 2 · 技術踩坑 1/5

踩坑一:JSON 幻覺與「永遠選第一個」

問題 1|格式幻覺

  • 4B 模型輸出 JSON 常漏括號、混入自然語言
  • 前端解析失敗率高,必須加 fallback

要求小模型「邊寫作文邊排版」,兩件事都做不好

問題 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 of 26

10

✖ 我做了最直覺的事

prompt += 「禁止推薦飲料」

→ 它推薦得更兇了

Ironic Process Theory

Wegner 1987 白熊實驗:越叫人不要想白熊,滿腦子都是白熊。

​

否定句在 attention 裡沒有「刪除」的效果,只有「提及」的效果——「飲料」進了 context,機率反而被拉高。

✔ 解法:正向鎖定

✖ 不准說 X → ✔ 只說 Y

✖ 禁止括號旁白 → ✔ 標點白名單

  (逗號、句號、驚嘆號、問號)

同一個坑的另一個版本

prompt 裡放了 👉【】當排版 → 輸出跟著長出這些符號,語氣還變得像客服。

prompt 是模型的「模仿對象」,

不是「規則手冊」。

你給它看什麼,它就長成什麼樣子。

4B 的規則承載力

單一風格的輸出規則 ≤ 5 條最穩,超過之後遵守率快速下降。

全域安全規則另計——靠 fallback 與硬編碼補強,不依賴模型自律。

✔ 解法�Prompt 完全移除特殊符號�+ 單樣本示範(One-Shot)�一個好範例 > 五條規則

✦ PART 2 · 技術踩坑 2/5

踩坑二:白熊效應與符號模仿

11 of 26

11

踩坑三:記憶污染 —— 五種人格開始互相抄

6/21 同一句輸入,四分鐘內連續換人格

「今天終於讓小零的五種風格都調得比較符合期望了,非常開心」

22:27 🌳 林蔭型(溫柔)

先喝口溫開水、伸個懶腰,身體跟心情都輕鬆一點

22:23 ✦ 星光型(鄉民)

先去泡杯溫開水,然後站起來用力伸個大懶腰拉拉筋!

兩個語氣完全相反的人格,講了同一件事:溫開水 + 伸懶腰。

同一批裡還有這個

使用者寫的是「五種風格都調好了」。��小零卻回:「我注意到你今天跟朋友逛街時感到很放鬆…同時在工作上雖然有壓力…」

「跟朋友逛街」是兩天前的日記。它在回應過期的紀錄。

根因:短期記憶餵進去的是「AI 上一次的回覆」——輸出被回灌成輸入,一次一次把語氣稀釋掉。

# ❌ STM = 上一則 ai_summary(AI 的話)

# ✅ STM = 24 小時內的 content(使用者原話,最多 3 筆)

記憶是用來記住「這個人說了什麼」,不是記住「我上次說了什麼」。

✦ PART 2 · 技術踩坑 3/5

12 of 26

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 of 26

13

踩坑五:無中生有 —— 三個月,同一個根因

5 / 04

使用者:看到你就煩

小零:平靜的一天,沒有什麼特別的波動 🌾

情緒完全沒被接住,還長出 emoji

6 月

使用者:今天聽演講學到很多

小零:…不過嘛,股票賠錢這種事就不妙了…

這個人根本沒有講股票

上禮拜

使用者:今天有好多事

小零:這主管根本是在哈囉!下班前塞事情…

也沒有講主管,沒有講下班

同一個根因,橫跨三個月

我的五個 golden example,使用者輸入全部都有「主管」。輸入模糊時,模型就拿它唯一看過的具體場景來填空。

而 prompt 裡明明有一條「事實鎖定」,白紙黑字禁止這件事。

規則跟範例在同一份 prompt 裡互相矛盾,而具體的範例贏了抽象的禁令。

✦ PART 2 · 技術踩坑 5/5

14 of 26

14

每次我以為修好了,就會冒出下一個問題。

而每一次的解法,都是同一件事:

把某個工作,從模型手上拿回來。

我試著把這件事交給模型

結果

輸出結構化格式(JSON)

格式跟語氣只能顧一邊

從三個行動裡隨機選一個

幾乎永遠選第一個

管理記憶

五種人格互相污染

自己想一個行動

文字講 A,底下的行動欄寫 B

判斷情緒

無中生有

每一次的解法,都是同一件事:把某個工作,從模型手上拿回來。

✦ PART 2 · 技術踩坑小結

15 of 26

15

這是一場實驗

2026 / 05 / 08 開始調整 AI · 三個月

① 讓它「說得更好」

珍奶執念、白熊效應、JSON 幻覺、

記憶污染、無中生有的災難

✔ 做到了

② 讓它「說得更快」

KV cache 前綴共用、多開快取槽、

模型常駐記憶體

✘ 大部分沒用

✦ PART 3 · 技術踩坑小結ACT

(品質軸)

(速度軸)

16 of 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 倍

生成速度倒是很穩

3.8 – 4.1 token/s

五個人格幾乎一模一樣。

prompt 長度只影響「讀」,不影響「生成」。

「388 倍」只是讀 prompt 那一段。使用者實際感覺到的,是總延遲的 9 倍。

20 / 26

17 of 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

18 of 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 長度無關。

19 of 26

✦ ACT 3 · 收斂

那三個月,我其實一直在做同一件事

把每一項工作,搬到「做這件事最便宜」的地方。

這件工作

早期交給

現在交給

為什麼搬

選一個行動

模型

random.choice()

一次函式呼叫,不必跑完 4B 參數

輸出格式

模型

純文字+前端排版

4B 顧了格式就顧不了語氣

要記得什麼

模型

一句 SQL(24 小時窗)

條件判斷不需要生成模型

情緒分流

模型

關鍵字比對

二分類不用 4B

危機偵測

模型

關鍵字+硬編碼回應

這條線不能賭機率

把話說得有溫度

模型

模型

只有它做得到

模型是這台機器上最貴的資源。把它留給只有它能做的那一件事。

20 of 26

✦ ACT 3 · 收斂

微調,是同一個原則的下一步

知識也可以搬

前面搬的是「工作」。

這次搬的是「知識」——

​

把每次都要重讀的 1859 字說明書,

搬進模型的權重裡,變成 135 字。

誠實一點說

影片裡的快,是「prompt 變短」加上

「跑在有顯卡的機器上」,兩件事一起。

語氣還不夠台——那是下一版

資料層要處理的事。

21 of 26

✦ 最後

量能越多,問題就換了一個

量能不足的時候

你會被迫問:

​

「這件事,真的需要模型嗎?」

​

因為問錯了,使用者要等一百多秒。

量能充足之後

那個問題不會消失。

​

它只是變得容易忽略——

​

因為問錯了也不會有人抱怨。

換成 GPU 之後,這些工作還是不該回到模型身上。

省下來的量能,要留給真正複雜、真正只有模型做得到的事。

在 AI 時代,擁有的量能越多,

問題就從「怎麼完成一件事」,變成「怎麼有效地完成一件事」。

22 of 26

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 of 26

23

✦ 回到那個問題

不是模型越強越好,是分工越清楚越好。

1

邏輯解耦

選擇/計算交給 Python

2

純文字輸出

廢除 JSON 結構要求

3

正向鎖定

說「只做 X」

4

符號潔淨

prompt 有什麼就長什麼

5

記憶存原話

AI 輸出絕不回灌

今天一開始,你們看到的是一棵會長大的樹。

但我真正想做的,從來不是一棵樹。

而是一個在人最需要的時候,願意陪他二十秒的 AI。

它不用最聰明。

只要剛好在。

24 of 26

✦ PART 3 · 收斂

主軸收斂:小模型的正確用法

小模型不必是全能巨獸。�在明確的心理學邊界 + 後端邏輯解耦 + 護欄保護下,�Twinkle AI 4B 可以是很棒的台灣語境在地化生活 Agent。

✦

明確邊界

模型只負責「語氣」

邏輯全部後端處理

✦

護欄設計

fallback · SAFETY_LOCK · 危機保護

不依賴模型自律

✦

在地語境

Twinkle AI 把「繁體中文」

升級成「台灣人在說話」

可複用到:學習陪伴工具 · 個人工作流 Agent · 任何需要台灣語境的 LLM 應用

11 / 14

25 of 26

25

beta.zerotriz.com · Twinkle AI 4B · CPU-only

謝謝 Twinkle AI 社群,也謝謝今天在場的每一位。

beta.zerotriz.com零樹計畫測試版 – 30天刪除版

Contact me

歡迎加入~Twinkle AI

26 of 26

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