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