urscos Tech - AI
type: channel
link: https://novel.urscos.com/category/AI/
items: OSM 的隱藏陷阱：λ 指數修正 2 —— 建照數據校準與方法論局限
items: LLM 記憶壓縮實戰：滑動視窗 vs 摘要記憶，用答對率與 Token 成本量化取捨
items: LLM 為什麼沒有記憶？Stateless 原理與 Prompt 層記憶模擬實戰（含 Token 成長曲線）
items: Prompt Engineering 實戰：Zero-shot vs Few-shot vs CoT 的 Token 成本與情感分析對比
items: OSM 的隱藏陷阱 - 以 λ 指數修正志願者貢獻偏差
items: CodeGraph 實戰心得：用「地圖導航＋精確搜索」提升 Claude Code 程式碼理解效率
items: 用 OSM 打造城市空間指標：從歷史快照抓取到 500m 緩衝區七大指標計算
items: CodeGraph 深度解析：codegraph.db 資料庫結構完整拆解（Nodes、Edges、索引與觸發器）
items: CodeGraph 安裝與使用教學：整合 Claude Code、Cursor、Codex 的程式碼圖譜工具
items: 用 Unicode 單引號為 LLM 輸出加水印：防抓取與叛徒追蹤實戰

OSM 的隱藏陷阱：λ 指數修正 2 —— 建照數據校準與方法論局限
type: item
date: 2026-07-18T16:54:00+00:00
description: 延續上一篇的話題，關於 λ 的修正計算，如何更準確的預估或是計算出 λ 呢？ 理論上 λ 應由「外部基準」決定，以下是三條可行的校準路徑： PATH 1：對照官方建照數據 最直接的方法——以政府公布的「年度核發建照數」作為 ground truth，反推 λ： λ* = log(N_t/N_0 / G_real) / log(C_t/C_0) 其中 G_real 為官方建照累計增長率。台中建管處（CPA）有公開資料，可據此反算出城市特定的 λ 值。 PATH 2：跨城市穩定性檢驗 若同一個 λ 值應用於台北、高雄、台中均能與官方數據對齊，則可視其為 OSM 系統性偏差的穩健估計，與個別城市無關。 PATH 3：保守採用 λ = 0.5 在缺乏 ground truth 校準的情況下，以 0.5 作為「未知條件下假設各佔一半」的保守選擇。報告中務必附上靈敏度分析表，讓讀者知悉結論在 λ ∈ [0.25, 0.75] 範圍內的穩健性。 批次修正多項指標 以下一鍵完成所有指標的偏差修正： # osm/bias_correction.py:146-179 def run_bias_correction(osm_metrics, target_cols=None, base_year=BASE_YEAR, lambda_=DEFAULT_LAMBDA): if target_cols is None: target_cols = ["building_count", "amenity_count", "shop_count", "poi_diversity"] community_proxy = compute_total_elements(osm_metrics) yearly_means = osm_metrics.groupby("year")[target_cols].mean() all_results = [] for col in target_cols: series = yearly_means[col].dropna() df = bias_correction_index(series, community_proxy, base_year, lambda_) df.insert(0, "indicator", col) all_results.append(df) return pd.concat(all_results, ignore_index=True) 輸出兩份結果檔： output/osm_bias_corrected.csv——各指標各年的原始與修正後增長指數 output/osm_bias_sensitivity.csv——λ 靈敏度分析結果 修正前後各 OSM 指標的 8 年總增長率對比（λ = 0.5）： 指標 原始增長 修正後增長 差距幅度 building_count +393% +140% 巨大 amenity_count +44% −30% 中等 shop_count +61% −22% 中等 poi_diversity +37% −33% 中等 為何 amenity / shop / poi_diversity 修正後呈現負增長？ 這個結果頗具啟示性——它說明：設施、商店、POI 多樣性的增長速度，低於社群整體活躍度的增長速度。以白話解讀： 社群志願者在 8 年間新增了 4.23 倍的「總元素」 其中建築物增長最為顯著（4.93 倍），佔新增貢獻的絕大部分 設施、商店類元素雖有增長，但比例上未能跟上社群整體活躍度的提升 修正後的負值意味著：這些指標的「真實增長」可能接近零，幾乎全數源於社群回填效應。 這一發現本身即具有獨立的研究價值——它告訴規劃者一個有悖直覺的事實：從 OSM 觀察到的「商店設施大量增加」，很可能並非真實現象。 八、方法論的局限 誠實面對這套修正方法的潛在問題： 1. 假設「真實建設增長」與「社群活躍度」相互獨立 此假設未必成立。當城市快速擴張時，社群往往也更有動力更新 OSM（例如建商人員主動補登自家樓盤）。兩者之間實際存在正相關。 即便如此，修正的方向仍是正確的——只是修正幅度可能略有過度。 2. C_t 以「總元素數」代理過於粗略 更精細的做法：以 OSM changeset 數量、活躍編輯者數，或某類「不會新增的歷史地物」（如山脈、河川的 way）作為更純粹的社群活躍度代理。但 changeset 資料須從 OSM Planet 下載，工程量較大。 3. λ 不宜跨指標共用 修正建築物所用的 λ，未必適用於設施類指標。理論上每個指標都應有其專屬 λ，但此舉會使校準工作量大幅增加。 4. 全域 λ 假設的侷限 目前 λ 假設在整座城市中均一。實際上市中心與郊區的…

