2026年3月22日

碼農二三事之加密~JWT

 前言:

在我百寶箱中,JWT不常用,但幾乎每個實做都用了,因為JWT基本上已經嵌入在.NET中開箱即用了。😁

說明:

我一般會形容JWT是鑰匙,你通過了認證並授權後,就發給你一把鑰匙,拿著鑰匙就有權限進行操作,這把鑰匙可以有時效,也可以沒有時效,端看需求。
JWT是標準,有標準格式,它標準格式分成3段,每段用.隔開,第二段和第三段是重點,第二段放了所有資訊,JWT標準中規範了必要和可選的資訊,必要的包括sub(subject), iss(issuer), exp(expired), nbf(NotBeFore), iat(IssueAt)。
其中必要中的必要是exp,這個標記了JWT的到期時間。
JWT沒有明確規定時間是哪種Timestamp,但一般用UTC。

第三段則是第二段的簽章,用加密算法把第二段用特定密碼算一個簽章放第三段,因此第二段 + 密碼計算簽章後,必定等於第三段,如果不等於,就是被竄改了。第三段的JWT規範是BASE64。

目前的登入Token,幾乎都用JWT,.NET c# 直接做好JWT中間件,套用即可,基本上是業界標準了。
JWT的第二段資訊顯然是關鍵,第二段資訊稱為Payload,是json用BASE64編碼後的字串。
這裡畫重點,是BASE64編碼後的字串,這表示它沒有加密,JWT不具備加密功能,只具備簽章功能,能夠驗證JWT是否合法沒被竄改,而根據合法JWT,則可以判斷是否過期,並根據sub(Subject)判斷持有者是不是本人,根據iss(Issuer)判斷簽發者是不是授權簽發者。

好~說完了~但JWT故事還沒結束,有沒有特殊用法?

特殊用法:

既然我們知道JWT是根據第二段的Payload紀錄資訊,Payload本身是json,那我們完全可以自行設計非標準的JWT格式,對吧!?
在c#/Java中,我們只需要建立一個Model,我一般取名為AuthToken,裡面參考JWT放入sub,iss,exp,iat,nbf。

使用時
var token = new AuthToken() {
    sub = "{user}",
    iss = "{BestServer}",
    exp = now + 1440 timestamp,
    iat = now timestamp,
    nbf = now - 5secs timestamp
}
var SpecifyJWT = AESCipher.Encrypt(token.toJson());

打完收工。
當然,檢查一樣,反過來,先用AESCipher解密,接著依序檢查 exp, nbf 。
一個具有加密效果的AccessToken就完成了,如果原本用c# JWT中間件,那只要實做一個自己的中間件替換上去,就完成高強度客製化的認證Token設計了。

JWT用法:

實務上,我常用JWT系列,因為它是分散式的,不受限Session,任何後端都能檢核。
但它可以有幾種變種:
  1. 後端統一用一台認證服務簽發和檢核JWT,所有服務都先把Client端JWT送來檢核,變成認證授權與服務分離
  2. 後端服務都使用同一個Jwt工具包進行簽發和檢核
  3. 資料庫中設計黑名單,特定JWT寫入黑名單後失效,新認證不予簽發JWT
  4. 資料庫中設計白名單,所有JWT簽發後都寫入資料庫,檢核時查找資料庫,如果使用者異常,從資料庫中刪除或註記這筆JWT,新認證不予簽發JWT,這種做法等同於把JWT當作AccessToken使用,差別是,AccessToken較短,通常是隨機字串,沒有隱含資訊,JWT則比較長,但有隱含資訊

碼農二三事之加密~非對稱加密~ECC(橢圓曲線加密)

前言:

在我百寶箱中,最近新入的是ECC(橢圓曲線加密),它可以做為RSA非對稱加密的平替。
ECC的特點是,它是開放的,不是公司產品。
ECC相關的實做和過程學習,基本上完全是AI實做,但因為要進入百寶箱,我有查核和測試過程式碼。

程式碼:

這份程式碼裡面包括多個程式語言的ECDH和ECIES實做,也是我百寶箱中新加入的工具。

