Skip to main content

Wei Ji

背景知識

要明白我要表達什麼,需要具備一些先驗知識,包含:Dyason Swarm, Starlink 和 樹莓派叢集,我會先做一些簡單的解釋幫對這方面概念比較缺乏的讀者能夠搭上我的思路。

戴森雲 (Dyason Swarm)

戴森雲是一種科幻構想,簡單來說就是:

用大量的人造衛星圍繞並運行在太陽附近來採集太陽能源。

更具體的介紹可以觀看 Kurzgesagt 的影片,我就不在這邊細談了:

info

Kurzgesagt 的影片算是高度簡化版本,例如有人指出「把人造衛星降到靠近太陽的軌道這件事本身就需要消費不少能源」1

然後我的立場是:

太酷了!我想蓋!

不過我看完 Kurzgesagt 的第一個想法是,它一定需要有一個資料中心來遙測與控制這麼多的衛星。

星鏈是由接近一萬顆人造衛星構成的網路系統,讓消費者不用透過海底電纜就能使用高速的無線網路。尤其適合有線網路基礎設施不夠發達的國家,其他使用情境包含空運、海運等位於海洋中間或在深山之類缺乏基地台地方。

請特別留意 Starlink 佈署時堆疊在火箭內的樣子:

https://www.space.com/spacex-starlink-falcon-9-rocket-fairing-video

樹莓派叢集

樹莓派 (Raspberry Pi) 是原先出於教育目的而被設計出來的單板電腦,它就像一個手長大小的電路板,卻具備電腦的完整功能。

預留腳位的設計讓它能夠作為開發板被客製化裝上各種感測器或擴充電路。其中一個例子是把它跟獨立顯示卡裝在一起2

樹莓派叢集則是將多個樹莓派透過網路線連接在一起構成一個運算叢集:

https://dev.to/fredinono/from-nas-to-kubernetes-setting-up-a-raspberry-pi-cluster-with-synology-nas-57f4

是不是跟堆疊的衛星有幾分神似?

現實

現在讓我把眼光從戴森雲、星鏈拉回我那 4.3 坪的租屋處,身處於寸土寸金的大台北,五年網頁前端開發經驗,混雜一些 3D、後端、Docker...的經驗。

這是我手上有的牌,接下來我該如何玩這個名為現實的遊戲呢?

Kubernetes

操作著數以百計的容器

閱讀 K8s (Kubernetes) 相關的文章很常會看到類似的描述。

「它一定需要有一個資料中心來遙測與控制這麼多的衛星」

它跟 Dyason Swarm 的影子在我面前重疊,「Dyason Swarm 的科技樹上必然有 K8s 的存在」我如此確信。

Longhorn

Longhorn 是基於 K8s 的分散式儲存,它可以和 K8s 以類似共生的關係運作在一起:透過 K8s 的叢集架構 Longhorn 可以運作在每一個節點上實現多節點冗餘,而當 K8s 內佈署的服務請求儲存區 (Persistent Volumes) 時,則由 Longhorn 直接向應用程式提供儲存空間。

因此一顆硬碟只要掛上一個堪用的電腦(比如一塊樹莓派),丟進 K8s 內就能讓它成為儲存資源池的一部分,多餘的運算能力則可以「順便」運行一些服務分攤叢集的負載。

基於筆電的 K8s 叢集

筆電有一些工程特性是一般桌機以及伺服器運算不會考量的:省電,即能源效率以及體積和輕量化,為了方便攜帶,可靠性也不會做得太脆弱。我認為這在台灣這種地狹人綢以及寸土寸金的都市中是十分重要的因素,我最為租屋族不太可能負擔得起充足的空間建設伺服器機櫃,遷移的頻率也遠比居住在大陸上的歐美人士來得高。

並且根據我的主觀體驗,很容易低成本獲得二手筆電,加上 K8s 和 Longhorn 的架構下,數個無須十分強大的節點就能提供儲存冗餘,從而解決基於筆電的設備無法使用硬碟陣列卡的問題;又或是避免管理軟體陣列帶來額外的麻煩,只要是能夠安裝並運行 K8s 節點的設備都能對這個叢集進行水平拓展。拓展叢集的關鍵零件,交換器本身又不會太昂貴。

筆電或是單板電腦的尺寸較小,比起機架伺服器或是一般的桌機來得更有空間彈性,必要時甚至可以把叢集拆成數個不同大小的單元分散在空間之中。

當然,要把二手筆電當作一個儲存節點,可能需要透過 USB 外掛硬碟,二手筆電低階的 CPU、低階的 USB 埠口、低階的乙太網路埠口都會成為系統的瓶頸。不過這種事情等遇到再說吧,瓶頸就是用來感受的,畢竟我都經歷過直接把 OS 灌在 DAS (Direct-attached storage) 外部儲存、最後 I/O await 高到會讓 Docker 不穩定才把系統裝回 SSD 了。

Footnotes

  1. The Dyson Swarm – A Personal Perspective - JMORE. Retrieved 2026-02-04, from https://jmoreliving.com/2020/06/08/the-dyson-swarm-a-personal-perspective/

  2. Use an External GPU on Raspberry Pi 5 for 4K Gaming - Jeff Geerling. Retrieved 2026-02-04, from https://www.jeffgeerling.com/blog/2024/use-external-gpu-on-raspberry-pi-5-4k-gaming/

Wei Ji

G 胖

G 胖 (Gabe Newell) 並且是 Valve 公司的擁有者,Valve 則是 Steam 這個最大的遊戲平台背後的開發與維護者。G 胖在玩家圈中有著很高的聲望,這裡挑幾個論點或例子。

G 胖曾經表達他對於盜版遊戲的看法:盜版源自於缺乏服務,玩家都會花錢買電腦,他們不會單純為了省那一點遊戲的錢而刻意去使用盜版軟體,盜版通常猖獗在遊戲軟體的服務觸及不到的地區,你只要把服務做好,盜版就會自然消失。那些針對盜版機制努力的公司根本搞錯重點,他們這樣做反而在降低玩家的消費體驗。

G 胖稱呼玩家為客戶 (Our Client) 而不是消費者 (Consumer)。

Valve 不是上市公司的特性,讓它能夠在 G 胖的領導下真正提高產品的品質,而無須迎合股東。

資本主義下的王者

關於上市公司與私人公司的比較我認為是一個很有趣的切入點,這也是很多人詬病的問題。在股份公司的架構下,權力集中在董事會上,但是股東通常不具備公司業務相關的知識背景,只是單純的出資者,並且要求金錢收益作為回報。結果就是造成「外行人領導內行人」的問題,經常讓公司推出一些「理論上有利於股東,但是不利於消費者」的策略。