LLM 記憶壓縮實戰：滑動視窗 vs 摘要記憶，用答對率與 Token 成本量化取捨
type: item
date: 2026-07-13T16:00:00+00:00
description: 本章節要實作滑動視窗與摘要壓縮兩種記憶，並用事實問答量化「省 token vs 記得住」的取捨 一、滑動視窗記憶 實作只保留最近 N 輪的 SlidingWindowMemory，並驗證在 Context Window 中「忘掉久遠的事」 """SlidingWindowMemory：只保留最近 N 輪對話（FIFO）（W2）。""" class SlidingWindowMemory: def __init__(self, system=None, max_turns=3): """max_turns：保留最近幾輪（1 輪 = 1 user + 1 assistant）。""" self.system = system self.max_turns = max_turns self._messages = [] # 內部保留完整歷史（除錯用） def add_user(self, text): self._messages.append({"role": "user", "content": text}) def add_assistant(self, text): self._messages.append({"role": "assistant", "content": text}) def build_messages(self): """只回傳最近 max_turns 輪 = 最多 2*max_turns 則訊息。""" window = self._messages[-2 * self.max_turns:] # 確保以 user 開頭（避免 assistant 開頭導致 API 報錯） while window and window[0]["role"] != "user": window = window[1:] return window def __len__(self): return len(self._messages) 接著寫一個測試用例 import sys, os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from memory.llm import chat from memory.window import SlidingWindowMemory SYSTEM = "你是助理，用繁體中文簡短回答。" mem = SlidingWindowMemory(system=SYSTEM, max_turns=2) # 只記最近 2 輪 script = [ "我對甲殼類過敏，請記住。", # 埋在最早——會被滑出去 "我最喜歡的顏色是藍色。", "幫我推薦一部電影。", "再推薦一首歌。", "我對什麼過敏？", # 回頭考：早已滑出視窗 ] for user in script: mem.add_user(user) reply, _ = chat(mem.build_messages(), system=SYSTEM) mem.add_assistant(reply) print(f"你 > {user}\nAI > {reply}\n (視窗內 {len(mem.build_messages())} 則)\n") 執行測試 python demos/w2_window_probe.py 你 > 我對甲殼類過敏，請記住。 AI > 好的，我會記住您對甲殼類過敏。 (視窗內 2 則) 你 > 我最喜歡的顏色是藍色。 AI > 知道了，您最喜歡的顏色是藍色。 (視窗內 4 則) 你 > 幫我推薦一部電影。 AI > 如果您喜歡藍色，或許您會喜歡《藍色是最溫暖的顏色》（Blue Is the Warmest Color），這是一部法國電影，講述了一段深刻的愛情故事。 (視窗內 4 則) 你 > 再推薦一首歌。 AI > 推薦您聽周杰倫的《青花瓷》，這是一首融入中國傳統元素的現代流行音樂，歌詞優美，旋律動聽。 (視窗內 4 則) 你 > 我對什麼過敏？ AI > 要確定您對什麼過敏，需要進行具體的過敏原檢測。這通過皮膚點刺試驗、血液中的IgE測試或其他相關的過敏檢查來進行。如果您有過敏症狀，建議咨詢醫生或過敏科專家並進行專業檢測。 (視窗內 4 則) 可以觀察得到最早的對話内容已經不見了 二、摘要壓縮記憶 實作由滑窗 + 滾動摘要，當—舊對話滑出視窗時，則并入一段由 LLM 維護的摘要。 在下面新增滑動窗口功能 """SummaryMemory：近期用滑動視窗保留原文，滑出的舊對話併入滾動摘要。""" from memory.llm import chat SUMMARIZE_SYSTE…