https://gitlab.com/ycfunet/my_ecdh_ecies_code_test

說明:

ECC包括3種,ECDH、ECIES、ECDSA,其中ECDSA是簽章用的,這裡不談,ECIES是我們要用的,但它是ECDH的擴展,因此要先從ECDH說起。
  • ECDH:
ECDH邏輯是,我有我的公鑰和私鑰,你有你的公鑰和私鑰,我用「我的私鑰+你的公鑰」可以生成共享金鑰,你用「你的私鑰+我的公鑰」可以生成相同的共享金鑰,沒錯,這就是神奇的地方,「我的私鑰+你的公鑰」,「你的私鑰+我的公鑰」都可以生成相同金鑰。那麼這個共享金鑰就可以拿來當AES金鑰加密和解密資料。

 

ECDH使用時,不會直接給共享金鑰,而是雙方會把公鑰給對方,加密和解密前,才會組合出共享金鑰後,再用共享金鑰以AES加密解密資料,所以ECDH又稱為「交換金鑰加密系統」。
  • ECIES:
ECIES可以做為RSA的平替,它是以ECDH為基礎的擴展。
使用概念上,一樣是接收端先建立一組公鑰和私鑰,然後把公鑰給對方,自己保留私鑰。

但在邏輯上,如何套入ECDH?
ECIES引入「臨時金鑰」的概念,我(後稱接收端)會先產生一組公私鑰,公鑰提供,私鑰保存。
發送端拿著我的公鑰,在每次傳送資料時,都先產生一組公私鑰,這組公私鑰就稱為「臨時金鑰」,發送端會用「臨時金鑰的私鑰+我的公鑰」「產生共享金鑰」,用這組共享金鑰以AES加密資料。

 

還記得AES提到加鹽的IV技巧嗎?這裡一樣的行為,發送端會把加密後的資料,在最前面加上「臨時金鑰的公鑰」,因此整個資料變成
臨時金鑰的公鑰 + AES編碼後密文

接收端收到後,用「我的私鑰 + 臨時金鑰的公鑰」產生共享金鑰後,用共享金鑰以AES解密密文。
因為臨時金鑰的公鑰長度不一定,AI在最前面加上4 Bytes,記載臨時金鑰的字元數,所以完整資料變成 
臨時金鑰的公鑰長度數值(4Bytes) + 臨時金鑰的公鑰 + AES密文

 因為這樣的設計,儘管加密解密流程比較複雜,但包裝後可以完全平替RSA的非對稱加解密,接收端只要產生一組公私鑰,公鑰提供後,不論是臨時金鑰、AES加解密,其實都包裝在Encrypt和Decrypt裡面了。
RSA是公司產品,同時也有專利,我懷疑ECIES是為了規避RSA專利而用ECDH流程設計出的標準。


碼農二三事之加密~非對稱加密~RSA

 前言:

在我百寶箱中,使用頻率也很高的,就是RSA加解密。

程式碼:

這份程式碼裡面包括多個程式語言的RSA實做,也是我自己常用的百寶箱工具。

https://gitlab.com/ycfunet/my_rsa_code_test

說明:

RSA包括幾種功能,這裡只說加解密,不說憑證、簽章。
AES是對稱式加解密,就是固定用密碼把字串加密和解密,但一個情境是,我不能讓人知道我的密碼(金鑰),但我要能讓別人能加密,我能解密,這就是非對稱加解密,常見的是RSA。

RSA是RSA公司的產品,因為是公司產品,使用簡單,不用管細節,是業界標準。

RSA就3個Function:
  • GenerateKey: 產生一組公私鑰,公鑰給別人加密,私鑰自己留著解密。
  • Encrypt: 用公鑰加密明文成密文。
  • Decrypt: 用私鑰解密密文成明文。
咦...就這樣...對...就這樣,簡單吧 🤣。

公私鑰格式說明:

比較要注意的知識點是公私鑰的格式,一般有2種,PEM和XML。
c#用XML,其他通常用PEM,PEM以 -----BEGIN...----- 和 -----END...----- 當作開頭標記和結尾標記
另外,在Java中,Java要求的格式是PEM去除標記,只有Key的內容。


