2026年11月12日 星期四

[AI 分享] 讓 .NET MAUI App 不只會聊天,還能替使用者行動

[AI 分享] 讓 .NET MAUI App 不只會聊天,還能替使用者行動

摘要:介紹如何結合裝置端 AI、工具呼叫與 .NET MAUI,讓 App 理解需求並執行操作。



內容:


在 .NET Conf 2026 的這場分享中,微軟 .NET MAUI SDK 團隊的軟體工程師 Gerald 探討如何把 AI 加入 .NET MAUI 應用程式。他認為,AI 不該只是 App 裡的另一個聊天視窗。使用者真正需要的,是能理解意圖、找到資訊,並在適當確認後完成操作的 App。


行動裝置特別適合這類體驗,因為它可以透過相機、麥克風、定位及其他感測器提供情境資訊,也可能在取得適當授權後,運用行事曆或健康資料。例如,旅行時掃描菜單,結合所在地點,協助有芝麻過敏的使用者判斷需要留意的餐點。這些資訊讓 AI 的用途超越單純問答,轉向解決具體問題。


Gerald 以一款開放原始碼的 .NET Conf App 示範。使用者初次開啟 App 時,可以用自然語言描述感興趣的內容,例如指定講者、.NET MAUI 議程,或「更多 Copilot 內容」。也能設定希望觀看的時段;App 會依使用者時區和時間偏好推薦議程,經使用者檢查後加入個人行程。即使輸入有些微拼寫錯誤,模型也可能理解原意,但裝置端模型通常比雲端大型模型更需要明確的指令。


這款 App 的 AI 不只回覆文字。當使用者提出「找星期四的 Blazor 議程」,它會切換到星期四並套用 Blazor 篩選條件;提出「儲存所有 Aspire 議程」,它會直接將符合條件的場次加入個人行程;要求查看某位講者或特定議程時,也能導向對應的詳細頁面。App 另有尋找行程空檔的功能,可依既有安排和可觀看時段提出候選議程,讓使用者自行決定是否加入。


支撐這套設計的核心之一是 Microsoft.Extensions.AI。它提供 IChatClient 等抽象介面,讓 App 的頁面、ViewModel 和服務不必直接綁定單一模型來源。開發者可以接上裝置端模型、自帶模型或雲端服務,並視需要設定工具呼叫、函式執行與 OpenTelemetry 遙測。不同功能也可以使用不同的 AI 用戶端:較簡單、重視隱私的任務交給裝置端;需要更強能力的任務則交給遠端模型,而不必為整個 App 只選一種方案。


另一個重點是 .NET MAUI 團隊正在 MAUI Labs 實驗的 Essentials AI。它延續 Xamarin Essentials 將跨平台裝置能力整合為統一 API 的思路,嘗試為 iOS、Android 和 Windows 的裝置端 AI 模型提供一致的使用方式。開發者可依平台註冊對應的 IChatClient 實作,再讓應用程式其他部分透過共同介面呼叫。Gerald 說明,Essentials AI 目前以 MAUI Labs 預覽套件形式提供,方便團隊快速實驗並收集回饋;它受到團隊積極維護,但尚不等同納入正式產品支援承諾。


使用裝置端 AI 前,App 必須檢查裝置是否具備相應能力、模型是否已準備就緒,以及是否需要下載模型。不同平台與裝置支援的功能也不完全相同;以分享當時的情況來說,工具呼叫能力尤其需要逐一確認。這意味著開發者不能只因某台裝置「有 AI 模型」,就假設所有 AI 功能都能執行,仍須做好能力檢測、替代流程和實機測試。


模型部署各有取捨。作業系統內建的裝置端模型有離線、隱私及較低使用成本等優點,但能力與硬體支援可能受限;透過 ONNX 自帶模型,能針對特定任務選用模型,卻可能增加大量 App 體積與整合工作;雲端模型能力較強、App 本身也較輕量,但需要網路、持續付費,並須考量資料傳輸與隱私。若使用雲端 API,金鑰不應直接放在行動 App 中,而應妥善保存在後端。


要讓 AI「採取行動」,Gerald 展示了 AI attributes 的做法。開發者可在既有 C# 方法上標註匯出 AI 函式的屬性,替函式及參數寫出清楚描述,使模型能根據使用者語句判斷何時呼叫工具,以及該傳入哪些參數。例如,開啟講者頁面、搜尋議程或調整個人行程,都可以轉為模型可呼叫的工具。相關原始碼產生器會建立呼叫程式碼,避免在執行時依賴反射。工具最終仍透過 App 原本的 ViewModel、模型和服務執行,不必另建一套與既有介面脫節的操作邏輯。


讓 App 能行動,也必須讓使用者保持控制權。示範中,移除已儲存議程屬於可能造成損失的操作,因此 AI 會先列出將移除的項目並要求確認;使用者可以選擇移除或保留。Gerald 建議開發者驗證每項請求與工具參數、留意提示注入風險、清楚說明 AI 能做什麼,並只公開必要且願意讓模型操作的工具。AI 可以協助判斷意圖,但不應繞過安全檢查或替使用者擅自決定重大操作。


分享最後也提出更多延伸方向。語意搜尋可將文字轉成向量,讓使用者不必輸入完全一致的關鍵字,就能找到意思相近的議程。AI 也可能協助新使用者找到設定、推薦入門內容、說明觀看某場演講前應具備的背景知識,或將議程摘要翻譯成偏好的語言。MAUI Labs 中的 AI Wayfinding 實驗則進一步思考:如果使用者能直接表達目的,App 是否可以替他找到操作路徑,甚至呈現更符合個人偏好的介面?


整體而言,這場分享鼓勵開發者從「使用者想完成什麼」出發,而不是只在 App 裡增加聊天功能。Microsoft.Extensions.AI 提供連接不同模型的共同介面,Essentials AI 探索跨平台裝置端能力,AI attributes 則讓既有程式碼成為可呼叫的工具;三者結合後,AI 才能在明確邊界與使用者掌控之下,真正改善 .NET MAUI App 的使用體驗。

[AI 分享] .NET 11 的 ASP.NET Core 與 Blazor:效能、安全與開發效率

 [AI 分享] .NET 11 的 ASP.NET Core 與 Blazor:效能、安全與開發效率

摘要:整理 .NET 11 網頁開發新功能,涵蓋效能、資安、Blazor 與開發工具。



內容:


Daniel Roth 是 .NET 團隊負責 ASP.NET Core 與 Blazor 的首席產品經理。他在演講中指出,選擇網頁開發平台是一項長期投資:團隊不只需要它能穩定執行,也需要它持續應對安全威脅,並在產品需求成長時,協助開發者有效交付。這場演講以「效能、防護、開發效率」三個面向,介紹 .NET 11 中 ASP.NET Core 與 Blazor 的改進。


效能:從大規模服務累積的改進


.NET 支撐 Microsoft 多項服務,包括 Copilot、Teams、Bing、Azure 與 Xbox。因此,執行階段與網頁技術堆疊的效能改善,不只來自基準測試,也受到每天處理大量請求與使用者的實際工作負載驅動。


以 MSN、Edge 和 Windows 的個人化內容提要為例,這些服務每月提供數十億次網頁瀏覽。演講中提到,相關團隊從 .NET 8 升級到 .NET 10 後,回報 P95 垃圾回收暫停時間降低 29%、非同步外掛程式延遲降低 11%,執行緒集區佇列也縮短 20%。應用程式不必具備同等規模的流量,仍能受益於這類執行階段改進。


在 .NET 11 中,效能工作延伸至整個網頁堆疊,包括 JIT 最佳化、降低記憶體配置壓力、加快 JSON、字串、正規表示式與 HTTP 處理,以及減少可觀測性功能的額外負擔。對 Blazor 而言,改進包括縮小網頁元件下載大小、支援靜態伺服器端渲染快取,以及暫停閒置的伺服器連線通道,以釋放伺服器資源。其中許多改善可透過升級直接取得。


Kestrel 的不良請求處理是具體例子。過去,格式錯誤的 HTTP 請求會經過例外傳遞與一般回應設定流程;在 .NET 11 中,剖析器可直接回傳錯誤結果,Kestrel 再使用預先配置的片段寫出回應。HTTP 行為與診斷資訊維持不變,但處理成本更低。在單核心基準測試中,不良請求的處理量因此約提高 30%,也讓伺服器有更多運算資源處理正常流量。


安全:減少重複工作,持續更新防護


瀏覽器、驗證方式與攻擊手法不斷演變,安全性不可能在單一版本中「完成」。.NET 11 持續改善應用程式所依賴的基礎設施,包括自動跨網站請求偽造(CSRF)防護、更一致的授權中繼資料處理、長時間連線的驗證狀態更新,以及傳輸和請求處理強化。Blazor 對限制較嚴格的內容安全政策(CSP)也有更好的相容性。這些平台防護不能取代應用程式本身的安全設計與設定,但能減少團隊必須自行實作的基礎工作。


CSRF 防護旨在避免其他網站濫用使用者已登入的瀏覽器,向應用程式送出偽造請求。演講介紹的 .NET 11 機制會利用現代瀏覽器提供的 Fetch Metadata 標頭判斷請求來源,並預設啟用防護;開發者不必為此產生與驗證防偽權杖。基準測試顯示,相較於以權杖驗證為基礎的方式,新的標頭判斷方式每秒可處理約四倍的請求,且每個請求的記憶體配置量約減少 65%。


示範使用客服退貨網站:客服人員可為客戶選擇退款或更換商品,最後確認處理結果。當另一個來源的網站嘗試偽造確認請求時,ASP.NET Core 將其拒絕並回傳 HTTP 400;由原應用程式送出的表單請求則可正常通過。瀏覽器開發工具顯示了用於判斷來源的 Fetch Metadata 資訊,而應用程式的 Program.cs 並未加入防偽權杖的產生或驗證程式碼。


另一項安全改進是長時間連線的驗證更新。SignalR 連線可能持續到原有憑證過期,導致連線保留過時的驗證狀態。在 .NET 11 中,SignalR 可使用應用程式取得的新憑證,更新既有連線的使用者驗證資訊,而不必先中斷再重新連線。開發者可在 Hub 選項中啟用驗證更新,也可設定在更新失敗時關閉連線;JavaScript 用戶端可透過 withAuthenticationRefresh 選用自動更新。Blazor Server 則已為元件 Hub 與用戶端安排相關設定。


演講以客服權限變更展示這項能力:管理員移除某位客服的退款核准權限後,客服頁面在下一次驗證更新時改變顯示狀態,並不再允許退款。既有連線沒有中斷,客服先前輸入的更換商品資料也沒有遺失。示範中的介面透過授權政策呈現權限狀態,儲存時也會再次執行授權檢查。講者特別說明,這是依權杖到期時間定期觸發的更新,不是事件發生當下立即推送;示範縮短了更新間隔,以便觀察效果。


開發效率:共用 C# 技能與生態系資源


ASP.NET Core 讓團隊運用共通的 C# 技能、程式庫與工具,建立 API 和網頁介面;Razor 工具、Hot Reload 與 AI 輔助則有助於縮短迭代時間。講者舉 Toscano Digital 為例:其五人開發團隊在既有 Blazor 內部 CRM 之外,建立公開房地產入口網站,每年服務約 500 萬次工作階段與 1,100 萬次頁面瀏覽。團隊結合 .NET 與 Blazor 的不同渲染策略,兼顧搜尋可見度、行動裝置效能和豐富互動,並透過 C# 全端開發及平台內建功能提高生產力。