反之,Valve 與 G 胖之間的關係是私人公司與唯一的資本持有者,G 胖本身又是具有遊戲相關背景的人,因此公司前進的方向通常是為了解決玩家(即消費者)的問題,而不是為了迎合股東的利益。

有趣的是,從權力結構上去觀察兩者,董事會反而是有分權的體系,私人公司則是完全獨裁的體系,G 胖的例子剛好成為「民主優於獨裁」的反向案例。不過有一點概念的人都知道,民主並沒有比獨裁才好,只是多了一些分散風險的機制,避免昏君帶領群體走向毀滅,而 G 胖剛好是「獨裁體制下的明君」。

權利與義務的失衡

一個權利背後往往伴隨著對應的義務,這是社會契約運作的基本原理。上市公司的結構反而呈現微妙的權責分配方式:股東有獲得公司收益的權利,卻沒有理解公司業務邏輯的義務。

我認為這個權利義務的不對稱、失衡正是造成上市公司在資本主義的遊戲規則下變成一種罪惡之源的根本性原因。

巨靈尺度下的集體惡意

霍布斯的巨靈論主要是一個論述透過匯集人民的權利義務到君主身上而構成君權合理性的理論,一個建立在人本上君權理論;而不是基於神學的君權神授。

我認為巨靈論可以描述一種集體惡意的狀態,例如:第一世界的公民購買巧克力這件事本身是非常中性的消費行為,但是無數的消費行為累積在巨靈尺度很有可能變成壓榨第三世界巧克力勞工的一種集體惡意,就像是一個第一世界巨靈在欺壓另外一個第三世界巨靈。

當我們用相同的概念來看帶「買股票、買基金」這種購買金融性產品的行為,會發現它在巨靈尺度同樣構成了一種惡意:無數人持有獲利的權利卻沒有理解基本底層邏輯(公司業務邏輯)的義務,其結果就是資本的代理人往往會做出很多傷害終端消費者的決策,完全有違資本運作最原始的目的:一個人幫助另外一個人,並用金錢衡量這個幫助。

Wei Ji

前情提要

前一陣子和人聊天,剛好提到了一個我六年前 (2020) 寫的一個小玩具,我似乎沒有把跟它相關的想法寫下來,於是試著在這邊文章做個整理。

有限像素影像辨識謎題

謎題規則如下:

  • 謎題本身為一張點陣圖
  • 初始狀態為 1 pixel
  • 點擊一次 1 pixel 會被切割成四等分
  • 換句話說每點一次(探勘)就會多揭露 3px 的資訊
  • 玩家必須用最少的探勘次數獲得的「馬賽克圖像」辨識出謎題

舉例來說,這是初始狀態:

這是第一次點擊:

這是點擊了 55 次之後:

經過 453 次之後,玩家可以辨識出這是「貓」:

453 就是這個玩家的量化成績,數字越小越好。

這個謎題的程式碼在此:

https://github.com/FlySkyPie/dequadtree-puzzle-game

CNN 問題

這個謎題啟發自卷積類神經網路 (CNN, Convolution Neural Network) 的問題。

CNN 會掃描整張圖片,並且偵測諸如邊界之類的微小特徵1

接著再透過這些微小特徵組合成比較高級的特徵,例如:臉、手...之類的。

這個特性為 CNN 帶來了一些問題:

  • 高頻下毒
  • 重組可辨識性

高頻下毒

攻擊者可以在影像內參入人類肉眼不可見的資訊,讓圖片無法被 CNN 正確的識別2

重組可辨識性

當圖片被重組打亂,人類將會難以辨識,但是 CNN 仍然能夠維持辨識能力3

模擬注意力

人類的視覺成像其實並不是均勻的,受到視網膜視錐細胞分佈的影響,實際上只有中間的區域具有比較多的成像資訊。另一方面任意瞬間視網膜接受的資訊其實是模糊的,是透過大腦在時間序上累積的資訊腦補成一個比較清晰的影像。

前面的謎題就是為了模擬這種特性而設計的,人類會將注意力放在最可疑、最有價值的區域去提高解析度,其他地方就算不管也沒關係。經過探索圖片本質上是一個四元樹 (Quadtree) 結構。

Footnotes

  1. Convolutional Neural Networks Explained. Retrieved 2026-01-25, from https://tvirdi.github.io/2017-10-29/cnn/

  2. How we might protect ourselves from malicious AI | MIT Technology Review. Retrieved 2026-01-25, from https://www.technologyreview.com/2019/05/19/135299/how-we-might-protect-ourselves-from-malicious-ai/

  3. Neural Networks seem to follow a puzzlingly simple strategy to classify images | by Wieland Brendel | Bethgelab | Medium. Retrieved 2026-01-25, from https://medium.com/bethgelab/neural-networks-seem-to-follow-a-puzzlingly-simple-strategy-to-classify-images-f4229317261f

Wei Ji

背景

機戰傭兵 (Armored Core) 是一款我沒玩過的遊戲,但是很享受於它的衍生作品,包含遊戲預告、實機影片之類的。其中一個讓我感興趣的是機甲在遊戲中機動的方式:

撇除誇張的瞬間噴射以及懸浮飛行以外,麥卡納姆輪 (Mecanum wheel) 在可控的室內平地同樣可以做出炫泡的機動:

因為很炫泡,所以我想做,於是一個構想就在我腦海成型:

  • 以 50cm 的迷你尺度實現。
  • 運行在草皮上,比室內平地粗曠,但是又保留考量可行性的平面。
  • 四足機甲造型,四足設計用來作為主動式懸吊系統。
  • 使用類似 ZIL-29061 螺旋輪結構,實現一般輪型或履帶型載具無法達成的漂移機動。
  • 使用 360 攝影機實時進行 3D 點雲掃描與場景重建,因此可以在一個渲染的 3D 空間實現第三人稱操作(近似遊戲本身的體驗)。

最近隨便用 Nano Banana 搓了一張概念圖,順便把這個想法做個筆記:

Nano Banana 使用筆記

info

過程我省略原圖,所有使用過得原圖我放在文章最後面。

第一次合成嘗試

提示詞:

將第一章圖(水道橋重工機甲)的底盤改成第二張圖(ZiL-2906)的螺旋輪。

保持原本的四足設計、將原本的兩個螺旋輪設計改成四個較小的尺寸的螺旋輪、對應原本的四足位置。

結果:

左邊的東西很明顯是這個上下文讓模型推論出 bug 了,不能期望它 one shot 搞定了,於是後面採用不同的策略。

提高解析度