LLM 為什麼沒有記憶？Stateless 原理與 Prompt 層記憶模擬實戰（含 Token 成長曲線）
type: item
date: 2026-07-12T16:00:00+00:00
description: 一、大模型沒有記憶 由於 LLM 本質上是無狀態的（Stateless）。每一次 API 呼叫都是獨立的、全新的計算過程，模型內部不會保留任何「記住上一句話」的狀態。因此，兩次呼叫彼此獨立，第二次完全不知道第一次發生過的事情。於是乎就有了以下的做法。 Prompt 層模擬記憶 把「歷史聊天記錄」+「當前新問題」打包拼接，作為一個完整的 Prompt 一次性傳給模型，讓它在 單次推理中 看到全部上下文。 我們會把對話歷史塞進 context 中，如下： messages = [ {"role": "user", "content": "我叫小明"}, {"role": "assistant", "content": "你好小明！很高興認識你。"}, {"role": "user", "content": "我叫什麼名字？"}, ] 這裏面的role代表如下： role 作用 典型內容 system 設定助理的身份、規則、語氣（Anthropic 放在獨立的 system 參數） 「你是一位耐心的數學家教」 user 使用者說的話 「幫我解這題」 assistant 模型過去的回覆（由你把它加回歷史） 「好的，第一步…」 兩種計費 token 類型 說明 相對成本 input_tokens 輸入進去的（包含全部對話歷史） 便宜 output_tokens 模型生成輸出的 貴（通常 4~5×） 問題：每一輪你都把 全部歷史 當作 input 重新送一次。第 10 輪的 input 包含了前 9 輪的所有內容。 累計 input token ≈ 每輪歷史長度的總和 ≈ 1 + 2 + 3 + … + n ≈ O(n²) Anthropic 定價（每 MTok = 100 萬 token） 模型 Input Output claude-opus-4-8 $15 $75 claude-sonnet-5 $3 $15 claude-haiku-4-5 $0.80 $4 Context Window 上限 每個模型有 上下文視窗（context window） 上限——單次呼叫能吃下的最大 token 數（input + output 合計）。將所有對話記錄都塞進去 context，歷史記錄一多就會超出限額。 模型世代 典型視窗 早期 GPT-3.5 4K / 16K GPT-4 8K / 32K / 128K Claude 3/4 200K 前沿長上下文 1M 撰寫一個簡單的 anthropic API 呼叫 import anthropic client = anthropic.Anthropic() # 讀取環境變數 ANTHROPIC_API_KEY resp = client.messages.create( model="claude-haiku-4-5", max_tokens=512, system="你是一位友善的助理，用繁體中文回答。", messages=[ {"role": "user", "content": "你好"}, ], ) print(resp.content[0].text) # 模型的文字回覆 print(resp.usage.input_tokens, resp.usage.output_tokens) # token 用量 幾個重點 system 是獨立參數，不放在 messages 裡（這是 Anthropic 與 OpenAI 的差異）。 messages 必須 user / assistant 交替，且以 user 開頭、user 結尾（送出時最後一則是使用者的新問題）。 回覆是 content 陣列：純文字對話取 resp.content[0].text。 resp.usage 直接告訴你這次用了多少 token——這是 Lab 3 畫曲線的資料來源，不需自己估算。 如果想知道精確 token 數量，可用： count = client.messages.count_tokens( model="claude-haiku-4-5", system="你是一位友善的助理。", messages=messages, ) print(count.input_tokens) 下面是全量拼接對話流程，並添加用於管理對話的 BufferMemory class BufferMemory: def __init__(self, system=None): self.system = system self._messages = [] # [{"role": ..., "content": ...}, ...] def add_user(self, text): self._messages.append…