Blazor 生態系也獲得合作夥伴投入。Infragistics 將超過 50 個 Ignite UI for Blazor 元件以免費、開源方式提供,涵蓋資料格、清單、輸入、導覽與版面配置等。Syncfusion 則推出免費開源的 Syncfusion Toolkit for Blazor,包含圖表、輸入、行事曆與對話方塊。這兩項產品均採 MIT 授權,可用於商業用途,且與廠商原有的商用元件套件分開提供。除了元件,Infragistics 也為 .NET 11 貢獻可找出常見錯誤的 Blazor 分析器;Syncfusion 則投入框架與鷹架修正、診斷訊息改善,以及新功能測試。


C# Union、OpenAPI 與非同步驗證


.NET 11 的 ASP.NET Core 支援在 API 和網頁介面中使用 C# Union。以退貨處理為例,同一個結果可以是「退款」或「更換商品」,兩者具有不同欄位。開發者可用 Union 表達這兩種情況,讓 System.Text.Json 依實際值序列化對應內容,並由 ASP.NET Core 產生描述各種可能結果的 OpenAPI 3.2 Schema。示範中的 Minimal API 分別回傳退款與更換商品資料,不需要額外包裝物件或型別辨識欄位;Razor 與 Blazor 介面也可依 Union 的實際情況,呈現對應內容。


商業規則常需要查詢資料庫或呼叫其他服務,因此不一定能同步完成。.NET 11 的 Data Annotations 加入非同步驗證 API:AsyncValidation 屬性可建立可重複使用的自訂規則,IAsyncValidatableObject 則可進行物件層級、涉及多個欄位的檢查。Minimal API 與 Blazor 都支援這兩種方式。


示範中,客服選擇更換商品型號時,系統會非同步查詢庫存。頁面在等待期間顯示載入提示,並依結果告知商品是否有貨。實作上,更換商品的請求模型沿用一般資料註解驗證,另以自訂的 InStockAttribute 執行庫存檢查;Blazor 表單仍使用一般的 EditForm 與 Data Annotations 驗證器,並可透過相關 API 偵測驗證是否仍在進行。相同模型也能用於 Minimal API:無庫存的型號會得到驗證失敗結果,有庫存的型號則回傳 HTTP 200。


Blazor 大型清單與 Razor 工具改進


Blazor 的 Virtualize 元件會優先渲染可視範圍及附近的項目,避免一開始就建立整份大型清單。.NET 11 進一步支援高度不一的項目,讓展開後的列能隨內容增高;新的 Anchor 模式可控制新增或移除項目時,捲動位置如何維持,並改善清單變動時畫面跳動的情況。


客服佇列示範包含約 10,000 筆資料,仍可順暢捲動,項目也能展開為不同高度。使用者可跳至指定項目、清單開頭或結尾;若選擇將畫面固定在底部,且目前正位於底部,新增項目時畫面會跟隨。若使用者正在清單中段閱讀,新增項目則不會打亂目前位置。開發者可設定固定於頂端、底端、兩者或皆不固定,並透過新的 ScrollToItemAsync API 跳至指定項目。


Razor 在 Visual Studio 與 VS Code 中的開發體驗也持續改善,包括更豐富的程式碼動作、跨 C# 與 Razor 的重構支援,以及更快的 Hot Reload。開發者現在可將 .editorconfig 中的 C# 格式偏好套用到 Razor 檔案,排序或移除未使用的 using 指示詞,並以較精簡的 ~/ 語法指定網頁資產路徑,取代較舊的 @assets 寫法。Visual Studio 的 Razor 編輯器也支援 Emmet 縮寫,可快速展開 HTML 結構。


其他工具改進包括:可透過命令列的 .NET scaffold 工具,或 Visual Studio 的「新增鷹架項目」,為 Blazor 網頁應用程式設定以 Entra ID 為基礎的驗證。團隊也與 GitHub Copilot 合作,在 github.com/dotnet/skills 提供 .NET 相關 AI 外掛與技能,涵蓋 ASP.NET Core API、Blazor 元件、表單、驗證,以及一般 .NET、C# 和 Razor 開發知識,並以評估測試持續檢驗其效果。


Aspire、Blazor WebAssembly 與背景 Web Worker


Aspire 可協助團隊在本機連接、執行及偵錯完整應用程式,現在也能與 Blazor WebAssembly 應用程式整合,使瀏覽器端與後端的開發流程結合設定、服務探索與遙測。新的 Blazor Gateway 元件可在開發及正式環境中,為 Blazor WebAssembly 應用程式提供一致的託管方式。相關專案範本也包含用於用戶端整合的 Client Defaults,以及執行背景工作的 Worker 專案。


.NET 11 亦支援讓 Blazor WebAssembly 的 .NET 程式碼在背景 Web Worker 執行,將耗費 CPU 的工作移出 UI 執行緒。示範中的裝置支援網站可上傳診斷資料並進行分析:若在 UI 執行緒分析,介面會短暫失去回應;改用 Web Worker 後,分析仍可持續,使用者也能操作頁面。示範同時展示 Aspire 儀表板中的 Blazor 應用程式與後端 API,以及瀏覽器偵錯入口;介面則使用了 Infragistics 和 Syncfusion 提供的免費開源 Blazor 元件。


總結而言,Daniel Roth 希望開發者將 ASP.NET Core 與 Blazor 視為能長期信賴的網頁平台:以持續的效能改善支援應用程式運作,以平台層級的安全機制保護使用者,並藉由共通的 C# 開發體驗、工具與生態系資源協助團隊交付產品。演講涵蓋了 .NET 11 的部分新功能;想進一步了解其他改進,可參考 .NET Conf 的相關場次。

[AI 分享] Claude Fable 5.2 的十項提示詞調整建議

 [AI 分享] Claude Fable 5.2 的十項提示詞調整建議

摘要:影片整理 Fable 5.2 的十項使用建議,涵蓋思考強度、驗證、程式碼管理與成本控制。



內容:


影片認為,沿用舊模型的提示詞與工作習慣,未必能發揮 Claude Fable 5.2 的能力。以下是講者提出的十項調整建議;其中效能、成本與模型行為的數據,均為影片所述。


一、優先嘗試高思考強度。 講者指出,Fable 5.2 在 API 中預設為 high,但在 Claude Code 中可能預設為 medium。他建議處理較困難的任務時,先從 high 開始,不足時再提高至 extra high,而非直接使用 max。影片引用 Frontier Code 2.0 代理式程式設計基準:high 得分為 45.3%,成本為 6.72 美元;medium 得分為 37.6%,成本為 2.48 美元。講者認為,high 的表現接近 Opus 5.5 的 max 設定,而所列成本約為後者 12.82 美元的一半;max 則未必能帶來相稱的效益。


二、要求模型驗證成果,而不只宣稱完成。 講者認為,Fable 5.2 有時會把推論說成事實,或聲稱程式碼支援了實際上未實作的功能;在較低思考強度下,也可能執行較少測試。他引用系統卡表示,在需要的檔案不存在時,模型偶爾會編造替代內容,並可能採取捷徑。因此,提示詞應明確要求它在交付前執行相關測試,核對檔案、功能與結論是否確實存在。影片提到畫面上有範例提示詞,但逐字稿未提供原文。


三、明確要求完成整項任務。 面對耗時較長的代理式工作,講者提醒,模型可能在尚未達到目標時就停止,低或中思考強度時尤其需要留意。他建議在指令中寫清楚完成條件,要求模型持續處理未完成的部分,直到可以交付,並在結束前檢查結果。影片稱,加入這類要求可減少提早停止的情況。


四、重新檢查舊有指令檔。 講者建議檢視既有的 CLAUDE.md、規則檔與其他指令,而不是不加修改地沿用。其理由是新模型會認真遵循規則;過時、重複或過度嚴格的要求,可能限制它選擇更合適的做法。尤其是細到不必要的流程與風格規定,應評估是否仍有保留價值。這並非表示一律刪除指令,而是只留下確實需要的限制。


五、不要以「不要思考」來期待節省成本。 講者表示,與前一版本不同,要求 Fable 5.2「直接回答、不要推敲」,不會使它完全略過內部思考。若希望降低思考量與費用,應調整 effort 設定,例如使用 low 或 medium,而不是只改變提示詞措辭。


六、要求說明結論依據,避免索取完整逐步思考。 影片稱,直接要求模型完整揭露「如何一步一步思考」,可能觸發其對模型蒸餾行為的防護。實務上仍可要求它說明答案的理由、依據或簡短的判斷邏輯,但不必要求重現完整的內部推理過程。


七、說清楚模型對程式碼庫的責任範圍。 若希望模型從整體角度維護程式碼庫,講者建議明確告訴它可以負責整體設計與修改。影片認為,這樣可能減少重複程式碼,產生較精簡、較有整體性的結果,但也可能增加測試、額外功能及輸出 token。反過來說,若只希望最小幅度修改,就應清楚限定修改範圍與目標,避免授予過大的自主空間。


八、不要無條件強制使用子代理。 講者認為,Fable 5.2 在需要時能自行委派子代理,特別是在較高思考強度下。如果舊規則要求它每逢特定任務都啟動子代理、反覆檢查,模型可能照做,導致用量與成本快速增加。他建議移除沒有必要的固定委派規則,讓模型按任務需要決定。


九、長時間任務可指定進度回報頻率。 模型執行大量工具呼叫時,可能長時間沒有文字更新。若使用者希望掌握進度,可以要求它在重要階段、完成一批工具操作後,或遇到阻礙時回報。講者同時提醒,回報太頻繁會增加 token 用量;影片舉例,每次首次工具呼叫後都要求發言,可能使用量增加約 20%。因此,階段性更新比逐次回報更折衷。


十、用指令控制語氣與篇幅。 講者表示,effort 設定影響的是思考投入程度,不能用來指定回答長短或語氣。如果需要簡短結論、詳盡報告,或固定的寫作格式,應直接寫在提示詞中;長期適用的偏好則可放入 CLAUDE.md、AGENTS.md 等指令檔。


影片中途也宣傳講者重新設計的 Claude Code 課程,稱課程有 48 個模組,面向不同技術背景的學習者,著重實際開發案例。結尾則再次提及課程與模型使用額度。


整體而言,這十項建議的核心是:以合適的思考強度處理任務、要求可驗證的完成結果,並清理不必要的舊規則。對成本、修改範圍、進度回報及輸出格式有特定需求時,再明確寫進指令。

[AI 實作] 從本機到 Azure Foundry:.NET Agent 的部署與維運之路

 [AI 實作] 從本機到 Azure Foundry:.NET Agent 的部署與維運之路

摘要:從最小的 .NET Agent 出發,逐步實現本機託管、Foundry 部署,並探索決策模型與維運功能。



內容:


講者 Bruno 是 GitHub 開發者關係團隊成員。本次分享聚焦一個許多開發者都會遇到的問題:使用框架寫好 AI Agent 之後,要如何讓它離開自己的電腦,成為可供他人使用、能在正式環境中管理的服務?他以數個示範,沿著「本機執行、提供端點、部署至 Azure Foundry」的路徑,展示同一個 Agent 如何逐步走向正式環境。