碼農二三事之加密~AES

前言:

這篇不會跟你解釋AES裡面怎麼XOR、怎麼移位,這篇我想說的是哪幾個規格以及怎麼寫。
在我的百寶箱中,使用頻率最高也最重要的莫過於加密相關,其中AES是重點之一。

程式碼:

這份程式碼裡面包括多個程式語言的AES實做,是我自己常用的百寶箱工具。
裡面的AES程式碼基本上相互驗證過,原則上都能互相正常編碼解碼(除了my_browser_js_test,能編碼,不能解碼)。

https://gitlab.com/ycfunet/my_aes_code_test

說明:

AES是對稱加密演算法,邏輯是你用一個密碼把字串加密,另一個人用同一個密碼把字串解密,密碼就是鑰匙,關鍵字叫金鑰(Key),加密後的資料叫密文,未加密的原始字串叫明文。



AES的核心邏輯是,用固定長度的金鑰,把固定長度的明文加密成密文,反過來,固定長度的密文用固定長度的金鑰解密成明文。
因此,最基本的認知是,金鑰長度固定,明文長度也固定,咦...我使用時,沒聽說明文規定長度啊,有些AES金鑰也沒規定長度啊。
這時引出兩個AES的重點,一個叫金鑰雜湊,一個叫Padding。

因為密碼(金鑰)可能長度不一,為了讓密碼(金鑰)可以是任意長度,通常會用SHA256把密碼進行雜湊,就能取出固定長度的金鑰,而這個長度就跟使用的加密規格相關,例如AES256,就表示256 Bits,SHA也用256 Bits。

Padding翻譯叫填充,剛剛提到加密和解密時,明文必須是固定長度,不固定長度怎麼辦呢?會把明文填充固定字元,讓它長度固定。
Padding有多種填充內容,直覺想到填0,對這是一種,目前常用的是根據空白數量填數字,例如空3格就填0x03 0x03 0x03,這種目前是主流叫做PKCS7Padding。

到這裡,我們知道能用SHA256把密碼雜湊成密文,能用Padding把明文填充成固定長度,這時候會發現,固定的金鑰+固定的明文=固定的密文。

這很OK,但感覺每次的密文都一樣好像怪怪的,有沒有辦法讓它每次密文都不同?這時引出加密常見的重點,加鹽(Salt)。
所謂的加鹽,真的如字面意思,就是加料,在AES中,規範了多種模式,我們只要記得常用的2種,ECB和CBC,ECB就是上面寫的未加鹽的就稱為ECB,加鹽的模式就稱為CBC。
AES-CBC的加鹽是說,CBC規定了一個欄位,稱為IV,這個欄位就是加鹽的鹽巴,AES-CBC加密時將密鑰+IV當密鑰,一起將明文加密,解密時將密鑰+IV當密鑰,一起將密文解密。IV通常是16字元(128Bits)。

因為IV是鹽巴,通常會用亂數產生,那既然是亂數產生,解密時如何知道?
一般會把IV加在密文最前面,變成
IV(16個字)+密文

解密時,先切割出IV和密文,然後把設定IV和手上的密鑰,把密文解密成明文。
IV放密文最前面是慣例,也可以和密文一起放在兩端,傳送時只傳送密文,但既然是加鹽,通常目的是讓密文判讀性更低,放在密文最前面會讓密文每次都不一樣,減少一致性。

最後一個知識點,實作會發現,AES加密後的密文,是Bytes的Binary,這不好處理呀。
在最後要提供密文時,Binary不容易使用,通常會編碼讓它字串化,一般都用BASE64或HEX,這裡有個小小誤區,BASE64有2種,一種是一般的BASE64,但一般的BASE64會有特殊字元,在網頁傳輸時,可能因為HTTP GET不支援的字元,因此有個變形是BASE64 URLEncoding,這兩種BASE64的結果是不同的。

以上就是AES知識點,還是蠻長的,但不用管底層XOR、移位邏輯。