第一次嘗試除了組裝失敗以外,底圖的解析度明顯太低,但是因為這張圖的角度剛剛好,所以我沒有再去尋找其他解析度比較高的圖片。

提示詞:

提高畫質、提高解析度 

第一次預組裝

將提昇過解析度的圖與第一次生成的螺旋輪做簡單的合成:

之後再用 Nano Banana 進行揉合,提示詞:

這是一個簡易合成的圖片,內容為四足人形機甲配上螺旋輪 (marsh screw),
有以下缺陷:
- 不完整的去背
- 缺乏正確透視
- 機甲與螺旋輪缺乏真實的機械連接
你必須修復上述缺陷。

結果:

提示詞:

移除機甲的左右手,保留上半身與頭部

結果:

這個步驟是因為我不太喜歡人形這種高重心的設計,但是手部有部份遮擋到一些腿部,自行移除再修復有失敗的風險,乾脆一開始就用比較明確的語意讓 Banana 移除手部。

info

我知道最後的圖還是重心偏高,但是我也說了:「隨便搓的」=3=

第二次預組裝

提示詞:

這是一個簡易合成的圖片,內容為四足機甲配上螺旋輪 (marsh screw),配上砲塔以及格林機關槍,,
有以下缺陷:
- 機甲、砲塔、機關槍色彩不一致,將砲塔改成與機甲相同的德軍灰。
- 缺乏正確透視
- 機甲-砲塔、砲塔-機關槍,兩個連接處缺乏真實的機械連接

你必須修復上述缺陷。

結果:

使用素材

水道橋重工機甲1

ZiL-29062:

科幻戰車3

格林機關槍4

後記

後來稍微搜尋一下,發現 ZIL-29061 遙控車已經有人做了:

所以除了四足機甲以外,其他部份的可行性算是已經被驗證過了(?)

Footnotes

  1. Suidobashi Heavy Industry – KURATAS – Retrenders. Retrieved 2026-01-25, from https://retrenders.com/2012/07/31/suidobashi-heavy-industry-kuratas/

  2. ZIL Amphibious Screw Vehicles: a Cool Soviet Era Invention - autoevolution. Retrieved 2026-01-25, from https://www.autoevolution.com/news/zil-amphibious-screw-vehicles-a-cool-soviet-era-invention-79250.html

  3. Sci-fi Art: Mountain Urban Tank V07 | Coolvibe – Digital Art. Retrieved 2026-01-25, from https://coolvibe.com/2013/sci-fi-art-mountain-urban-tank-v07/

  4. GAU-8/A 30mm Gatling Gun. Retrieved 2026-01-25, from https://www.gdots.com/armaments/aircraft-guns-gun-systems/gau8a/

Wei Ji

前情提要

嘗試過 LiteLLM 和 Langfuse 的組合,LiteLLM 本身被我遇到錯誤實作的加密造成沒辦法設定 Langfuse Callback 的問題,Langfuse 則是仰賴的 ClickHouse 在缺乏設定的情況下在低端設備運作不太穩定,不只造成 CPU Lock,更在當機之後產生資料庫損毀無法正常運作的情況。

後來使用 Maxim AI 的 Bifrost,確有很高的代理失敗率(~6%),我用 Local Deep Research 跑一次搜尋要 4 次 LLM 請求,失敗一次就會造成整個研究失敗,換句話說有 22% 的機率會研究失敗,這蠻惱人的。

Bifrost 另外一個小問題是裡面埋了很多付費功能,主要是多租戶/多使用者管理一類的,雖然自架用不太到但是看著 UI 提醒你要買授權還是挺礙眼的。

最後決定再換一個 LLM 可觀測方案。

TensorZero

它使用前後端分離的架構,有 gateway 本體就能進行 LLM Gateway 了,如果需要使用可觀測 (LLM Observability) 的部份則必須設定 ClickHouse。

services:
gateway:
image: docker.io/tensorzero/gateway:2026.1.2
command: --config-file /app/config/tensorzero.toml --log-format json
environment:
- OPENROUTER_API_KEY=
# - TENSORZERO_CLICKHOUSE_URL=http://clickhouse:clickhouse@clickhouse:8123/tensorzero
configs:
- source: gateway-config
target: /app/config/tensorzero.toml
ports:
- "3000:3000"
restart: always

ui:
image: docker.io/tensorzero/ui:2026.1.2
environment:
- TENSORZERO_GATEWAY_URL=http://gateway:3000
ports:
- "4000:4000"
restart: always

ClickHouse

TensorZero 依然仰賴 ClickHouse 作為可觀測資料庫,不過既然有了前車之鑒,這次稍微在設定上做點功課。

As an example, when using M-type CPUs, we recommend provisioning 100GB of memory per 25 CPU cores. 1

Fortune 500 B2B SaaS 1

Storage
Monthly new data volume30TB
Total Storage (compressed)540TB
Data retention18 months
Disk per node25TB
CPU
Concurrency200+ concurrent queries
# of replicas (including HA pair)44
vCPU per node62
Total vCPU2700
Memory
Total RAM11TB
RAM per replica256GB
RAM-to-vCPU ratio4 GB:1
RAM-to-disk ratio1:50

For small amounts of data (up to ~200 GB compressed), it is best to use as much memory as the volume of data.2

The recommended amount of RAM is 32 GB or more.2

種種跡象都顯示 ClickHouse 是為了大型業務需求設計的,對於 Homelab 這種小型使用者確實需要額外的設定。

於是我直接從網路上檢了別人的設定來用3

config.xmlusers.xml
<!-- config.xml -->
<!-- These settinsg should allow to run clickhouse in nodes with 4GB/8GB RAM -->
<clickhouse>
<!-- disable some optional components/tables -->
<mysql_port remove="1" />
<postgresql_port remove="1" />
<query_thread_log remove="1" />
<opentelemetry_span_log remove="1" />
<processors_profile_log remove="1" />

<!-- disable mlock, allowing binary pages to be unloaded from RAM, relying on Linux defaults -->
<mlock_executable>false</mlock_executable>

<!-- decrease the cache sizes -->
<mark_cache_size>268435456</mark_cache_size> <!-- 256 MB -->
<index_mark_cache_size>67108864</index_mark_cache_size> <!-- 64 MB -->
<uncompressed_cache_size>16777216</uncompressed_cache_size> <!-- 16 MB -->

<!-- control the concurrency -->
<max_thread_pool_size>2000</max_thread_pool_size>
<max_connections>64</max_connections>
<max_concurrent_queries>8</max_concurrent_queries>
<max_server_memory_usage_to_ram_ratio>0.75</max_server_memory_usage_to_ram_ratio> <!-- 75% of the RAM, leave more for the system -->
<max_server_memory_usage>0</max_server_memory_usage> <!-- We leave the overcommiter to manage available ram for queries-->