Prompt Engineering 實戰：Zero-shot vs Few-shot vs CoT 的 Token 成本與情感分析對比
type: item
date: 2026-07-11T16:05:00+00:00
description: 「將原始模型輸出轉化為結構化、可靠、可預測的行為」。 這是 prompt engineering 的工程定義。其中三個層次： 策略：zero-shot / few-shot / Chain-of-Thought — 用什麼方式引導模型推理 約束：JSON mode、schema、格式規範 — 讓輸出可被程式接住 曡代：用標註資料集量化準確率，像調程式一樣調 prompt Context Engineering（上下文工程）：重點不是單句 prompt，而是 「在有限 context window 裡，放對的範例、對的格式說明、對的歷史」。 一、三種 prompt 策略並排比較 這邊設計腳本對同一句話組出三種 prompt，逐一送模型，並示範各策略的輸出怎麼解析： def chat(messages, temperature=0.0): """呼叫真實 LLM 取得文字回覆；無任何憑證則回傳 None，由呼叫端決定是否走 offline。""" kimi = _kimi_cfg() try: if kimi: from openai import OpenAI # Kimi 為 OpenAI 相容 API cli = OpenAI(api_key=kimi["api_key"], base_url=kimi["base_url"], timeout=120) r = cli.chat.completions.create(model=kimi["model"], messages=messages, temperature=temperature) return r.choices[0].message.content if os.environ.get("ANTHROPIC_API_KEY"): import anthropic cli = anthropic.Anthropic() sys_txt = "\n".join(m["content"] for m in messages if m["role"] == "system") usr = [m for m in messages if m["role"] != "system"] r = cli.messages.create(model="claude-opus-4-8", max_tokens=1024, system=sys_txt or None, messages=usr, temperature=temperature) return r.content[0].text if os.environ.get("OPENAI_API_KEY"): from openai import OpenAI cli = OpenAI() r = cli.chat.completions.create(model="gpt-4o", messages=messages, temperature=temperature) return r.choices[0].message.content except Exception as e: print(f"⚠ 線上呼叫失敗，改用 offline mock：{e}", file=sys.stderr) return None return None def build_prompt(task, strategy): """回傳 (messages, 說明) — 示範三種主流策略如何組 prompt。""" system = "你是情感分類器。只輸出標籤本身，不要解釋。" if strategy == "zero-shot": user = f"判斷情感（正面/負面/中性）：\n{task}" elif strategy == "few-shot": ex = "\n".join(f"文本：{t}\n標籤：{l}" for t, l in FEWSHOT) user = f"依照範例判斷情感（正面/負面/中性）：\n\n{ex}\n\n文本：{task}\n標籤：" elif strategy == "cot": user = (f"判斷以下文本的情感（正面/負面/中性）。\n" f"先用一句話分析語氣與關鍵詞，最後一行只寫『標籤：X』。\n\n文本：{task}") else: raise ValueError(strategy) return [{"role": "system", "content": system}, {"role": "user", "content": user}], strategy 執行策略看效果 python prompt_lab.py strategies --task "這家餐廳雷到爆，再也不…

OSM 的隱藏陷阱 - 以 λ 指數修正志願者貢獻偏差
type: item
date: 2026-07-11T13:20:00+00:00
description: 本篇要講述的是台中 OSM 建築數量 2018→2025 增長 392%，但其中有多少是真實建設、多少是社群回填？本篇提出一個可量化、可控制修正強度的 λ 偏差修正指數。 關於 OSM 其中的 392% 增長，有多少是真的？ 先來檢視台中市 OSM 數據 8 年的原始增長： 年份 OSM 建築物總數 設施 商店 休閒 2018 16,926 1,015 408 30 2019 12,771 1,207 466 32 2020 16,117 1,520 541 39 2021 18,500 1,599 534 47 2022 24,973 1,640 590 49 2023 41,055 1,762 647 57 2024 47,584 1,874 690 59 2025 50,208 1,921 705 61 採樣點 500m 範圍內的「平均建築數量」更為顯著——從 2018 年的 27.6 棟，增至 2025 年的 136 棟，+392.1%。 然而其中一個問題隨之而來： 台中市真的在 8 年內新建了比原有存量多 4 倍的建築嗎？ 答案顯然是否定的。即便是開發最密集的城市，10 年內新建建築數量也不會超過存量的 1 倍。這 392% 中，相當大一部分其實是「OSM 社群志願者持續將既有建築數位化補登」，也就是「歷史回填」（backfilling）效應。 量化「回填效應」：社群貢獻量代理變數 若某城市/地區 2018 年 OSM 共有 1 萬個元素（建築 + 設施 + 商店 + 休閒），2025 年增至 5 萬，則社群整體活躍度約提升 5 倍。建築數的增長，有相當一部分是被這個系統性的「社群更積極補登」推高的。 我們在這邊定義「社群貢獻量代理」C_t： C_t = building_count_t + amenity_count_t + shop_count_t + leisure_count_t 將所有有測量目標的標籤加總，作為志願者整體投入程度的代理變數。 # osm/bias_correction.py:37-54 def compute_total_elements(osm_metrics: pd.DataFrame) -> pd.Series: """ 計算每年 OSM 快照的「總元素數」（社群貢獻量代理）。 """ count_cols = [c for c in ["building_count", "amenity_count", "shop_count", "leisure_count"] if c in osm_metrics.columns] osm_metrics = osm_metrics.copy() osm_metrics["total_elements"] = osm_metrics[count_cols].sum(axis=1) return osm_metrics.groupby("year")["total_elements"].mean() 台中 8 年的 C_t（每採樣點 500m 範圍內均值）： 年份 C_t（總元素均值） 2018 34.6 2021 57.1 2025 146.4 C_2025 / C_2018 ≈ 4.23，社群貢獻量翻了逾四倍。 核心假設：社群貢獻量增長與真實建設增長是兩個獨立過程。OSM 元素總數的增長是兩者的疊加，而研究者真正關心的是後者。 修正公式 定義第 t 年的「校正後增長指數」： G_t_corr = (N_t / N_0) × (C_0 / C_t)^λ 各符號意義： 符號 意義 N_t / N_0 原始增長率（基準年 N_0 = 2018） (C_0 / C_t) 社群貢獻量的衰減倒數（C 愈大，此因子愈小） λ ∈ [0, 1] 修正強度，由分析者根據研究假設設定 三種直覺解讀： λ = 0：完全不修正 → G_corr = N_t / N_0，還原為原始指數 λ = 1：完全比例修正 → 假設所有 OSM 元素增長均來自社群活躍度提升 λ = 0.5：中度修正，假設增長中一半來自真實建設、一半來自社群回填 # osm/bias_correction.py:80-97 v0 = series[base_year] c0 = community_proxy[base_year] for year in sorted(series.index): v_t = series[year] c_t = community_proxy.get(year, c0) raw_growth = v_t / v0 if v0 > 0 else np.nan # 社群貢獻量修正因子 = (C_t0 / C_t)^λ if c0 > 0 and c_t > 0: c…