結論:

這次的文章放了一陣子,圖片是Claude-Code (GLM-5/GLM-4.7) 用Excalidraw MCP畫的


2026年2月14日

MustOn Cluster - Cluster & PostgresqlLib & DeployLib & FSM Sample

前言:

延續上次的 MustOn Cluster。
實做進行中,這次先把開發中的PostgresqlLib、DeployLib以及FSM Sample Code放上來。
另外,覺得MustOn這名字不錯,順手把mustoncluster.com註冊了。

成果:

https://gitlab.com/mustoncluster/messagelib_clusterapp
https://gitlab.com/mustoncluster/postgresqllib
https://gitlab.com/mustoncluster/deploylib
https://gitlab.com/ycfunet/my_csharp_fsm_test

實做過程:

幾個點,首先,上次放上gitlab的MessagesLib & ClusterApp有問題,AI騙了我,我跟它說要用Raft Cluster的Metadata進行狀態共享,結果AI偷雞用Thrift API實做,好笑的是,我跟它說:「你好髒喔」,它還知道用Thrift API偷雞是不正確的作法。
這次一樣是用Claude Code搭配GLM-4.7開發,寫到後來基本上有點崩。Cluster目錄\用submodule的方式加入MessagesLib、PostgresqlLib、DeployLib,AI光是用git就差點把本地程式碼和遠端repo(家裡的)的程式碼給刪了。
再來,這樣的架構目前開發,AI經常卡住,改程式碼容易亂改,要嘛改錯東西,要嘛刪除無關的程式碼。感覺這樣的程式碼體積和複雜度,GLM-4.7就有點頂不住了,正好最近GLM-5.0剛發表,初步可能伺服器資源不太夠,反應非常慢,一個問題可能要5分鐘才回覆,但品質看起來不錯,等它穩定性好些,反應速度快些,說不定能提昇目前的處理能力。
再一點是,我嘗試用OpenCode替代Claude-Code進行開發,變成OpenCode + GLM的開發模式,目前還在熟悉和習慣中,OpenCode有不少特異功能,它直接內置Server,讓界面和操作可分離,界面可以是應用程式,也可以是CLI,Model不綁定,可直接用Google Gemini、GLM系列,彈性比較大。

程式還在開發中,正如上面講的,架構比較大,程式碼比較複雜,開始寫得卡卡的,今天情人節,就用這些更新來過吧,農曆新年也要繼續努力。

2026年2月1日

MustOn Cluster - Cluster & Message Layer

 前言:

上次提到想嘗試開發分散式儲存系統 - GreeStorage,分散式儲存系統有很多塊,但不論哪一塊,分散式系統的基底,訊息中心和通訊層是最核心的部份,這部份也是分散式系統的主軸,因此先從這個部份開始。

成果:

整份專案在
https://gitlab.com/mustoncluster/messagelib_clusterapp

設計過程:

在和AI討論的過程和Storage思考設計過程,避免不了WAL,所有儲存系統的資料寫入都是強一致性行為,無法迴避WAL,而在分散式儲存系統中,WAL就不只跨磁碟,也需要跨網路,跨網路的WAL必定會有Cluster和Message Layer,先攻破這塊。
有趣的事情是,在查找WAL時,想先從既有WAL的應用著手,赫然想到DB HA、DB DR,這需求在之前不只一家公司也不只一次遇到,DB和儲存系統很像,也需要WAL,而DB的WAL在DB中都已經做好了,所以也許可以先從DB HA(高可用性)、DB DR(異地備援)應用著手,既能熟悉WAL也能做出一個應用。
MustOn的名稱由來很好玩,查找DB高可用性當然要看Microsoft SQL Server的Solution,赫然發現,Microsoft把SQL Server的高可用性解決方案重新設計了,設計方向感覺跟OpenAI最近PostgreSQL的優化設計有點像,我懷疑Microsoft的SQL Server在針對AI應用情境優化設計,但我沒有證據 😂
喔~離題了,Microsoft新的SQL Server高可用性設計稱為AlwaysOn,於是我就問AI找個always同義詞,Usually不行,頻率比Always低,這表示比較容易故障,那Must如何~於是MustOn名字就出現了。
原本MustOn應該是DB高可用性方案名字,結果AI亂套,把Cluster命名用上了,那就.....這樣吧 😂