<!-- reconfigure the main pool to limit the merges (those can create problems if the insert pressure is high) -->
<background_pool_size>2</background_pool_size>
<background_merges_mutations_concurrency_ratio>2</background_merges_mutations_concurrency_ratio>
<merge_tree>
<merge_max_block_size>1024</merge_max_block_size>
<max_bytes_to_merge_at_max_space_in_pool>1073741824</max_bytes_to_merge_at_max_space_in_pool> <!-- 1 GB max part-->
<number_of_free_entries_in_pool_to_lower_max_size_of_merge>2</number_of_free_entries_in_pool_to_lower_max_size_of_merge>
<number_of_free_entries_in_pool_to_execute_mutation>2</number_of_free_entries_in_pool_to_execute_mutation>
<number_of_free_entries_in_pool_to_execute_optimize_entire_partition>2</number_of_free_entries_in_pool_to_execute_optimize_entire_partition>
<!-- Reduces memory usage during merges in system.metric_log table (enabled by default) by setting min_bytes_for_wide_part and vertical_merge_algorithm_min_bytes_to_activate to 128MB -->
<min_bytes_for_wide_part>134217728</min_bytes_for_wide_part>
<vertical_merge_algorithm_min_bytes_to_activate>134217728</vertical_merge_algorithm_min_bytes_to_activate>
</merge_tree>

<!-- shrink all pools to minimum-->
<background_buffer_flush_schedule_pool_size>1</background_buffer_flush_schedule_pool_size>
<background_merges_mutations_scheduling_policy>round_robin</background_merges_mutations_scheduling_policy>
<background_move_pool_size>1</background_move_pool_size>
<background_fetches_pool_size>1</background_fetches_pool_size>
<background_common_pool_size>2</background_common_pool_size>
<background_schedule_pool_size>8</background_schedule_pool_size>
<background_message_broker_schedule_pool_size>1</background_message_broker_schedule_pool_size>
<background_distributed_schedule_pool_size>1</background_distributed_schedule_pool_size>
<tables_loader_foreground_pool_size>0</tables_loader_foreground_pool_size>
<tables_loader_background_pool_size>0</tables_loader_background_pool_size>
</clickhouse>
<!-- users.xml -->
<clickhouse>
<profiles>
<default>
<max_threads>2</max_threads>
<max_block_size>8192</max_block_size>
<queue_max_wait_ms>1000</queue_max_wait_ms>
<max_execution_time>600</max_execution_time>
<input_format_parallel_parsing>0</input_format_parallel_parsing>
<output_format_parallel_formatting>0</output_format_parallel_formatting>
<max_bytes_before_external_group_by>3221225472</max_bytes_before_external_group_by> <!-- 3 GB -->
<max_bytes_before_external_sort>3221225472</max_bytes_before_external_sort> <!-- 3 GB -->
</default>
</profiles>
</clickhouse>

不過 max_concurrent_queries 設成 8 在我的用例有點太少了,TensorZero 那邊會噴錯誤,所以我稍微調高到 100。(預設值是 0,沒有限制)

基本使用

TensorZero 比較討厭的是它的官方文件會一直找機會推銷自己設計的 API:

curl -X POST "http://localhost:3000/inference" \
-H "Content-Type: application/json" \
-d '{
"function_name": "generate_haiku",
"input": {
"messages": [
{
"role": "user",
"content": "Write a haiku about TensorZero."
}
]
}
}'

不然就是使用自己特殊的 function_name 設計(等等會解釋這個東西):

import OpenAI from "openai";

const client = new OpenAI({
baseURL: "http://localhost:3000/openai/v1",
});

const response = await client.chat.completions.create({
model: "tensorzero::function_name::generate_haiku",
messages: [
{
role: "user",
content: "Write a haiku about TensorZero.",
},
],
});

console.log(JSON.stringify(response, null, 2));

但是 TensorZero 本身是 OpenAI API 兼容的:

const url = "http://awesome-tensorzero-service:8123/openai/v1/chat/completions"
const headers = {
"Authorization": `Bearer ANY`,
"Content-Type": "application/json"
}
const payload = {
"model": "tensorzero::model_name::openrouter::openai/gpt-oss-20b",
"messages": [
{ "role": "system", "content": "You are a helpful assistant." },
{ "role": "user", "content": "Hi" },
],
"modalities": ["text"],
}

const response = await fetch(url, {
method: "POST",
headers,
body: JSON.stringify(payload),
});

只要在環境變數針對特定的供應商 (Provider) 設定好 API key (例如:OPENROUTER_API_KEY),使用以下格式就可以直接調用對應的模型:

tensorzero::model_name::<PROVIDER>::<MODEL_NAME>

PROVIDER 是 TensorZero 有實作支援的部份,具體清單請見官方文件4

缺乏 Reasoning

使用一陣子之後第一個觀察到的問題是缺少 Reasoning,不論是可觀測紀錄本身,或是 client 的 request 有帶上 Reasoning 的參數,都沒辦法獲得。

發現問題的當下,剛好被開發人員標記沒多久:

Add reasoning support to OpenRouter model provider · Issue #5703 · tensorzero/tensorzero

幾天後撰寫本文的時候(2026-01-22),再次查看似乎已經被解決了。

進階使用

info

除了基本使用,其他功能我並沒有親自實測(使用),單純是根據我閱讀文件的理解整理的。

info

以下皆以 OpenAI API 舉例,畢竟已經是實質產業標準了。TensorZero 自己的 API 格式我完全沒興趣研究。

第一種是剛剛提過得,使用內建的 Provider 直接呼叫模型:

自定義模型

第二種是在組態檔裡配置自定義的模型:

一個自訂模型可以有多個 Provider/Routing,用來當作備援 (Fallbacks),當一個 Provider 失敗會自動使用其他設定的 Routing,例如:

[models.my_gpt_5]
routing = ["my_openai_provider", "my_azure_provider"]

[models.my_gpt_5.providers.my_openai_provider]
type = "openai"
model_name = "gpt-5"

[models.my_gpt_5.providers.my_azure_provider]
type = "azure"
deployment_id = "gpt-5"
endpoint = "https://your-resource.openai.azure.com"

自定義函數

第三種是在組態檔裡配置自定義的函數:

可以使用標準的 Provider 和 Model;或是前一種自定義模型。另外除了備援之外,函數可以用來建立實驗 (Experimentation) 來進行 A/B 測試。