CodeGraph 實戰心得：用「地圖導航＋精確搜索」提升 Claude Code 程式碼理解效率
type: item
date: 2026-07-08T06:52:00+00:00
description: 以下將 codegraph 納入實際項目運作查看使用效益 $ (py3.10) D:\OlgCase\RS\HXXR_3>codegraph init ┌ Initializing CodeGraph │ ◆ Initialized in D:\OlgCase\RS\HXXR_3 | | * Scanning files - 2,371 found | * Parsing code - done | * Resolving refs - done │ ◆ Indexed 2,371 files │ ● 69,568 nodes, 192,594 edges in 53.1s │ └ Done $ (py3.10) D:\OlgCase\RS\HXXR_3>codegraph status CodeGraph Status Project: D:\OlgCase\RS\HXXR_3 Index Statistics: Files: 2,371 Nodes: 69,568 Edges: 192,594 DB Size: 147.56 MB Backend: node:sqlite - built-in (full WAL) Journal: wal Nodes by Kind: method 25,000 field 16,000 import 8,000 enum_member 3,000 property 3,737 class 3,500 constant 2,515 file 2,365 namespace 1,581 struct 527 enum 421 variable 276 function 253 interface 207 Files by Language: csharp 2,285 python 42 xml 14 javascript 12 php 10 yaml 6 c 1 objc 1 [OK] Index is up to date 進入項目后提出需求給 cc cc先是透過llm分析出 keyword，然後查詢 codegraph 可用工具，再將關鍵詞輸入給codegraph "query": "UIAIDebugMenu AIDebugMenu aiwnd close hide AICondBlock refresh ai decision" 對話内容如下： 後續都是 Grep、Read ，如圖： codegraph 的實際使用流程并不是全面替代 grep、read，而是將項目 code roadmap 查找出來 先用 codegraph 做「地圖導航」 用戶提出需求 ↓ mcp__codegraph__codegraph_explore ← 只用一次 ↓ 目的：快速了解整個專案結構 "讓我先知道這個 codebase 長什麼樣" codegraph 的作用是給 Claude 一個高層次的程式碼地圖，知道哪些檔案、哪些 class、哪些 function 存在。 再來用 Grep + Read 做「精確搜索」 已知地圖後 ↓ Grep × 8 ← 搜尋具體關鍵字（如 "aiwnd", "CloseUI", 事件名稱） Read × 4 ← 讀取找到的具體檔案內容 後面因爲 Claude 已經知道方向了，就不需要再用 codegraph，改用更精準、更輕量的工具。 本質差異 工具 用途 比喻 codegraph 理解整體結構、依賴關係 看地圖 Grep 搜尋特定字串/pattern 用關鍵字查字典 Read 讀取完整檔案內容 打開具體頁面閱讀 沒有全部使用 codegraph 的原因大概也是如下： codegraph 執行成本 每次呼叫需要分析程式碼結構，在 CPU、IO 上比 grep 慢且耗資源。 必要性 第一次 codegraph 已給出足夠的「索引」，後續只需要針對性搜尋。 Claude 的工具選擇策略 不確定結構 → codegraph（掃描全局） 已知方向 → grep（精確定位） 找到目標 → read（深入閱讀） 如下是 Claude 的工作流程： codegraph（1次）→ 建立認知地圖 ↓ grep（8次）→ 精確定位關鍵程式碼 ↓ read（4次）→ 深入理解細節