實做過程:

這次基本上都是Claude Code搭配GLM-4.7開發,設計階段則是用Google AI Studio聊天方式討論設計的。
因為現在新的Cluster好像從Paxos演算法改用Raft演算法,因此實做就用Raft的Cluster套件實做,但實做發現,Raft演算法只有Leader -> Follower,並沒有Follower -> Leader,所有訊息都是由Leader往外推送,而不是往內收,因此,基本上無法透過Raft層進行資料傳輸,說人話就是,dotNext Raft的實做就是Raft Cluster的實做,不包括其他API訊息處理,因此dotNext自己另外有一包ASP.NET Core的版本,透過ASP.NET HTTP API做這塊,dotNext官方網站也提到它們有多種傳輸方式,顯然意思都是,Raft Cluster只有Raft唷~其他傳輸行為要另外定義API,這既合理又方便。
我在設計選型階段,就考慮用Apache Thrift當API訊息層傳輸,因為Thrift跟gRPC一樣直接有IDL可以定義出API名稱和參數(gRPC是protobuf),這會讓API設計更乾淨、清楚,我認為這在Cluster的通訊層設計很重要,因為既然是通訊層,就很容易設計一堆通訊用API和Model,如果不透過IDL或protobuf管理,寫到後面就整個大混亂,完全不知道API有哪些,作用是什麼。
而不用gRPC的原因很簡單,Thrift是binary格式,而gRPC是json文字格式,這表示要傳遞檔案或資料,Thrift也能處理,傳輸內容也會稍微小一點。

結論:

GLM-4.7開發後端的確相當不錯,穩定性很好,趁著特價,我也入坑了。😂
最近好像有龍蝦之亂,看起來能用GLM玩龍蝦,最近很熱門,過陣子有心得,如果很特別再貼吧。


2026年1月27日

開源AI-Agent聊天室 (OpenSource AI-Agent ChatRoom) - SongAiChat

前言:

之前曾在Threads上分享過別人寫的AI-Agent聊天室的MCP工具,但我用起來總覺得沒有很好用。
我在開發時,會遇到需要不同AI-Agent間通訊的問題,常見的就是跟AI-Agent說:
你給我一段說明,我轉給另一個處理的AI-Agent,然後問另一個AI-Agent,你看了有什麼疑問要我轉達?
或者是跟AI-Agent說:
你撰寫一份串接文件,我給其他AI-Agent閱讀和實做。
不論哪種,都要人工轉發,那是否能夠有一個聊天室,讓每個AI-Agent在裡面自己對話,我也能在裡面參與或了解對話內容呢?能夠紀錄更好。
於是,就有了寫個聊天室的念頭。

成果:

整份專案在
https://gitlab.com/ycfunet/songaichat

設計過程:

這套聊天室的設計過程很復古。😆
我其實想的原型是很早期的CGI聊天室,那種一堆人在裡面,然後一直洗版的那種聊天室,因此版面和系統設計借鑒了早期那種聊天室設計,加上了WebSocket(這邊我用的是SignalR)長連接的功能,達到即時通知的功能。
名字雖然是Song,但跟唱歌一點關係都沒有😂,它的命名是起源於高中松山工農時期的BBS站~松間小棧,我是那時候的站長😅。
因為源於松山 - Songshan,因此取前面的Song😁。
設計過程很復古吧。

實做過程:

這次實做換成Gemini-CLI實做。
Gemini-CLI使用感覺反應慢很多,可能是現在很多人在使用,很容易忙碌吧。
但以寫程式碼的穩定性,尤其是前端,感覺比GLM-4.7穩,React + FluentUI基本上一次完成,版型也沒什麼大問題,配色也是標準正常。
不過Gemini-CLI很多時候問它問題,它想到就直接改下去了,然後改的方向或作法可能是錯的,這時候就得從git退版,因此Gemini-CLI和GLM-4.7的git版控很重要,要經常讓它commit,有問題隨時退版。