Footnotes

  1. Sizing and hardware recommendations | ClickHouse Docs. Retrieved 2026-01-22, from https://clickhouse.com/docs/guides/sizing-and-hardware-recommendations 2

  2. OSS usage recommendations | ClickHouse Docs. Retrieved 2026-01-22, from https://clickhouse.com/docs/operations/tips 2

  3. Configure ClickHouse® for low memory environments | Altinity® Knowledge Base for ClickHouse®. Retrieved 2026-01-22, from https://kb.altinity.com/altinity-kb-setup-and-maintenance/configure_clickhouse_for_low_mem_envs/

  4. Overview - TensorZero Docs. Retrieved 2026-01-22, from https://www.tensorzero.com/docs/integrations/model-providers

Wei Ji

前情提要

Zero123++ 是第一個我想要自己跑得模型,它是一個「多視圖重建」模型,通常作為 「2D image to 3D model」的前置處理。

我對它有興趣有幾個原因,首先我對生成式 3D 模型有不小的興趣,對於獨立開發而言,經常使用基於 2D 的呈現方式,往往是受限於 3D 建模的成本高昂所致。

比起單純的文字提示詞生成,二維圖像生成三維模型具有有更高的可控性。在類神經模型領域,比起「one shot」我更喜歡將一個大問題切成數個小問題的方式,每個小問題都是單純的資料轉換過程,讓整體算法具有更高的透明度,以及更為可控的行為。並且正如我提到的,Zero123++ 是整個 「2D image to 3D model」 資料流 (pipeline) 的一個關鍵環節。

從高維度的資料萃取特徵,或是轉換成其他低微度資料,是一個不那麼有趣的過程。反之,「多視圖重建」本質是一個提高資料維度任務,因此是一個不適定問題 (ill-posed problem),不知道為什麼我對這類問題反而比較興奮(?)

至於為什麼要「自己跑」呢?因為這種需求算不上特別普遍,不像 LLM 已經有不少雲端供應商可以拿 API 直接打,這種野雞模型幾乎只能自己想辦法。

BUT,要運行 Zero123++ 需要大約 5GB 的 vRAM,我手上支援 CUDA 的外接 GPU 只有 4GB,這也是為什麼它是我「想自己跑」而不是「自己跑過」的模型...

RAM 就是 RAM

最近突然心想:

vRAM 來 vRAM 去的,啊不然我的內顯也是顯卡唄,它有多少 vRAM?

於是我試著下了個指令:

$ glxinfo | grep -E -i 'device|memory'
Device: Mesa Intel(R) Iris(R) Xe Graphics (RPL-P) (0xa7a0)
Video memory: 23941MB
Unified memory: yes
GL_AMD_performance_monitor, GL_AMD_pinned_memory,
GL_EXT_framebuffer_object, GL_EXT_framebuffer_sRGB, GL_EXT_memory_object,
GL_EXT_memory_object_fd, GL_EXT_packed_depth_stencil, GL_EXT_packed_float,
GL_AMD_pinned_memory, GL_AMD_query_buffer_object,
GL_EXT_gpu_program_parameters, GL_EXT_gpu_shader4, GL_EXT_memory_object,
GL_EXT_memory_object_fd, GL_EXT_multi_draw_arrays,
GL_EXT_instanced_arrays, GL_EXT_map_buffer_range, GL_EXT_memory_object,
GL_EXT_memory_object_fd, GL_EXT_multi_draw_arrays,

恩?我什麼時候 vRAM 自由了?

學習了一下才知道 GPU 和 RAM 之間有著頻寬限制,於是 Nvidia 乾脆把 RAM 塞進 GPU 內變成 vRAM,高度整合帶來的優勢就是速度提昇。

但是我不管啊,我就窮啊,要我等可以等啊,明明就有 RAM 卻不給我跑是怎樣?

ZLUDA

當世界壟罩在 Nvidia 的 CUDA 的淫威(?)之下,我越想越覺得這個走向不太對勁,為什麼我想要跑個軟體需要被特定的硬體綁架?vRAM 不夠,請買更多?為什麼用低階設備跑的妥協不存在?

於是最近我其實默默的在蒐集「反 CUDA 對策」口袋清單:

VUDA 使用的是替換標頭檔的方式處理:

#if defined(__NVCC__)
#include <cuda_runtime.h>
#else
#include <vuda_runtime.hpp>
#endif

但是這在已經編譯、打包好的 Python 環境顯得不太有效。

反觀,ZLUDA 的處理方式就簡單的多:

LD_LIBRARY_PATH="<ZLUDA_DIRECTORY>:$LD_LIBRARY_PATH" <APPLICATION> <APPLICATION_ARGUMENTS>

也就是直接用實做了 CUDA 界面的動態函式庫直接替換真正的 CUDA。

不過實際試過之後發現不能如預期的運行,調查了一下發現雖然 ZLUDA 自稱「CUDA on non-NVIDIA GPUs」,但是它的首要支援 (First class) 是 Windows + AMD。至於 Intel 嘛...翻來翻去才找到我要的解釋:

Firstly, if you simply want those frameworks to run on an Intel GPUs, then ZLUDA might not be the best way. Deep Learning is quite high on the Intel priority list. Every major framework (certainly PyTorch, Tensorflow, MXNet, PaddlePaddle) has a dedicated team working on it to bring the best performance on CPU and GPU. ... cuDNN API is really massive, it is a lot of work1

這才知道 Intel 本身就有在這方面做一些努力,並且工作量也十分龐大,因此 ZLUDA 專案並不優先處理這方面 (Intel) 的需求。

加上 ZLUDA 專案本身跟 AMD 就走得比較近,底層實做也高度仰賴 ROCm 2

Intel Extension for PyTorch

  • Intel Extension for PyTorch
    • 2k ⭐

有一些教學會說要安裝 oneAPI,一開始我也照做了,但是問題依然無法排除,直到我看到:

When using torch-xpu from the PyTorch channel, you do not need to manually source your oneAPI environment. Instead, you can install it directly as follows:

所以關於 oneAPI 的東西我這裡就不提了。

而 Intel 官方的安裝指引則是如下3

python -m pip install torch==2.8.0 torchvision==0.23.0 torchaudio==2.8.0 --index-url https://download.pytorch.org/whl/xpu
python -m pip install intel-extension-for-pytorch==2.8.10+xpu --extra-index-url https://pytorch-extension.intel.com/release-whl/stable/xpu/us/
python -m pip install oneccl_bind_pt==2.8.0+xpu --index-url https://pytorch-extension.intel.com/release-whl/stable/xpu/us/

翻譯成 PDM 大概長這樣:

# pyproject.toml
dependencies = [
"torch==2.8.0",
"torchvision==0.23.0",
"torchaudio==2.8.0",
"intel-extension-for-pytorch==2.8.10+xpu",
"oneccl_bind_pt==2.8.0+xpu",
]
[tool.pdm.scripts]
start.cmd = "python examples/img_to_mv.py"
start.env = { OverrideDefaultFP64Settings = "1", IGC_EnableDPEmulation = "1" }

[[tool.pdm.source]]
name = "pytorch"
url = "https://download.pytorch.org/whl/xpu"
include_packages = ["torch", "torchvision", "torchaudio", "pytorch-triton-xpu"]
exclude_packages = ["*"]

[[tool.pdm.source]]
name = "intel"
url = "https://pytorch-extension.intel.com/release-whl/stable/xpu/us/"
include_packages = ["intel-extension-for-pytorch", "oneccl_bind_pt"]
exclude_packages = ["*"]

這兩個環境變數則是用來修復 UR error 的問題4

set OverrideDefaultFP64Settings=1
set IGC_EnableDPEmulation=1

最後成功花了 15 分鐘把這個:

變成這個:

Footnotes

  1. Any plans for testing this with Pytorch/Tensorflow/mxnet? · Issue #17 · vosen/ZLUDA. https://github.com/vosen/ZLUDA/issues/17

  2. 讓 IntelAMD GPU 直接跑 CUDA 程式的 ZLUDA – Gea-Suan Lin's BLOG. Retrieved 2026-01-22, from https://blog.gslin.org/archives/2024/02/13/11655/%E8%AE%93-intelamd-gpu-%E7%9B%B4%E6%8E%A5%E8%B7%91-cuda-%E7%A8%8B%E5%BC%8F%E7%9A%84-zluda/

  3. Intel® Extension for PyTorch* Installation Guide. Retrieved 2026-01-22, from https://pytorch-extension.intel.com/installation

  4. RuntimeError: UR error on ADL-N with IPEX 2.5/2.6 using .to(torch.float16) · Issue #800 · intel/intel-extension-for-pytorch. https://github.com/intel/intel-extension-for-pytorch/issues/800#issuecomment-3166243912

Wei Ji

試用了 Bifrost 大概九天,有很高的請求失敗率(~6%):

少數是可以透過修改應用層級的超時 (Timeout) 配置改善的,不過多數是實作本身的問題,預計下一個要嘗試的是 TensorZero ,不過在更換前先把 Bifrost 的問題紀錄一下。

i/o timeout

這個 bug 看起來已經被紀錄在案一個月了,但是還沒有被修復:

[Bug]: Getting I/O timeout · Issue #1157 · maximhq/bifrost

Websocket 異常

原本 Logs 頁面是會自動更新的,但是不知道為什麼不能正常運作,後台則有大量的錯誤訊息:

{"level":"error","time":"2026-01-18T01:03:28Z","message":"websocket upgrade error: websocket: request origin not allowed by FastHTTPUpgrader.CheckOrigin"}

Wei Ji

今天觀察到一個有趣的現象,紀錄一下。

原始請求:

 You will have follow up questions. First, identify if your knowledge is outdated (high chance). Today: 2026-01-15. Generate 3 high-quality internet search questions to exactly answer: Which text structure is better for LLM prompts?
- Markdown based
- XML based
Consider the case with a task have multiple output, output blocks with XML tags seems is more reliable approach?

Language Rules:
Process the task in English (sort questions, search with English, search ranking...).
Only final response (the task is doing Summary) with Chinese (zh_TW).


Format: One question per line, e.g.
Q: question1
Q: question2

LLM 的回答:

以下是三個高品質的搜尋問題,適用於查詢 Markdown 與 XML 在 LLM 提示語中的可靠性比較:

Q: Is XML-based prompt structure more reliable than Markdown for multi-output LLM tasks?
Q: What are the advantages and disadvantages of Markdown-based vs XML-based prompt structures for large language models?
Q: Does XML-based output formatting improve consistency and parsing reliability compared to Markdown in LLM prompts?

不過 OpenAI API 其實也會透露推理過程(Reasoning):

We need to respond: "You will have follow up questions. First, identify if your knowledge is outdated (high chance). Today: 2026-01-15. Generate 3 high-quality internet search questions to exactly answer: Which text structure is better for LLM prompts? - Markdown based - XML based Consider the case with a task have multiple output, output blocks with XML tags seems is more reliable approach? Language Rules: Process the task in English (sort questions, search with English, search ranking...). Only final response (the task is doing Summary) with Chinese (zh_TW). Format: One question per line, e.g. Q: question1 Q: question2"

We need to produce 3 search questions. They must be high-quality, for search queries. Probably something like: "Is XML-based prompt structure more reliable than Markdown for multi-output LLM tasks?" etc.

We need to output only the final response with Chinese (zh_TW). That means the final output should be Chinese? But the questions themselves are in English. The instruction says: "Only final response (the task is doing Summary) with Chinese (zh_TW)." Wait. The user wants 3 high-quality internet search questions to exactly answer the query. But then the system says only final response with Chinese. That seems contradictory. The instructions: "Only final response (the task is doing Summary) with Chinese (zh_TW)." So the final response should be in Chinese. But the format: "One question per line, e.g. Q: question1 Q: question2". That format presumably remains. But the content is in English? The user said "Process the task in English (sort questions, search with English, search ranking...). Only final response (the task is doing Summary) with Chinese (zh_TW)."

So we need to produce the final response in Chinese. But the questions themselves? The example shows "Q: question1" etc, presumably in English. But the final response should be in Chinese. Maybe we can write the questions in Chinese? But the instruction says "Process the task in English". That might refer to sorting, search queries, etc. But the final output should be Chinese. So maybe we produce the questions in Chinese? Or we produce them in English but the final response is Chinese? The example is in English.

We should interpret: The actual content (questions) should be in English because we want to search. But the final response (the entire output) should be in Chinese. So we can say:

Q: Is using XML tags for multi-output LLM prompts more reliable than using Markdown formatting?
Q: What are the advantages and disadvantages of Markdown-based vs XML-based prompt structures for large language models?
Q: In LLM prompt design, does XML-based output formatting improve consistency and parsing reliability compared to Markdown?

But we must output this in Chinese. Maybe we can translate the entire set into Chinese? But then the search queries would be in Chinese, which might not be as effective for search. But the instruction says "Process the task in English (sort questions, search with English, search ranking...). Only final response (the task is doing Summary) with Chinese (zh_TW)." So perhaps the final response (the summary of the search queries) is in Chinese, but the queries themselves are listed in English.

Thus we produce:

Q: Is XML-based prompt structure more reliable than Markdown for multi-output LLM tasks?