用 OSM 打造城市空間指標：從歷史快照抓取到 500m 緩衝區七大指標計算
type: item
date: 2026-07-06T19:30:00+00:00
description: 在項目開始之初爲什麽選 OSM 而不是政府開放資料 做城市量化分析時，第一個問題永遠是「資料從哪來」。選項通常有三類： 來源 優點 缺點 政府開放資料（國土測繪、CPA 建照） 權威 各年版本不一致、API 不穩、跨城市格式差異大 商業 GIS（Google / HERE） 完整 商用授權昂貴、無歷史快照 OpenStreetMap（OSM） 免費、全球統一 schema、可拉歷史 由社群貢獻、覆蓋率隨時間變動 項目選擇 OSM 的理由很簡單：做跨年比較、加上跨城市複用，OSM 是唯一能在「歷史一致性 × schema 統一」兩個維度同時滿足的來源。 只是代價是要面對「社群貢獻偏差」（後面 A3 篇會專門講怎麼修正），但這個問題是可量化、可修正的。 從 Overpass 拉歷史 OSM 的關鍵：date 過濾器 OSM 整個資料庫是時間序列，而每個 element 都有 timestamp，Overpass 支援用 date 過濾器查「某個時間點的快照」： # dataloader.py:56-64 date_str = f"{year}-01-01T00:00:00Z" ox.settings.overpass_settings = f'[out:json][timeout:180][date:"{date_str}"]' gdf = ox.features_from_place(city, tags) file_path = f"{SAVE_DIR}/osm_{year}.geojson" gdf.to_file(file_path, driver='GeoJSON') NOTE： [date:"2018-01-01T00:00:00Z"]：設定 Overpass 獲取 2018/1/1 OSM 快照」。 ox.features_from_place(city, tags) ：osmnx 將 city name 解析成 polygon，再用該 polygon 去 Overpass 抓 elements。 獲取后存成 .geojson 本地快取，方便後續繼續處理。這邊需要注意以下幾點 Overpass 公開端點有 query quota，重複跑很容易被擋。 一次抓 8 年 × 5 種 tag 的 query，沒快取每次重跑要 2 小時起跳。 後續所有指標計算都從本地 geojson 讀，跟網路完全脫鉤。 設計本地 geojson 快取的雙路徑架構 項目中 OSM 模組有兩層 cache： # osm/fetch_osm.py:44-69 # 1. 優先從本地 osm_{year}.geojson 讀取並過濾 main_geojson_path = save_dir / f"osm_{year}.geojson" if main_geojson_path.exists() and main_geojson_path.stat().st_size > 0: full_gdf = gpd.read_file(main_geojson_path) filtered_gdf = full_gdf.copy() for key, value in tags.items(): if value is True: filtered_gdf = filtered_gdf[filtered_gdf[key].notna()] else: filtered_gdf = filtered_gdf[filtered_gdf[key] == value] return filtered_gdf # 2. 從快取文件讀取 cache_path = save_dir / f"osm_{label}_{year}.geojson" if cache_path.exists(): return gpd.read_file(cache_path) # 3. 本地沒有，返回 None（不打網路） 設計雙層緩存 cache 主要基於檔案處理時間跟空間考量，如下：： 主檔 osm_<year>.geojson 一次下完所有 tag（建築 + 設施 + 商店 + 休閒），體積大但只下載一次。後續按 tag 過濾在記憶體裡做，幾毫秒就完成。 快取檔 osm_ _<year>.geojson 是針對單一 tag 的精細下載，給某些主檔太大、想精細控制的場景用。 第三條路徑直接回 None 而不打網路 —— 這是工程上很重要的決定。讓「沒資料」變成可預期的訊號，而不是讓 pipeline 卡在 retry。 OSM 數據處理 - 500m 緩衝區 + 空間索引 抓到資料後，每個採樣點要算它周圍 500m 範圍的指標： # osm/fetch_osm.py:29 BUFFER_…