這個版本目前算是v0.8.5 Beta,基本能用,還沒有仔細測試,每次測試都會修改增加功能或調整,因此沒有完整一輪測完,但基本功能使用上沒什麼問題。

雜談:

DotPusher之後,我原本寫OmniPusher,實做Electron版的WebFcm和WebPush推播,但實做差不多了才發現,Electron在WebFcm與WebPush相容性有問題,靠~於是就放著了。
另一點是,OmniPusher用GLM-4.7實做前端,寫的有點卡,讓它計劃書內畫Wireframe都畫了,結果實做時直接偷工,Sidebar沒處理乾淨,Main Content直接沒做,叫它用FluentUI做,又做成HTML簡易版,連基本的標題列和副標題列樣式都沒處理,變成單純的HTML,然後越改越撞busy,蠻昏倒的,寫到真的是不想看。

另一個點是這樣的,SongAiChat主要作用是AI-Agent間對話用的,我在考慮撰寫用AI開發比較大的專案,目前打算寫套名為GreeStorage的儲存系統,GreeStorage名字取自Great Storage的諧音
以前有段時間專門在弄Ceph,Ceph是Object Storage分散式儲存系統,架構比較大,那個作者很妙,作者就寫Ceph,一路寫一路發Paper,寫了10幾篇寫完博士讀完😆,然後就成立了公司賣儲存系統,接著又被RedHat收購。重點是,因為架構比較大,各組件也多,我在想有沒有可能定好架構後,一段一段讓AI寫,可能每個AI負責一段,然後就透過聊天室互相溝通和串接。這目前還在初期想法階段,如果能做出來,應該很屌😆。


2026年1月16日

開源推播通知服務閘道(Push Notification Service)系統 - DotPusher

前言:

手機要收通知,通常需要個Line、Telegram、DCard之類的通訊軟體,或者是pushover之類的推播軟體,如果是Line、Telegram串接太複雜,pushover要收費,原理不難,而且有現成的gorush可以參考,乾脆自己做。

成果:

整份專案在
https://gitlab.com/ycfunet/dotpusher

實做過程:

實做過程還好,事實上,整個專案還未完成,它的完整版應該包括2塊:
  1. DotPusher - 推播閘道:這個是把各平台的推播轉換成簡易的RESTFul API呼叫,並加上Queue處理
  2. OmniPusher - 使用者推播服務:這個是個推播帳號註冊平台,目前參考pushover實做中,它的功能是註冊使用者,綁定裝置到使用者,推播給使用者時,底層會直接呼叫DotPusher API進行裝置推播,其實就是使用者轉換成裝置的服務平台
設計上,DotPusher可能放在雲端,OmniPusher則可能放在Web應用服務端,甚至可能整合到Web應用服務當中。
DotPusher的定位是能支援各個推播平台的推播服務,目前能支援MacOS/iOS的Apple APNS、Android/Web的Google FCM、Web原生的WebPush,正在實做Windows的WNS以及華為的Push Kit。

喔~對了~測試過程中,Android收FCM遇到,連發3次,但只收到第1次,後面幾次收不到,雖然AI是說FCM節流,但我猜可能是Android端實做有問題,因此目前Android端測試程式沒放。

結論:

這次結論比較特殊 😄
這次的專案實做有個很大的不同點,這次第一次用GLM-4.7的Claude-Code開發,使用心得是:
以程式開發來說,GLM-4.7和Opus, Sonnet相比,基本上差異不大
Opus, Sonnet的語句理解能力的確比GLM-4.7精確,另外一個有趣的點是,GLM-4.7比較不討論,直接就做了,對話的語句比較少,跟它說,按照「這樣這樣」修改,它就改成「這樣這樣」,如果是Opus, Sonnet則會潤飾後寫出。
Opus, Sonnet通常會討論,會回應,你是否想這麼做,或者說,你覺得下面這些方案如何,GLM-4.7則比較直接回覆「你想要怎麼做?」,也比較不幽默 😄,我跟它說作者要寫你唷,它就只是把作者加上它名字,但在Opus, Sonnet則會說,我感到很榮幸。