從最小的 Agent 開始


第一個範例是一個簡單的 .NET 主控台程式。程式使用 Microsoft Agent Framework 與 Azure AI Projects,透過 Azure CLI 認證連接至已部署模型的 Foundry 專案,建立 AI Project Client,再建立 Agent 並提出問題。執行 dotnet run 後,Agent 便能回答關於 .NET 的提問。


這個範例只需要少量程式碼,足以說明 Agent 的基本建構方式。不過,問題也隨之出現:提問目前寫在程式裡,只有執行主控台程式的人能使用。若要讓外部應用程式與 Agent 互動,就需要將它託管為服務。


為本機 Agent 提供服務端點


第二個範例保留原本建立 Agent 的程式碼,再加入 Web Application Builder、Agent Host 與 Foundry Responses 協定的註冊。啟動應用程式後,Agent 便透過本機端點提供服務;示範中使用的是 localhost:8088。


講者也展示另一個連接此端點的本機聊天介面。透過介面向 Agent 發問時,開發者除了能看到回答,還能觀察互動步驟、Token 使用量、事件與工具資訊,並查看背景服務的執行紀錄。Agent Inspector 也可在 Visual Studio Code 中使用。這個階段的關鍵,是讓原本只能在主控台回答固定問題的 Agent,變成可由其他程式呼叫的本機服務。


Foundry 中的 Prompt Agent 與 Hosted Agent


在部署前,講者區分了 Azure Foundry 中兩種 Agent 形式。Prompt Agent 主要在 Foundry 介面中設定:開發者可以選擇模型、撰寫系統指令、連接工具與知識來源,並使用記憶、安全防護、追蹤、監控及評估等功能。


Hosted Agent 則適合已經自行撰寫程式邏輯的情境。開發者將 Agent 打包並部署至 Foundry,由程式碼管理系統指令、工具與模型的使用方式,同時取得平台提供的託管與維運能力。對講者而言,這相當於讓同一個 Agent 換一個執行場所,而不是重新實作一次。


示範中的 Hosted Agent 沿用本機託管時的建構方式:建立 Agent、設定指令與工具、註冊 Foundry Responses 協定,然後執行應用程式。部署設定檔則描述要執行的 .NET 專案、對外提供的協定,以及 Foundry 專案端點、模型部署名稱等環境變數。透過示範中的工具與部署指令,Agent 可被打包並發佈到 Azure Foundry。


部署後的版本、追蹤與整合能力


Agent 成為 Foundry Hosted Agent 後,可以管理部署版本、指定作用中的版本,並藉此進行測試。講者在介面中示範了如何對 Agent 提問,再從追蹤資料查看完整流程:使用者提出問題、Agent 呼叫模型、執行工具,最後產生回答。當一次提問觸發多個工具時,也能逐步檢視各次呼叫。


Foundry 還提供對話檢視、執行狀態、耗時與版本篩選等資訊;結合 Application Insights,開發者可觀察 Agent 呼叫、錯誤、工具使用情況及模型用量。這些資訊對正式環境尤其重要,因為開發者需要知道 Agent 實際做了什麼,以及問題發生在哪一步。


講者也展示了 Hosted Agent 的其他使用方式,包括取得呼叫端點、設定身分、建立 Agent Card、透過 A2A(Agent-to-Agent)連接其他 Agent,以及整合 Teams 或使用平台提供的網頁聊天介面。Agent 亦可設定排程,或與 GitHub Actions 等流程連接。這些平台能力可以加在既有的 Agent 程式邏輯之上。


Agent 不一定要使用聊天式大型語言模型


接著,講者以客服案件分流說明:Agent 雖然經常使用 LLM,但不必只依賴聊天模型。他使用決策模型,先定義「帳務」、「技術支援」、「帳號存取」與「人工審查」等選項及其判斷條件,再讓模型依據使用者描述選出適合的類別,並提供各選項的分數或信心資訊。


例如,使用者表示訂閱費用被收了兩次,Agent 便將案件分到帳務團隊;付款成功卻找不到發票的問題,也會被判定為帳務相關。這個範例的重點不在生成一段長篇回答,而在依照明確的選項與條件完成路由。


示範錄製時,講者所使用的 AI 擴充套件尚未內建所需的決策協定支援,因此他先建立自己的 Decision Client 與支援案件分流的 Agent,實作一般執行及串流所需的方法。這個自訂 Agent 同樣可以在本機執行,也能註冊協定、打包成 Hosted Agent 並部署到 Foundry。


部署後,決策型 Agent 一樣能使用 Foundry 的追蹤與版本功能。講者還示範啟用語音模式,直接口述搜尋功能故障的問題,讓 Agent 判斷應交由技術支援處理。他也指出,示範中的結果以 JSON 呈現,若要設計良好的語音體驗,不應直接把整段 JSON 念給使用者;但這個實驗說明了 Foundry 的平台功能可以套用在非聊天式模型的 Agent 上。


更換模型,不必改變整體部署思路


另一個範例使用部署在 Foundry 中的 Kimi 模型,搭配工具執行計算與字詞統計等工作。講者從對話追蹤中查看 Agent 呼叫模型、執行工具、再返回模型產生結果的過程。這說明 Hosted Agent 的程式邏輯與託管方式,不限於單一模型供應者;開發者可以依需求選擇不同模型,或在 Agent 中整合多種工具與系統。


在身分與安全方面,講者特別建議盡可能使用受控身分等身分驗證機制,避免在程式碼中放入 API 金鑰。部署 Agent 時,除了讓服務能執行,也必須考慮由誰呼叫、Agent 以什麼身分存取資源,以及需要哪些安全界線。


整場分享從約十行程式碼的本機 Agent 出發,逐步加入服務端點,再將相同的核心邏輯部署為 Foundry Hosted Agent。講者的核心觀點是:Agent 是由開發者掌握的程式碼;託管改變的是它執行與被管理的方式,不必改變它的核心邏輯。 有了 Foundry 提供的版本、追蹤、監控、安全與整合能力,開發者便能在維持本機開發與測試流程的同時,讓 Agent 更有條理地走向正式環境。

2026年11月11日 星期三

[AI 觀點] Linux 核心撤除 AI 標註規定:工具不是問題,責任才是關鍵

[AI 觀點] Linux 核心撤除 AI 標註規定:工具不是問題,責任才是關鍵

摘要:Linux 核心撤除 AI 使用標註要求,引發工具使用與開發者責任之爭。



內容:


2026 年 10 月,Linux 核心社群決定撤除一項實施不到一年的規定:開發者提交程式碼時,不必再於變更紀錄中標註是否使用人工智慧工具。這項決定引發爭論。反對者擔心,缺少揭露機制會讓維護者更難辨識不負責任的提交;Linux 創始人林納斯·托瓦茲則主張,審查重點應是開發者是否理解並能為程式碼負責,而不是他使用了什麼工具。


爭議始於 2026 年初。Linux 核心 7.0 版的開發文件加入〈寫程式助手指南〉,要求開發者若使用進階寫程式工具協助開發或修補,便在變更紀錄中加入「Assisted-by」標籤,說明所用模型。根據 Linux 週刊主編強納森·科貝特的統計,Linux 7.3 版共合併 18,533 筆非合併變更,其中 1,884 筆帶有這項標籤,約占一成。


標註數量雖然可觀,部分維護者卻質疑它的實際價值。紅帽公司的核心維護者傑夫·雷頓於 7 月提議廢除標籤,理由包括:規定無法確保所有人如實遵守;標籤可能使核心變更紀錄成為商業模型的免費宣傳;更重要的是,知道開發者用了哪個模型,既不能證明程式碼正確,也不能取代漏洞審查。


穩定版核心維護者葛雷格·克羅哈特曼起初反對廢除,認為標籤至少能提示哪些變更值得特別留意。但到了 10 月 8 日布拉格核心維護者峰會,他改變立場,認為這套做法無法有效運作,反而增加維護者負擔。峰會形成共識後,維護者薩莎·萊文隔天送出修補程式,刪除提交指南中強制加入「Assisted-by」標籤的要求。


影像子系統開發者洛朗·潘夏爾反對直接拿掉規定。他擔心有人不理解核心運作,只把大型語言模型產生的內容複製貼上至郵件列表,成為他所稱的「人肉代理」(meat proxies)。他主張,即使取消原有標籤,也應建立其他揭露或約束機制,例如要求提交說明由人類親自撰寫,藉此表明提交者確實參與並理解工作。他也指出,規則未必能百分之百防弊,但仍能向社群清楚傳達期望;開發者的 Signed-off-by 簽名同樣無法從技術上杜絕作弊,卻仍有其規範意義。


這份憂慮並非憑空而來。科貝特的峰會記錄提到,Linux 7.3 版週期出現將近 800 名首次貢獻者;其中有些人在短時間內跨多個子系統提交複雜變更,卻未必理解修改內容,也不一定會留下來維護。對維護者而言,真正令人不安的是程式碼湧入的速度,可能超過社群理解、審查與長期維護的能力。


然而,核心安全維護者基斯·庫克質疑,靠提交說明的文風辨識 AI 並不可靠。他指出,自己也曾與模型協作撰寫提交紀錄,還曾把另一位維護者親手寫的詳細說明誤認為模型產物。人類撰寫與工具協助之間,已很難僅憑肉眼劃出清楚界線。


10 月 13 日,林納斯·托瓦茲回信反駁禁止工具的思路。他以反諷方式表示:若認為不用 AI 才能證明開發者理解程式碼,那是否也該禁止編譯器,要求所有人手寫組合語言?若要確保提交說明是親手寫的,是否連可修改錯字的文字編輯器都該禁止?他的核心論點是,社群應要求貢獻者全力以赴、真正理解自己的修改,不能把「未使用某種工具」誤當成能力證明。


到了 10 月 26 日,面對大型語言模型輸出不具確定性的質疑,托瓦茲再次指出,人類提交的結果同樣不具確定性,不同版本的編譯器也可能產生不同輸出;單靠這一點,無法構成全面排斥工具的理由。在他看來,問題是有人不理解核心,卻提交自己無法解釋或維護的變更,而不是工具本身。


企業界也出現相近觀點。Shopify 執行長託比·呂特克認為,反 AI 討論有時高估了人類日常撰寫程式碼的品質:人寫的程式碼一樣可能粗糙,AI 工具也可能協助改善。這並不表示 AI 產出可以免於審查,而是提醒人們,純手寫不等於高品質,使用工具也不必然代表缺乏能力。


目前開源社群對此仍有不同路線。System76 的桌面環境與 NetBSD 等專案,選擇限制提交 AI 生成的程式碼;Linux 核心則撤除強制標註要求,採取更重視成果與責任的做法,並嘗試運用 Sashiko 等代理式工具協助審查。


撤除標籤不代表撤除責任。Linux 核心仍保留開發者來源證明與 Signed-off-by 要求。當工具能迅速產生大量程式碼,工程師的價值不只在於寫出程式,更在於理解修改、說明判斷、修正問題,並以自己的名字承擔後果。

[.NET11 新功能] .NET 11 System.Text.Json:聯集、封閉類別與 AOT 支援

[.NET11 新功能] .NET 11 System.Text.Json:聯集、封閉類別與 AOT 支援