CodeGraph 深度解析：codegraph.db 資料庫結構完整拆解（Nodes、Edges、索引與觸發器）
type: item
date: 2026-07-04T10:13:00+00:00
description: 這篇延續上一個 CodeGraph 的深度解析數據庫 在上一篇文章CodeGraph 安裝與使用教學：整合 Claude Code、Cursor、Codex 的程式碼圖譜工具中資料如下： (py3.10) D:\Git\mitagent>codegraph status CodeGraph Status Project: D:\Git\mitagent Index Statistics: Files: 25 Nodes: 423 Edges: 982 DB Size: 1.26 MB Backend: node:sqlite - built-in (full WAL) Journal: wal 其中，Nodes（423）代表程式碼裡的「實體」 每個 node 是一個可識別的程式碼符號： Kind 數量 意思 function 127 獨立函式（def foo() / function bar()） import 81 import 語句本身（被視為一個節點） method 58 類別裡的方法（class.method()） constant 57 常數定義（const X = ... / UPPER_CASE） variable 34 模組層級的變數 file 25 每個原始碼檔案本身 class 23 類別定義 interface 9 TypeScript interface route 9 HTTP 路由端點（自動偵測到了！） Edges（982條）= 節點之間的「關係」 邊代表符號之間的連結，例如： A 呼叫 B（call edge） A import 了 B（import edge） A 繼承自 B（extends edge） 檔案 A 包含函式 B（contains edge） 使用 codegraph init 後生成 codegraph.db，在 D:\Git\mitagent\.codegraph\ 目錄，下讀取 sqlite db 如下 結構解釋 這個 codegraph.db 裡面分成四類： 資料表（13個）— 核心數據 表名 存什麼 nodes 所有程式碼符號（函式、類別、變數等），就是那 423 個 node edges 符號之間的關係（呼叫、import、繼承等），就是那 982 條 edge files 索引的 25 個原始碼檔案（路徑、語言、hash） nodes_fts 虛擬表，FTS5 全文搜尋引擎，讓 AI 可以模糊查符號名稱 nodes_fts_* FTS5 的內部輔助表（config/data/docsize/idx），不用管 unresolved_refs 解析失敗的參照（例如 import 了找不到的模組） project_metadata 專案設定（版本、最後更新時間） schema_versions DB schema 版本管理 sqlite_sequence / sqlite_statl SQLite 內建系統表 索引（18個）— 加速查詢 都是幫核心表建的查詢加速索引，例如： idx_nodes_kind — 快速找所有 function/class/method idx_nodes_lower_name — 大小寫不敏感搜尋符號名 idx_edges_source_kind / idx_edges_target_kind — 快速走訪關係圖 觸發器（3個）— 自動同步 FTS Trigger 時機 nodes_ad 刪除 node 時，自動更新全文搜尋索引 nodes_ai 插入 node 時，自動更新全文搜尋索引 nodes_au 更新 node 時，自動更新全文搜尋索引

CodeGraph 安裝與使用教學：整合 Claude Code、Cursor、Codex 的程式碼圖譜工具
type: item
date: 2026-07-03T18:18:00+00:00
description: 在大型程式項目中通常會透過不同手段來優化 llm token 的使用效益,本篇文章將介紹如何使用 codegraph 安裝 CLI npm install -g @colbymchenry/codegraph 進入到項目中，安裝 codegraph，它會自動偵測並設定 Claude Code、Cursor、Codex $ codegraph install 選擇對應 AI 模型 其餘選擇如下： ◇ Which agents should CodeGraph configure? │ Claude Code (detected), Cursor (detected), Codex CLI (detected) — global only, opencode (detected), Gemini CLI (detected), Antigravity IDE (detected) — global only, Kiro (detected) │ ◇ Install the codegraph CLI on your PATH? (Required so agents can launch the MCP server) │ Yes │ ◇ Installed codegraph CLI on PATH │ ◇ Apply agent configs to all your projects, or just this one? │ Just this project │ ◇ Auto-allow CodeGraph commands? (Skips permission prompts in Claude Code) │ Yes │ ◇ Share anonymous usage stats? (No code, paths, or names — see TELEMETRY.md) │ Yes │ ● Thanks! Exactly what is collected: https://github.com/colbymchenry/codegraph/blob/main/TELEMETRY.md │ ◇ Front-load CodeGraph on “how / where / trace” prompts? Auto-injects structural context so answers need fewer steps (adds a moment to those prompts; Claude Code only). │ Yes 生成了這些檔案 建立程式碼索引 (py3.10) D:\Git\mitagent>codegraph init ┌ Initializing CodeGraph │ ◆ Initialized in D:\Git\mitagent | | * Scanning files - 25 found | * Parsing code - done | * Resolving refs - done │ ◆ Indexed 25 files │ ● 423 nodes, 982 edges in 721ms │ └ Done 查看目前 codegraph 狀態 (py3.10) D:\Git\mitagent>codegraph status CodeGraph Status Project: D:\Git\mitagent Index Statistics: Files: 25 Nodes: 423 Edges: 982 DB Size: 1.26 MB Backend: node:sqlite - built-in (full WAL) Journal: wal Nodes by Kind: function 127 import 81 method 58 constant 57 variable 34 file 25 class 23 interface 9 route 9 Files by Language: python 12 javascript 11 tsx 2 [OK] Index is up to date 安裝完成後，AI Agent 會自動透過 MCP 查詢圖譜 指令 用途 codegraph status 查看索引狀態 codegraph sync 手動同步（通常不需要） codegraph uninit 移除專案索引 codegraph uninstall 從所有 Agent 移除設定 查某個函式的所有關係（呼叫者、被呼叫者） (py3.10) D:\Git\mitagent>codegraph explore "ZOOM_SENSITIVITY" **Explorat…