2026年1月7日

開源 OIDC 單一登入(Single Sign-On, SSO)系統 - SSO-Yarper

 前言:

家裡有些Web服務使用反向代理,有網站沒有認證功能,需要個SSO。
之前工作做過SSO SAML IdP,這次就嘗試OIDC IdP + 反向代理 + Cookie-Based授權閘道。

成果:

整份專案在
https://gitlab.com/ycfunet/sso-yarper

實做過程:

這個實做其實不太順利,最早讓claude-code寫過OAuth2 Demo Site,看起來會動。
之後又讓claude-code寫過OIDC IdP,但OAuth2和OIDC IdP卻寫出不同的程式碼內容。
再者,claude-code寫yarp不太行,它會一直混淆yarp的設定檔設定,以及.NET Middleware,甚至都跟它說,就是要用yarp的pipeline都還會改在Middleware,最後是自己參考Microsoft的yarp sample實做後,要求claude-code按照我寫出的sample進行修改,並且指定修改程式碼位置才改出。
因為反向代理本來就有在用,要測試就得先關閉原服務測試,因此斷斷續續實做sso-yarper。
另外,早期規劃是把oidc和yarp實做在一起,但實際測試發現,yarper內的路由會和oidc的登入頁面衝突,登入頁面是SPA純前端,它會透過path在前端路由,yarper在認證狀態時,也會需要path,這造成兩個功能衝突,因此後來直接拆開,oidc idp歸idp,yarp認證時則改做成oidc rp,跳轉認證。
這套程式從DBML可以看得出,實做的時間跨度很長,事實上也在家裡運作一段時間了,還不錯用。

結論:

OIDC SSO是頗有價值的實做,跟Yarp結合是很關鍵的實現,cloudflare的零信任架構其實就是相同作法,透過反向代理與認證結合,做到Web Service安全防護。

2026年1月3日

雜談: AI Fine-tuning之我見,為何要Fine-tuning AI模型?

這是一篇雜談,自以為是的認知。😂

我先前在survey gemma3:270m fine-tuning時才發現,gemma3是不支援MCP的,而Twinkle AI的成果有把MCP功能,也就是function call的能力加進去。

於是我就疑惑了,看了才發現AI的秘密 - Tokenizer,也可算是Embedding模型,它作用是分詞,把整句話切成一個個單字、名詞、動詞。

LLM起手式第一動就是分詞模型分詞,這個分詞的詞就是Token,然後根據模型把每個Token的結果回覆。 簡單說,「這裡有一隻貓。」,經過Tokenizer之後,會切成「這裡」「有」「一隻」「貓」「。」,這一個個詞,會轉換成數字(句點會變成<end_oooooo>,這樣的特殊tag😁),稱為「Token」。

MCP的核心其實是function call,而function是以tag(<function>)的形式加在資料集裡面訓練的,沒有這個定義,AI不會知道要呼叫工具。 

白話說,<func>要定義在Tokenizer,要描述在Training Datasets,兩者都有,訓練後AI才會用工具。 今天的慶生會詢問後證實了,<function>無法透過RAG讓AI模型了解,只能透過fine-tuning在訓練集和Tokenizer加入。

例如:
Tokenizer 裡面定義 <func> = 呼叫工具 DataSet 裡面有一筆「問題: 請問台北的天氣如何?」「答案: 台北的天氣<func>。」

訓練後,當詢問時,如下:
「新竹現在天氣好不好?」
「天氣有<func>」=> 「是否允許呼叫 weather function 擷取天氣資訊? true」
「新竹的天氣,目前溫度58度,濕度125%」

最近LVA,機器人模型有些討論,但我們先不討論物理知識相關的訓練集處理,如何控制機器人? 機器人如何知道「往前走」=「A motor+B motor+C motor+D motor」的連續操作?是不是需要<function>。