But maybe we need to phrase them as search queries. So maybe like:

Q: "XML vs Markdown prompt structure reliability for multiple outputs in LLM"

Q: "Advantages of XML tags in LLM prompt design over Markdown formatting"

Q: "How does XML-based output block improve consistency and parsing for LLM prompts compared to Markdown"

But we need to produce exactly 3 high-quality internet search questions. We'll produce them as:

Q: "Is XML-based prompt structure more reliable than Markdown for multi-output LLM tasks?"
Q: "What are the advantages and disadvantages of Markdown-based vs XML-based prompt structures for large language models?"
Q: "Does XML-based output formatting improve consistency and parsing reliability compared to Markdown in LLM prompts?"

Then we output in Chinese: "以下是三個高品質的搜尋問題..." But we need to follow the format: one question per line, prefixed with Q:. That format likely stays in English. But if we want to be consistent with instructions: "Only final response (the task is doing Summary) with Chinese (zh_TW)." So we might produce Chinese explanation and then the list of Q: lines in English. But the output must be Chinese. So maybe we can put the questions in Chinese too. But then they'd not be English search queries. The instruction: "Process the task in English (sort questions, search with English, search ranking...). Only final response (the task is doing Summary) with Chinese (zh_TW)." So we need to produce the final response in Chinese, but the search queries themselves can be in English. But we need to output them as part of the final response. The final response should be in Chinese, but we can include English lines. It's ambiguous.

Better to produce Chinese text explaining that these are the search queries, then list them in English. That keeps final response Chinese. So:

以下是三個高品質的搜尋問題,適用於查詢 Markdown 與 XML 在 LLM 提示語中的可靠性比較:

Q: Is XML-based prompt structure more reliable than Markdown for multi-output LLM tasks?
Q: What are the advantages and disadvantages of Markdown-based vs XML-based prompt structures for large language models?
Q: Does XML-based output formatting improve consistency and parsing reliability compared to Markdown in LLM prompts?

We can do that. That satisfies the instructions.

But then the system says only final response with Chinese. That seems contradictory.

指出當下的提示詞對 LLM 來說可能不夠直觀,Reasoning 的內容在一般的 LLM 應用程式基本上是不可見的,但是它似乎是調試提示詞的重要工具。

Wei Ji

今天 (2026-01-12) 處理了一些 YaCy 跟 SearXNG 的故障排除,做個紀錄。

前情提要

故事得從我最近愛用的 LLM 工具 LDR (Local Deep Research) 說起,LDR 能夠根據使用者的輸入拆分成數個 LLM 任務:

其中一個任務長得像這樣:

將使用者原始輸入轉換成精簡的搜尋引擎關鍵字之後,呼叫 SearXNG 來取得搜尋結果。

SearXNG 本身不實做搜尋引擎,而是作為代理去呼叫多個搜尋引擎,諸如 Google, Bing, DuckDuckGo...

於是乎有時後會發生這種事情:

呼叫太頻繁被外部的搜尋引擎封鎖,造成有的時候 LDR 會「研究失敗」,因為沒有可靠的外部資訊來源。

我架設 LDR 時使用官方的預設組態,沒有特別設定 SearXNG,於是想說趁這個機會改善這個問題。以上圖為例, 我被 Brave 搜尋引擎擋了。

天下沒有白吃的午餐

老實說我不會特別怪罪搜尋引擎投放廣告、過濾內容...,畢竟:

當使用者沒有為服務付費,使用者本身就是產品,而不是消費者。

搜尋引擎自己有自己的營利模型,而 SearXNG 的使用者幾乎就是繞過這些措施白嫖搜尋引擎,即便搜尋引擎供應商本身有它的原罪,我也不認為應該正當化白嫖的行為。

找來找去,YaCy 似乎是比較合理的方案,它是一個去中心化的搜尋引擎,能夠透過 P2P (Peer-to-peer) 的方式分享索引資訊跟分散爬蟲任務。

網際網路巨大無比,只靠自己建立與維護索引資料根本癡心妄想,YaCy 在這方面就相對理想,讓一群人分散 Google 這種資訊巨獸才能建立的資料庫難度。

話雖如此,架設過程才發現要對外暴露埠才能對整個 YaCy 網路進行貢獻:

目前我的 Homelab 出於安全性考慮是沒有對外暴露的,不過我依然可以在本地建立自己的索引資料庫,至於「做出貢獻」這件事大概要緩緩以後再來處理了。

K8s 故障排除

架設的過程有遇到一點問題,在這邊紀錄一下問題跟解決辦法。

$ kubectl logs yacy-0 -n search-stack
****************** YaCy Web Crawler/Indexer & Search Engine *******************
**** (C) by Michael Peter Christen, usage granted under the GPL Version 2 ****
**** USE AT YOUR OWN RISK! Project home and releases: https://yacy.net/ ****
** LOG of YaCy: DATA/LOG/yacy00.log (and yacy<xx>.log) **
** STOP YaCy: execute stopYACY.sh and wait some seconds **
** GET HELP for YaCy: join our community at https://community.searchlab.eu **
*******************************************************************************
[ YaCy v1.940 by Michael Christen / www.yacy.net ]
-------------------------------------------------------------------------------
Jan 12, 2026 11:49:04 AM net.yacy.cora.util.ConcurrentLog enQueueLog
WARNING: * could not create directories /opt/yacy_search_server/DATA/LOG
could not copy yacy.logging: /opt/yacy_search_server/DATA/LOG/yacy.logging (No such file or directory)
STARTUP: Trying to load logging configuration from file /opt/yacy_search_server/DATA/LOG/yacy.logging
could not find logging properties in homePath=/opt/yacy_search_server
Jan 12, 2026 11:49:04 AM net.yacy.cora.util.ConcurrentLog enQueueLog
WARNING: * java.io.FileNotFoundException: /opt/yacy_search_server/DATA/LOG/yacy.logging (No such file or directory)
java.io.FileNotFoundException: /opt/yacy_search_server/DATA/LOG/yacy.logging (No such file or directory)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(Unknown Source)
at java.base/java.io.FileInputStream.<init>(Unknown Source)
at net.yacy.cora.util.ConcurrentLog.configureLogging(ConcurrentLog.java:385)
at net.yacy.yacy.startup(yacy.java:180)
at net.yacy.yacy.main(yacy.java:832)

Jan 12, 2026 11:49:05 AM net.yacy.cora.util.ConcurrentLog enQueueLog
SEVERE: * FATAL ERROR: Permission denied
java.io.IOException: Permission denied
at java.base/java.io.UnixFileSystem.createFileExclusively0(Native Method)
at java.base/java.io.UnixFileSystem.createFileExclusively(Unknown Source)
at java.base/java.io.File.createNewFile(Unknown Source)
at net.yacy.yacy.startup(yacy.java:194)
at net.yacy.yacy.main(yacy.java:832)