摘要:整理 .NET 11 System.Text.Json 的聯集與封閉類別支援、來源產生器改進及新增的 JSON 註記功能。



內容:


Microsoft .NET Libraries 團隊的首席工程師 Eric 介紹了 .NET 11 中 System.Text.Json(STJ)的更新。今年的重點包括支援聯集型別與封閉類別、改善來源產生器和原生 AOT 的使用體驗、提供更具表達力的型別註記,以及修正可靠性問題。團隊共合併 60 個 PR,其中 17 個涉及新功能或 API、16 個改善來源產生器與 AOT、8 個改善效能、19 個修正錯誤。


聯集型別的序列化與反序列化


STJ 能辨識聯集型別,並直接依照目前聯集成員的型別輸出 JSON。例如,一個可容納整數、字串或一般物件的聯集,序列化後分別會得到 JSON 數字、字串或物件;不會額外加入包裝結構或型別識別欄位。


反序列化時,STJ 通常根據 JSON 值的類型選擇聯集成員。因此,包含數字、字串與物件的 JSON 陣列,可以反序列化成對應的聯集陣列。但這也帶來限制:如果多個聯集成員都對應同一種 JSON 類型,STJ 就無法僅憑 JSON 值判斷目標型別。例如 DateOnly 與 Guid 都會表示為 JSON 字串;由兩者組成的聯集可以正常序列化,反序列化時卻可能因目標型別不明確而失敗。


STJ 採取這種保守的型別推斷方式,是為了避免不受限制的多型反序列化所帶來的安全問題。若需要處理同一 JSON 類型對應多個 .NET 型別的情況,可以註冊型別分類器,讓程式檢查 JSON 內容,再決定要繫結至哪個型別。例如,分類器可以嘗試將字串解析為日期或 GUID,並據此選擇聯集成員;若兩者都不符合,則回報錯誤。


對於由不同物件組成的聯集,STJ 也提供內建的結構分類方式。以 Dog 與 Cat 兩種物件為例,若它們具有足以區分彼此的屬性,反序列化時就能依 JSON 中出現的屬性選擇型別。這種分類只看屬性結構,不會深入分析屬性值,因此較為脆弱:一旦兩種物件日後新增相同屬性,原本可區分的結構就可能變得模糊。


封閉類別與多型 JSON 契約


講者接著介紹封閉類別。過去若要序列化多型的事件模型,通常必須為每個衍生型別明確加入 JsonDerivedType 等註記,並設定型別識別值。一般類別的繼承階層是開放的,編譯時無法得知所有可能的衍生型別,因此需要逐一宣告。


封閉類別的衍生型別則限定於目前的編譯範圍,編譯器可以提供完整清單。STJ 因而能在啟用封閉型別多型推斷後,自動取得這些衍生型別,減少隨模型擴大而增加的註記程式碼。


若能自行決定 JSON 契約的設計,講者建議優先使用封閉類別階層來表達多種選擇。封閉類別同樣能提供窮舉處理的保證,且可使用型別識別欄位,使結構在日後擴充時較為穩定。聯集型別不使用這類識別欄位;當它必須依內容或屬性結構判別型別時,契約演進便較容易受到影響。若既有結構無法更改,例如必須與 TypeScript 風格的聯集資料互通,聯集仍是合適選擇。


來源產生器與原生 AOT 改進


.NET 11 縮小了 STJ 反射式序列化與來源產生器之間的功能差距。過去,透過 JsonConstructor 或 JsonInclude 存取私有成員的情境,來源產生器未能完整支援;現在可在較新的執行環境利用 unsafe accessors 存取,在較舊環境則透過相容方式處理,同時兼顧 AOT 安全性。


init 屬性的指派行為也獲得改善。過去即使 JSON 中沒有對應屬性,來源產生器仍可能指派預設值,導致原有資料被覆蓋;現在可以依欄位是否出現在 JSON 中,有條件地進行指派。此外,JSON 序列化內容類別可宣告為 partial,方便將較大的內容定義拆分至多個檔案。


另一項重要改進是泛型轉換器註記。以往泛型物件若需要自訂 JSON 轉換器,往往還得撰寫使用反射的 JsonConverterFactory;這不但程式碼繁複,也不利於原生 AOT。新版允許在轉換器註記中使用開放泛型宣告,由來源產生器補入對應的泛型參數。使用時,轉換器的泛型參數必須與目標型別相符。


更具表達力的 JSON 註記


.NET 11 新增可套用於型別的命名原則註記,使特定型別的 JSON 屬性名稱不再完全取決於執行時的 JsonSerializerOptions。例如,可以要求某個型別的屬性一律使用 camelCase,同時針對個別屬性覆寫為 kebab-case。這有助於讓 JSON 契約不受外部選項變動影響。


原本主要用於個別屬性的忽略條件註記,也擴充至型別層級。若希望某個型別的所有 null 屬性都不輸出,便不必在每個屬性上重複加入相同註記。


其他更新與使用建議


其他更新包括:新增 JSON Lines 序列化支援,以補齊既有的反序列化能力;加入 F# 辨識式聯集的內建支援,但講者提醒聯集支援目前使用反射,尚不支援來源產生器;支援泛型多型階層及相關的開放泛型註記;新增 PascalCase 命名原則,以及支援 .NET 11 新增的數值與集合型別。


整體而言,這次更新一方面讓 STJ 配合新的語言型別模型,另一方面著重減少來源產生器與 AOT 情境中的限制。實際設計資料模型時,若 JSON 結構由自己掌控,應優先考慮可使用型別識別欄位的封閉階層;若必須遵循既有、沒有識別欄位的 JSON 結構,再選用聯集,並留意反序列化時的型別歧義。

[.NET11 技術解析] .NET MAUI 在 .NET 11 轉向 CoreCLR:原理、效能與遷移重點

[.NET11 技術解析] .NET MAUI 在 .NET 11 轉向 CoreCLR:原理、效能與遷移重點

摘要 : .NET MAUI 在 .NET 11 改用 CoreCLR,整合診斷能力,並透過 Ready to Run 改善行動應用體驗。



內容:


.NET MAUI 與 .NET 工程師 John Peppers 分享了 .NET MAUI 在 .NET 11 轉向 CoreCLR 的歷程。這項工作橫跨多年、涉及多個團隊,核心目標是讓行動應用開發不再像獨立於 .NET 之外的體系,而是真正融入統一的 .NET 平台。他以較熟悉的 Android 為主要例子說明;其中許多概念也適用於 iOS。


從 Xamarin 走向統一的 .NET


過去的 Xamarin 由多個部分組成:Xamarin.Forms 讓開發者使用 XAML 或 C# 建立跨平台介面,角色相當於今日的 .NET MAUI;Xamarin.Android 與 Xamarin.iOS 負責銜接各平台 API,並將應用程式封裝成可提交至商店的形式;底層則使用開源的 Mono 執行階段及其基礎類別庫。由於 Xamarin 原本有一套平行實作,微軟在 2016 年收購 Xamarin 後,便持續推動行動平台與 .NET 的整合。


2020 年左右,Mono 執行階段的程式碼被移入 .NET runtime 儲存庫,並保留原專案歷史。隨後 .NET 6 於 2021 年推出,.NET MAUI 先以預覽版亮相,再於隔年 5 月正式發布。團隊也提供升級工具,協助使用者從 Xamarin 遷移。儘管如此,行動應用底層仍持續使用 Mono;轉向 CoreCLR,是這段整合歷程的重要一步。


執行階段究竟負責什麼?


C# 程式碼編譯後會成為組件中的中介語言(IL)。.NET 執行階段負責載入並執行這些程式碼,依裝置架構與作業系統運作,同時提供垃圾回收、型別系統及例外處理等能力。它也透過基礎類別庫銜接底層平台:例如開發者呼叫 System.IO.File 時,.NET 會使用所在作業系統對應的功能完成檔案操作。


CoreCLR 是已廣泛用於 .NET 伺服器與桌面應用的成熟執行階段。將它帶到 .NET MAUI,除了統一底層架構,也能讓行動應用逐步受益於相同的偵錯、追蹤及診斷能力。過去 Mono 有自己的偵錯器,部分追蹤事件及當機報告的呈現方式也與 CoreCLR 不同;改用 CoreCLR 後,這些工具體驗有機會更加一致。


切換後,應用程式如何啟動?


行動應用首先由作業系統啟動,接著才輪到 .NET 程式碼執行。以 Android 為例,建置程序會替應用程式設定一個用來啟動 .NET 執行階段的 Content Provider,使執行階段能在主要 Application.OnCreate 等生命週期程式碼執行前就緒。之後,Android 才會呼叫應用程式及 Activity 的相關方法。這些細節多半已由專案範本和建置工具處理,開發者通常不必手動介入。


在這個啟動環節,底層原本會啟動 Mono,改版後則啟動 CoreCLR。團隊希望這項替換對一般開發者盡可能透明:既有 C# 程式碼與大多數 NuGet 套件,原則上應維持原有行為。


兩種執行階段仍有較底層的差異。Mono 提供豐富的嵌入 API,讓原生 C 或 C++ 程式碼載入型別、呼叫方法並檢查 .NET 型別;CoreCLR 沒有完全對等的 API 集合。因此,依賴 Mono 嵌入 API 的特殊專案,例如某些遊戲引擎整合方式,可能需要重新設計。不過,這通常不是一般 .NET MAUI 應用會碰到的層次。


Mono AOT、Ready to Run 與 Native AOT 有何不同?


這次轉換容易令人混淆的地方,是「AOT」並非只指一種技術。一般而言,C# 程式碼先編譯成 IL;若在執行時由即時編譯器將 IL 轉為原生碼,稱為 JIT。Android 可以使用 JIT;但 iOS 對執行記憶體中的即時產生程式碼有限制,因此不能以相同方式使用純粹的 JIT,必要時也會用到直譯器。相對地,預先編譯(AOT)會在建置期間先產生原生碼。


過去使用 Mono AOT 時,建置工具 mono-aot-cross 會處理應用程式的 DLL,並為各 DLL 產生對應的原生函式庫。改用 CoreCLR 後,對應的重要技術是 Ready to Run(R2R):CrossGen2 會在建置時產生原生碼,並將其放入仍含有 IL 的 DLL。這也是啟用 Ready to Run 後,DLL 可能變大的原因。


在 Android 上,.NET MAUI 預設採用部分 Ready to Run,而非將所有程式碼都預先編譯。團隊透過具有代表性的 MAUI 應用記錄啟動時用到的方法,將資料保存為 MIBC 檔案,交給 Ready to Run 編譯器使用。如此可優先加快啟動路徑,同時避免完整預先編譯造成應用程式體積大幅增加。開發者仍可評估自己的應用,決定是否以較大的檔案換取完整 Ready to Run 可能帶來的效能收益。iOS 則因無法以相同方式使用 JIT,沒有採用 Android 的這種部分 Ready to Run 預設方案。


應用程式的封裝方式也會隨之變化。在 Android 上,.NET 組件會被放入名為 libassemblystore 的原生函式庫中,便於依裝置架構封裝與配送。使用 CoreCLR 時,其中除了 IL,也可能包含 Ready to Run 映像;一般開發者通常不需要直接處理這些內部檔案。


Native AOT 是另一條路,不等於這次的 CoreCLR 切換


