
給一個 AI 產品不斷增加能力時最省事的做法是繼續往聊天框里塞入口。文件處理、圖片理解、熱點發現、項目搜索都可以被包裝成一句自然語言請求。問題是這些任務并不共享同一份數據合同。文檔總結依賴用戶剛剛上傳的材料熱點發現依賴當前采集結果開源項目選擇還需要倉庫身份、許可證和可回溯證據。如果界面只剩一個輸入框用戶很難判斷回答究竟來自當前數據、歷史緩存還是模型記憶。我在實現一個 FastAPI AI 工作站時最終把“工作區”和兩個“當前發現頁”拆成了獨立路由。本文只談這個拆分背后的工程邊界。圖 1開源項目頁的用途分類控件9月9日真實頁面局部。分類用于發現候選不代表項目能力或許可證已經核驗圖中數量是截圖時的界面顯示值。1. 先按數據合同拆路由而不是按菜單拆頁面當前的路由關系可以簡化成/ 工作區文件、圖片、鏈接和自然語言任務 /topic-radar/ 當前題材來源、市場、證據狀態和新鮮度 /githubai/ 開源項目倉庫身份、分類、榜單和核驗信息 /api/v1/ai/... 對應的只讀數據接口在 FastAPI 入口中頁面和數據路由分別注冊。核心思路不是“多做兩個頁面”而是讓三類請求擁有不同的失敗方式、緩存方式和證據要求。app.get(/topic-radar,include_in_schemaFalse)app.get(/topic-radar/,include_in_schemaFalse)deftopic_radar_entry(request:Request)-Response:...app.include_router(create_topic_radar_router(api_prefixAPI_PREFIX))app.include_router(create_github_ai_radar_router(...))工作區可以容忍一次模型調用失敗后重試當前題材頁不能在來源異常時把舊數據繼續標成“正在上升”項目頁也不能因為后臺正在刷新就臨時從 GitHub 拉取一批未經校驗的數據直接返回。2. 當前數據不能從模型記憶里“猜”出來熱點和開源項目都屬于時效性數據。模型適合解釋一條已知記錄卻不應該負責證明這條記錄是今天的。因此數據流被放在對話之前來源采集 - 規范化與身份去重 - 來源健康和新鮮度判斷 - 生成可發布快照 - 只讀 API - 網頁篩選與詳情 - 用戶需要時再交給模型研究這條順序帶來一個很實際的好處頁面可以明確展示“目前知道什么”而模型只負責后續理解和表達。即使模型服務暫時不可用用戶仍然可以瀏覽已經發布且通過檢查的數據。3. 來源健康必須進入公開數據合同只記錄抓取成功或失敗還不夠。一個來源連續失敗時系統至少要知道上次成功時間、當前是否降級、數據是否仍在新鮮窗口內以及下一次探測時間。對外可以投影成類似下面的結構{source:example-source,last_success_at:2026-08-30T02:10:00Z,freshness:fresh,degraded:false,next_probe_at:2026-08-30T02:20:00Z}當單個來源進入降級狀態時頁面仍可服務其他健康來源該來源的舊記錄則不再作為“當前熱點”繼續參與排序。這樣處理比在接口最外層返回一個籠統的 500 更有用也避免把歷史數據偽裝成實時結果。圖 2熱點頁的狀態分組、搜索與計數控件9月9日真實頁面局部。事件數與來源信號數采用不同口徑不能互換也不能把平臺熱度當成事實確認。4. 公共 GET 不做采集也不臨時調用模型開源項目頁的后臺處理比普通列表更重項目需要綁定上游倉庫身份、README 和 Release 證據中文內容還要與同一代事實和引用保持一致。如果每次 GET 都動態拼裝這些內容延遲、成本和一致性都會失控。因此公開讀取采用不可變發布版本staging 數據 - 構建候選 Release - 校驗項目、證據和引用的同代關系 - 原子切換 current 指針 - 預熱緊湊的公開緩存 - 公共 GET 只讀取當前健康版本候選發布失敗時舊的健康版本繼續服務。公共請求不掃描后臺目錄、不現場采集 GitHub也不調用模型生成正文。這個限制看起來保守卻能讓頁面性能和內容一致性變得可驗證。5. HTML 和靜態資源使用不同緩存策略實時頁面的 HTML 殼需要及時拿到新的資源版本因此入口返回Cache-Control: no-cache, must-revalidate Content-Language: zh-CNCSS、JavaScript 和圖片則使用版本化 URL允許瀏覽器長期緩存。也就是說“頁面入口是否更新”和“大體積資源是否重復下載”是兩個問題不能靠統一關閉緩存解決。本地驗證時我會分別檢查 HTML 響應頭和帶版本號的資源curl-Ihttp://localhost:9010/topic-radar/curl-Ihttp://localhost:9010/githubai/6. 拆分后更容易定義失敗邊界這套結構最終得到四條比較清楚的約束采集失敗只降級對應來源不拖垮整個工作區模型失敗不影響已經發布的瀏覽數據新版本校驗失敗時保留上一版健康 Release頁面把日期、來源和待核驗項展示出來不把排名寫成結論。用戶仍然可以從當前題材或項目進入下一步研究但這時模型拿到的是一條有身份、有日期、有來源的記錄而不是一句“幫我找最近熱門內容”的模糊請求。7. 頁面拆開不代表工作流割裂工作區負責接收材料和組織輸出題材頁負責發現當前內容線索項目頁負責發現并初步核驗開源項目。它們在交互上是三個頁面在工作流上仍然可以前后銜接。這篇文章討論的是我開發的 AI 工作站中的工程取舍。截圖只展示本文討論的分類、篩選與計數控件不作為功能完整性或服務效果的證明。對我來說這次拆分最重要的結果不是多了兩個入口而是用戶終于能看出哪些內容來自自己的材料哪些來自當前數據哪些只是模型參與后的解釋。這個邊界一旦模糊功能越多產品反而越難被信任。8. 驗收不能只看頁面有沒有打開下面是一份可在自己的本地環境執行的檢查清單不是本次已經全部通過的測試報告。入口與資源分開驗證。對 HTML 使用curl -I檢查緩存頭再從 HTML 取出實際腳本路徑用curl --compressed -i檢查版本化資源的響應。不要只看文件名變了就認定瀏覽器拿到了新內容。列表與詳情交叉驗證。記錄同一項目在列表和詳情中的標識、發布版本、數據觀察時間以及 Star 數。版本相同仍不代表每個字段觀察時間相同有差異應回溯字段來源不能直接取較大的數字。標簽與證據分開驗證。展示層存在許可證標簽不等于證據層已經取得許可證文本。證據缺失時應明確未知不能由摘要或模型補成“已核驗”。計數口徑單獨驗證。一條事件可能聚合多條來源信號因此事件數不應直接等同于來源條數。來源身份去重也應獨立于標題去重。失敗注入只在隔離環境執行。模擬某個來源超時或候選發布校驗失敗檢查其他健康來源與上一健康版本是否仍可讀取不要為寫文章在生產環境停服務。目前仍有需要改進的地方抽查中見到開源合集與詳情的 Star 顯示差異原因還沒有查明部分項目有許可證標簽但沒有直接 License 文本證據。因此上文描述的是設計邊界和檢查方法不意味著每一條公開記錄都已通過完整核驗。本文由項目開發者提供文字含 AI 輔助整理代碼片段用于解釋結構截圖為真實界面局部不包含客戶數據。