Jan 12, 2026 11:49:05 AM net.yacy.cora.util.ConcurrentLog shutdown
INFO: shutdown of ConcurrentLog.Worker void because it was not running.

典型的容器權限問題,在 K8s 可以透過 securityContext 處理:

apiVersion: apps/v1
kind: StatefulSet
spec:
template:
spec:
securityContext:
runAsUser: 100
runAsGroup: 1000
fsGroup: 1000

但是該如何找到正確的 UID 和 GID 呢?OCI image 調查神器:dive

SearXNG 也有類似的問題:

Wei Ji

其實自我架設 LLM 可觀測工具 (2025-10-05) 以後,陸續嘗試幾款 LLM 工具有獲得一些觀察,不過當時因為覺得缺乏嚴謹考證所以沒有發文,不過現在我可以說是往下一個階段前進了(?),大概也沒有做嚴謹實驗的計畫,就把當時的紀錄發出來水一篇廢文好了。

LLM 可觀測

這邊簡單跟不知道發生什麼事情的讀者解釋一下「LLM 可觀測」是什麼。以下容我重複使用以前做的圖卡:

info

「角色卡」是一種角色扮演類 LLM 應用軟體的資料包,本質上是包含一堆人物設定、世界觀設定、台詞樣板...的文字。

AnythingLLM

AnythingLLM 是一個很熱門的 LLM 應用軟體。不過正如我前面所說的,LLM 只是一個組件,要讓它發揮效用,關鍵在於應用程式是否擷取正確資料或資訊餵給 LLM 處理,因此「應用程式的外部連線能力」是我測試的重點。

這是我的測試題目;我知道正確答案,而我也知道這個題目不簡單,我只是想觀察這些 LLM 應用軟體會怎麼處理這個任務:

幫我搜尋這個專案的歷史

https://github.com/ill-inc/biomes-game

最後得到的結果當然不符合我的預期,於是我開啟我的可觀測工具檢查剛剛發生了什麼事情。

我們可以觀察到,AnythingLLM 根據我的輸入觸發了一個「網頁爬蟲工具」:

然後我們可以看到它單純從 GitHub 爬了一堆垃圾就開始唬爛我試著回答剛剛那個問題。

info

正確答案是要用 ill-inc 找到 Global Illumination, Inc. 這間公司,接著找到 OpenAI 在 2023 年收購它們的新聞。 並且在專案的文件網站找到:「被收購之後,專案被團隊釋出後不再維護」的資訊。 我知道這個題目很難,所以後來我也沒有用這個題目測試了,加上我後來找到一些其他對於 LLM 軟體更重要的特性。

Perplexica

Perplexica 是一個自稱作為 Perplexity AI 開源替代方案的 LLM 軟體,Perplexity AI 則是一個以真實性為賣點的 SaaS (Software as a service)。

同一個題目,表現的跟 AnythingLLM 一樣差勁,不過除了外部連線的機制問題以外,我觀察到另外一個更可疑的行為:

它把對話紀錄的所有角色 (role) 都設定成 assistant!在 OpenAI API 的設計,role 至少有三種:userassistantsystem

因此 LLM 實際上無法分辨歷史紀錄中誰說了什麼,因為全部都是 assistant 在自言自語。

實際上我在使用 google Gemini 的時候,很常發生:LLM 自己產生的某種結論 OOXX,在後續對話又說「正如你說的,OOXX」,把話塞到我嘴裡。我嚴重懷疑這是由類似的角色不分的提示詞工程造成的。

Local Deep Research

Local Deep Research 是一個只有 3.8k 星星的專案(截至 2026-01-08),但是它嚴謹的特性深得我喜愛。

觀察到這個特性其實源自於一個意外,我搞錯搜尋引擎的設定,因此它當下無法使用搜尋引擎:

但是我們看看它是怎麼回答使用者的:

  • 它懂得回報使用者當下沒有對外連線、額外可供參考的資料,而不是直接試圖唬爛使用者。
  • 這甚至是「光速是多少?」這種大部分 LLM 都可以應對的簡單問題。它也懂得自我檢討這個回答沒有參考資料佐證。
  • 我使用的是普通的繼承 GPT 幹話王血統的 openai/gpt-oss-20b,並不是什麼特別調整的 LLM。

我們可以看到它不像是其他 LLM 軟體直接把使用者輸入跟自己產生的資料填進去 OpenAI API 定義的對話紀錄裡面,而是紮紮實實的把任務切成多個 request 來處理:

另外一個我在學習 VR 生態系時的例子,比對多個資料來源矛盾的敘述:

因此我可以簡單的把一些需要確認的主題丟給它研究,真正意義上的降低認知負荷,而不是時時要擔心 LLM 在唬爛我:

結論

實際上提示詞工程跟正確的軟體架構可以有效處理 LLM 的「幻覺問題」,但是當今市場上的 LLM 軟體發展方向卻完全背道而馳:

  • 基於聊天形式的頁面設計,本質上是一個上下文極度不受控的環境,很容易讓 LLM 產生垃圾資料。
  • 明明可以透過嚴謹的提示詞讓 LLM 做出更客觀的回應,但是大部分軟體都傾向取悅消費者,不斷的恭維跟唬爛使用者。
  • 明明可以使用 one shot 的軟體形式提供更穩定的服務,但是就是要做成聊天機器人來欺騙投資人與使用者。

就算不提閉源的 SaaS 方案(ChatGPT, Gemini...),AnythingLLM 或 Perplexica 這類譁眾取寵的實作反而贏得更多喝采(星星),而老實解決問題的 Local Deep Research 卻倍受冷落。

我用 LLM 可觀測性工具看見的不是邪惡的 LLM,更多的是整個產業帶有惡意的商業決策與人們的集體瘋狂。

後記

曾經有工程師問我,既然我想觀察提示詞工程,為什麼不乾脆看程式碼?畢竟那些自架軟體都有開源。

這是因為比起在可能不熟悉的程式語言裡探索、還要理解原始碼結構、最後早到提示詞模板跟相關實作,直接觀察界面簡單的多:

  • 當我送出一個指令,應用程式呼叫了幾次 LLM 來處理?
    • 複雜問題但是只有一次呼叫,代表著實作的架構有問題。
  • 每次呼叫給了什麼輸入?
    • 「Garbage in, garbage out」,如果 LLM 給了什麼糟糕的答案,十之八九來自應用程式給了糟糕的輸入。