Native AOT 不只是「把 Ready to Run 開得更完整」,而是另一種執行階段與建置模式。它在建置時完成編譯,不依賴 JIT,並積極移除未使用的程式碼。其成果通常是啟動更快、體積更小的應用;例如 Android 可產生一個包含執行階段與已編譯 .NET 程式碼的 .so 檔案。不過,在這場分享所述的階段,Android 上的 Native AOT 仍屬預覽功能。


Native AOT 也要求開發者更仔細處理動態行為。例如,執行時才從磁碟載入 DLL 的外掛模式,便不適合直接沿用。序列化也是常見例子:若程式依賴 System.Text.Json 在執行時動態檢查物件屬性,程式碼裁剪器可能無法判斷哪些成員必須保留;改用 System.Text.Json 的原始碼產生器,則能提供明確的型別資訊,使裁剪與 Native AOT 建置更可靠。既有應用若要導入 Native AOT,除了自身程式碼,也須檢查第三方 NuGet 套件是否相容。


目前觀察到的效能與體積變化


John 展示了以 Samsung Galaxy A16 等裝置測試的代表性結果,涵蓋 MAUI 專案範本、加入更多控制項與圖表的範例,以及實際以 .NET MAUI 建置的 Microsoft 365 Admin 應用。在 Android 上,Mono、預設的部分 Ready to Run,以及完整 Ready to Run 的啟動表現大致可比;部分與完整 Ready to Run 哪個較快,會因應用而異。在 iOS 的測試中,展示的應用啟動時間則普遍有所改善。


體積方面,Android 的部分 Ready to Run 與原先 Mono 版本相近,完整 Ready to Run 通常會讓 APK 稍微變大;展示的 iOS 測試則呈現應用程式體積縮小的趨勢。這些結果並不保證每個應用都會相同,因此團隊希望開發者以自己的實際專案測量;若出現明顯異常,也歡迎回報以便調查。


升級至 .NET 11 時應檢查什麼?


分享指出,在 .NET 10 中,開發者可透過將 UseMonoRuntime 設為 false,以實驗性方式試用 CoreCLR;而在其介紹的 .NET 11 預覽版規劃中,切換目標框架即可預設改用 CoreCLR。CoreCLR 自 Preview 4 起成為預設,Preview 6 起則移除了在 .NET 11 中指定 Mono 的能力。若遇到必須依賴 Mono 的罕見相容性問題,團隊希望開發者回報,並可暫時留在 .NET 10。


理想情況下,升級不需要修改應用程式碼,但仍應完整執行測試,特別留意啟動時間、記憶體用量、安裝檔大小及實際功能行為。診斷流程也值得重新確認:改用不同執行階段後,當機報告與堆疊追蹤的格式或內容可能有所不同。若專案使用了 Mono 嵌入 API,則需要另外評估替代設計;特定原生程式碼呼叫 .NET 的情境,也可研究 Native AOT 與 UnmanagedCallersOnly 是否適用。


從 Xamarin、.NET MAUI 到 CoreCLR,這次轉換的方向始終一致:讓行動應用成為 .NET 平台更完整的一部分。團隊期望開發者在盡量不更動既有程式碼的前提下,取得更一致的執行階段、診斷能力與具競爭力的效能,並持續推進 Native AOT 等後續工作。

2026年11月10日 星期二

[AI 趨勢] AI 降低創業門檻後,什麼能力變得更值錢?

[AI 趨勢] AI 降低創業門檻後,什麼能力變得更值錢?

摘要:YC 觀察到獨自創業、硬科技與營收成長加速;當寫程式碼變便宜,產業經驗與選題能力更顯珍貴。



內容:


二十年前,Y Combinator(YC)聯合創始人 Paul Graham 曾把「一個人創業」列為可能害死新創公司的頭號錯誤。如今,YC 總裁 Gary Tan 卻在播客《Lightcone》中說:「YC 自己就是獨自創業者的 YC。」這並不代表找搭檔已經過時,而是 AI 正在改變創業初期需要多少人、哪些能力最難取得。


**第一個變化,是獨自創業者變多了。**YC 合夥人 Diana 表示,過去獲選公司的獨自創業者約占 5%,如今已超過 18% 至 19%,接近原來的四倍。過去創業往往需要一人擅長銷售與推廣,另一人具備頂尖技術能力;兩種本事很難集中在同一人身上,因此找一位技術共同創始人,常被視為必要條件。現在,AI 程式設計代理能承擔更多開發工作,創業者更有機會先把產品做出來。難題也因此轉向:要知道該解決什麼問題、該讓 AI 做什麼,以及什麼產品值得做。


不過,YC 仍認為共同創始人是加分項。Instacart 和 Coinbase 進入 YC 時都只有一位創始人,之後才找到搭檔。因此,更貼切的說法或許是「先一個人開工,跑通後再找人」。數據也須留意口徑:有人逐一統計某屆冬季班的 199 家公司,其中 22 家是獨自創業,約占 11%,低於 YC 提到的比例;但相較於過去約 5%,增加的方向一致。


**第二個變化,是資深創業者更能發揮優勢。**Gary Tan 說,他最近見到的一些出色創始人,年齡在三十多、四十多歲,甚至五十多歲。他舉 OpenCLAW 作者 Peter Steinberger 為例:對方四十歲,先前有十年經營公司的經驗。合夥人 Jared 指出,管理 AI 程式設計代理,在某些方面與管理人員相似:都要拆解任務、訂立標準、驗收成果,並預判容易出錯之處。這些能力往往來自長年的實務經驗。當「不會寫程式碼」或「找不到技術搭檔」不再像以前那樣難以克服,熟悉一個行業的人,也更有機會親手驗證自己的想法。


**第三個變化,是更多新創走向硬科技。**Diana 表示,YC 批次中硬科技公司的占比,從約 8% 升至 20%。機器人、工廠、國防、晶片與電力等領域,都出現顯著增長。YC 將這股趨勢稱為「從位元走向原子」:不只開發螢幕裡的軟體,也開始處理真實世界中摸得到的設備與設施。


AI 讓軟體更容易開發,為什麼反而有利於硬體?原因之一是,許多硬科技公司的瓶頸不只在硬體本身。無人機或機器人背後,都需要控制系統、韌體及後台軟體;過去要組建足夠強的軟體團隊,往往得與大型科技公司爭奪人才。如今,少數熟練工程師配合 AI,可能就能完成更多開發工作,讓團隊有餘力處理製造與現場部署等難題。


YC 提到的 Knox Metals,便在底特律閒置多年的老工廠中重新從事金屬加工。Paul Graham 曾形容,這家金屬加工公司的成長速度像軟體公司;其需求來自一批急需供貨、傳統供應商卻難以跟上的國防科技新創。節目還提到,某屆夏季班的創始人約每六人就有一位博士,顯示不少人選擇挑戰技術門檻更高的題目。


**第四個變化,是新創更快取得收入,產品也從「工具」走向「完成工作」。**YC 投資的公司剛加入時,通常尚無收入;三個月後參加 Demo Day,過去月收入中位數約為 8,000 美元,如今約為 20,000 美元,是原來的兩倍半。節目也提到,部分公司能在這三個月內,從零成長至百萬美元級別營收;過去達到類似規模,可能需要十八個月以上。


YC 同時觀察到,提供「端到端完成一份工作」服務的公司,占比從約 10% 升至超過 25%。招聘產品 Juicebox 是一個例子:過去它主要幫客戶找到符合條件的人選,後續聯絡、安排面試仍由招聘人員處理;現在,其 AI 代理也能發訊息、約面試,接手更多流程。同一位客戶願意支付的費用可能因此增加兩到三倍,而招聘人員則能把時間用在判斷人選是否適合團隊等較難交給 AI 的工作。


這也是 YC 對「軟體已死」的回應:軟體並沒有消失,而是在改變角色。它可以像員工一樣直接把事辦完,也可以成為讓 AI 穩定工作的環境與流程,也就是所謂的 harness。客戶付費購買的,越來越不只是操作工具的權利,而是工作完成的結果。


**第五個變化,是模型所需的資料與練習環境,正在形成生意。**Jared 表示,過去兩年 YC 投資了十多家相關公司,每家年收入都超過一千萬美元,部分達到數億美元。它們主要向大模型公司提供兩類東西:資料,以及強化學習環境。


強化學習環境可以理解為 AI 的模擬考場。例如建立一套虛構的公司財務系統,放入帳本、發票和待辦事項,讓 AI 練習對帳、報稅,並依結果自動評分。要讓 AI 學會處理真實工作,除了讀取資料,還需要能反覆操作、檢驗對錯的場景;環境越貼近現實,就越有價值。機器人領域也有類似需求:有公司在各地工廠,以第一人稱視角拍攝工人的操作,再將資料提供給機器人實驗室。YC 認為,部分業者不願公開談論這門生意,正因為不想讓競爭對手注意到機會;YC 於 2016 年投資的 Scale AI,則是這個領域的早期代表。


這五個變化,可以用同一條線串起來:**當寫程式碼的成本下降,人才與資金會流向仍然昂貴、仍然困難的地方。**一個人更容易開始創業;多年產業經驗更容易轉化為產品;硬科技團隊能跨過部分軟體門檻;軟體能直接交付工作成果;而越強的模型,越需要真實資料與練習環境。


當然,這些數字主要來自 YC 自己挑選與投資的公司,不能直接代表所有新創。若 YC 主動增加硬科技投資,批次中的硬科技比例也自然會上升。即使如此,背後的問題仍值得思考:當技術實作變得更容易,你在一個行業裡累積的判斷——客戶真正頭痛什麼、哪些坑不能踩、什麼需求不值得做——或許比「會不會寫程式碼」更難被取代。

[AI 分享] 從單一對話到多代理協作:四招打造 AI 工作流程

 [AI 分享] 從單一對話到多代理協作:四招打造 AI 工作流程

摘要:文章分享以總管代理、技能庫、系統串接與任務複述,建立可重複運作的 AI 協作流程。



內容:


一段談論 AI 工作方式的影片近期引發討論。原文介紹,一名網名「土豆女神」的工程師 Lauren Tan,展示了由一個 AI 協調多個 AI 代理(Agent)處理工作的做法。原文也提到「指揮 50 多個 AI」、「完成 20 多名工程師的工作量」及「公司九成以上員工都在使用」等說法;這些是原文轉述的宣稱,不能僅憑這段文字確認。


這套方法的核心,不是同時打開更多對話視窗,而是把常做的工作拆分、交給不同代理執行,並建立可重複使用的規則。原文將其歸納為以下四個做法。


第一招:先建立「總管」代理,讓 AI 協調 AI。 與其自己在多個視窗之間分派、追蹤任務,不如先設定一個負責規劃的總管代理。它不必親自完成每項工作,而是先詢問使用者:每天最耗時的三件事是什麼?哪個環節最容易卡住?釐清需求後,再協助設計分工,例如讓不同代理分別負責蒐集資料、修改程式碼及撰寫通知。


在原文描述的流程中,總管也會定期檢查各代理的產出,發現偏離目標或耗用過多資源時調整任務。這樣做的目的,是讓使用者從逐一指揮每個步驟,轉向設定目標、檢查結果與處理例外狀況。