非常多年前還在學校時,當時選修柔性計算有幾個機械系同學在做機器人,當時問他們,怎麼讓機器人動作? 它們跟我說: 「機器人所有關節都是伺服馬達,一個姿勢等於所有馬達的特定角度,可定義為一個frame,走的連續動作等於播放frames,像動畫片」 底層控制不會變,但LLM介入後,也許連續動作的控制能變成LLM自動生成。

那以前或者目前怎麼做? 常見的方式是人做動作後錄製,例如: 人彎腰,透過攝影機和關節姿勢擷取後,轉換成各個關節以及伺服馬達的角度,並且錄製。 接著讓機器人按照這些錄製的結果播放,因此很久前會看到機器人跌倒後,先定住,然後很制式的特定動作站起來。 這也是大陸很常看到的狀態,或者看到有人遙控機器人的情況。 Elon Musk機器人厲害的地方是,你看它動作行雲流水非常自然,這表示它各個關節的動作已經不是制式的play frames,而是根據動作,由LLM自動自然的生成,根據的不是單純的frames,而是有點像End-to-End的方式,讓AI直接理解行為,理解動作,把行為直接轉換成frame(motor控制動作)。

放了1個月沒提的知識點😁

結論:

訂閱AI重要,但AI fine-tuning模型裡面有些知識點,沒做過不會發現🤫  整理DataSets是苦工,而且不說沒人知道,說了也沒人知道能幹麻,它是「開發AI」的基礎,但不是AI應用的成果。 Fine-Tuning很重要,但參加活動問了人之後發現,幾乎都建議直接用RAG,我認真想過,也和AI討論過😂,目前Fine-Tuning在一般AI應用或開發中,成效有限,相同作用基本上都能在RAG中完成,除了Custom Tokenizer和Custom function之外。😂

發想:

基於貓砂機馬達控制FCM的延伸思考,結合function call和彈性化的增減function call工具的使用。 我在想是否根據特定情境修改和調整資料集,custom function tag,讓它能適應特定特殊應用。 而這點帶出2個延伸

一個是,以既有資料集修改增加tag以滿足特定問答時的特定工具應用。

一個是我在想的,用模板語言與特定語法進行Fine-Tuning,因為模板語言語意、語法和既有AI模型重疊度比較低,對於已有的模型影響也許比較小,那麼就能教會AI看特定的模板語言,就想像成我們用模板語言定義了瓦干答語,這個語言根本不存在,Fine-Tuning時等於讓AI學一個全新語言。 學會後,我們在RAG中根據瓦干答語~不是,是模板語言,寫出制式的語句和答案,那麼AI就能根據RAG查詢的的結果生成準確的答案。 這樣的應用,也許能用在機器人控制、End-To-End API、程式碼產生器,這些輸入或者輸出不是自然語言,或者用自然語言查詢(輸入)得出制式格式資料(輸出)的情境。

這應該能讓Fine-Tuning這件事變得有價值,如果輔以RAG進行模板語言查詢,應該能同時做到彈性和準確性兼備。 如果真如想像,那麼能帶出2點: 1. 地端或Edge端模型能和雲端模型搭配,Edge端負責控制,雲端負責行為判斷,例如,「小呆同學,我要喝水」,雲端AI能給出「水在水壺,水壺在廚房,到廚房拿杯子裝好水之後,拿到房間」,Edge端能給出「我控制腳步移動到廚房」「我控制手部抓取水杯」「我控制水壺倒水到水杯」「我拿起水杯」「腳步移動到房間」 當然,也可以是End-To-End,直接一個Edge端AI模型內部直接轉出成這些動作控制

2. 關鍵點會需要Training Model,這無法透過RAG處理,因此需要AI公司因應需求Fine-Tuning模型,或者根據需求新增RAG資料,而RAG資料的原型是模板語言,語法與Tag則是Fine-Tuning而成,應該能保護住知識產權

以上都是嘴砲,function calling的Fine-Tuning有拿網路上資料Training,但成果不明 😂 發想還沒實做,它的起手勢,要先學Mustache

雖然不願意承認,但「使用AI」比較容易,使用產品還是簡單些,要造輪子,太累了😂