每一題都是現場真的會被問的。答得出來,代表你真的懂這套系統;答不出來的那幾題, 就是上台前要補的洞。「看畫面的人問的」與「看架構的人問的」混在一起排, 因為現場本來就是混著問的。
這十句是骨架。現場八成的問題都可以從其中一句展開,答不下去時也可以退回這十句。
| # | 一句話 | 為什麼這句重要 |
|---|---|---|
| 1 | AI-Brain 是一條固定的八步主幹:提問 → 前處理 → 判意圖 → 選流程 → 取證 → 證據池 → 統整 → 呈現。只有「取證」和「呈現」隨題目變,其餘六步每題都一樣。 | 被問「這到底是什麼」時的標準答案。也說明了它不是一個自由發揮的 Agent。 |
| 2 | 流程由人設計,寫在 YAML;模型不決定流程。 | 這是整套設計的核心主張,也是跟 ChatGPT 最大的差別。 |
| 3 | 先取證再回答。統整的模型只看得到取回來的證據,證據裡沒有就說沒有。 | 回答「會不會亂編」的唯一正確路徑。 |
| 4 | 數字歸程式,語意歸模型。門檻、比例、筆數由 SQL 與程式判;只有「意圖是什麼」「這段話怎麼寫」問模型。 | 被問「AI 會不會算錯」時用。它把責任切得很乾淨。 |
| 5 | AI-Brain 自己一顆資料庫都不連。結構化資料一律問 AI team 的 text-to-SQL,查哪張表、產什麼 SQL、在哪執行都是他們的事。 | 很多人會假設我們自己存了一份 SAP 資料。沒有。 |
| 6 | 每一句答案都能點回原文。答案裡的 [1] 是證據編號,點下去看得到那一片原文。 |
「可稽核」的具體證明,不是形容詞。 |
| 7 | 查不到就說查不到,而且不呼叫模型。0 筆是短路條件,直接回「查無資料」。 | 反直覺但最有說服力的一條:系統的可信度來自它願意說「沒有」。 |
| 8 | 全部在內網。語言模型是自架的 Qwen(192.168.170.31),OCR 在 .28,資料庫在 .31。沒有任何一段送到外部雲端。 |
日本客戶一定會問,而且這是加分題。 |
| 9 | 建單一定要人確認,沒有全自動。兩次確認(要不要建、欄位對不對)是刻意的。 | ERP 客戶最怕的就是 AI 自己去改資料。 |
| 10 | 準確率照實講:text-to-SQL 自評 76%。所以畫面上一定看得到實際的 SQL 與系統理解的條件。 | 主動講比被拆穿好。而且它接著就帶出「所以我們把依據攤開」這個設計。 |
不懂技術的人會指著畫面問。這一節照著畫面由上到下排,每一個會被指到的東西都有答案。
回答上面那一行小字(「已完成分析 · 1.6 秒」)點開,會看到這一輪經過的步驟、每一步的結果、耗時, 最底下還有一組請求編號。典型的一題長這樣:
理解問題 期間 2022 年 11 月(2022-11) preprocess 完成 判斷意圖 分析 · LLM 124 ms intent 完成 查資料 1 筆,38 ms sql 完成 統整回答 108 字 synthesize 完成
(這是問「2022 年 11 月有多少張訂單」時的實際畫面。)
左邊是這一步的中文名稱,中間是這一步實際得到什麼,右邊灰色小字是這一步的程式代號
(preprocess/intent/sql/synthesize),
最右邊是狀態。
順序是固定的,而且有理由:一定是先「理解問題」再「判斷意圖」。 因為前處理要把「第二名呢」這種追問補成完整問句,而意圖分類器只看得到那一句話—— 沒補的話它只收到三個字。詳見 §02。
為什麼要給使用者看?因為這套系統的賣點是「講得出依據」。 如果只回一段話,使用者沒辦法判斷該不該相信;把經過攤開,他至少知道系統查了什麼、用什麼條件查。
總共只有四類:
分成四類的目的是決定接下來走哪一條流程。問候語不需要去翻資料庫; 問「你用什麼模型」不需要去查知識庫(以前沒分這類的時候,這一題會跑去翻公司的 AI 使用政策文件)。
這一步同時還做了第二件事:順便選好要走哪一條流程。所以它不是「先分類、再另外選流程」兩次呼叫,是一次解決。
LLM 是指這一步用語言模型判的。旁邊還可能出現另外兩種字樣:
一百多毫秒大約就是一次網路來回的時間(實測穩定約 130 ms)。 冷啟動第一次會比較久,實測約 250 ms。
分析 → data_query · LLM 124 ms| 那一段 | 回答什麼 | 可能出現什麼 |
|---|---|---|
分析 | 分到哪一類 | 分析/系統說明/一般對話/業務操作,只有這四種 |
→ data_query | 接下來走哪一條流程 | 流程代號。模型沒直接回代號時這一段不會出現 |
· LLM | 怎麼判出來的 | LLM=問模型;規則=模型連不上時的備援;附件=看請求裡有附件,根本沒問模型 |
124 ms | 問模型那一趟花多久 | 用附件判的是 0 ms,所以不印 |
「理解問題」那一行的規則不一樣:抽到什麼就列什麼 (期間、客戶、商品、單號、「補上前文 →⋯」),一個都沒抽到就寫「無特別條件」。 所以那一行其實是在告訴你「我從你這句話裡聽懂了哪些條件」。
label
(而且是寫成中/英/日三語的),以及後端程式裡的固定字串(理解問題、判斷意圖)。這件事的意義:畫面上看到的順序,就是後端真的執行的順序。 要改順序只能改後端,改不動前端。這也是為什麼「進度列是真的不是動畫」這句話站得住。
為什麼要有上限:一個沒寫好的查詢可能回幾十萬列,那會把畫面、記憶體、 以及送進統整的內容全部撐爆。
為什麼要明說:如果默默只給前 200 列,使用者會以為那就是全部, 然後根據不完整的資料做判斷。寧可讓畫面難看一點,也不要讓人算錯。
這跟「查無資料」是同一個原則的兩面——系統寧可少答、寧可承認限制,也不要講一個 看起來完整但其實不完整的答案。
這一步把人話換成機器用得上的條件,做四件事:
0000104705(SAP 的單號是固定十碼)。那一行顯示的就是它從你這句話裡聽懂的東西,三種可能:
分界線是:猜錯了還救得回來的,就猜並講出來;猜錯會給出一個看起來合理但錯誤的答案的,就停下來問。
後者在畫面上的樣子是:不給表格,直接回「請告訴我訂單號碼」。
這一步的完整經過是:把問句原文送給 AI team 的 text-to-SQL 服務 → 他們的模型當場產出一句 SQL → 在他們那邊執行 → 把「實際跑的那句 SQL」和「查到的資料列」一起回來。
同一行還可能多出兩段括號,都是值得多看一眼的訊號:
資料在一顆叫 middledb 的 PostgreSQL(192.168.170.31),
裡面是 SAP 銷售模組 12 張表的鏡像子集——不是 SAP 本尊。
為什麼不直接連 SAP,見 §03。
這一步是唯一一個「讓模型寫字」的步驟。有引用到證據的題目,這一格還會多一個數字:
321 字,引用 3 片——引用了幾片證據。
那個「引用幾片」才是真正有用的訊號:如果一段答案寫了 300 字卻一片證據都沒引用,
那就是模型在自由發揮,是要抓出來的狀況。實體的監控系統就有一條固定查詢在找這種案例
(引用數 = 0 而且有回答)。
實體:每一則進度都是後端真的執行到那一步才送出來的,內容是那一步的實際結果 (命中幾片、判斷結果是什麼)。技術上是用 SSE 一則一則推給前端的。
雛形:沒有後端,進度列的毫秒數是腳本裡寫死的示範值。 被直接問「這是真的嗎」,照實講——而且緊接著補一句:但知識庫的檢索是真的在瀏覽器裡算的 (見下面 tf-idf 那題),可以當場換一個沒準備過的問題試。
ChatGPT 那類產品的目標是讓你忘記背後有流程;這套的目標相反—— 使用者必須看得到它查了什麼、用什麼條件查、依據是哪幾片文件, 因為它回答的是會被拿去做決策的公司數字。
更實際的理由:text-to-SQL 的正確率是 76%,不是 100%。 所以判斷「這個答案對不對」的責任,一部分必須留在看的人身上。 系統能做的是把依據完整攤開,讓他有東西可以判斷——這就是為什麼 SQL 也給看。
abbe4831a86d1de0d57c40ba9667fb2f——這個請求編號是做什麼的?使用場景很具體:使用者說「剛剛那題答錯了」。沒有這串編號,你要從幾千筆紀錄裡猜他問的是哪一題; 有這串,貼進查詢介面就直接跳到那一次的完整經過——他問了什麼、系統理解成什麼條件、 產出哪句 SQL、撈到哪幾片文件、每片分數多少、最後答了什麼。
所以畫面上它旁邊有一顆複製鈕:設計上就是要使用者截圖或複製回報用的。
流水號需要有人負責發號,而且只能有一個人發,否則兩台機器會發出同一個號。 這套系統橫跨後端、模型服務、text-to-SQL 服務,未來還會更多。 改成 128 位元的隨機值,每台機器各自產生也幾乎不可能撞號——128 位元寫成十六進位就是 32 個字。
而且它的用途不是給人記的,是給人複製的,所以長度不重要。
雛形沒有後端,所以沒有真正的追蹤系統;它只是產一個「格式一模一樣」的 32 位十六進位字串, 讓畫面跟實體長得一樣。
實體那邊是 OpenTelemetry 這套追蹤工具在請求進來時產生的, 而且會寫進後端每一行日誌,所以日誌跟畫面上那串對得起來。
被問到時的答法:「雛形這一格是演給你看格式;實體那邊它是真的可以拿去查的。」 不要說「這是真的」——現場如果有人要你當場拿它去查,就下不了台。
知識庫裡有十幾份文件,被切成幾十到上百個小段落(每段約 450 字)。 使用者問一句話,系統要從這些段落裡挑出最相關的五段,交給後面統整成答案。 「怎麼挑」就是 tf-idf 在做的事。
tf-idf 的邏輯用一句話講:一個詞如果在這一段裡出現,而且它在其他段落裡很少出現, 那這個詞就很能代表這一段。
所以問「達交率怎麼定義」,講達交率定義的那幾段自然浮上來,而講資訊安全的那幾段沉下去。
這一點值得講清楚,因為它正好示範了這套系統的態度: 能用確定的方法做的事,就不要用模型做。
tf-idf 的好處是:零依賴(不用另外架一個服務)、速度極快、結果完全可重現、 而且可以解釋為什麼這一段被選中——因為它命中了哪幾個詞、每個詞值多少分,全都算得出來。
代價是它只看字面:問「交貨慢了怎麼辦」,文件裡寫的是「延遲交付的處理程序」, 字不一樣就對不上。這正是後面要換成向量檢索的理由。
| 版本 | 誰在算 | 在哪 |
|---|---|---|
| 實體程式(現在) | AI-Brain 的 Java 後端自己算 | rag/LocalRagClient.java,約 140 行,零外部相依 |
| 雛形 | 瀏覽器裡算 | app.js,同一套演算法搬到前端(所以雛形不連任何服務也能真的檢索) |
| 正式版 | AI team 的檢索服務 | 改一個設定值 aibrain.rag.provider=http,其他一行不動 |
它在程式裡的名字就叫「本地假 RAG」,而且註解直接寫著 「等 AI team 的 API 上線就刪」——不是偷偷留一套自己的實作,是明確標示的過渡。
為什麼要先做一個頂著?因為這樣整條路可以先跑通:意圖、流程、證據池、統整、引用、 監控全部都能驗,不用等別人。而且因為回傳的形狀跟對方 API 的規格一模一樣, 換過去的時候畫面與流程完全不用動。
三個常見的誤會,一個一個拆:
| 以為它是 | 其實 |
|---|---|
| 沒有 AI 時的備案 | 不是。這套系統確實有真正的備案——意圖分類的規則版, 模型連不上時退回,畫面上會顯示「規則(LLM 不可用)」。 tf-idf 不是那種:它旁邊沒有一個「主要方案」在等著, 它就是目前唯一的檢索實作 |
| 分擔模型的負載 | 不是。它確實有個副作用會少叫一次模型——全部低於門檻就短路、不呼叫統整—— 但那是「不知道就別答」的結果,不是目的 |
| AI 的簡化版 | 不是。檢索那一格本來就不該是 LLM。見下面 |
幾百片文件 ──[ 檢索 ]──> top 5 ──[ LLM ]──> 一段有引用的話
↑ ↑
tf-idf 站這格 統整站這格
(之後換成向量檢索) (永遠是 LLM)
所以接上 AI team 的正式服務時,被換掉的是「用什麼方法比對相關度」 (字面比對 → 語意比對),不是「這一格改成給 LLM 做」。 那一格未來也不是 LLM,是嵌入模型加向量比對——那是另一種模型,不是語言模型。
順帶一提它為什麼現在夠用:知識庫目前是幾百片的規模,線性掃就夠快; 而且 tf-idf 的結果可以解釋(命中哪幾個詞、各值多少分), 向量檢索反而說不清楚為什麼這片排前面。等文件量上千,才輪到向量檢索的優勢。
「達交率定義」會被拆成 達交、交率、率定、定義。
英數字則整個詞取(vbak、s4hana)。
為什麼不用正規的中文斷詞工具?因為那要多一個函式庫、多一份詞典, 而且專有名詞(料號、客戶代號)反而容易被斷錯。 bigram 的做法雖然土,但不需要詞典也不會漏,對這個規模剛剛好。
實體程式與雛形用的是同一套演算法(雛形搬到瀏覽器裡跑),所以兩邊的檢索行為一致。
設計上這兩者是可以直接替換的:介面(送什麼、回什麼)已經定好寫成規格交給 AI team,
本地這版回傳的形狀跟他們的 API 一模一樣。
換過去只要改一個設定值 aibrain.rag.provider=http,其他一行都不用動。
這正是「先把流程跑通、能力之後再換」這個設計的實例: 沒有向量模型也能演完整條路,而且之後換上去,畫面與流程完全不變。
這件事很重要,因為它很容易被誤讀成「系統只有 52% 的把握」。不是。 這個分數是排序用的,不是信心度。
系統真正用它做的判斷只有一個:如果最高分的那一片都低於門檻,就當作查無資料, 而且不呼叫模型寫答案。雛形的門檻是 0.1,另外還要求至少命中 2 個不同的詞 (實測發現完全不相關的問題偶爾也能拿到 0.11,但它只命中 1 個詞,真命中都是 2~6 個)。
之後接上正式的檢索服務,如果對方有「重排」(reranker)這一層,分數的性質會變成校正過的、 可以用絕對門檻;現在沒有,所以只能用相對的。
先講數字本身,兩邊還不一樣:
| 門檻 | 另外要求 | |
|---|---|---|
| 實體程式 | 0.12 | 無,只看分數 |
| 雛形 | 0.1 | 而且至少要命中 2 個不同的詞 |
為什麼要加「至少命中 2 個詞」——這是實測記在程式註解裡的,可以直接當答案講:
所以誠實的答法是:「這個數字沒有理論依據,是拿實際問題試出來的。 而且試的過程發現單看分數不可靠,所以加了一個更穩的條件。」
這件事本身也回答了「為什麼分數不能當信心度」: 0.11 的雜訊跟 0.14 的真命中差不多高,這個分數根本沒有絕對意義, 它只能在同一次檢索裡排序。
[1] [2] 方框是什麼?滑過去會浮出預覽卡(標題+那一段原文+章節/頁碼/來源),點下去會打開右側面板並高亮那一片。
這是整套系統「可稽核」最具體的證明:使用者不需要相信模型,他可以自己去看原文。
編號是程式給的不是模型給的——證據被撈回來時就依序編號了,模型只能引用存在的編號, 超出範圍的編號會被程式過濾掉。所以不會出現「引用了一個不存在的來源」這種事。
已完成分析 · 1.6 秒 ← 點這一行展開 ┌──────────────────────────────────────┐ │ 查結構化資料 · data_query │ ← 走哪條流程 │ 期間 2018-10-29 ~ 2024-11-15(未指定, │ ┐ │ 預設全期間) 幣別 USD 基準日 … │ ┘ ← 「我理解的條件」在這排 ├──────────────────────────────────────┤ │ 理解問題 期間 … 完成 │ ┐ │ 判斷意圖 分析 · LLM 124 ms 完成 │ │ ← 步驟清單 │ 查資料 1 筆,38 ms 完成 │ ┘ │ … │ │ 請求編號 abbe4831… ⧉ │ ← 最底下 └──────────────────────────────────────┘
所以它跟「走哪條流程」並排,在步驟清單前面——順序是有意義的: 先告訴你「我理解成什麼、要走哪條路」,再讓你看「我實際做了哪幾步」。
第二個來源特別值得講:因為它沒有經過語言模型,所以即使這一題查無資料,它也可以安全地顯示出來。 使用者就看得到「系統是照什麼條件去查的、才查不到」,而不是只拿到一句冷冰冰的「查無資料」。
為什麼這一排是整個畫面最該先看的東西:實務上答錯最多的不是 SQL 寫錯, 而是條件理解錯——問的是 8 月、它算成全期間;問的是甲客戶、它對到乙客戶。 這一排就是拿來當場抓這種錯的。
不是出錯——它最後成功了。但這是一個值得多看一眼的訊號:模型卡過, 代表這一題對它來說不直觀,產出的 SQL 比較可能語意上偏掉。
所以前端的處理是:一旦重修超過一輪,就把 SQL 區塊預設展開, 讓使用者第一眼就看到它到底查了什麼。這是對方(AI team)建議的做法。
兩個理由:
SELECT/WITH 開頭。
但我方那一層已經不存在了——拔掉本機資料層時整支刪掉,
現在拿到 SQL 只拿來顯示,一個字都不檢查。
順帶一提:正因為我方沒有連線,模型產的 SQL 再糟也碰不到任何東西—— 這是拔掉資料層的附帶好處,可以接著講。
每一張卡可以展開完整片段,也可以開啟整份來源文件(會標出這次引用的是哪一段)。
「撈到但沒用上」這一組看起來多餘,其實是檢索品質的指標: 如果長期撈五片只用一片,代表檢索抓得不夠準。 實體程式的監控紀錄就有逐片記「這片有沒有被引用」這個欄位,用途正是這個。
整段等完再一次顯示,使用者要盯著轉圈好幾秒;逐字送出則是一有內容就看得到。
技術上用的是 SSE(見 §08),一條單向的通道,後端有東西就推一則過來。 進度列能一步一步亮起來也是同一條通道。
順帶一提,逐字生成時 Markdown 會有一瞬間的中間狀態(粗體還沒收尾之類), 那不是壞掉,實體程式也一樣。
這一點在實體程式是刻意做的:前端斷線(按停止、關分頁)會通知後端, 調度層每跑一步、串流每送一個字都會檢查這個旗標。 理由很實際——不要讓模型和資料庫替一個沒人在看的請求繼續跑,那張 GPU 是共用的。
被放棄的請求在紀錄裡會標成 cancelled,所以事後統計得出「有多少題使用者等不下去」。
「重新產生」只出現在最後一則回答。它會原樣重送當初那次請求—— 這在建單流程特別重要:在草稿卡那一步按下去,是「再辨識一次」,不會變成一句普通閒聊。
實體程式開機會自動載入 15 份示範文件(出貨與交期 SOP、達交率定義、分批交貨規範、 開票與請款規範、客戶分級與信用政策、資安規範、AI 使用政策……),切成片放進檢索索引。
目的是讓 demo 一開機就有東西可以查,不必先上傳。真文件到了,從這一頁上傳取代即可。
可以現場演的一段:在這一頁匯入一份 .md,回到聊天室馬上就問得到、引用得到。 這一段雛形是真的(在瀏覽器裡真的切片、真的重建索引),不是腳本。
懂技術的人會問這個。從使用者按下送出到答案寫完, 每一步是誰做的、有沒有碰到模型、碰的是哪一顆、為什麼要有這一步。
兩條線的形狀差很多,所以分開畫,用下面的頁籤切換。 最大的差別是 OCR:問一題完全不碰 OCR,建單才會用到,而且那是另一套模型。
以「2022 年 11 月有多少張訂單」為例。 最右邊那一欄是使用者在畫面上真的會看到的東西——現場可以直接指著對照。 橘色=有呼叫模型,其餘都是程式。
這張圖刻意不用泳道,因為建單的重點不是「誰做什麼」,是 「它是三次獨立的請求,人的動作夾在中間」。 每一塊灰底就是一次完整的請求,跑完就結束,伺服器上不留任何半成品。
| # | 步驟 | 前端 | 後端(Java) | 模型 | 為什麼要有這一步 |
|---|---|---|---|---|---|
| 0 | 送出 | 送 POST /api/chat,帶 sessionId、問句、介面語言。開一條 SSE 接回應 |
驗證擋空問題與超長(上限 2000 字,text-to-SQL 那端只收 500 字所以另擋) | — | SSE 一開,後面每一步才推得回去 |
| 1 | 判語言 | — | Lang.detect:看字元。有假名→日文;有漢字→看介面;只剩拉丁字母→英文 |
無 | 回答語言跟問句走,不跟介面。用規則不用模型:零延遲、結果固定 |
| 2 | 前處理 理解問題 |
顯示「理解問題 · 進行中」 | Preprocessor:全形數字轉半形、時間詞→日期區間、別名→代號、單號補十碼、追問沿用上一輪期間 |
無 | 把人話變成可以綁進查詢的條件。規則問題用規則解,可測試、可重現 |
| 2b | 追問還原 | 進度列顯示「補上前文 →『…』」 | FollowUpRewriter:只在「有前文」且「這句看起來依賴前文」時才跑 |
Qwen 非串流 約 140 ms |
「第二名呢」補成完整問句。非做在判意圖之前不可——後面的意圖分類與 text-to-SQL 都只看得到一句話 |
| 3 | 判意圖 判斷意圖 |
顯示「判斷意圖 · 進行中」 | IntentClassifier:有附件直接判業務操作(0 ms);否則問模型 |
Qwen 非串流 上限 16 token 溫度 0 約 130 ms |
決定走哪條流程。四類+順便回 pipeline 代號,一次呼叫解決兩件事 |
| 4 | 選流程 | 顯示「分析 · data_query」 |
PipelineResolver:模型給的代號合法就用;否則同一類只有一條就用它,多條比關鍵字;都沒中用預設那條 |
無 | 把「這是哪種問題」變成「要跑哪個 YAML 檔」 |
| 5 | 取證 查資料/檢索文件 |
收到 sql 事件就先把表格畫出來 |
Orchestrator 照 YAML 呼叫 tool。wren=問 text-to-SQL;rag=檢索文件碎片 |
對方的模型 (產 SQL 在 AI team 那側) |
唯一隨題目變的兩步之一。AI-Brain 不寫 SQL、不連資料庫 |
| 6 | 分岔判斷 | 顯示「hits == 0 → 否」 |
GatewayEvaluator:單一數值比較。變數由上一步的 tool 回填 |
無 | 「夠不夠往下做」是數值問題不是語意問題。門檻由人事先訂在 YAML,程式每次照套 |
| 7 | 統整 統整回答 |
逐字顯示;收到 citations 後把 [n] 變成可點的方框 |
Synthesizer:把問題+條件+每一片證據編號後組成 prompt,串流呼叫模型 |
Qwen 串流 上限 1200 token 溫度 0.2 |
把證據寫成人看得懂的話。它只看得到證據池,看不到資料庫、也不能自己算數字 |
| 8 | 呈現 | 照 render 型別決定畫什麼:數字卡/表格/查無資料/草稿卡 |
送出 render 與 done,寫一列請求紀錄 |
無 | 另一個隨題目變的步驟。單列單欄走數字卡,多列自動退回表格 |
實測案例(Adam 記在程式註解裡):使用者先問「2025 年營業費用最高的五個科目」, 接著問「第二名呢」。
關鍵在於一次請求裡有三個地方需要上下文,但只有統整拿得到對話歷史; 意圖分類與 text-to-SQL 都只收到一句話。所以脈絡必須在入口就補進那一句裡。
已經對齊的(每一項都指得回程式碼):
| 項目 | 原本 | 現在 |
|---|---|---|
| 前兩步順序 | 判斷意圖 → 整理查詢條件 | 理解問題 → 判斷意圖(11 段) |
| 步驟名稱 | 整理查詢條件/查詢資料 | 理解問題/查資料 |
| 缺前處理的段落 | 7 段完全沒有這一步 | 全部補上,含「你好」「你是誰」與建單第一段 |
| 查資料那行 | 「1 次查詢,共 38 ms」 | 「1 筆,38 ms」(10 段,數字取自各段真實的筆數與耗時) |
| 未開票那段 | 「3 次查詢,共 262 ms」 | 「147 筆,262 ms(重修 2 輪)(已截斷)」 |
| 查回那張單的意圖 | 查詢 · LLM 128 ms |
分析 → data_query · LLM 128 ms
——原本是錯的,實體的意圖只有四類,沒有「查詢」 |
| 兩個流程代號 | delivery_ontime/customer_idle |
ontime_rate/orders_idle(實體真的有這兩條,形狀也一樣) |
還沒對齊、而且被問到要答得出理由的:
| 項目 | 雛形 | 實體 | 答法 |
|---|---|---|---|
| 沒講期間時 | 預設全期間 | 預設本月 | 刻意不改。雛形資料只到 2024-11-15,用「本月」會查到 0 筆 |
| 四段純資料題 的統整那行 | 「268 字」 | 「268 字,引用 1 片」 | 硬改會壞掉。實體的查詢結果本身就是一片證據,答案引用得回去; 雛形的表格與證據走兩條不同的路,加了引用編號會變成點了沒東西的連結。 其餘 12 段本來就一致 |
| 建單第二、三段 | 沒有前處理也沒有判意圖 | 三次請求每一次都會送這兩步 | 那兩段在雛形接在同一則訊息裡播,要補得先拆成獨立的執行紀錄 |
| 意圖那行的寫法 | 多數是「分析 · LLM 124 ms」 | 「分析 → data_query · LLM 124 ms」 | 下一行本來就顯示同一個代號,資訊沒有缺,所以先不動 |
問題:哪些訂單有交貨但還沒開票? 查詢條件:期間 2018-10-29 ~ 2024-11-15(未指定,預設全期間);幣別 USD 資料片段(只能根據這些回答): [1] 查詢結果:履約分類(5 筆)(WrenAI text-to-SQL,2026-09-15) 分類 | 項目數 | 金額 已開票 | 1,204 | … [2] 開票與請款規範 › 第三節 開票時點(規範,財務部,2026-05-04) …原文… 請依規則回答。
另外還有一份「規則」(system prompt),六條,關鍵的是這幾條:
[1];沒有依據的句子不要寫。還會附上最近四輪對話,讓它接得上追問。
COUNT、SUM 這種聚合查詢永遠回一列。
所以「0 列=查無資料」擋不住「回一列 {count: 0}」這種情況。
實測發生過:問「這個月有多少張訂單」,對方把它對成 COUNT(會計憑證) 回了一列 0,
統整很有自信地寫出「本月訂單數量為 0 張」。
而事實是那套系統裡根本還沒有訂單資料——系統斷言了一個業務事實,這比說查無資料糟得多。
修法是把判斷條件收窄成:單列、而且每一個欄位都是 null/0/空字串,才算查無資料。 只要有任何一欄帶著資訊(例如分組用的公司名)就當成真的有資料。
代價誠實地寫在程式註解裡:真正答案就是 0 的題目(例如本月逾期 0 張)也會被說成查無資料。 這個方向是故意的——寧可少答,不要講錯。
| 段 | 實測 | 說明 |
|---|---|---|
| 判意圖 | 約 130 ms | 冷啟動第一次約 250 ms |
| 追問還原 | 約 140 ms | 只有追問才付這個錢,完整問句 0 成本 |
| 非中文翻譯 | 約 300 ms | 中文問句 0 成本 |
| 本地檢索 | 幾十 ms | 幾百片線性掃,夠用 |
| text-to-SQL | 一輪 8~13 秒 | 模型要產 SQL、跑、失敗還會自己重修 2~3 輪 |
| 統整 | 約 1 秒(182 字) | 逐字送,使用者第一個字就看得到 |
逾時設定:text-to-SQL 給 60 秒(對方上限 120 秒、內部會重試 2~3 輪,實測一輪 9~13 秒, 60 秒涵蓋三輪),調度層總預算 90 秒。不跟到 120 秒是刻意的—— 等到那時才失敗,連前面成功的查詢都會被一起丟掉。
「為什麼要有這一層」每一題都有答案,而且答案的形狀都一樣: 拿掉它會發生什麼壞事。這一節照架構圖由上而下走。
order_ledger 建單流水帳。
AI-Brain 自己不連前者,一律問 text-to-SQL前端直接打模型,能做到的只有「把問題丟給模型、把回答顯示出來」。 這樣做會失去四件事:
用 Tom 那張架構圖的說法:應用層管 WHAT NEXT 與 WHEN STOP,AI 能力服務管 HOW TO EXECUTE。 能力服務不准自己決定整個任務完成沒有。
這是刻意的取捨,理由寫得很直白:決策要可預期、可稽核。
保留的彈性是一格「AI 判斷」:只在資料不足時被問一次「還能查什麼」, 而且只能從已定義的能力裡選,步數上限仍然由調度層守著。它是流程裡的一個節點,不是流程本身。
具體的壞處,每一個都是真的發生過或會發生的:
意圖這一層的產出其實是一句話:「接下來走哪一條 pipeline」。
三個選項的比較:
| 做法 | 好 | 壞 |
|---|---|---|
| 關鍵字規則 | 零延遲、零成本 | 沒列到的問法就漏;問法的變化是無窮的 |
| 向量比對 | 更快更省 | 要多一個端點、多一套例句要維護 |
| 模型四選一 | 問法變化再多都吃得下;之後要多抽欄位也不用換路 | 多 130 ms |
四類這個規模下,130 ms 已經夠快,而且是同一顆模型順手做的,不增加任何依賴。 規則版沒有丟掉,它留著當模型連不上時的備援(畫面上會顯示「規則(LLM 不可用)」)。
所以 prompt 明寫「不確定時回分析」,程式解析不出模型回覆時也預設回分析。 分析是「有證據才回答」的安全方向。
這是一個很好用的答題模式:被問「XX 出錯會怎樣」時, 先講兩種錯的後果不對稱,再講設計往哪邊倒、為什麼。
入口這一層其實有兩條路,走哪條看請求長什麼形狀,不問任何模型:
| 請求裡有什麼 | 直接決定走哪條流程 |
|---|---|
| 帶了附件 | order_intake(判讀這是不是訂單) |
| 帶了附件+「要建單」這個動作 | order_extract(辨識欄位、產草稿) |
| 帶了確認過的草稿 | order_create(打 SAP、寫流水帳) |
| 只有文字 | 才交給意圖分類器 |
好處有三個,都很實際:
可以濃縮成一句:「能從請求本身確定的事,就不要交給模型猜。」 這跟「數字歸程式、語意歸模型」是同一個原則往入口延伸。
data_query 是什麼意思?檔案裡只有三種步驟:
hits == 0)實體目前啟用 11 條:
基礎 3 條 knowledge_search 查文件(分析類選不到專屬流程時的預設)
general_chat 閒聊
about_system 問系統本身(固定答案,不進 RAG、不生成)
資料 1 條 data_query 所有結構化資料問題都走它
綜合 3 條 ontime_rate 達交率:先查數字 → 再取規範文件 → 一起統整
orders_idle 閒置訂單
split_delivery 分批交貨
建單 3 條 order_intake / order_extract / order_create
附件 1 條 attachment_chat 繼續問剛剛傳的那份文件
加一條情境=寫一個 YAML、在設定檔加上代號、需要新能力就實作一個 tool。主幹一行都不用改。
| 型別 | 步驟誰決定 | 用在哪 |
|---|---|---|
| Fixed | 人,設計時寫死多步 | 重要且會反覆被問的題目,例如毛利歸因(查兩期 → 拆營收成本 → 下鑽 → 查採購價 → 不夠再翻文件) |
| Generic | 人,但只有一輪 | 查一次 → 有結果就統整、沒有就查無資料。沒預期到的問題的保底 |
| Dynamic | 模型自己 | 本階段不做,圖上與程式裡都明確標示 |
不做 Dynamic 的理由一句話:客戶面前不能讓模型臨場發明查詢步驟。 不可預期、不可重現、出錯不知道怪誰。Fixed + Generic + 一次 AI 判斷已經涵蓋這階段的情境; Dynamic 是之後有明確需求再開的選項。
data_query 一條?不是應該訂單一條、財務一條嗎?問句原文直接交給 text-to-SQL,選哪個資料模型、產什麼 SQL、在哪執行都是他們的事。 我們這邊分成兩條,只是把同一個判斷做兩次,而且兩邊會不一致。
這條設計已經兌現過一次:2026-09-15 AI team 把語意層整個換版 (原本 5 個彙總模型清空,改成直接對應 middledb 的 12 張原表), AI-Brain 一行都不用改。因為它從來沒有假設過對方有哪幾張表。
歷史包袱也值得講:原本訂單那七題各有一條寫死 SQL 的 YAML,打本機的假資料庫。 那是 text-to-SQL 還沒交付之前的過渡,後來整個拔掉—— 留著會變成同一份業務口徑有兩套定義(我的 SQL 一套、他們的語意層一套), 不一致的時候沒人知道該信哪個。
以達交率為例:數字(準時幾張、延遲幾張)來自 text-to-SQL, 但「達交怎麼算」——以計畫出貨日還是客戶要求日為準、缺實際出貨日的要不要進分母—— 寫在規範文件裡,要靠檢索取回來。
統整的時候:數字照查詢結果,口徑掛文件引用。 所以門檻改了改文件,不用改程式。
這三題(達交率、閒置訂單、分批交貨)也是最能展示這套架構價值的題目—— 單純的 text-to-SQL 工具答不出「怎麼算才算達交」。
hits、rows、failed)模型只在它呼叫的 tool 裡面(llm tool、之後的 AI 判斷 tool)。
這樣切的好處是責任清楚:它是一個 Java 類別,不是一個模型,
所以它的行為完全可預測、可以寫單元測試、每次執行都一樣。
停止條件有三個,每一個都回明確訊息:
每一步還會開一個追蹤節點(span),所以在監控畫面上看得出來是卡在哪一步。
gateway 的條件長這樣,全部是 SQL 或檢索回來的數字在比大小:
hits == 0 檢索命中 0 片 → 回查無資料 failed == true 產不出 SQL → 回「這題沒辦法轉成查詢」 empty_result == true 查到但整列都是空的 → 回查無資料 due_orders == 0 該出貨的單有 0 張 → 短路
Adam 的說法:訂門檻是人做的(設計時一次),算數字是程式做的(每次執行), 模型在這兩層都不出現。好處是可重現、可測試、快、而且之後可以直接對應到 Maestro 的流程變數。
實作上刻意做得很陽春:只支援單一比較(變數 運算子 值),
不支援 and/or/算式。程式註解自己寫著「條件變複雜再換運算式引擎」——
先做剛好夠用的。
三個不做的事,每一個都是刻意的:
雛形有一題「模擬錯誤」就是在演這個:錯誤卡、狀態列寫「處理中斷」而不是「已完成」。
證據池就是這一輪所有取回來的東西的集合,每一片都帶著: 編號、種類(查詢結果/文件碎片)、標題、原文、來源、日期、相關度分數。
三個它必須存在的理由:
[1] 必須永遠指向同一片。
編號是進池子時就發的,模型只能引用已存在的編號。[1] 可以指向一張表嗎?這一點常被誤會成「引用只能指文件」。實際上每次查完資料,那張表會被包成一片證據, 帶著標題、實際跑的 SQL、資料列、筆數、來源(text-to-SQL)與日期, 然後跟文件碎片排在同一個編號序列裡。
所以達交率那種題目的答案可以長這樣:
整體交貨準時率是 88.6%(374 / 422)⋯⋯ [1] ← [1] 是查詢結果 這是相對計畫出貨日的準時率,跟客戶要求日 是兩個口徑,不能互相代替 [2] ← [2] 是規範文件
數字照查詢結果、口徑掛文件引用——同一段話裡兩種來源各自掛得回去。 這正是「綜合判斷」那幾條流程的價值,也是單純的 text-to-SQL 工具做不到的事。
實測可以當例子講:問「採購政策對供應商調價怎麼規定」,
撈回來的片段裡沒有直接規定,它的回答是「片段中沒有提到具體規定」,
然後列出相關的兩點並標上 [1]、[4]。這正是要的行為。
還有一條容易被忽略的:數字誰算。統整規則明寫「不要自己計算」,
毛利率、下降幅度都是查出來的欄位,模型只負責把它寫成句子。
連小數的格式都處理過——690801320.97 若用程式預設的方式轉成文字會變成
6.9080132097E8,模型就照抄進答案裡了,所以有一段程式專門把它轉回十進位。
順帶回答一個常見誤解:撈五片不是為了丟進模型五片。 檢索是漏斗——幾千片初篩到 30,重排取 5,進模型的永遠只有 top 5。 多要幾片是為了之後接重排器有東西可排。
原本是有一顆的:本機 PostgreSQL、140 張假訂單、十題的 SQL 寫在各自的 YAML 裡。 後來整個拔掉,連資料庫相依一起。
拔乾淨的理由寫得很清楚:「達交」怎麼算、「前十大」依什麼排, 我的 SQL 一份、他們的語意層一份,遲早不一致,而且不一致的時候沒人知道該信哪個。
有一個例外,而且它正好證明了原則:order_ledger 建單流水帳。
Adam 的答法值得原樣記起來——
好在哪:分析查詢打在鏡像上,不影響生產系統; 模型產生的 SQL 再糟也碰不到 S/4——它根本連不到。
代價要誠實講。原本的評估是走 SAP 的 CDS view(一種官方的資料視圖), 它會免費幫你解掉四個地雷;但現在的路徑是「SAP 匯出 CSV → 灌進 PostgreSQL」,中間沒有 CDS view, 所以那四件事都得自己補:
| 地雷 | CDS view 有解 | CSV 匯出的現況 |
|---|---|---|
| 多公司資料隔離(MANDT) | 解決 | 要自己確認匯出時有沒有濾 |
| 表跟表怎麼 join | 解決 | 要自己在語意層補 |
| 多語系文字 | 解決 | 要自己處理 |
| 權限控管 | 自動套列層級權限 | 完全沒有 |
| 代號前導零(4711 存成 0000004711) | 部分 | 查錯會回空集合而不是報錯 |
這也是為什麼前處理要把「訂單 104705」補成十位數——就是在補這個。
SSE 還順帶解決了另一件事:因為使用者已經看得到進度,後端呼叫 text-to-SQL 就可以同步等, 不需要做成非同步再輪詢。等 13 秒但一路看得到進度,跟等 13 秒看著空白,是兩種體驗。
它的限制也直接決定了建單的設計:SSE 是單向的,沒辦法中途停下來等人回答。 所以建單拆成三次獨立請求,見 §05。
sessionId 是前端產生的,使用者重新整理就換一顆新的。
所以後端重啟清掉上下文,不會比使用者按 F5 更嚴重。
後端每個 session 只留最近 20 則,追問時帶最近四輪給模型。 畫面上看得到的對話紀錄存在瀏覽器本機,不在後端。
程式註解自己寫著:「記憶體版;要跨重啟或多台才需要外部儲存」—— 這是明確標示的簡化,不是疏忽。要上正式環境時換掉那一個類別即可。
因為系統裡有三段東西是中文的,而且改不動:
英文句子直接送進去,這三段全部失效。 所以做法是在邊界翻一次:非中文問句先由模型翻成中文(順便補前文,同一通呼叫), 中間所有規則與流程一行不改,只有進度文字、固定文案與最後統整換語言。
代價寫得很誠實:非中文問句多約 300 ms,中文問句零成本。 比起讓每一段各自支援三種語言,這樣便宜也不會漏。
判斷語言不用模型:有假名一定是日文;有漢字看介面語言;只剩拉丁字母就是英文; 什麼都沒有(只打一個單號)就跟介面走。
| 誰 | 負責 | 狀態 |
|---|---|---|
| Adam(AI-Brain 本體) | 入口、意圖、流程、調度、證據池、統整、前端、知識庫管理 | 已完成並在跑 |
| AI team(Ian) | Qwen 端點、text-to-SQL API、RAG API、之後的 AI 判斷 API | 前兩者已好;RAG 規格已交付、還在做;AI 判斷還沒開始 |
| Tom/Brandon | SAP 建單 API、鏡像資料 | 建單 API 已對真機驗過 |
| 我方(SA 這條線) | 情境設計、雛形、文件、對焦 | — |
Tom 的責任邊界主張值得記:應用層管 WHAT NEXT/WHEN STOP,能力服務管 HOW TO EXECUTE。 「AI 判斷」那一格被切出去給 AI team,正是照這條線切的。
Maestro 是商業流程編排工具,它取代的正好是 Java 那條「照步驟跑、分岔、等人確認」。 對應關係很乾淨:
右邊每一格能力 API 都不用動,前端、知識庫、檢索契約、統整、資料層全部沿用。
為了那條縫,Java 端才刻意不把流程寫死在程式裡。 這是「為什麼用 YAML」的第四個理由,也是最現實的那個。
「哪邊有串模型、串的是哪種模型、為什麼要這樣串」——這一節直接列完。 重點是:整套系統只有一顆語言模型,用在七個地方(判意圖、追問還原、非中文翻譯、 統整、一般對話、看圖判讀、標籤對應);OCR 是另一套模型; 文件檢索目前沒有模型;產 SQL 的模型在對方的服務裡。
| 畫面上那一步 | 我方呼叫幾次 | 哪一顆、怎麼叫 |
|---|---|---|
| 理解問題 | 通常 0 次 | 純規則,不問模型。只有兩種情況才 +1 次: 這句話依賴前文(追問還原)、或問句不是中文(先翻成中文)。 Qwen 非串流、上限 80 token,約 140 ms |
| 判斷意圖 | 1 次 | Qwen 非串流、上限 16 token、溫度 0,約 130 ms。 帶附件時是 0 次——看請求形狀直接決定,不問模型 |
| 查資料 | 我方 0 次 | 模型呼叫發生在對方那邊:AI team 的 WrenAI 語意層+Qwen 產 SQL, 產不出來還會自己重修 2~3 輪(畫面上的「重修 N 輪」就是它)。 我方只是送一句問句過去 |
| 統整回答 | 1 次 | Qwen 串流、上限 1200 token、溫度 0.2、關思考模式 |
| (檢索文件) | 0 次 | tf-idf,純算數。見 §01 |
算總數:一題純文件問答=我方 2 次(意圖+統整)。 一題資料問答=我方 2 次+對方 1~3 次。
建單那條線另外還有三個,而且其中一個不是 Qwen:
192.168.170.28)| 用在哪 | 哪一顆 | 怎麼呼叫 | 目的 | 為什麼是這樣 |
|---|---|---|---|---|
| 判意圖 | Qwen3.8-27B | 非串流 上限 16 token 溫度 0 |
四選一,順便回流程代號 | 做單選題不是寫文章,所以限得很死。溫度 0=同樣的問句永遠同樣的答案。
上限設 16 而不是 8,是因為回的是 knowledge_search 這種長代號,截斷會解析不出來 |
| 追問還原 | Qwen3.8-27B | 非串流 上限 80 token |
「第二名呢」補成完整問句 | 只在「有前文」且「這句看起來依賴前文」時才呼叫,完整問句不花這一趟 |
| 非中文翻譯 | Qwen3.8-27B | 同上(同一通呼叫) | 英/日問句翻成中文 | 內部一律用中文跑,在邊界翻一次比每一段各自支援三語便宜 |
| 統整回答 | Qwen3.8-27B | 串流 上限 1200 token 溫度 0.2 關閉思考模式 |
把證據寫成人看得懂的話 | 溫度 0.2=要穩定不要創意。關思考模式省六成 token——統整不需要它先自言自語一段 |
| 一般對話 | Qwen3.8-27B | 串流 | 回招呼、說明能做什麼 | 這條路不取證,所以意圖分類寧可錯往分析也不要錯往這裡 |
| 看圖判讀 | Qwen3.8-27B (多模態) |
非串流 上限 400 token 圖片直接進 prompt |
判斷「這份文件是不是要建訂單」 | 長邊超過 1600 px 先縮成 JPEG。解析不出結果一律當「不是訂單」—— 寧可多問一句,不能憑壞掉的輸出去建單 |
| 標籤對應 | Qwen3.8-27B | 非串流 上限 600 token 用選單鎖住答案 |
「這個沒看過的標籤是哪個欄位」 | 模型看不到值,只能從欄位清單裡選。規則認得的標籤根本不問它—— 實測一份訂單 59 個欄位,規則對出 18 個、一題都沒問模型 |
| AI 判斷 | 未定 | — | 資料不足時問「還能查什麼」 | 還沒接。定位是 AI team 提供的一個 API,跟其他能力並列 |
| 產 SQL | 在 AI team 那側 (WrenAI 語意層 + 模型) |
HTTP POST /text-to-sql |
把問句變成 SQL 並執行 | 不是我們呼叫模型,是呼叫他們的服務。 選哪張表、產什麼 SQL、在哪執行都是他們的事。所以語意層換版時我們一行都不用改 |
| OCR | PaddleOCR-VL (同事的服務) |
HTTP POST /extract_form逾時 120 秒 |
把圖上的字讀成「標籤:值」清單 | 是另一套模型(版面分析+視覺語言模型),不是 Qwen。它只負責認字,不負責判斷欄位 |
| 文件檢索 | 目前沒有模型 | 程式內建 | 從知識庫挑出最相關的五片 | 現在用 tf-idf(純統計)。AI team 的檢索 API 好了改一個設定值就切過去 |
目前這幾種用法的需求其實都在同一顆 27B 模型的能力範圍內: 做單選題、補一句話、翻譯、寫一段有引用的文字、看一張圖說是不是訂單。 沒有一項需要專門的模型。
真正省下來的是營運成本:一個端點、一套監控、一張 GPU。 而且介面是 OpenAI 相容的,之後真的要換或要加一顆,改設定檔兩行就好。
唯二用了別的技術的地方,都是因為那件事 Qwen 做不好或不該做: OCR(認字是專門的活)與檢索(那是排序不是生成)。
192.168.170.31:7100,用 vLLM 這套推論伺服器架起來,
對外提供 OpenAI 相容的介面。選它的實際理由:開源可自架(資料不出公司)、中英日都行、有多模態版本可以看圖。
| 元件 | 位置 |
|---|---|
| 語言模型(Qwen / vLLM) | 192.168.170.31:7100 |
| text-to-SQL | 192.168.170.31:6100 |
| 資料庫 middledb | 192.168.170.31:5432 |
| OCR | 192.168.170.28:6600 |
| SAP 測試機 | 192.168.170.23:44300 |
「OpenAI 相容」只是指介面格式跟 OpenAI 的 API 長得一樣(方便換), 不是指會連到 OpenAI。這個詞很容易被誤會,講的時候要補一句。
因為介面是 OpenAI 相容的,任何 vLLM、Ollama 或雲端 API 都接得上。 統整用的那份規則(prompt)也是中性的,沒有綁 Qwen 的特性。
唯一要注意的是看圖那一段:換的模型要支援多模態, 否則會退回「先 OCR 成文字再讓模型讀」的路徑——程式已經寫好這個退路, vLLM 的圖片配額被關掉時會自動走。
關掉思考模式後,統整一次約幾百 token;一天幾百次問答對一顆 27B 模型是輕載。
要盯的地方要講出來:text-to-SQL 跟統整共用同一張顯卡。 多人同時問的時候會排隊,這已經提醒過 AI team。 緩解手段是關思考模式(省六成 token)與 SSE(讓使用者看得到進度,等待比較不難受)。
完整版在姊妹文件 建單架構與Demo問答_20260915.html。
這裡只留現場最常被問、以及那份文件寫完之後才發生的變化。
建單這條線的流程圖放在 §02, 跟問答那張並排、可以直接切換對照——差別最大的就是 OCR 那一段。
0 上傳 POST /api/attachments → 拿到一個附件編號(存記憶體) 1 判讀 order_intake 模型看圖 → 是不是訂單 → 問「要用這份文件建立訂單嗎」 ↓ 人按「要」(這一輪請求已經結束了,按鈕送出的是新的一次請求) 2 辨識 order_extract OCR 讀成「標籤:值」清單 → 規則對進欄位 → 草稿卡 ↓ 人核對/修改(草稿整包在前端) 3 建單 order_create 先佇一列 → 打 SAP 拿單號 → 補回流水帳 → 顯示單號
「等人確認」不是後端的一個狀態,是這一輪回出一個問題,下一次請求把答案帶回來。 所以使用者關掉分頁、重整、隔天再來,伺服器這邊沒有半張單要清。
每一次請求都是完整的一輪:一樣先走前處理、再判意圖,然後才進建單那一段。 進度列上三次都看得到「理解問題 → 判斷意圖」,只是這兩步在建單時幾乎不花錢—— 意圖是看請求形狀直接決定的,0 ms,沒問模型。
換來的是三件事:
真正的代價是每段都要重跑一次前置:按「要建單」那一步會再 OCR 一次。 這件事程式裡有處理——每則訊息記著當初送出的參數, 所以按「重新產生」是「再辨識一次」而不是變成閒聊; 已經建完的那則按下去,因為冪等,拿回的是同一個單號。
「我們刻意不給信心度分數。因為值是 OCR 原文照抄、欄位對應是規則或模型二選一, 中間沒有一個可以量的連續量。給一個數字只會讓人以為它是量出來的,反而不去檢查。」
「取而代之的是可追溯:每一格都標它來自文件上哪個標籤、會寫到 SAP 哪個欄位。 對錯一眼看得出來,而且是人在確認,不是機器打分數。」
舊版曾經有過:讓模型自己回報信心度,前端拿 0.85 當門檻標黃。 拿掉的理由是——那個數字是模型自己說的,不是量出來的。 模型說 0.94 不代表它有 94% 的機率是對的,同一份文件跑兩次還可能給不同分數。 給一個看起來精確的假數字,比不給更危險。
值永遠是 OCR 原文照抄。這句話可以直接拿來講: 「模型負責理解,程式負責搬運。值從頭到尾沒有經過模型的手。」
為什麼要這樣改?因為舊做法(把 OCR 整包丟給模型填成 JSON)實測出三個問題: 長文件輸出會被截斷、鎖了結構之後模型會在空白處打轉直到上限、 而且模型會順手改值——把數量印成 $120。
每張草稿在辨識那一步會拿到一個唯一編號。要建單就得先用這個編號在流水帳表插入一列, 而那個欄位是資料庫層級的唯一鍵——插得進去代表這張草稿是你的,插不進去代表別人先拿走了。
順序也是刻意的:先佇一列,再去打 SAP。 萬一 SAP 成功但寫紀錄失敗,至少看得到有人試過這一單, 不會變成「SAP 有單、我們完全沒紀錄」的孤兒。
兩層原因:
中間缺的那一段(建單後要回寫中繼資料庫哪幾張表、哪些欄位)連規格都還沒定, 歸 Tom/Brandon。
建議的講法:「辨識、判讀、欄位對應、流水帳都是真的在跑; SAP 建單那一步目前發模擬單號,真的連線程式已經寫好,等帳密與主檔對照就能切過去。」
「已經寫好等切換」本身是加分,不用心虛。技術細節(如果被追問): 走 S/4HANA 的標準銷售訂單 API、憑證釘選、只跑 TLS 1.2、 切換只要改一個設定值加上帳密。
2026-09-15 對真機(192.168.170.23)實跑的結論:
11 張單全部建成,4 張金額錯,其中一張正好是兩倍(OCR 把品項區塊吐了兩次)。
而且沿路每一步的狀態都是正常的——每個欄位都合法、必填全過、SAP 也收下並回了單號。
這催生了一道新的檢查,叫對帳守衛:建完單之後, 拿單據自己印的合計對SAP 用自己的定價算出來的淨額, 差超過 0.01 就把警告接在完成訊息後面。
為什麼這道檢查非有不可:它擋的是欄位檢查看不到的一整類錯—— 品項多讀、少讀、重複讀。每個欄位都正常,只有總額對不起來。
為什麼做成事後警告而不是事前攔截:因為價格是 SAP 算的, 在我們這邊自己算價等於養第二套定價邏輯。 模擬模式沒有定價引擎所以回空值——空值是「沒得對」,不是「對過了」。
建單架構與Demo問答_20260915.html 寫完之後才進去的
(f60f830/23b7268),那份文件裡沒有。
它是目前最能展示「這個團隊有在認真對帳」的一個細節,值得主動講。
這幾題的共同點是:主動講比被拆穿好,而且每一題後面都接得上一個好設計。 答法的形狀固定——先承認限制,再講因為有這個限制所以做了什麼。
這套系統從頭到尾的設計主張就是這句話:它不要求你信任它,它把依據攤開讓你自己驗。 所以「怎麼知道對不對」不是一個尷尬的問題,是這個產品的賣點。
照這個順序看,越前面越容易錯、也越容易看出來:
| # | 看什麼 | 在哪看 | 怎麼判斷錯了 |
|---|---|---|---|
| 1 | 條件對不對 | 「我理解的條件」那一排 | 最常錯的一層。你問 8 月它寫全期間、你問甲客戶它對到乙客戶——一眼看得出來 |
| 2 | 查了哪張表、怎麼算 | 展開 SQL | 懂的人直接讀。標了「重修 N 輪」的更要看——那代表模型卡過 |
| 3 | 口徑對不對 | 答案裡的引用 [n],點開看原文 |
「達交」算的是計畫出貨日還是客戶要求日?點引用看它依據的是哪份規範 |
| 4 | 數字對不對 | 表格本身 | 表格是完整的,答案只點名前三筆。數字以表格為準,不是以文字為準 |
「第一,我們照實講:text-to-SQL 那一段對方自評 76%,不是 100%。」
「第二,所以我們不要求你相信它。每個答案都看得到系統理解的條件、實際執行的 SQL、 以及每一句話依據的原文——三樣東西你都可以當場驗。」
「第三,判斷的責任本來就該留在人身上。 系統的責任是把依據完整攤開,讓你有東西可以判斷; 一個不給你依據、只給你數字的系統,才是危險的那個。」
「你們有量化的驗收標準嗎?」——目前沒有,但機制已經備好了。 每一次請求的問題、條件、SQL、撈到的碎片、答案、引用全部留成紀錄, 所以只要有一份「標準答案題庫」,就能回放算出:SQL 對不對、引用有沒有掛回、 答案有沒有超出證據。這是請求紀錄存在的主要理由,不只是除錯。
要讓這件事成立,缺的不是工程,是業務端先把口徑定義下來—— 沒有定義就沒有正確答案,沒有正確答案就沒有準確率可以算。
對方(AI team)自己在介面規格裡特別註明「這句要照實講給客戶聽,不能當成查得到就是對的」。
接下來這句才是重點:「所以畫面上一定看得到實際執行的 SQL 與系統理解的條件。 判斷對不對的責任在看的人身上,系統的責任是把依據攤開。」
可以再補一個技術性的緩解:用訂單編號精確查一筆這種簡單的條件查詢相對穩 (實測一次就對、8 秒);複雜的聚合與多表關聯才是風險區。
擋不住的那一類也要講得出來:SQL 語法正確、條件也對,但業務口徑理解錯 (例如「前十大客戶」依金額還是依張數)。這一類只能靠把口徑寫進規範文件, 讓系統取文件來回答,而不是讓模型自己決定——那正是「綜合判斷」那三條流程在做的事。
為什麼這件事不能含糊:如果讓人以為那是自家報表,任何一個數字被拿去引用都是問題。 而且真實資料進來之後,數字一定會變,先講清楚就不會有落差。
這不是壞掉,是兩件事撞在一起:時間詞換算用的是真實的今天, 而資料是 2018~2024 的歷史快照。
反過來也成立,而且對 demo 有利:新建的單如果是當天日期,問「這個月」只會查到那幾張, 舊資料一張都不出現——畫面很乾淨。要知道那是這個機制造成的,不是系統特別聰明。
雛形這邊的處理方式不同:預設「全期間」而不是「本月」,正是為了避開這個問題。
時序是這樣:
vbak、vbap、kna1、likp、lips…)。
實測「訂單 0000004826 的客戶是誰」一次就對,8 秒回。/internals 說明頁與四份文件仍停在舊敘述,
現場如果有人打開那一頁,會看到跟事實相反的內容。這已經整理成回報清單要給他們。
那一頁在實體程式裡藏著(左上標誌兩秒內點五下解鎖), Adam 把「每一格怎麼達成、被問到時怎麼答」都寫在那裡,所以它很容易被當成權威答案。
| 位置 | 頁面上寫的 | 實際 |
|---|---|---|
| Pipeline 那格 | 「現在 7 條 YAML」 | 11 條 |
| 建單那格開頭 | 「模型把『標籤:值』重排成草稿」 | 已改成規則優先、模型只對不認得的標籤。同一格下面的細節是新的、開頭是舊的 |
| 調度層那格的待辦 | 「等人確認要存 pending、之後 resume 續跑」 | 建單已完成,而且刻意不採用這個設計 |
| 訂單資料狀態 | 「訂單類目前一律回查無資料」 | 語意層換版後已經查得到 |
| 第 02 節的事件序列 | 意圖在最前面,沒有前處理這一步 | 程式後來把前處理提到意圖前面(因為追問還原必須先做) |
被問到的答法:「那一頁是開發方的內部說明,有幾節還沒跟上最新的程式, 已經整理好要回報給他們了。」然後照這份文件講實際的做法。 不要當場改口說雛形是對的——雛形演的是實體,不是另一套設計。
開發方在 2026-09-12 自己做過一份資安健檢,把發現分成高/中/低。 「有一份寫得出來的清單」本身就是答案的一部分——這是「知道要做但還沒做」,不是「沒想到」。
| 現況 | 後果 | |
|---|---|---|
| H1 | 後端沒有任何身分驗證,而且綁的是 0.0.0.0 |
同網段任何人都能問公司資料、看知識庫全文、上傳文件, 甚至刪除任何文件——沒有確認也沒有稽核 |
| H2 | 監控用的 Elasticsearch/Kibana 對整個網段開放、沒有帳密 | 裡面放的是每一次請求的完整問題與答案。同網段任何人可以免登入翻、也可以刪掉整個索引 |
| H3 | 日誌收集端的 9880 埠沒有驗證 | 任何人 POST 一段 JSON 就能偽造紀錄 |
開發方自己排的順序是 H1 先做——因為身分驗證一解,下面好幾條的風險同時降下來。
| XSS | 前端零 v-html,模型輸出與文件內容全部走文字節點。已驗證 |
| 惡意連結 | 外部連結一律過白名單,只放行 http/https/mailto,
javascript: 會退化成純文字 |
| 檔案上傳 | 大小上限、副檔名白名單、解析失敗就拒、 檔名只當顯示用不落地 → 沒有路徑穿越 |
| 錯誤處理 | 堆疊只進日誌,回應只有錯誤碼與訊息,不外洩內部結構 |
| 金鑰 | 沒有任何寫死的密碼或 token |
| 相依套件 | npm audit 0 個漏洞 |
| 檔案 | 去了哪 | 要知道的 |
|---|---|---|
| 訂單圖片 | 存後端記憶體(不落地,總量上限 200 MB,滿了淘汰最舊的) | 建完單就沒用了,重啟就消失 |
| 圖片內容 | 縮到長邊 1600 後送內網的模型服務判讀 | 圖片內容會進那台機器的日誌(如果他們有開) |
| 圖片原檔 | 整份送到另一台內網 OCR 服務(192.168.170.28) |
文件全文會經過第三台機器,他們的日誌政策我方不知道 |
| 知識庫文件 | 切片後存後端記憶體 | 重啟會清掉,只重載預設那 15 份 |
已知還沒做的:沒有病毒掃描;沒有擁有者——拿到附件編號的人就能用它建單。 後者要等身分驗證一起解,不是這一塊能補的。
被問到時的答法:「全部在內網,沒有一段出公司。 但誠實講,文件會經過兩台我們不擁有的內網服務(模型與 OCR), 它們的日誌政策要跟那兩個團隊確認——這一條已經寫在資安清單上了。」
第三點很值得主動講,因為它把球丟回給業務方,而且是真話。
這一節是上台前最該讀熟的。雛形刻意做成跟實體畫面一樣, 所以任何差異都會被當成「系統的說法」。下表按「被抓到的機率」排。
| 項目 | 雛形 | 實體 | 要不要擔心 |
|---|---|---|---|
| 底下是不是真的 | 不呼叫任何 API,問答是寫死的腳本 | 全部真的在跑 | 最重要的一條。被直接問就照講,不要模糊。 但要補:知識庫檢索是真的,可以當場換題目試 |
| 整條進度列 | 理解問題 → 判斷意圖 → 查資料(N 筆,N ms)→ 統整回答 | 同左 | 2026-09-15 已對齊:順序、步驟名稱、缺的前處理(7 段)、 查資料那行的格式(10 段)、以及一個錯的意圖類別。 原本順序是反的,因為照著開發方過期的說明頁畫 |
| pipeline 代號 | margin_structure、cycle_time、
order_stage、revenue_pipeline |
這四個實體沒有,那些題目會走 data_query |
刻意保留。原本有六個,
delivery_ontime→ontime_rate、
customer_idle→orders_idle 已改名(實體真的有)。
剩下四個演的是實體還沒有的情境,全改成 data_query 會讓四題顯示同一條 |
| 統整那行(四段) | 「268 字」 | 「268 字,引用 1 片」 | 其餘 12 段已一致。這四段硬改會做出點了沒東西的引用—— 實體的查詢結果本身就是證據,雛形的表格與證據走兩條路 |
| 沒講期間時 | 預設全期間 | 預設本月 | 不用擔心,兩邊都對。雛形資料只到 2024-11,用「本月」會查到 0 筆 |
| 請求編號 | 前端亂數產生,格式一樣 | OpenTelemetry 產生,可以拿去查 | 只有被要求「當場查給我看」才會露餡。答法見 §01 |
| 毫秒數、相關度、重修 N 輪 | 腳本裡寫死的示範值 | 實測值 | 被問到就講「這幾個數字是示範值,實體那邊的實測數字我有」 |
| 建單 | 三段都是腳本,OCR 的值與單號寫死 | OCR 真的在跑(PaddleOCR-VL)、SAP 連線程式已寫好、流水帳真的寫進資料庫 | 形狀一樣(三條流程、語意欄位、無信心度),所以講得通 |
| 建完能不能查回來 | 演得回來 | 查不回來 | 會被追問。雛形演的是目標狀態,實體缺的那一段連規格都還沒定 |
| 附件格式 | 只收 PNG/JPG/JPEG | PNG/JPG/WebP/PDF,8 MB | 刻意的。WebP 與 PDF 是為後續階段鋪的,九月 demo 只用三種 |
| 知識庫份數 | 10 份示範文件 | 15 份 | 不會被抓到 |
| GenBI 分析、Dashboard | 有(預設隱藏,側欄點五下解鎖) | 沒有這兩頁 | 沒解鎖時畫面跟實體一模一樣,這是刻意的。 演完記得再點五下收回去 |
| 三語 | 介面切得動,但回答內容還是中文 | 回答語言跟問句走 | 日本客戶那場之前要補。目前日文問句會落到本地檢索,演不出完整那題 |
| 信心度 | 已拿掉 | 已拿掉 | 兩邊已對齊,可以放心講 |
每個詞三件事:它是什麼、這套系統哪裡用到、為什麼要用它。 現場被問到某個詞,翻到這裡照著講就完整。
| 名詞 | 是什麼 | 哪裡用到 | 為什麼要用 |
|---|---|---|---|
| AI-Brain | 整套系統的名字,寫法固定用連字號 | 品牌字樣、畫面標題、所有文件 | 2026-09-14 拍板。舊名 PRISM 已退場,公司內舊叫法「AI Server Demo」別人口中還會出現 |
| UiPath Maestro | 商業流程編排工具 | 還沒接。十月評估要不要用它取代調度層 | 它是 AI-Brain 底下的編排層,不是同級的別名,也不是更上層。講的時候層級不要顛倒 |
| Intent (意圖) |
把一句話分成四類:分析/系統說明/一般對話/業務操作 | 進度列第一(或第二)步。由 Java 的 IntentClassifier 呼叫模型判定 |
不同種類的問題該走的路差很多。沒有這層的話,「你好」也會去翻資料庫 |
| Pipeline (流程) |
一種問題的處理流程定義,一條=一個 YAML 檔 | 實體目前 11 條。畫面上「分析 · data_query」那個代號就是它 |
流程寫在設定檔不寫在程式裡,改門檻不用改程式,之後搬去 Maestro 搬的是流程不是程式 |
| Orchestrator (調度層) |
讀 YAML、照步驟跑、分岔、命中停止條件就停的那個 Java 類別 | 每一題都會經過。每一步會開一個追蹤節點 | 它一行模型呼叫都沒有,所以行為完全可預測、可測試、每次一樣 |
| Tool (能力) |
流程裡「做事」的那種步驟。目前有 wren(查資料)、rag(檢索)、
llm(統整)、ocr、sap、ledger、vision、static |
YAML 裡用名字引用,調度層不知道它裡面在做什麼 | 加一個新能力=實作一個 Tool,流程主幹不用動。模型全部關在 Tool 裡面 |
| Gateway (分岔) |
流程裡的判斷步驟,內容是一個數值比較,例如 hits == 0 |
「產不產得出查詢」「有沒有資料」「資料夠不夠」都是它 | 「夠不夠」是數值問題不是語意問題。訂門檻是人做的,算數字是程式做的,模型兩層都不出現 |
| Render (呈現) |
流程的結束步驟,告訴前端要畫成什麼:answer/data/
empty/confirm/order_draft/order_created |
每條流程的最後一步 | 前端不做業務判斷,畫什麼由後端決定,兩邊不會各有一套邏輯 |
| Evidence Pool (證據池) |
這一輪取回來的所有東西的集合,每片帶編號、原文、來源、日期、分數 | 統整只看得到它;右側「依據與來源」面板顯示的就是它 | 要混不同來源(查詢結果+文件碎片)、編號要穩定才能引用回原文、事後要能稽核 |
| 前處理 (Preprocessing) |
把人話換成條件:時間詞→日期、俗名→代號、單號補十碼 | 畫面上的「理解問題」那一步 | 完全不用模型。規則問題用規則解,零延遲、可測試、結果固定 |
| 追問還原 | 把「第二名呢」補成完整問句 | 前處理裡面,只在「有前文」且「這句依賴前文」時才呼叫模型(約 140 ms) | 意圖分類與 text-to-SQL 都只看得到一句話。不還原的話「第二名呢」會查成完全不相干的東西 |
| 統整 (Synthesize) |
把證據寫成人看得懂的一段話,每句掛引用編號 | 最後一步,串流逐字送給前端 | 它是唯一讓模型寫字的地方,所以規則也下得最重:只看證據、標編號、不准自己算數字 |
| Fixed/Generic/ Dynamic |
三種流程型別,差別在「步驟誰決定」 | Fixed=人寫死多步;Generic=人寫死但一輪;Dynamic=模型自己規劃,本階段不做 | 不做 Dynamic 是因為「客戶面前不能讓模型臨場發明查詢步驟」——不可預期、不可重現、出錯不知道怪誰 |
| Session | 一場對話。後端每個 session 留最近 20 則,統整時帶最近四輪 | 追問沿用上一輪期間、attachment 續問都靠它 | 放在記憶體,因為 sessionId 是前端發的、重新整理就換一顆,後端重啟不會更嚴重 |
| 冪等 (Idempotency) |
同一個動作做幾次,結果都跟做一次一樣 | 建單:草稿編號是資料庫的唯一鍵,插得進去才准建單 | 連點、重送、重整、兩個分頁同時按確認,都只會有一張單。做在資料庫不做在程式判斷,因為程式判斷擋不住同時進來的兩個請求 |
| 對帳守衛 | 建完單之後,拿單據自己印的合計對 SAP 算出的淨額,差超過 0.01 就警告 | 建單最後一步(2026-09-15 新增) | 擋的是欄位檢查看不到的一類錯:品項多讀、少讀、重複讀。實測十份單據錯四份,其中一份正好兩倍 |
| 名詞 | 是什麼 | 哪裡用到 | 為什麼要用 |
|---|---|---|---|
| LLM (大型語言模型) |
讀文字、產文字的模型 | 判意圖、追問還原、翻譯、統整、看圖判讀、標籤對應 | 這套系統的立場是只在「語意」的地方用它,數字與流程都不交給它 |
| Qwen3.8-27B | 阿里巴巴開源的語言模型,270 億參數,有多模態(看得懂圖)版本 | 上面那六種用法全部是它,同一個端點 | 開源可自架(資料不出公司)、中英日都行、有看圖能力。整套只養一顆 |
| vLLM | 把開源模型架成服務的推論伺服器 | 192.168.170.31:7100,AI team 維護 |
提供 OpenAI 相容介面,所以換模型只要改設定檔兩行 |
| OpenAI 相容 | 指介面格式跟 OpenAI 的 API 長得一樣 | 呼叫 Qwen 的那支程式 | 不是指會連到 OpenAI。這個詞最容易被誤會成資料外流,講的時候一定要補一句 |
| token | 模型處理文字的最小單位,大致是一個詞或幾個字 | 各種上限:判意圖 16、追問還原 80、看圖 400、統整 1200 | 它同時是成本與延遲的單位。設上限是為了「這一步不該講那麼多話」 |
| temperature (溫度) |
控制模型有多「隨機」。0=每次都給一樣的答案 | 判意圖用 0;統整用 0.2 | 判意圖是單選題要完全穩定;統整要能寫成通順的句子但不要發揮,所以只給 0.2 |
| thinking mode (思考模式) |
模型先自言自語推理一段,再給答案 | 關掉(enable_thinking=false) |
統整不需要它——證據都給好了。關掉省六成 token,也就是省六成時間 |
| context (上下文長度) |
模型一次最多能讀多少字 | 這顆是 80k | 決定一次能塞多少證據進去。目前五片碎片+一張表遠遠用不完 |
| 量化 (NVFP4) |
把模型壓小,讓它塞得進顯卡記憶體、跑更快,代價是精度略降 | 這顆 Qwen 就是量化過的 | 27B 模型不量化很吃卡。這是自架必然的取捨 |
| prompt | 送給模型的那段指示與資料 | 統整的 prompt 裡有:規則六條、問題、條件、每一片編號證據 | 「不准亂編」不是模型的天性,是靠 prompt 規則加上「只給它證據」一起做到的 |
| 多模態/vision | 模型看得懂圖片,不只文字 | 建單第一步:判斷「這份文件是不是要建訂單」 | 比先 OCR 再讀文字準(版面、表格、蓋章都看得到)。配額被關時程式自動退回 OCR 路徑 |
| 幻覺 (hallucination) |
模型憑空編出看起來合理但不存在的內容 | 這整套架構有一半是在防它 | 防法三道:只給證據、要求標編號、0 筆根本不呼叫模型(沒被叫醒就沒機會編) |
| RAG | 「先檢索、再生成」——先去文件庫找相關段落,再讓模型根據那些段落回答 | 知識庫那條路。達交率、分批交貨這類「口徑寫在文件裡」的題目 | 公司內部規範不可能塞進模型的訓練資料。而且要引用得回原文,模型才不能亂講 |
| 切片 (chunk) |
把一份文件切成一段一段,每段約 450 字,帶著章節路徑 | 知識庫匯入時切,檢索時是以「片」為單位比對與引用 | 整份文件太大塞不進模型,也沒辦法精確引用。切片的規則是一片要能單獨讀懂、不跨章節 |
| tf-idf | 一種計分方法:一個詞在這一段裡出現、而在其他段落很少出現,就很能代表這一段 | 目前的文件檢索(實體與雛形用同一套演算法) | 純統計、零依賴、速度快、而且可以解釋為什麼這片被選中。代價是只看字面 |
| bigram | 把相鄰兩個字當成一個詞。「達交率」→ 達交、交率 |
tf-idf 的中文處理 | 中文沒有空格。用 bigram 就不需要詞典,也不會把料號、客戶代號斷錯 |
| embedding (向量) |
把文字變成一串數字,意思接近的文字數字也接近 | 還沒用。AI team 的檢索 API 會用 | 比的是意思不是字面,「交貨慢了」對得上「延遲交付」。代價是要多一個服務、結果比較難解釋 |
| reranker (重排) |
把初篩出來的幾十片,拿問題跟每一片一起重讀、重新打分 | 還沒有。契約留了位置 | 解決的是誤判不是速度:向量相似度會把「講到同一個詞但講的是別的事」排很前面 |
| top-k | 取分數最高的前 k 片。這裡 k=5 | 檢索回傳、進統整的永遠只有 5 片 | 檢索是漏斗:幾千片→初篩 30→重排取 5。多要幾片是為了給重排器排,不是為了丟進模型 |
| below_threshold (低於門檻) |
撈回來的片段全部低於相關度門檻 | 觸發時直接回「查無資料」,不呼叫統整 | 這是「查不到就說查不到」的實作點。也是雛形可以現場亂問一題示範的地方 |
| text-to-SQL | 把人話問句轉成 SQL 查詢並執行的服務 | AI team 的服務 192.168.170.31:6100。所有數字問題都走它 |
AI-Brain 不寫 SQL、不連資料庫。一份資料一個來源,不要兩邊各養一套業務口徑 |
| WrenAI | 做 text-to-SQL 的開源產品,核心是一層「語意層」 | 就是上面那個服務的底層 | 它是右邊泳道的一格能力,不是編排層,也不做統整。這個定位常被搞混 |
| 語意層/MDL | 告訴模型「這張表是什麼、欄位的中文名、同義詞、指標怎麼算」的定義層 | WrenAI 裡面,由 AI team 維護 | 沒有它,模型面對 VBAK、NETWR 這種欄位名根本不知道在幹嘛。
它是準確率的關鍵,也是最大的人工工項 |
| OCR | 把圖片上的文字讀出來 | 建單第二步,同事的 PaddleOCR-VL 服務 192.168.170.28:6600 |
它回的是「標籤:值」清單,只負責認字,不負責判斷哪個值屬於哪個欄位——那是我們這邊的規則在做 |
| enum 鎖答案 | 要求模型只能從一份固定清單裡挑一個,不能自由作答 | 標籤對應那一題:模型只能從欄位名清單裡選 | 模型只能選、不能寫,所以它不可能發明一個不存在的欄位,也碰不到值 |
| 名詞 | 是什麼 | 哪裡用到 | 為什麼要用 |
|---|---|---|---|
| Quarkus | 一套 Java 框架,主打啟動快、吃記憶體少 | 後端整個應用層 | 實測後端 2 秒就起來。傳統 Java 框架要十幾秒,開發時每改一次都要等 |
| Vue 3 + Quasar | 前端框架+元件庫(按鈕、表格、對話框那些現成的東西) | 聊天畫面、知識庫頁、草稿卡 | 元件庫省掉自己刻 UI 的時間。雛形也用同一套(走 CDN),所以兩邊長得一樣 |
| SSE Server-Sent Events |
一條單向的長連線,後端可以一則一則推訊息給前端 | 整個問答過程:進度、表格、逐字答案、引用、結束 | 需求正好是單向。比 WebSocket 省(不用連線升級),比一般請求好(不用等全部寫完才給看) |
| YAML | 一種給人讀寫的設定檔格式,靠縮排表示層次 | 每一條 pipeline 一個 .yml 檔、別名表、知識庫清單 |
業務口徑與門檻要讓不寫程式的人也看得懂、改得動 |
| REST API | 用網址與 HTTP 動詞提供服務的介面約定 | POST /api/chat、POST /api/attachments、知識庫的增刪查改 |
最通用的介面形式,前端、其他系統、測試腳本都接得上 |
| DTO (資料傳輸物件) |
專門用來在系統之間傳遞的資料結構 | 建單的 OrderDocument:每個欄位標著它在文件上會印的標籤、會落到 SAP 哪裡、必不必填 |
草稿卡是照這個結構動態畫出來的。改欄位改一處,畫面、驗證、SAP 對應全部跟著改 |
| LRU | 「最久沒用到的先丟」的快取淘汰規則 | 上傳的附件存在記憶體,總量上限 200 MB;對話 session 也是 | 附件建完單就沒用了,不值得寫進磁碟或資料庫 |
| PostgreSQL | 一套開源關聯式資料庫 | middledb(SAP 鏡像,AI team 讀)+ order_ledger(建單流水帳,我們寫) |
兩張表在同一顆資料庫但用途完全不同,這個區分要講清楚(見 §03) |
| 外鍵 (Foreign Key) |
資料庫層級的規則:這一欄的值必須在另一張表裡存在 | 訂單的客戶代號必須在客戶主檔裡、料號必須在料號主檔裡 | OCR 辨識出一個不存在的客戶代號,資料庫會直接擋下來,不會寫進一筆孤兒資料 |
| 唯一鍵 (Unique) |
資料庫層級的規則:這一欄的值不能重複 | 流水帳的草稿編號 | 防重複建單就靠它。做在資料庫才擋得住同時進來的兩個請求 |
| OData | SAP 對外提供 API 的標準格式 | 打 S/4HANA 建銷售訂單(API_SALES_ORDER_SRV) |
是 SAP 官方的標準介面,不是自己挖資料庫。抬頭與明細可以一次送出(deep insert) |
| 憑證釘選 (pinning) |
只信任指定的那一張憑證,不接受其他任何憑證 | 連 SAP 測試機時釘住一張 .pem,只跑 TLS 1.2 |
測試機用的是自簽憑證。釘住指定那張,比「全部都信」安全得多 |
| OpenTelemetry | 一套「產生追蹤資料」的開放標準 | 每次請求產生 traceId,每一步產生一個 span | 它負責「產生」,不負責「存」。所以之後換監控平台只改設定不改程式 |
| traceId (請求編號) |
一次請求的唯一識別碼,128 位元=32 個十六進位字 | 畫面上可複製的那一串;後端每一行日誌也帶著它 | 使用者截圖回報,貼進查詢介面就找得到那一次的完整經過 |
| span | 一次請求裡的一個小段落(一個步驟、一次外部呼叫) | 每一步、每一次呼叫模型或 text-to-SQL 都開一個 | 有了它才看得出來「這一輪 12 秒,其中 11 秒卡在 text-to-SQL」 |
| EFK | Elasticsearch(存與搜)+ Fluent Bit(收集)+ Kibana(看) | 請求紀錄的去處 | 用 Fluent Bit 而不是常見的 Logstash:C 寫的常駐約 5 MB,Logstash 是 Java 要 600 MB 起跳,做的事一樣 |
| JSONL | 一行一個 JSON 物件的檔案格式 | 請求紀錄先落成這種檔,再送去 Elasticsearch | 檔案是原始紀錄,Elasticsearch 是可搜尋的副本。所以「檔案 1000 筆、介面 997 筆」是預期行為不是故障 |
| ECS | Elastic 的通用欄位命名標準 | 紀錄的信封部分(時間、traceId、服務名、錯誤)用它的名稱 | 跨服務才對得起來。但只用信封——這套系統自己的執行軌跡不硬塞進去,那會製造語意錯配 |
| 名詞 | 是什麼 | 哪裡用到 | 為什麼要用 |
|---|---|---|---|
| S/4HANA | SAP 現在這一代的 ERP 系統 | 資料的源頭;建單的目的地 | 分析查詢不直連它,只有建單會真的打它的 API |
| SD | SAP 的銷售與配銷模組 | middledb 裡那 12 張表全部屬於這個模組 | 這次 demo 的題目(訂單、出貨、開票)都在這個模組裡 |
| VBAK/VBAP KNA1/LIKP… |
SAP 的表名。VBAK=訂單抬頭、VBAP=訂單明細、KNA1=客戶主檔、 LIKP/LIPS=交貨、VBRK/VBRP=發票、MARA/MAKT=料號 | 畫面上展開 SQL 就會看到它們 | SAP 的表名是德文縮寫,本來就不好懂。這正是需要語意層的原因 |
| 中繼庫 (middledb) |
SAP 資料的鏡像子集,一顆獨立的 PostgreSQL | 192.168.170.31:5432,text-to-SQL 讀它 |
S/4 不直連是老闆的限制,也是好限制——查詢打在鏡像上不影響生產系統 |
| CDS view | SAP 官方提供的資料視圖,已經處理好權限、多語、表間關聯 | 這次沒有用到——走的是 CSV 匯出 | 要知道它存在,因為沒有用它就要自己補那四件事(見 §03 那張表) |
| 前導零 (ALPHA) |
SAP 把代號補滿固定長度,客戶 4711 存成 0000004711 |
前處理把「訂單 104705」補成十位數 | 查錯會回空集合而不是報錯——這種錯最難發現,所以要在入口就補好 |
| 銷售組織 1710 | SAP 示範資料裡的美國銷售公司 | 目前資料就只有這一家、幣別 USD | 被問「有幾家公司」時要答得出來:示範資料只有一家,不要照舊文件講三家 |
最後一定會有人問這個,而且問得很直接。這一節的每個答案都要能講出 「那個替代方案在哪裡會斷掉」,而不是比功能多寡。
直接用它,四件事會斷掉:
| 要做的事 | 只有 WrenAI | 有 AI-Brain |
|---|---|---|
| 口徑寫在文件裡的題目 (達交率怎麼算、幾天算閒置) |
答不了。它只看得到資料表,看不到公司的規範文件 | 先查數字、再取規範文件,一起統整,口徑掛引用 |
| 非資料的問題 (你是誰、分批交貨要誰核准、上傳訂單建單) |
全部落在它的範圍外 | 意圖分類先分流,各走各的路 |
| 查不到時的行為 | 回 0 筆。實測它把「這個月有多少張訂單」對成別的東西回一列 0, 直接用的話畫面上就會出現「本月 0 張」這個錯誤的業務結論 | 擋在 gateway:單列且每欄都空才算查到,寧可少答不要講錯 |
| 換掉它 | 畫面、流程、稽核全部綁死在它身上 | 它只是一個 tool。換成別家或自建,其他一行不動 |
一句話版本:「WrenAI 回答『資料是什麼』,AI-Brain 回答『這個問題該怎麼被回答』。」
VBAK 是什麼、
不知道你們的「達交」怎麼定義、也連不到那顆資料庫。要接上去,
你要做的正好就是這套系統在做的事——語意層、取證、流程。可以再補一句軟的:「通用助理解決的是『我不會寫』;這套解決的是『我不知道公司的數字』。 兩者不衝突,但後者只能自己做。」
更務實的講法:這套架構的每一格都是可替換的。 哪天原廠方案成熟了,它可以變成右邊泳道的一格能力, 左邊的流程、稽核、知識庫全部沿用。賭的不是哪個模型贏,是流程要握在自己手上。
兩者不是替代關係:固定要看的指標就該做成報表,快又準,不需要 AI。
這套系統的位置是報表的縫隙:
而且這套系統不取代報表的權威性——它每個答案都附 SQL 與引用, 正是因為它知道自己不是權威報表。
兩條線的成本結構完全不同:
| 建單那條 | 問答那條 | |
|---|---|---|
| 最貴的工 | SAP 介接、主檔對照、欄位規則 | 語意層與業務口徑定義(人工,最大工項) |
| 價值多久看得到 | 快。省的是打單時間,算得出來 | 慢。要先有題目與口徑才驗收得了 |
| 風險 | 建錯單。已經有對帳守衛在擋 | 答錯數字。靠攤開 SQL 與引用讓人判斷 |
所以如果要分階段,先做建單是合理的——它的效益最好量化。 但要講清楚:共用的那一層(意圖、流程、調度、稽核、前端)兩條線都要, 所以先做一條省下來的不是一半,大概是三分之一。
不屬於單一元件、但幾乎每場都會出現的問題。
這一題答錯的代價很高,因為它同時碰到「資安」與「期待管理」。
那它怎麼變好?靠人改三個地方:語意層的定義、知識庫的文件、流程 YAML 的門檻。 這反而是優點——改哪裡、誰改的、什麼時候改的,全部說得出來, 不像微調完之後沒人知道它為什麼改變了。
| 症狀 | 是哪一層的錯 | 改哪裡 |
|---|---|---|
| 走錯路(問數字卻去翻文件) | 意圖或流程選擇 | 流程 YAML 的關鍵字與說明 |
| 條件理解錯(期間、客戶抓錯) | 前處理 | 別名表,或時間詞規則 |
| SQL 查錯表、算錯口徑 | text-to-SQL 的語意層 | AI team 的語意層定義 |
| 口徑對但說法錯 | 規範文件 | 知識庫那份文件 |
| 有依據但寫得不好 | 統整 | 統整的規則(prompt) |
能這樣分,本身就是這個架構的賣點。如果整套是一顆模型直接回答, 答錯了只能說「模型不夠好」,沒有第二句可以講。
而且每一次請求的完整經過都留著(問題、條件、SQL、撈到哪幾片、答案、引用), 所以要判斷是哪一層錯,是查得出來的,不用猜。
一列裡有:問題、抽出的條件、意圖、走哪條流程、每一步耗時、 實際執行的 SQL、撈到的碎片與分數、每一片有沒有被引用、答案、引用編號、狀態。
可以直接查出來的幾種問題:
答案沒掛任何引用 → 模型在自由發揮 回了查無資料的比例 → 覆蓋率不足 某一步超過 10 秒 → 哪個服務在拖 分類方式是「規則(LLM 不可用)」 → 那段時間模型連不上 撈到卻沒被引用的比例 → 檢索品質的訊號
更進一步的用法(還沒做):拿一份情境題庫定期回放, 算 SQL 對不對、引用有沒有掛回、答案有沒有超出證據。 請求紀錄存在的主要理由就是這個,不只是除錯。
所以「同時問數字的人」超過兩個就會開始排隊。 AI-Brain 這側因此刻意不做任何並行呼叫——多打只會讓對方更慢。
另一個共用瓶頸是那張 GPU:text-to-SQL 產 SQL 與統整寫答案共用它。
誠實的講法:「目前是 demo 規模,沒有做過壓力測試。 瓶頸在哪我們知道(text-to-SQL 併發 2、GPU 共用),要放大是加資源的問題,不是改架構。」
要放大之前已知還要補的:請求紀錄轉送目前沒有上限保護(流量大時會往記憶體堆), 這一點程式註解自己寫著「目前靠流量低不會發生,不是設計保證」。
「整批重建」的意思是:重建時會先刪掉那些鏡像表再重灌, 所以任何直接寫進鏡像表的資料,重建時會一起消失。
頻率目前沒有定案,這是要跟資料那條線(Tom/Joey)確認的事。 被問到就照講「目前是匯出的快照,更新頻率還在定」,不要編一個數字。
有一件事是確定的、而且值得講:建單流水帳不受重建影響, 因為重建只刪它自己的表名。這個保證是開發方明確確認過的。
不用改的:意圖、流程、調度、證據池、統整、前端、知識庫、監控。
要換的:
所以答案是:「架構本身不綁 SAP,但語意層必須重做, 而那正是這類專案最花時間的地方。不要以為換個資料庫就搬得過去。」
三件事分開講:
最該強調的那句:「沒有定義就沒有正確答案可以驗收。」 這不是工程能解決的,加人加錢都沒用。
怎麼知道是誰的問題?靠請求編號查那一輪的完整經過: 走錯流程是應用層、SQL 查錯是語意層、口徑錯是業務定義。 這套系統的稽核設計,同時也是一套除錯的分工表。
現場一定會有答不出來的。答不出來不扣分,亂答才扣分。
| 情況 | 句型 | 為什麼有效 |
|---|---|---|
| 知道有這回事,不確定細節 | 「這一塊是開發方那側的實作,我知道它的做法是 ⋯⋯, 確切的參數我回去確認再回覆你。」 | 先證明你懂結構,再誠實承認邊界。比硬掰一個數字好太多 |
| 完全沒想過 | 「這個問題很好,我們還沒有評估過。 我把它記下來,跟開發方確認過再回覆。」 | 「還沒評估」是一個正當狀態。承認它反而顯得這個團隊分得清做過與沒做過 |
| 牽涉到別人負責的範圍 | 「這是 AI team/Tom 那條線的, 我可以說明它跟我們這邊的介面是什麼,細節要問他們比較準。」 | 不要替別人承諾。分工清楚本來就是這個架構的賣點之一 |
可以當場開的:實體程式的 /internals 運作說明頁(左上標誌兩秒內點五下)。
但先讀過 §06 那張表——那一頁有五處停在舊版本,
被問到要能說明哪幾處是舊的。
建議不要主動開。它是內部技術文件,含「還沒做的」清單,對外場合通常不演。
「裝在機器上讓日本人開箱即用」「要 training 多久」「要派人駐點多久」—— 這幾題文件裡沒有現成答案,但有明確的拆法。 這一節給的是誠實回答的結構與已知會擋住的具體事項,不是承諾數字。
| 元件 | 現在在哪 | 裝箱時的重點 |
|---|---|---|
| 語言模型(Qwen 27B / vLLM) | .31:7100 |
要顯卡。27B 就算量化過也吃卡,這是整台機器的成本主軸 |
| text-to-SQL | .31:6100 |
跟模型共用同一張卡,所以規格要一起算 |
| 資料庫(鏡像) | .31:5432 |
資料量取決於要放幾張表、多久的歷史 |
| OCR | .28:6600 |
另一套模型(PaddleOCR-VL),只有建單那條線需要 |
| AI-Brain 本體+前端 | 本機 | 最輕的一塊。Java 後端實測 2 秒啟動 |
| 監控(EFK) | 本機 docker | 可選。不裝的話請求紀錄仍然會落成檔案 |
不需要裝的:SAP。建單是打對方既有的 SAP API,分析是查鏡像, 兩者都不需要在這台機器上裝 SAP。
CN=vhcals4hci.dummy.nodomain、簽發者 cal.dummy.nodomain),
不是正式機。所以正確的說法是
「切到客戶自己的 SAP 時,憑證本來就會換掉」——
到期日是示範環境的限制,不是產品缺陷。
真正要排進維護計畫的是「換憑證這件事有沒有人負責、怎麼換」。已經完成的:
還沒完成、而且是真正的工:
| 要做什麼 | 為什麼非做不可 | 誰做 |
|---|---|---|
| 語意層重做 | 指標定義、欄位中文名、同義詞現在都是照台灣這套資料做的。 換成日本客戶的資料就要重來。這是整個專案最大的人工工項 | AI team + 對方的業務 |
| 知識庫換成日文文件 | 設計原則是「資料語言 ≠ 對話語言」,文件本身不翻譯。 所以日本客戶要用,知識庫裡就得是他們自己的日文規範 | 對方提供 |
| 別名表換掉 | 客戶與商品的俗名對代號,現在是中文的 | 對方提供主檔 |
| 業務口徑拍板 | 「達交」「前十大」「閒置」在他們公司怎麼算。 沒有定義就沒有正確答案可以驗收 | 對方的業務,不能代打 |
| 雛形腳本三語化 | 介面切得動,但回答內容還是中文。 日文問句目前會落到檢索、演不出完整那題 | 我方,10-15 之前 |
一句話版本:「翻譯不是問題,定義才是。介面與回答的語言已經做好了, 真正要花時間的是把他們的資料與口徑放進語意層——而那件事需要他們的人一起做。」
建議的答法:
為什麼不要給數字:這件事的工期不由我方單方面決定。 現場給一個數字,之後對方投入不如預期,超期會被算在我們頭上。
如果一定要給個量級,可以拆成三段講,讓對方自己感覺得到比例:
把第二段點名成瓶頸,而且說明它為什麼是雙方共同的工, 比給一個會被拿來對帳的數字安全得多——而且這是真話。
已經有的:每一次請求留一列完整紀錄(問題、條件、SQL、撈到什麼、答案、引用、耗時、狀態), 而且畫面上有可複製的請求編號。對方回報問題時只要給那串編號,就查得到那一次的完整經過—— 這是遠端支援最實際的基礎。
還沒決定的(不要自己承諾):
這幾條列出來本身就是答案的一部分——「知道要做但還沒決定」跟「沒想到」是兩件事, 現場講得出清單,對方就知道是前者。