第二招:把做成功的方法存成獨立技能。 原文認為,最值得養成的習慣,是在 AI 完成一項工作後,不只保留結果,也把有效的處理方法整理成可複用的說明。做完會議紀要,就記下整理格式與判斷規則;排除一次系統故障,就記下檢查順序;將長文改寫成短影片腳本,也可以留下轉換步驟和品質標準。


這些「技能」可以存放在私有程式碼倉庫或其他知識庫。日後遇到類似任務,便能提供既有規則,減少反覆解釋背景與要求的時間。原文強調的是:不要讓一次成功的對話,隨著視窗關閉而消失。


第三招:串接帳號與工具,減少人工複製貼上。 如果一項工作需要人把郵件內容複製給 AI,再把 AI 的結果貼進表格、手動新增日程,流程仍高度依賴人工搬運。原文提出,可讓負責特定任務的代理透過工作帳號與系統連接器,直接取得所需資訊並執行後續步驟。


例如,收到客戶郵件後,由代理擷取關鍵資訊、更新線上表格,再建立跟進提醒。這類自動化的價值在於讓資訊於工具之間順暢流動。不過,實作時仍應依任務給予必要權限,並對寄信、修改資料等重要動作設置審核,避免因授權過大或判讀錯誤造成損失。


第四招:交代任務時說清楚背景,並要求 AI 先複述。 原文指出,AI 答非所問,有時不是缺少複雜的提示詞,而是使用者沒有交代完整情境。對於背景較多的任務,可以用語音一次說明業務場景、對象、現有資料,以及怎樣的成果才算合格。


交代完後,先請 AI 用自己的話重述需求,不要立刻開始執行。如果它理解正確,再讓它開工;若有遺漏或誤解,就先修正。這一步旨在把確認需求的工作提前,減少完成後才發現方向錯誤、反覆修改的情況。


整體而言,原文想傳達的觀點是:AI 的用途不只是一問一答,也可以透過分工、保存方法、串接工具與確認需求,形成較穩定的工作流程。真正需要人持續把關的,則是任務目標、權限範圍與最終成果的品質。

2026年11月9日 星期一

MFA015 - Responses API 代理

MFA015 - Responses API 代理

在上一篇關於 Microsoft Agent Framework 系列教學文章 "MFA014 - 多模態輸入(圖片)" 中,我們學會了使用 Microsoft Agent Framework 建立能夠處理圖片輸入的 AI 代理。讓模型接收到多模態的資料,在這裡使用的圖片,這裡的圖片將會是冰箱的照片。透過這個練習,我們了解了如何讓 AI 代理分析圖片內容並給出建議。


在完成這篇文章的練習之後,你將能夠使用 Microsoft Agent Framework 建立一個能夠處理多輪對話的 AI 代理,並了解 Responses API 與 Chat Completions 代理在多輪對話中的差異。所謂的 Chat Completion API 則是每輪都需要將完整對話歷史送給模型,服務端不會保存對話狀態。而對於 Responses API,服務端會保存對話狀態,因此每輪不需要重送完整對話歷史,且能保留上一輪的推論摘要。那麼對於使用者而言,面對問題需求的時候,該如何挑選適合的代理,就可以根據是否需要服務端保存對話歷史以及對訊息數與 Token 使用量的考量來決定。

接下來看看如何做到這樣的效果。

建立 Console 專案

  • 開啟 Visual Studio 2026
  • 選擇「建立新專案」
  • 在 [建立新的專案] 視窗中,在右方清單內,找到並選擇「主控台應用程式」 項目
  • 然後點擊右下方「下一步」按鈕
  • 此時將會看到 [設定新的專案] 對話窗
  • 在該對話窗的 [專案名稱] 欄位中,輸入專案名稱,例如 "csResponsesAgent"
  • 然後點擊右下方「下一步」按鈕
  • 接著會看到 [其他資訊] 對話窗
  • 在這個對話窗內,確認使用底下的選項
    • 架構:.NET 10.0 (或更新版本)
    • 勾選 不要使用最上層陳述式 (這是我的個人習慣)
  • 然後點擊右下方「建立」按鈕
  • 現在,已經完成了這個 Console 主控台 專案的建立

安裝需要用到的 NuGet 套件

要完成這個第一次接觸 Microsoft Agent Framework 練習專案,需要安裝以下幾個 NuGet 套件:

Microsoft.Agents.AI.OpenAI

這個套件是微軟官方為 .NET 整合 AI 服務所提供的擴充套件,專門用於與 OpenAI 進行整合。它實現了 IChatClient 與 IEmbeddingGenerator 核心抽象介面,讓開發人員能以一致的程式碼,輕鬆串接 OpenAI 或相容的 API 端點(例如 Azure OpenAI、GitHub Models 等)。

  • 滑鼠右擊 [csResponsesAgent] 專案節點
  • 點選彈出功能表的 [管理 NuGet 套件] 項
  • 在 [瀏覽] 索引標籤中,搜尋並且安裝底下的 NuGet 套件
    • Microsoft.Extensions.AI

宣告 API 端點與 Key 環境變數

關於這個小節的內容,將會在這個系列每篇文章中都會用到,因此,可以參考 MFA001 Microsoft Agent Framework 初次體驗開發出 AI 代理 文章中的 [宣告 API 端點與 Key 環境變數] 說明內容,若這裡提到的環境變數已經建立好了,則可以跳過這個小節內容。

修改 Program.cs 程式碼

為了要完成這個需求 : 如何用 Azure OpenAI 的 gpt-5.6-luna 聊天模型建立一個簡單 AI 代理,並產生一篇與鵝有關的詩。 因此,我們需要修改 Program.cs 程式碼,這時需要完成底下的操作。

  • 在專案內,找到並且開啟 [Program.cs] 檔案
  • 使用底下程式碼內容來取代 [Program.cs] 檔案的內容
#pragma warning disable OPENAI001 // OpenAI SDK 2.14 的 ResponsesClient 仍標記為實驗性 API

using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI;
using OpenAI.Chat;
using OpenAI.Responses;
using System.ClientModel;
using System.Text;
using ChatMessage = Microsoft.Extensions.AI.ChatMessage; // OpenAI.Chat 也有 ChatMessage,這裡指定用 MEAI 的

namespace csResponsesAgent;

internal class Program
{
    private const string Instructions = "你是邏輯老師,請用繁體中文作答,先給結論再用三句話內說明理由。";

    // 同一道邏輯題:三種情境都問它,數字才有比較基礎
    // 刻意選一題「需要想一下」的題目:推論太短(約 200 token 以內)時,服務端常常不回推論摘要
    private const string Puzzle =
        "有四個盒子,標籤分別是「蘋果」「橘子」「香蕉」「混合」,但四個標籤全部貼錯了。" +
        "你只能從其中一個盒子拿出一顆水果來看(不能看盒子裡面)," +
        "能不能保證推出四個盒子的正確內容?請說明。";

    // 第二輪追問:故意不重述規則,只有記得上一輪的代理才答得出來
    private const string FollowUp = "那如果只有三個盒子(蘋果、橘子、混合),規則都一樣,應該從哪個盒子拿?";

    // 由聊天用戶端中介層記錄:這一輪「實際送出」幾則訊息、帶出去的對話 id 是什麼
    private static int lastSentMessages;
    private static string? lastSentConversationId;

    static async Task Main(string[] args)
    {
        Console.OutputEncoding = Encoding.UTF8;

        var apiKey = Environment.GetEnvironmentVariable("AzureOpenAI_Key");
        var endpoint = Environment.GetEnvironmentVariable("AzureOpenAI_Endpoint");
        var model = "gpt-5.6-luna";

        if (string.IsNullOrWhiteSpace(apiKey) || string.IsNullOrWhiteSpace(endpoint))
        {
            Console.WriteLine("請先設定環境變數 AzureOpenAI_Key 與 AzureOpenAI_Endpoint 後再執行。");
            return;
        }

        // ── Responses API 代理:ResponsesClient + AsAIAgent()(Microsoft.Agents.AI.OpenAI 提供的擴充方法)
        AIAgent responsesAgent =
            new ResponsesClient(
                    new ApiKeyCredential(apiKey),
                    new ResponsesClientOptions { Endpoint = new Uri(endpoint) })
                .AsAIAgent(
                    model: model,
                    instructions: Instructions,
                    name: "邏輯老師(Responses)",
                    clientFactory: client => client.AsBuilder().Use(CountMessages, null).Build());

        // ── Chat Completions 代理:就是 MFA001~MFA003 用的 ChatClient,只是改用同一個套件的 AsAIAgent() 建立
        AIAgent chatAgent =
            new ChatClient(
                    model,
                    new ApiKeyCredential(apiKey),
                    new OpenAIClientOptions { Endpoint = new Uri(endpoint) })
                .AsAIAgent(
                    instructions: Instructions,
                    name: "邏輯老師(Chat Completions)",
                    clientFactory: client => client.AsBuilder().Use(CountMessages, null).Build());

        Console.WriteLine($"使用的模型:{model}");
        Console.WriteLine($"邏輯題:{Puzzle}");

        // ── 示範一:設定 reasoning effort 與推論摘要,同一道題比較 Low 與 High
        Header("示範一:Responses API + 推論摘要(同一道題,effort = Low vs High)");

        foreach (var effort in new[] { ReasoningEffort.Low, ReasoningEffort.High })
        {
            Section($"reasoning effort = {effort}");
            AgentResponse response = await responsesAgent.RunAsync(Puzzle, options: ReasoningRunOptions(effort));
            PrintReasoningAndAnswer(response);
            PrintUsage(response.Usage);
        }

        // ── 示範二:服務端保存對話(Responses API)
        Header("示範二:服務端保存對話(Responses API,只傳上一輪的 response id)");
        var responsesTurns = await RunTwoTurnsAsync(responsesAgent);

        // ── 示範三:本機保存對話(Chat Completions,MFA003 的作法)
        Header("示範三:對照組 Chat Completions(每一輪都把整段歷史重送)");
        var chatTurns = await RunTwoTurnsAsync(chatAgent);

        // ── 對照小結:只看第二輪(追問那一輪)
        Header("對照小結:第二輪(追問)");
        Row("實際送出的訊息數", $"{responsesTurns[1].SentMessages} 則", $"{chatTurns[1].SentMessages} 則");
        Row("帶出的對話 id", responsesTurns[1].SentConversationId is null ? "(無)" : "上一輪的 response id", chatTurns[1].SentConversationId ?? "(無)");
        Row("輸入 token(其中快取)", $"{responsesTurns[1].InputTokens}({responsesTurns[1].CachedTokens})", $"{chatTurns[1].InputTokens}({chatTurns[1].CachedTokens})");
        Row("推論 token", $"{responsesTurns[1].ReasoningTokens}", $"{chatTurns[1].ReasoningTokens}");
        Row("兩輪中拿到推論摘要", responsesTurns.Any(t => t.HasSummary) ? "是" : "否", chatTurns.Any(t => t.HasSummary) ? "是" : "否");
        Row("對話歷史存在哪裡", "OpenAI 服務端", "本機 AgentSession");

        // 這是最容易誤會的地方:「不重送歷史」省的是傳輸量和自己管理歷史的工作,不是 token
        Console.WriteLine();
        Console.WriteLine("  觀察:Responses 第二輪只送 1 則訊息,但服務端會把上一輪的完整內容(含推論項目)接回上下文,");
        Console.WriteLine("        所以輸入 token 照樣計費,甚至可能比 Chat Completions 多;Chat Completions 重送的只有看得見的文字,");
        Console.WriteLine("        上一輪的推論內容直接丟掉。(提示快取要前綴超過約 1,024 token 才會命中,這個範例太短所以是 0)");
    }