用 Unicode 單引號為 LLM 輸出加水印：防抓取與叛徒追蹤實戰
type: item
date: 2026-07-02T11:25:00+00:00
description: 傳統的 IP 封鎖和 Rate Limit 正在失效。當對手能無限旋轉 IP、偽裝 Header， 你真正的防線藏在一個沒人注意的地方——U+2019，那個「右彎引號」。 問題在哪 假設你花了幾年訓練一個垂直領域的 LLM，或者你的 API 輸出本身就是有價值的資產。 現在有人開始大量抓取你的輸出，用來微調（fine-tune）他們自己的模型。 傳統反爬蟲？封 IP——對方換 Proxy；加 Rate Limit——對方放慢速度； 要求登入——對方批量買帳號。 文字層面的防禦，大部分工程師還沒想到。而這裡才是真正的機會。 核心技術：Unicode 符號替換 英文裡的單引號（apostrophe）有一個標準寫法，但 Unicode 裡長得幾乎一樣、 卻是完全不同 code point 的字元有好幾個。 肉眼看不出差別，但 tokenizer 會把它們分開處理。 字元 Unicode 名稱 用途標記 Tokenizer 行為 ' U+0027 Apostrophe 低風險 正常 token ' U+2019 Right single quotation mark Proxy 用戶 獨立 token A ʼ U+02BC Modifier letter apostrophe 異常網域 獨立 token B ʹ U+02B9 Modifier letter prime 黑名單 獨立 token C 系統在輸出文本時，根據該用戶的風險評級，動態決定要用哪一個 code point。 用戶看到的永遠是「一個引號」，但後端拿到的 token 序列已經帶著標記。 # 根據用戶風險評級選擇 Unicode code point APOSTROPHE_MAP = { "low": "\u0027", # ' 標準 "proxy": "\u2019", # ' Proxy 用戶 "anomalous": "\u02BC", # ʼ 異常網域 "blacklist": "\u02B9", # ʹ 黑名單 } def watermark_text(text: str, risk_level: str) -> str: src = "\u0027" # 原始單引號 dst = APOSTROPHE_MAP[risk_level] return text.replace(src, dst) 為什麼不用零寬字元（Zero-Width Characters）？ 最直覺的水印做法是在文字中插入 U+200B（零寬空格）或類似的隱形字元。 問題是太好清洗了——任何有點經驗的工程師一行 regex 就搞定： # 一秒清掉所有零寬字元——防禦形同虛設 import re clean = re.sub(r'[\u200b\u200c\u200d\ufeff]', '', scraped_text) 相比之下，把 U+2019 換回 U+0027 需要知道原文每一處 「語義引號」在哪、有沒有其他用途。如果盲目 normalize 全部引號， 會破壞文章原本的排版和引用結構，清洗成本驟升。 關鍵不在於「完全不可清洗」，而在於讓清洗的成本高到讓對手得不償失。 如果清洗掉水印需要人工審閱每一篇輸出，規模化抓取就失去意義。 語義等價格式調變 除了符號替換，還有一種更難察覺的方式：在不改語意的前提下，調整格式習慣。 最常見的例子是日期分隔符： 後端 LLM 或規則引擎可以直接從 token 序列辨識這些組合—— 不需要解析 IP，不需要額外的 Header，標記資訊已經藏在內容裡面。 應用場景：反蒸餾與叛徒追蹤 數據投毒（Data Poisoning） 當對手模型大量訓練被水印污染的文本後，那些罕見的 Unicode 符號 會和高品質的專業內容深度綁定。對手模型學到的分佈會把 U+02BC 視為「專業文本」的特徵，結果就是在某些情況下莫名其妙輸出奇怪的符號組合—— 這就是模型退化。 叛徒追蹤（Traitor Tracing） 如果懷疑對手抄了你的數據，只需要測試他們的模型： 測試腳本概念# 檢查對手模型是否輸出特定 Unicode 組合 SIGNATURES = [ ("\u02BCtoday", "水印 B + today 組合"), ("2025\u002F07", "斜槓日期格式"), ] for sig, name in SIGNATURES: if sig in competitor_output: print(f"偵測到水印特徵：{name}（可作為舉證）") 這些特定的 code point 組合就是證據，可以在法庭上或商業談判中直接使用。 用戶行為指紋（Fingerprinting） 反過來，這個技術也可以用在防禦帳號濫用。 每個用戶都有自己的打字習慣——習慣用 .…