我用 LangGraph 從零搭了個「反謠言」搜尋引擎
一個前端背景的程式設計師,學習 Python + AI Agent 管線的全記錄。
今年年初,知乎開放了他們的直答 Agent API。
說白了就一件事:你提一個問題,它自動去搜尋引擎搜、讀網頁內容、整合分析、給你一個帶來源引用的回答。不需要自己搭知識庫,不需要折騰 RAG 管線——搜尋引擎就是你的「資料庫」,各大引擎的 API Key 一配就能跑。
它提供了三檔模式,挺聰明:
模式干什麼的耗時簡單模式快速直答,適合「今天天氣」這種問題秒級深度模式基於知乎知識庫的深度分析幾十秒DeepSearch即時多引擎搜尋 + 多源整合 + 綜合分析幾分鐘我當時就在想:這其實就是個搜尋類 Agent 啊。
LangChain 學了點、LangGraph 翻了三天文件沒看懂、Agent 概念模模糊糊。但你讓我直接上手寫一個?好像可以。因為搜尋類 Agent 有個天然優勢——資料不用操心。
不做知識庫就不需要處理文件切分和 Embedding 品質;不做 RAG 就不需要調 chunk size 和檢索策略;不微調模型就不需要準備資料集。資料全在搜尋引擎裡,你要做的就是:搜 → 讀 → 分析 → 回答。鏈路清晰得跟流水線一樣。
對於想學 AI Agent 的新手來說,這是個絕佳的切入點——API 接入簡單、鏈路可見、效果立竿見影。於是我就動手了。
2026 年 2 月,公司宣布我「畢業」了。
說好聽點叫優化,說難聽點就是被 AI 卷掉了。我寫了七八年 React,元件樹倒背如流,Webpack 配置閉眼調。但今年再刷招募市場,純前端職位縮水了不止一半。履歷投出去,回覆越來越像 ChatGPT 寫的——「你很優秀,但目前我們更需要具備 AI 工程化能力的全端工程師」。
焦慮是真的焦慮。身邊不少同行在抱怨「AI 搶飯碗」,但我覺得與其抱怨,不如動手學。以前是 AI 取代我,那我能不能反過來用它?把 AI 當工具,用它做出以前一個人根本做不出來的東西?
還沒緩過來,家裡事情就一件接一件。索性在家待著,一邊處理家事一邊把一直想搞的 AI 方向認真補了補。
說是「補」,其實就是硬啃。以前寫 React、調 Webpack,腦子裡全是元件樹和狀態管理。突然要理解 Embedding、RAG、Agent 循環、狀態機編排這些概念,坦白說中間卡了很多次。對著 LangGraph 文件看了三天沒看懂,最後是邊抄程式碼邊跑才慢慢明白的。
這個 TruthSeeker 就是我在家鼓搗出來的。一個用 LangGraph 搭的深度研究引擎,前端、後端、模型呼叫、部署,全是我一個人弄的。說實話過程挺狼狽的——經常半夜對著一堆報錯發呆。但每解決一個問題,那種「原來是這樣」的感覺,比寫一百個 React 元件都爽。
這個專案有一個貫穿始終的硬約束:預算為零,Token 太貴了。
我沒有公司報銷 API 費用,用的都是自己充值的 DeepSeek 餘額。深度研究模式跑一輪,意圖分析 + 多引擎搜尋 + 原子聲明提取 + 信源畫像 + 三方共識 + 最終裁決,加起來可能呼叫十幾次 LLM。如果用 GPT-4o,一次深度研究的推理成本可能好幾塊錢。一個月跑幾百次,還沒找到工作就先破產了。
所以你要是看到後文各種「為什麼不那樣做」的決策,別覺得奇怪——它們背後都有一個統一的原因:窮。為什麼選 DeepSeek?便宜啊。為什麼搞兩級過濾?少餵 Token 給 LLM,省一分是一分。為什麼最多搜 3 輪?多一輪都是真金白銀。為什麼把模型切成「搜尋用便宜的、驗證用強的」?好鋼得用在刀刃上。這篇文章裡幾乎所有技術決策,都可以歸結為一句話:窮有窮的做法。
我不覺得這是「轉行」。前端工程師的核心能力從來不是寫 JSX,而是理解使用者需求、設計互動邏輯、工程化思維。這些能力換個語言、換個領域一樣能用。LangGraph 的狀態機不就是 Redux 換個馬甲嗎?管線的條件路由不就是 React Router 嗎?真正的壁壘不是語言,是你願不願意從頭開始。
這篇文章記錄的是搭建全過程:踩過的坑、做過的決策、以及 POC 階段來不及解決的妥協。不一定都對,但每一步都踩得真實。給同樣面臨焦慮、正在考慮轉型的前端同行一個參考。
先回答最直接的問題:這玩意兒能幹嘛?
簡單說,你提一個問題,它去全網搜,然後把不同來源的說法擺在一起對比,告訴你哪些是真的、哪些是矛盾的、哪些根本沒法驗證。
不追求「給你一個答案」,而是追求「告訴你這個答案可信嗎」。
比如你問:「Neuralink 首例人體植入後受試者出現感染,真的假的?」它會:
核心跟普通搜尋的區別在於那個交叉驗證環節——我後來管它叫「審判室」。
不是所有問題都需要這麼重的流程,所以做了四個檔位:
模式適合場景耗時
极速快問簡單確認,比如查一個價格幾秒
專家搜尋深入了解一個主題幾十秒
深度研究複雜問題、需要多源驗證幾分鐘
智能模式讓 AI 自己判斷該用哪個自適應
這個設計思路跟知乎直答 Agent 的三檔模式很像——都是從「秒回」到「深度挖掘」的分層策略。但 TruthSeeker 比它多了一個關鍵環節:交叉驗證。不是搜完就直接回答,而是讓不同信源「對質」之後再下結論。
我一個寫了七八年 JavaScript 的人,突然要搭一個 Python 後端的 AI 專案,說實話一開始是有點抗拒的。但 LLM 生態最好的工具鏈全在 Python 側——LangChain、LangGraph、FastAPI——這是現實,沒什麼好糾結的。
系統大概分三塊:使用者互動的前端、處理請求的後端、以及真正幹活的 Worker 行程。為什麼要把後端和 Worker 拆開?因為深度研究要跑幾分鐘,不能讓 HTTP 請求一直掛著等——後端收到請求就丟進佇列,Worker 在背景慢慢跑,結果透過 SSE 即時推回去。
Django 太重了。這個專案需要的是:路由層、中介軟體、非同步支援、SSE 串流回應。不需要 ORM 自帶的後台管理、不需要樣板引擎、不需要表單驗證——這些我前端都自己做了。
FastAPI 的好處:原生 async/await、Pydantic 請求校驗、Swagger UI 自動生成、啟動快。但後悔的是: FastAPI 的依賴注入系統一開始用得很爽,專案大了以後到處 Depends() 讓呼叫鏈很難追蹤。如果有下次,我會把業務邏輯更多放在 service 層,路由只做參數校驗和轉發。
剛開始我用的是 LangChain 的 LLMChain,就是最簡單的「給一個 Prompt,拿一個回答」。很快發現兩個問題:
LangGraph 解決的就是這兩個問題:它把整個流程定義成一個狀態機,每個節點是獨立的處理步驟,節點之間可以條件跳轉,而且每一步的狀態自動持久化——等於自帶斷點續傳。
當時沒對比的: 應該對比 LangFlow(視覺化編排)和 Haystack。但當時 LangGraph 文件最全、例子最多,就它了。這不算錯,但算偷懶。
本地開發一直用 SQLite,不需要裝任何東西,pip install 完就能跑。但 SQLite 有個致命問題:並發寫入鎖。當 Worker 在寫研究結果、同時 API 在查歷史記錄,SQLite 的寫鎖會導致讀超時。生產環境果斷切到 PostgreSQL。
教訓: 如果一開始就知道要上生產,直接 PG 起步。
資料庫遷移工具用了 Alembic——SQLAlchemy 官方遷移工具,自動生成遷移腳本、版本管理和回滾。Docker Compose 啟動時先跑 alembic upgrade head,保證 schema 和程式碼永遠對齊。
Redis 在這個專案裡幹了三件事,這是一個反覆糾結後做的取捨:
角色怎麼用的為什麼是 Redis
任務佇列 Worker 透過 BRPOP 拉取任務阻塞彈出天然支援優先級佇列
SSE 發布 / 訂閱 Worker 即時推送進度給 API PubSub 延遲接近零
快取 LLM 重複請求快取 內存讀寫比 PG 快兩個數量級
用 Redis 扛三個角色最大的好處是維運一致——Docker Compose 裡只多一個服務。代價是 Redis PubSub 不保證訊息送達(斷線就丟訊息),後面會用 Redis Stream 補救。
使用者的 API Key、使用者的模型列表、使用者的研究策略——這三樣東西不能混在一起存:
比如:使用者配了 DeepSeek 的 API Key(憑證層),註冊了 deepseek-chat 和 deepseek-reasoner 兩個模型(資產層),然後建立一個 Preset 說「意圖分析用 chat 模型、驗證用 reasoner 模型」(策略層)。三層解耦後,換 API Key 不需要重配策略,加新模型也不需要改預設。
最初的版本極其樸素:使用者提問 → 搜尋 → 把搜尋結果餵給 LLM → 生成回答。十幾個 LLMChain 串起來,跑得動,但問題一大堆:
所以後來徹底重構成了 LangGraph 的 StateGraph。
我把整個研究拆成了這些節點,每個節點幹一件事:
幾個有意思的節點:
意圖分析:把模糊問題變具體。 使用者經常問得很籠統,比如「AI 對就業的影響」。這個節點把它拆成可搜尋的子問題:AI 取代了哪些職位?創造了哪些新職業?各國政府怎麼應對?拆完之後用向量相似度去重——防止「AI 取代職位」和「AI 導致失業」這種同義拆解浪費搜尋次數。
向量去重用的是阿里雲的通義 Embedding Vision Flash,256 維,中文語義理解很穩,跟 DeepSeek 統一在 DashScope 閘道下接入。超過 0.85 相似度的判定為同義重複,整個去重邏輯不超過二十行。
兩級過濾,先快後慢。 粗過濾基於規則(去重 URL、去低質域名),能砍掉六七成雜訊。LLM 精過濾再砍兩三成。最終進驗證環節的通常只有原始結果的 20% 左右,但資訊密度高得多。
循環:驗證不過關就回頭搜。 驗證子圖跑完之後,如果存在矛盾維度且未達最大輪數(3 輪),管線自動回到搜尋節點,針對矛盾點追加搜尋。
LangGraph 的 Checkpointer 會在每個節點執行後把整個 ResearchState 序列化存到 PostgreSQL:
ResearchState
├── context → 身份資訊(誰、哪個租戶、哪個預設)
├── control → 控制參數(速度檔位、模式)
├── memory → 中間記憶(歷史訊息、已證事實、摘要)
├── runtime → 執行時資料(搜尋快取、管線狀態)
└── output → 最終產出(報告、聲明列表、置信度)
這意味著:使用者提問後關了瀏覽器,過十分鐘再打開,研究進度還在。Worker 繼續跑,前端重新連上 SSE 就能看到中間結果。這個體驗說實話挺爽的——第一次實現了「無感斷線」。
這是整個系統最核心的模組。我決定不信任任何單次 LLM 輸出,而是:從多個信源提取事實,對比它們的一致性,給出置信度評分。
這個模組叫「審判室」,四步流程:
把幾千字的新聞稿拆成原子聲明。比如這篇 Neuralink 報導:
"Neuralink 於 2024 年 1 月宣布完成首例人體腦機介面植入手術,受試者為一名因脊髓損傷而四肢癱瘓的患者..."
拆成:
聲明 1:Neuralink 於 2024 年 1 月完成首例人體植入 [primary]
聲明 2:受試者是脊髓損傷導致的四肢癱瘓患者 [secondary]
聲明 3:手術由史丹佛大學醫學中心執行 [secondary]
聲明 4:受試者術後出現感染症狀 [primary][爭議性聲明]
每條聲明標記重要級別,記錄來自哪個信源。拆解由 LLM 來做——因為同一事實在不同文章裡的表述可能完全不同。
這裡用的 LLM 是 DeepSeek。為什麼不是 GPT-4o?性價比——DeepSeek 價格大概是 GPT-4o 的 1/10,中文能力不輸甚至更好,API 完全相容 OpenAI SDK。一鍵切 base_url 就行。但英文信源推理確實不如 GPT-4o,所以我留了個開關:使用者可以在 Preset 裡給不同階段綁定不同模型,驗證階段用更強模型,搜尋階段用便宜的。
有了聲明列表,接下來判斷信源本身靠不靠譜:
評分維度看什麼內容品質 (0~1) 資訊密度、邏輯是否嚴密、有沒有資料支撐行銷傾向 (0~1) 是不是軟文、有沒有商業推廣意圖專家引用 (0~1) 有沒有引用權威機構或專家一個來自《自然》雜誌的報導,內容品質可能 0.9,行銷傾向 0.1。一個行銷號的文章,內容品質可能 0.2,行銷傾向 0.9。這些分數直接影響後面的裁決權重。
對每一條聲明,從所有信源中找相關證據,判斷一致性:
比如「受試者術後出現感染」——如果 3 個不同信源都報導了且細節吻合,就是 Consistent。如果一個說「感染」、一個說「無異常」、一個說「輕微不適」,就是 Contradictory。
綜合所有聲明的驗證結果和信源權重,給出裁決:
全域置信度分五級:verified → likely_true → disputed → uncertain → unverifiable。
說真的,這個四步流程沒多高深,就是照著法庭審判的套路來的:
法庭裡的角色 Verify Subgraph 裡的對應
收集證據 Atomize:把複雜資訊拆成原子事實
評估證人可信度 Profile:評估每個信源的品質
交叉質證 Tripartite:讓不同來源對同一個事實「對質」
法官裁決 Arbitrate:綜合證據和權重做最終判斷
我不是第一個拿法庭模型做資訊驗證的人,但你別說,每次跟別人解釋這個模組,一說「我給 AI 搭了個法庭」,對方表情立馬從「你說啥」變成「哦~懂了」。
整個核驗流程作為獨立的 LangGraph Subgraph 嵌套在主管線裡。Subgraph 的好處:
已知不足: 信源畫像用的是通用 LLM,沒有針對中文信源權威性做專門微調。「內容品質」打分有時偏高——有些 AI 摘要網站也被打高分,但其實是二手資訊。裁決邏輯目前是加權平均,沒有考慮信源之間的獨立性問題——兩個媒體可能引用了同一個原始採訪,但系統當成兩個獨立信源來計票。
最早版本特別天真,就一條 Redis 佇列,誰先來誰先走。結果快速問答經常排在深度研究屁股後面,得等三四分鐘。朋友試用完直接問我:「你是不是寫了個 Bug?查個天氣要等三分鐘?」
我趕緊去看日誌,一看就樂了——深度研究在前面吭哧吭哧跑兩三分鐘,後面堵了七八個快速問答。這體驗,就像超市只開一個收銀台,你買瓶水得等前面大媽結完一整車的年貨。
拆成三條佇列,加權輪詢:
佇列 對應模式 權重含義
ts:queue:fast 极速快問 4 每 7 次被消費 4 次
ts:queue:expert 專家搜尋 2 每 7 次 2 次
ts:queue:pipeline 深度研究 1 每 7 次 1 次
調度序列就是:fast ×4 → expert ×2 → pipeline ×1 → 迴圈。每次 BRPOP timeout 0.1s,一個迴圈總共 0.7 秒。即使在深度研究的高負載下,快速問答最多等零點幾秒。
那為啥不乾脆給快速佇列最高優先級呢?因為你想想,如果快速佇列只要有人排隊就打死不處理深度隊列,那深度研究可能一整天都排不上——這叫「飢餓」。加權輪詢的好處是每種任務都能被照顧到,只是頻率不一樣。而且你仔細品:選了深層研究的使用者,本來心裡就知道「這玩意兒得等幾分鐘」,多等一小會兒完全能接受。
一開始我啥限制都沒加,覺得自己寫的是非同步程式碼嘛,怕啥。結果 LangGraph 的圖執行本身也是非同步的,十幾個協程同時搶事件迴圈,Worker CPU 直接飆到 90%。後來老老實實加了個 asyncio.Semaphore(2),單 Worker 最多同時跑 2 個任務。簡單粗暴,但從此 CPU 就乖了。
Worker 內有個獨立協程,根據三個佇列的總深度調整輪詢頻率:少於 2 個任務休眠 5 秒省 CPU,超過 10 個任務休眠 1 秒快速消費。三段 if-else,沒什麼黑科技,但有效。
ARQ 是 FastAPI 作者 Samuel Colvin 開發的非同步任務佇列庫,跟 FastAPI 和 Pydantic 同一人出品,生態相容性天然好。對比 Celery:
維度 ARQ Celery
非同步模型 原生 asyncio prefork/thread pool
配置複雜度 1 個 Worker 函式 + 1 行啟動 多檔案配置
選它的理由跟 FastAPI 一致:夠用且輕量。POC 階段不需要 Celery 的複雜功能。
已知不足: 目前只有單 Worker,沒法水平擴展。加權輪詢的權重是拍腦袋定的(4:2:1),沒有基於實際負載資料調優。取消信號依賴 Redis PubSub,但 PubSub 不保證送達——網路抖動時可能丟掉取消指令。
說實話,做這個系統最讓我睡不著覺的事,就是使用者的 API Key。人家的 DeepSeek Key、搜尋引擎 Key 都填在我這兒了,這要是漏了,我拿什麼賠?
所有 SQL 查詢都帶兩個條件:WHERE tenant_id = ? AND user_id = ?。tenant_id 和 user_id 從 JWT 裡提取,API 中介軟體注入到請求上下文,不是靠前端傳參——後端解析 Token 綁定的,沒法偽造。
用 Fernet 加密(AES-128-CBC + HMAC-SHA256 簽名):
明文 API Key → AES 加密 → HMAC 簽名 → base64 編碼 → 存庫
讀取時 → base64 解碼 → 驗證 HMAC(防竄改)→ AES 解密 → 明文使用
加了 HMAC 意味著:即使有人黑了資料庫、改了密文,解密時會因為簽名對不上而直接報錯。這不只是加密,這是防竄改。
加密金鑰可以從 JWT 金鑰派生,也可以獨立設定環境變數,方便以後輪換。
Worker 在跑研究時會去請求外部 URL。如果有人提交惡意 URL 指向 http://169.254.169.254/metadata(雲伺服器中繼資料介面),Worker 如果傻傻去請求,等於把伺服器敏感資訊送出去了。
我的防護是 DNS 層級:解析 URL 的所有 IP,逐個檢查是否為私有位址(127.0.0.0/8、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、169.254.0.0/16)。還留了個特殊放行:198.18.0.0/15——Clash 代理用的保留網段,不放行的話 Worker 根本無法存取外部 API。
PBKDF2-SHA256 雜湊,48 萬輪疊代。驗證密碼時用 hmac.compare_digest() 做常數時間比較——不管你輸的密碼對不對,比較時間一樣長,防止側通道攻擊。
支援兩種登入:傳統使用者名稱密碼 + Logto OIDC。OIDC 接入不複雜:後端從 Logto 的 JWKS 端點拿公鑰,校驗 RS256 簽章。兩種方式並存——我自己用密碼登入省事,給別人示範時用 OIDC 顯得正式。
已知不足: SSRF 防護是 DNS 層級的,有繞過可能(DNS Rebinding、HTTP 重新導向到內網)。生產級應該用代理隔離。日誌裡目前沒有脫敏,有些錯誤日誌可能把解密後的 API Key 印出來——這個必須修。多租戶隔離靠 WHERE 條件,沒有做 PG 原生 Row-Level Security,萬一有查詢忘了帶 WHERE 條件就跨租戶洩露了。
起初我寫得很糙。博查、Tavily、知乎三個引擎各寫一套,到處散落著 if-else。想加個新的?得把四五個檔案翻一遍。
if engine == "bocha":
results = await bocha_search(query)
elif engine == "tavily":
results = await tavily_search(query)
elif engine == "zhihu":
results = await zhihu_search(query)
後來想加 Google Search 的時候終於受不了了——調度邏輯跟引擎邏輯攪成一鍋粥,改調度影響引擎,改引擎又影響調度。咬咬牙:拆。
外掛註冊中心(Registry): 全域字典,所有外掛透過裝飾器自註冊,不用手動維護列表。
搜尋編排器(Orchestrator): 拿到啟用的外掛列表,asyncio.gather 並發呼叫,最後跨引擎去重。
外掛基底類(SearchPlugin): 所有引擎必須實作的抽象類,就三個方法:
class SearchPlugin(ABC):
@property
def name(self) -> str:
"""引擎名稱"""
@property
def is_reader(self) -> bool:
"""是否內容讀取外掛"""
return False
async def search(self, query, api_key, **kwargs):
"""執行搜尋,返回統一格式"""
@plugin_registry.register() 裝飾器VALID_SEARCH_ENGINES 裡加一行名字編排器和去重邏輯一行不用改。
results = await asyncio.gather(
*[plugin.search(query, api_key) for plugin in active_plugins],
return_exceptions=True # 某個引擎報錯不影響其他
)
return_exceptions=True 是關鍵——某個搜尋引擎逾時了,例外被包裝成 Exception 物件放在結果列表裡,不會影響其他引擎已返回的結果。
重構前 重構後
加引擎改 4-5 個檔案 加引擎改 2 個檔案
調度邏輯和引擎耦合 調度和引擎獨立,各自測試
引擎報錯影響全域 單引擎故障不阻塞
搜尋結果重複 跨引擎 URL 去重統一處理
已知不足: 外掛系統只接了三個引擎,沒做過不同搜尋引擎結果品質的 A/B 對比——博查的中文搜尋比 Tavily 好嗎?不知道。去重目前只靠 URL 精確匹配 + 標題相似度,兩個不同 URL 的網頁可能互相抄襲同一篇文章,這種內容級去重還沒做。
第一版做完的時候我自己試了一下,差點把自己氣死。點「開始研究」→ 介面死了 → 過了三五分鐘 → 啪,糊一臉結果。
中間那幾分鐘,使用者就盯一個轉圈發呆。不知道系統掛沒掛、AI 在忙啥、還剩多久。我自己用了一次就想罵人。
一開始圖省事,沒搞 SSE。就返回個 task_id,讓前端每兩秒輪詢一次查進度。結果呢?百分之九十的請求都是白打的,而且進度是跳著走的,卡一下突然蹦一截。
這才老老實實上了 SSE:
Worker 執行 LangGraph 圖
→ 每個節點產生事件
→ 發布到 Redis PubSub
→ API 行程訂閱 PubSub
→ 格式化為 SSE 推給瀏覽器
事件 什麼時候發 前端幹嘛
step 進入新管線階段 更新進度條和思考鏈面板
model LLM 串流輸出 Token 追加訊息(打字機效果)
complete 研究完成 展示報告和聲明驗證卡片
error 任何節點報錯 顯示錯誤提示
sync 斷線重連時 恢復完整狀態
然後我就踩了整篇文章最大的一个坑:Redis PubSub 不存歷史。瀏覽器一斷線,掉線期間的事件全部蒸發了。你關了標籤頁重新打開,就看見進度條像個鬼一樣從 0% 直接跳到 80%,中間發生了什麼?不知道。這體驗比白屏還詭異。
後來想了個招——雙通道:
重連流程:前端調 /api/v1/chat/resume → API 從 Redis Stream 讀歷史事件 → 推送重建完整狀態 → 繼續從 PubSub 消費新事件。
proxy_buffering off; # 關緩衝,確保事件即時推送
proxy_read_timeout 86400s; # 長連線逾時 24h
proxy_cache off; # 禁用快取
第一次沒加 proxy_buffering off,前端收到的事件是「攢一波再推」——每隔 30 秒刷一大段,完全沒有即時感。排查了半天才發現是 Nginx 預設開了代理緩衝。
已知不足: SSE 斷線重連還沒大規模壓測。事件解析器目前靠一堆 if-else 匹配,隨著 LangGraph 版本升級事件格式可能變。最理想的是把 Parser 做成可配置的事件映射表。
我對部署的要求很簡單:隨便誰 git clone 下來,敲一個命令,所有東西跑起來。不需要裝 Python、不需要裝 Node、不需要裝 PG。Docker 就夠了。
對外只暴露 80 連接埠,剩下全是容器間內部通訊。
一個人維運,Kubernetes 太重了。編寫 Deployment、Service、Ingress、ConfigMap、Secret 就得半天。Docker Compose 一條 docker compose up -d 搞定。但這個選擇有代價: 沒有健康檢查自動重啟、沒有滾動更新、沒有資源限制細粒度控制。POC 階段能忍,有真實使用者後必須補。
坑一:預設 60 秒逾時。 深度研究可能跑幾分鐘,但 Nginx 的 proxy_read_timeout 預設 60 秒。結果研究跑了一分鐘多連線斷了,前端收不到後續事件。修復:proxy_read_timeout 86400s。
坑二:代理緩衝導致事件延遲。 proxy_buffering off 和 proxy_cache off 必須加。
應用服務啟動前必須先跑完資料庫遷移。Docker Compose 用 depends_on + healthcheck 解決:
postgres:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U truthseeker"]
interval: 5s
backend:
depends_on:
postgres:
condition: service_healthy
部署命令記不住,所以寫了個 Makefile:
deploy:
docker compose up --build -d
down:
docker compose down
logs:
docker compose logs -f
clean:
docker compose down -v # ⚠️ 刪資料
make clean 尤其危險——加 -v 會刪掉所有資料卷。我自己踩過這個坑,想「清理一下」結果把測試資料全清掉了。
已知不足: 就單機 Docker Compose,沒有容器編排、監控告警、日誌聚合、灰度發布、自動備份。SSL 依賴外部 Cloudflare。健康檢查只有一個
/health端點。這些都是 POC 階段故意欠的債——要加的話開發時間得翻倍。
前後斷斷續續寫了幾個月,目前跑在一台雲伺服器上,日常處理幾十次研究請求。TDD 寫了 30 多個測試檔案,配了 Logto OIDC 登入,搭了前後端全非同步鏈路。
這些都是我明知道該做、但 POC 階段來不及做的。寫出來不是示弱,是誠實——一個人做全端本身就到處妥協,關鍵是你得知道自己在妥協什麼。
現在管線裡的 Agent 還比較「規矩」——能搜、能讀、能推理,但始終在一個預設的流程裡跑。我感興趣的方向:
找工作優先,但這幾個方向會陸續寫出來——不會再停在 tutorial 級別。
這個專案也是一個 vibe coding 的嘗試。以前拿到需求就開寫,現在習慣先用 AI 聊清楚產品設計、用 AI 過需求方案,把自己的角色從「悶頭寫程式」慢慢轉變成了「想清楚再寫、寫完再審」。程式碼量沒以前大了,但想的時間多了不少。
對 AI 工程化有興趣的話,歡迎聊聊。
18251886173[email protected]