    // 兩種 API 都用 MEAI 的 ChatOptions.Reasoning 設定:Effort 是推論力道,Output = Summary 是要求回傳推論摘要
    private static ChatClientAgentRunOptions ReasoningRunOptions(ReasoningEffort effort) =>
        new(new ChatOptions
        {
            Reasoning = new ReasoningOptions { Effort = effort, Output = ReasoningOutput.Summary }
        });

    private static async Task<List<TurnStat>> RunTwoTurnsAsync(AIAgent agent)
    {
        AgentSession session = await agent.CreateSessionAsync();
        var turns = new List<TurnStat>();

        foreach (var (title, question) in new[] { ("第一輪:原題", Puzzle), ("第二輪:追問(不重述題目)", FollowUp) })
        {
            Section(title);
            AgentResponse response = await agent.RunAsync(question, session, ReasoningRunOptions(ReasoningEffort.Low));
            PrintReasoningAndAnswer(response);

            // Responses API 存在服務端時,ConversationId 就是這一輪的 response id;Chat Completions 則一直是 null
            var conversationId = (session as ChatClientAgentSession)?.ConversationId;
            Console.WriteLine($"  實際送出訊息數:{lastSentMessages} 則");
            Console.WriteLine($"  帶出的對話 id :{lastSentConversationId ?? "(無)"}");
            Console.WriteLine($"  回來後 session.ConversationId:{conversationId ?? "(無,歷史存在本機 session)"}");
            PrintUsage(response.Usage);

            turns.Add(new TurnStat(
                lastSentMessages,
                lastSentConversationId,
                response.Usage?.InputTokenCount ?? 0,
                response.Usage?.CachedInputTokenCount ?? 0,
                response.Usage?.ReasoningTokenCount ?? 0,
                GetReasoningSummary(response).Length > 0));
        }

        return turns;
    }

    // 聊天用戶端中介層:記下這一輪真正送到 API 的訊息數,以及帶出去的對話 id(Responses 的 previous_response_id)
    private static Task<ChatResponse> CountMessages(
        IEnumerable<ChatMessage> messages, ChatOptions? options, IChatClient innerClient, CancellationToken cancellationToken)
    {
        var list = messages.ToList();
        lastSentMessages = list.Count;
        lastSentConversationId = options?.ConversationId;
        return innerClient.GetResponseAsync(list, options, cancellationToken);
    }

    // 推論摘要放在回應訊息裡的 TextReasoningContent;答案則是一般的 TextContent(response.Text)
    private static string GetReasoningSummary(AgentResponse response) =>
        string.Join(Environment.NewLine, response.Messages
            .SelectMany(message => message.Contents)
            .OfType<TextReasoningContent>()
            .Select(reasoning => reasoning.Text)
            .Where(text => !string.IsNullOrWhiteSpace(text)));

    private static void PrintReasoningAndAnswer(AgentResponse response)
    {
        var summary = GetReasoningSummary(response);

        Console.WriteLine("  [推論摘要]");
        Console.WriteLine(summary.Length > 0
            ? Indent(summary)
            : "    (沒有收到推論摘要:Chat Completions 不提供;Responses 則在推論太短時會省略)");
        Console.WriteLine("  [答案]");
        Console.WriteLine(Indent(response.Text));
    }

    private static void PrintUsage(UsageDetails? usage)
    {
        Console.WriteLine(
            $"  token:輸入 {usage?.InputTokenCount}(快取 {usage?.CachedInputTokenCount ?? 0})" +
            $"/輸出 {usage?.OutputTokenCount}(其中推論 {usage?.ReasoningTokenCount ?? 0})" +
            $"/總計 {usage?.TotalTokenCount}");
    }

    private static string Indent(string text) =>
        string.Join(Environment.NewLine, text.Split('\n').Select(line => "    " + line.TrimEnd('\r')));

    private static void Header(string title)
    {
        Console.WriteLine();
        Console.WriteLine(new string('=', 70));
        Console.WriteLine($"  {title}");
        Console.WriteLine(new string('=', 70));
    }

    private static void Section(string title)
    {
        Console.WriteLine();
        Console.WriteLine($"【{title}】");
    }

    private static void Row(string label, string responses, string chat) =>
        Console.WriteLine($"  {label}:Responses = {responses}|Chat Completions = {chat}");
}

internal sealed record TurnStat(
    int SentMessages,
    string? SentConversationId,
    long InputTokens,
    long CachedTokens,
    long ReasoningTokens,
    bool HasSummary);

在上面的範例程式碼中,將會展示如何使用 Responses API 代理與 Chat Completions 代理,並比較它們在處理同一個邏輯題時的行為差異。為什麼會想要這麼來比較呢?因為,這裡提供兩種類型的 API,當我們遇到問題的時候,究竟要如何選擇採用哪種 API 呢?透過這個範例,我們可以觀察到兩者在訊息傳遞、對話管理以及推論摘要上的差異,進而做出更適合的選擇。

這個程式一開始將會讀取 API Key 與服務端點的數據,使用 ResponsesClient 類別 (這個類別定義在 OpenAI.Responses 命名空間中) 來建立一個 AIAgent 代理物件,變數名稱為 responsesAgent,這將會是一個採用 Responses API 代理,也就是採用 ResponsesClient + AsAIAgent()(Microsoft.Agents.AI.OpenAI 提供的擴充方法),這裡使用的系統提示詞為 "你是邏輯老師,請用繁體中文作答,先給結論再用三句話內說明理由。"

第二個 AIAgent 物件,變數名稱為 chatAgent,將會是一個採用 Chat Completions 代理(定義在 OpenAI.Chat 命名空間中),也就是使用 ChatClient + AsAIAgent() 建立,系統提示詞同樣為 "你是邏輯老師,請用繁體中文作答,先給結論再用三句話內說明理由。"

現在來針對定義在 Puzzle 這個字串的邏輯題,將其內容設為:"有四個盒子,標籤分別是「蘋果」「橘子」「香蕉」「混合」,但四個標籤全部貼錯了。你只能從其中一個盒子拿出一顆水果來看(不能看盒子裡面),能不能保證推出四個盒子的正確內容?請說明。",並將其傳遞給 responsesAgent 與 chatAgent 進行推論。單就這裡提出的提示詞而言,兩個代理都應該能夠給出正確的推論結果。但是,兩者在訊息傳遞、對話管理以及推論摘要上的行為可能會有所不同,這正是我們想要觀察的重點。

首先將會要來測試理解 API,這裡會分別對 responsesAgent 物件,傳入 ReasoningEffort.Low, ReasoningEffort.High 這兩個參數,ReasoningEffort 這個型別將會用來控制代理在推論時的努力程度,Low 代表低推理努力,High 代表高推理努力。這裡使用的程式碼將會為 await responsesAgent.RunAsync(Puzzle, options: ReasoningRunOptions(effort)); , 當執行完成後便會得到 AgentResult 物件,裡面包含了代理的推論結果以及相關的訊息傳遞與對話管理資訊。PrintReasoningAndAnswer 方法將會用來輸出推論過程與最終答案。在此方法內,將會呼叫 GetReasoningSummary 方法,這個方法透過 Linq 對訊息傳遞的內容進行分析,並整理出推論摘要,方便我們觀察代理在不同努力程度下的推理行為。GetReasoningSummary 方法執行結果將會儲存到 summary 物件內,也就是一段字串內容,代表了推論摘要

**Explaining the puzzle setup**

I’m considering a standard puzzle with three labeled boxes: apple, orange, and mixed, but they’re all incorrectly labeled. Typical strategies involve drawing from the mixed box to find the correct fruit, but here there are four types with three fruit kinds, which complicates things. It seems like one sample isn’t enough to solve this, and I should clarify using counterexamples to support that idea while keeping the explanation concise and clear.

上面的推論摘要之中文意思將會為:

**謎題設定說明**

我正在考慮一個標準的謎題,其中包含三個標有“蘋果”、“橙子”和“混合”的盒子,但它們的標籤都貼錯了。通常的解題策略是從「混合」盒子中抽取水果來找到正確的水果,但這裡卻有四種盒子,每種盒子裡又有三種水果,這使得問題變得複雜。似乎僅憑一個例子不足以解決這個問題,我應該用反例來闡明我的觀點,同時保持解釋的簡潔明了。

這次的推論結果將會儲存在 response.Text 這個屬性內

**結論:不能保證推出四個盒子的正確內容。**
應從標籤「混合」的盒子取水果,因為它不可能是混合盒,若取到蘋果,只能確定該盒是純蘋果,但其餘三盒仍可能有多種符合「全錯」的排列,例如「蘋果標籤盒」可為混合或橘子盒。
因此一次取樣所得資訊不足以唯一確定四個盒子的內容。

對於採用 ReasoningEffort.Low 方式來進行推論的使用摘要如下

token:輸入 118(快取 0)/輸出 418(其中推論 295)/總計 536

底下將會為採用 ReasoningEffort.High 的推論過程

(為什麼要採用兩種不同的推論努力程度來解題?ReasoningEffort.Low 代表快速、簡單的推論,可能忽略一些細節;ReasoningEffort.High 則代表更深入、全面的推論,會考慮更多可能性。透過比較兩種不同的推論努力程度,可以觀察代理在不同情境下的表現差異。)

【reasoning effort = High】
  [推論摘要]
    **Solving the labeling puzzle**

    I need to find a solution involving boxes labeled incorrectly with their contents. The mixed-labeled box will help identify what’s inside, which will then allow me to label the others correctly. If I draw an apple from the mixed box, I can eliminate that as a possibility for the remaining boxes, given the constraints of labeling. For instance, if I label one box correctly, I can’t have the others labeled correctly. I’ll explore all possible arrangements next.**Analyzing the box labeling puzzle**

    I’m trying to determine if the arrangement A-box B, O-box M, and B-box O is valid. Drawing from the mixed box could help identify one pure box, but it might not uniquely solve the problem with three remaining boxes. To answer the user’s question about guaranteeing the correct contents of four boxes, I need to explain clearly that it’s not possible with just one draw from the mixed box. I have to demonstrate this effectively in three sentences.**Examining labeling strategies**

    I need to illustrate that no strategy guarantees accurate labeling, although drawing from the mixed-labeled box seems to be the best option. If I draw an apple, I can identify the mixed box but face ambiguity with two potential assignments. To clarify, I'll label the boxes as Apple A, Orange O, Banana B, and Mixed M. If I draw an apple from M, the remaining contents could suggest two valid assignments for the other labels, which complicates things further.
  [答案]
    **不能保證推出四個盒子的正確內容。**例如從「混合」盒取出蘋果,可知該盒是純蘋果;但剩下仍可能是「蘋果標籤盒=混合、橘子標籤盒=香蕉、香蕉標籤盒=橘子」,也可能是「蘋果標籤盒=香蕉、橘子標籤盒=混合、香蕉標籤盒=橘子」,兩者都符合所有標籤貼錯,因此一次取樣不足以唯一判定。
  token:輸入 118(快取 0)/輸出 652(其中推論 516)/總計 770

從上面的 Low & High 的執行結果可以看出,ReasoningEffort.Low 的推論較為簡單快速,但可能忽略一些細節,例如:無法完整展示所有可能的盒子排列,對於保證正確標籤的推論不夠全面。而 ReasoningEffort.High 則提供了更完整的分析,考慮了多種可能性,能更清楚地說明為什麼無法保證推出四個盒子的正確內容。

接下來將會展示服務端保存對話(Responses API),這裡的目的將會是示範如何在同一個對話中,依序向代理提出多個問題,並追蹤每輪的訊息與用量。在此將會呼叫 RunTwoTurnsAsync 方法,在這個方法內,將會於傳入 responsesAgent 的 AIAgent 物件,呼叫 CreateSessionAsync 方法,取得一個 AgentSession 物件,接下來會使用同一個 AgentSession,依序向代理提出兩個問題,並記錄每輪的傳送與用量資訊:

  1. 依序詢問 Puzzle 原題和 FollowUp 追問;追問刻意不重述前題,藉此測試代理能否記住上下文。
  2. 每輪印出推論摘要、答案、實際送出的訊息數、對話 ID,以及 token 用量。
  3. 把這些數據整理成 TurnStat,最後回傳兩輪的統計清單。 這個方法本身不比較兩種 API;Main 會分別用 Responses API 代理和 Chat Completions 代理呼叫它,再比較兩者的結果。

另外一個將會是呼叫 RunTwoTurnsAsync 方法,但傳入的是 Chat Completions 代理,也就是 chatAgent 這個物件,這裡將會把每一輪的完整對話歷史重送給代理,藉此比較兩種代理在同一個對話中的表現差異。

當這兩輪呼叫完成 RunTwoTurnsAsync 之後,將會分別把結果存入 responsesResult 和 chatResult 兩個變數,之後可以比較兩者在同一個對話中的表現差異。底下將會為實際輸出內容

======================================================================
  對照小結:第二輪(追問)
======================================================================
  實際送出的訊息數:Responses = 1 則|Chat Completions = 3 則
  帶出的對話 id:Responses = 上一輪的 response id|Chat Completions = (無)
  輸入 token(其中快取):Responses = 347(0)|Chat Completions = 242(0)
  推論 token:Responses = 0|Chat Completions = 22
  兩輪中拿到推論摘要:Responses = 是|Chat Completions = 否
  對話歷史存在哪裡:Responses = OpenAI 服務端|Chat Completions = 本機 AgentSession

  觀察:Responses 第二輪只送 1 則訊息,但服務端會把上一輪的完整內容(含推論項目)接回上下文,
        所以輸入 token 照樣計費,甚至可能比 Chat Completions 多;Chat Completions 重送的只有看得見的文字,
        上一輪的推論內容直接丟掉。(提示快取要前綴超過約 1,024 token 才會命中,這個範例太短所以是 0)

觀察上面的對照小結:第二輪(追問)可以發現,Responses API 只送出一則訊息,但服務端會自動帶入上一輪的完整對話歷史,因此能保留推論摘要;而 Chat Completions 代理則需要每輪重送完整對話歷史,導致訊息數增加,且無法保留上一輪的推論內容。這也說明了兩種代理在處理多輪對話時的差異:Responses API 依賴服務端保存對話,Chat Completions 則依賴本機管理對話歷史。對於 Token 使用量而言,Responses API 可能因為帶入完整對話歷史而增加輸入 token,但仍能減少訊息數;Chat Completions 則每輪都需重送完整對話歷史,訊息數增加,且無法保留推論摘要。

執行結果

現在來看看這個範例程式碼的執行結果。

  • 在 Visual Studio 2026 下,按下 F5 鍵執行程式,將會看到程式輸出的結果。
使用的模型:gpt-5.6-luna
邏輯題:有四個盒子,標籤分別是「蘋果」「橘子」「香蕉」「混合」,但四個標籤全部貼錯了。你只能從其中一個盒子拿出一顆水果來看(不能看盒子裡面),能不能保證推出四個盒子的正確內容?請說明。

======================================================================
  示範一:Responses API + 推論摘要(同一道題,effort = Low vs High)
======================================================================

【reasoning effort = Low】
  [推論摘要]
    **Explaining the puzzle setup**

    I’m considering a standard puzzle with three labeled boxes: apple, orange, and mixed, but they’re all incorrectly labeled. Typical strategies involve drawing from the mixed box to find the correct fruit, but here there are four types with three fruit kinds, which complicates things. It seems like one sample isn’t enough to solve this, and I should clarify using counterexamples to support that idea while keeping the explanation concise and clear.
  [答案]
    **結論:不能保證推出四個盒子的正確內容。**
    應從標籤「混合」的盒子取水果,因為它不可能是混合盒,若取到蘋果,只能確定該盒是純蘋果,但其餘三盒仍可能有多種符合「全錯」的排列,例如「蘋果標籤盒」可為混合或橘子盒。
    因此一次取樣所得資訊不足以唯一確定四個盒子的內容。
  token:輸入 118(快取 0)/輸出 418(其中推論 295)/總計 536

【reasoning effort = High】
  [推論摘要]
    **Solving the labeling puzzle**

    I need to find a solution involving boxes labeled incorrectly with their contents. The mixed-labeled box will help identify what’s inside, which will then allow me to label the others correctly. If I draw an apple from the mixed box, I can eliminate that as a possibility for the remaining boxes, given the constraints of labeling. For instance, if I label one box correctly, I can’t have the others labeled correctly. I’ll explore all possible arrangements next.**Analyzing the box labeling puzzle**

    I’m trying to determine if the arrangement A-box B, O-box M, and B-box O is valid. Drawing from the mixed box could help identify one pure box, but it might not uniquely solve the problem with three remaining boxes. To answer the user’s question about guaranteeing the correct contents of four boxes, I need to explain clearly that it’s not possible with just one draw from the mixed box. I have to demonstrate this effectively in three sentences.**Examining labeling strategies**

    I need to illustrate that no strategy guarantees accurate labeling, although drawing from the mixed-labeled box seems to be the best option. If I draw an apple, I can identify the mixed box but face ambiguity with two potential assignments. To clarify, I'll label the boxes as Apple A, Orange O, Banana B, and Mixed M. If I draw an apple from M, the remaining contents could suggest two valid assignments for the other labels, which complicates things further.
  [答案]
    **不能保證推出四個盒子的正確內容。**例如從「混合」盒取出蘋果,可知該盒是純蘋果;但剩下仍可能是「蘋果標籤盒=混合、橘子標籤盒=香蕉、香蕉標籤盒=橘子」,也可能是「蘋果標籤盒=香蕉、橘子標籤盒=混合、香蕉標籤盒=橘子」,兩者都符合所有標籤貼錯,因此一次取樣不足以唯一判定。
  token:輸入 118(快取 0)/輸出 652(其中推論 516)/總計 770

======================================================================
  示範二:服務端保存對話(Responses API,只傳上一輪的 response id)
======================================================================

【第一輪:原題】
  [推論摘要]
    **Explaining a classic puzzle**

    I need to answer in traditional Chinese, starting with a clear conclusion followed by an explanation of three sentences maximum. The puzzle involves labeling where the contents are pure apple, orange, banana, or a mixed fruit, all with wrong labels. The strategy is to draw from the box labeled mixed, as it cannot contain mixed fruit, allowing identification of the true contents of the other boxes too.
  [答案]
    可以保證。先從標示「混合」的盒子取一顆,因標籤全錯,它不可能是混合盒,因此看到的水果即可確定該盒是對應的單一水果盒;再依「其餘標籤全錯」的條件,排除已知內容,唯一確定另外三盒的正確配置。
  實際送出訊息數:1 則
  帶出的對話 id :(無)
  回來後 session.ConversationId:resp_09570987efccb64b006af16ce2a45c8193bf29d2ab8c90fe24
  token:輸入 118(快取 0)/輸出 172(其中推論 81)/總計 290

【第二輪:追問(不重述題目)】
  [推論摘要]
    (沒有收到推論摘要:Chat Completions 不提供;Responses 則在推論太短時會省略)
  [答案]
    應從標示「混合」的盒子拿。因為標籤全錯,該盒必定是純蘋果或純橘子;若拿到蘋果,它就是蘋果盒,標示「橘子」的盒子只能是混合盒,剩下的是橘子盒(拿到橘子時反之亦然)。
  實際送出訊息數:1 則
  帶出的對話 id :resp_09570987efccb64b006af16ce2a45c8193bf29d2ab8c90fe24
  回來後 session.ConversationId:resp_09570987efccb64b006af16d266f98819398624f4df0d0cb1b
  token:輸入 331(快取 0)/輸出 89(其中推論 0)/總計 420

======================================================================
  示範三:對照組 Chat Completions(每一輪都把整段歷史重送)
======================================================================

【第一輪:原題】
  [推論摘要]
    (沒有收到推論摘要:Chat Completions 不提供;Responses 則在推論太短時會省略)
  [答案]
    **不能保證推出四個盒子的正確內容。**
    例如從「混合」盒取出蘋果,只能確定該盒是「蘋果」,但其餘仍可能是「蘋果標籤=橘子、橘子標籤=香蕉、香蕉標籤=混合」,也可能是「蘋果標籤=混合、橘子標籤=香蕉、香蕉標籤=橘子」,兩者都符合所有標籤貼錯。
    因此一次取出一顆水果的資訊不足以唯一確定四盒內容。
  實際送出訊息數:1 則
  帶出的對話 id :(無)
  回來後 session.ConversationId:(無,歷史存在本機 session)
  token:輸入 118(快取 0)/輸出 660(其中推論 512)/總計 778

【第二輪:追問(不重述題目)】
  [推論摘要]
    (沒有收到推論摘要:Chat Completions 不提供;Responses 則在推論太短時會省略)
  [答案]
    **應該從標籤為「混合」的盒子拿一顆水果。**
    因為所有標籤都錯,「混合」盒不可能是混合;若拿出蘋果,它就是蘋果盒,接著標籤「橘子」的盒只能是混合,標籤「蘋果」的盒則是橘子。
    若拿出橘子,推理對調即可,因此一定能推出三盒內容。
  實際送出訊息數:3 則
  帶出的對話 id :(無)
  回來後 session.ConversationId:(無,歷史存在本機 session)
  token:輸入 307(快取 0)/輸出 177(其中推論 58)/總計 484

======================================================================
  對照小結:第二輪(追問)
======================================================================
  實際送出的訊息數:Responses = 1 則|Chat Completions = 3 則
  帶出的對話 id:Responses = 上一輪的 response id|Chat Completions = (無)
  輸入 token(其中快取):Responses = 331(0)|Chat Completions = 307(0)
  推論 token:Responses = 0|Chat Completions = 58
  兩輪中拿到推論摘要:Responses = 是|Chat Completions = 否
  對話歷史存在哪裡:Responses = OpenAI 服務端|Chat Completions = 本機 AgentSession

  觀察:Responses 第二輪只送 1 則訊息,但服務端會把上一輪的完整內容(含推論項目)接回上下文,
        所以輸入 token 照樣計費,甚至可能比 Chat Completions 多;Chat Completions 重送的只有看得見的文字,
        上一輪的推論內容直接丟掉。(提示快取要前綴超過約 1,024 token 才會命中,這個範例太短所以是 0)